Skip to content

feat(catalog-mcp): add split and regexCapture - #3574

Open
josemontespg wants to merge 1 commit into
catalogs-mcp-json-examplefrom
catalogs-mcp-text-functions
Open

josemontespg wants to merge 1 commit into
catalogs-mcp-json-examplefrom
catalogs-mcp-text-functions

Conversation

@josemontespg

@josemontespg josemontespg commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Many MCP servers return list data only as text, usually as records of Key: value lines separated by blank lines. This PR adds two data functions for that output. split cuts the text of the callMcpTool payload into records. regexCapture turns each record into its capture groups, or null when it does not match. Both take a single string or an array of strings, so a chain like transformData(regexCapture(split(...))) turns a text result into rows without any client code.

regexCapture patterns use a portable subset that every renderer can implement: JavaScript syntax without lookahead, lookbehind or backreferences. isPortableRegex checks this before the isSafeRegex check for catastrophic backtracking, and any pattern that fails either check raises an A2uiExpressionError. The catalog ships its own copy of isSafeRegex (src/functions/safe_regex.ts, backed by redos-detector, the same check as the basic catalog's regex function), so @a2ui/web_core does not have to export it.

The READMEs now describe the four data functions as a closed set and explain why each one is included. They also show how a host adds its own parser in a catalog of its own and calls it with that catalog's catalogId.

Changes

  • src/functions/split.ts, src/functions/regex_capture.ts: SplitApi, SplitImplementation, RegexCaptureApi and RegexCaptureImplementation. The pattern description states the portable subset.
  • src/functions/regex_support.ts: isPortableRegex skips escaped characters and the contents of character classes. compileSafeRegex runs both checks and caches compiled patterns. mapTextValue does the string-or-array mapping.
  • src/functions/safe_regex.ts, package.json: the isSafeRegex copy and its redos-detector dependency (already in yarn.lock through @a2ui/web_core).
  • src/catalog.ts, src/index.ts, catalogs/mcp/v1/catalog.json: register, export and declare split and regexCapture.
  • catalogs/mcp/v1/examples/mcp-text-tool-result.json: a Tavily-style search. The button calls split on \n\n and regexCapture with ^Title: (.*)\nURL: (.*), then transformData drops the records that did not match, names the fields and writes them to /results.
  • Tests: split.test.ts, regex_capture.test.ts, regex_support.test.ts (portable and non-portable patterns, including escapes and character classes), safe_regex.test.ts (ported from @a2ui/web_core), and the text example's button in catalog.test.ts.
  • typescript/catalogs/mcp/README.md: a data functions section with the argument table, the regex rules, why each function is included, and a custom parseCsv function in a host catalog. catalogs/mcp/README.md has a short version.
  • CHANGELOG.md: adds split and regexCapture to the package's unreleased entry.

Verification

  • yarn workspace @a2ui/catalog-mcp run test: 220 node tests and 222 karma tests pass, and the test type check is clean.
  • yarn workspace @a2ui/catalog-mcp run lint: no errors.
  • uv run python3 specification/v1_0/test/run_tests.py: 179 passed.
  • I type-checked the README's custom function snippet against @a2ui/web_core.
  • ./scripts/fix_format.sh: clean.

@github-project-automation github-project-automation Bot moved this to Todo in A2UI Oct 23, 2026
@josemontespg
josemontespg force-pushed the catalogs-mcp-text-functions branch from 83bda22 to ec0060f Compare October 23, 2026 22:55

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds three new text-processing data functions (split, regexCapture, and regexReplace) to the A2UI MCP catalog, enabling surfaces to parse and shape plain text outputs from headless MCP tools. It includes schema updates, TypeScript implementations with safe regular expression compilation, comprehensive unit tests, and a practical example of directory listing parsing. The review feedback highlights two valuable improvements: ensuring Unicode-aware character splitting in the split function when the separator is empty, and wrapping the isSafeRegex check inside the try-catch block in regex_support.ts to prevent unhandled crashes during expression evaluation.

@josemontespg
josemontespg added this pull request to stack #3585 October 23, 2026 23:38
@josemontespg
josemontespg force-pushed the catalogs-mcp-text-functions branch from ec0060f to d704a18 Compare October 23, 2026 23:43
@josemontespg
josemontespg force-pushed the catalogs-mcp-text-functions branch from d704a18 to d240425 Compare October 23, 2026 23:56
@josemontespg
josemontespg force-pushed the catalogs-mcp-text-functions branch from d240425 to f39d1eb Compare October 24, 2026 00:05
@josemontespg
josemontespg force-pushed the catalogs-mcp-text-functions branch from f39d1eb to 5d4e7a0 Compare October 24, 2026 01:11
@josemontespg
josemontespg force-pushed the catalogs-mcp-text-functions branch from 5d4e7a0 to cf4a5d3 Compare October 24, 2026 01:24
@josemontespg
josemontespg removed this pull request from stack #3585 October 24, 2026 01:26
@josemontespg
josemontespg added this pull request to stack #3594 October 24, 2026 01:26
@josemontespg
josemontespg force-pushed the catalogs-mcp-text-functions branch from cf4a5d3 to 3f832f4 Compare October 24, 2026 01:30
@josemontespg
josemontespg force-pushed the catalogs-mcp-text-functions branch from 3f832f4 to d697a44 Compare October 24, 2026 01:44
@josemontespg
josemontespg force-pushed the catalogs-mcp-text-functions branch from d697a44 to 9e014b8 Compare October 26, 2026 09:07
@josemontespg
josemontespg force-pushed the catalogs-mcp-text-functions branch from 9e014b8 to b21fcd3 Compare October 26, 2026 09:48
@josemontespg
josemontespg force-pushed the catalogs-mcp-text-functions branch 2 times, most recently from 29b381c to 3053580 Compare October 26, 2026 17:28
@josemontespg
josemontespg added this pull request to stack #3640 October 26, 2026 20:34
@josemontespg
josemontespg force-pushed the catalogs-mcp-text-functions branch from de9379a to 9adad6e Compare October 26, 2026 20:36
@josemontespg
josemontespg removed this pull request from stack #3640 October 26, 2026 21:37
@josemontespg
josemontespg added this pull request to stack #3643 October 26, 2026 21:38
@josemontespg
josemontespg removed this pull request from stack #3643 October 26, 2026 22:01
@josemontespg
josemontespg force-pushed the catalogs-mcp-text-functions branch from 9adad6e to b2313d8 Compare October 26, 2026 22:01
@josemontespg
josemontespg added this pull request to stack #3645 October 26, 2026 22:04
@josemontespg
josemontespg removed this pull request from stack #3645 October 26, 2026 23:14
@josemontespg
josemontespg force-pushed the catalogs-mcp-text-functions branch from b2313d8 to ccb6d2d Compare October 26, 2026 23:14
@josemontespg
josemontespg added this pull request to stack #3647 October 26, 2026 23:17
@josemontespg
josemontespg marked this pull request as ready for review October 27, 2026 00:09
@josemontespg
josemontespg requested a review from ditman October 27, 2026 00:09
@josemontespg
josemontespg removed this pull request from stack #3647 October 27, 2026 00:30
@josemontespg
josemontespg added this pull request to stack #3649 October 27, 2026 00:32

@ditman ditman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for the same reason as #3573 (and per #3153 / #3171): split, regexCapture, and regexReplace are general string/regex utilities rather than MCP primitives and should not be part of catalogs/mcp/v1/catalog.json or @a2ui/catalog-mcp. Please keep them in samples/community/mcp/a2ui-over-mcp-filesystem/client/functions/ where they are used.

@wrenj

wrenj commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator

Requesting changes for the same reason as #3573 (and per #3153 / #3171): split, regexCapture, and regexReplace are general string/regex utilities rather than MCP primitives and should not be part of catalogs/mcp/v1/catalog.json or @a2ui/catalog-mcp. Please keep them in samples/community/mcp/a2ui-over-mcp-filesystem/client/functions/ where they are used.

Hi David, I discussed this with Jose and we need these functions to parse MCP responses. This catalog is being added to GE and is already wired up (with the functions) in the 1P Composer, Chat Canvas, and some new projects. I don't think we want to make shared libraries that require every client to make a custom catalog that rolls their own utility functions, it breaks portability and makes for bad devex (we'd basically have a README.md in the MCP Catalog saying copy these utility functions from samples and put them in your own catalog). The material3 catalog for instance has a ton of support functions (regex, dateLessThan) that could arguably fit better elsewhere but we just ship as part of that catalog. LMK what you think. thanks

@ditman

ditman commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator

we need these functions to parse MCP responses.

@wrenj I can be convinced that some functions that we're adding to this catalog are warranted (like allowing json selectors/transformers into a random data structure), but others, like "split" not so much. Why is "split" needed but not "trim" or "length" or... you name it? Where does this stop? Do we need to add as many functions as requested by users who "can't launch their startup" without them, instead of having something that lets users inject their own business logic?

This catalog is being added to GE and is already wired up (with the functions) in the 1P Composer, Chat Canvas, and some new projects.

That's OK, catalogs are composable, and apps should be able to add whatever parsing functions they need. For example, what if they need "parseProtoBuf" or "parsePrivateProtocol"? Should those functions also be part of the open source catalog? Probably not, but the assortment of functions that are adding to this catalog is so random that it makes it very hard to say "no" to other requests down the line.

The material3 catalog for instance has a ton of support functions (regex, dateLessThan) that could arguably fit better elsewhere but we just ship as part of that catalog.

Since you mention "regex", I think that's a great argument against adding utility functions like "regexCapture" to this catalog. Now for users that mix that catalog and this one. Are they going to end up with two similar-but-not-quite-the-same "regex" functions with different functionality. I'd imagine that more common functions (like "split", for example) may end showing up many times.

If functions "could arguably fit better elsehwere", we should find that place.

Anyways, onwards.

@ditman ditman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disagree and commit! Ship it!

Comment thread catalogs/mcp/README.md Resolved
@yjbanov

yjbanov commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

I'm with @ditman on this one. What GE or material3 or any other specific app does should not dictate what goes into the standard libraries that every app in the world will be using.

@wrenj Can you reframe the reasoning for why these functions should be part of the standard catalog without relying on GE's internal decisions about them?

@josemontespg josemontespg left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yjbanov This goes beyond GE. callMcpTool lets UI controls call third-party MCP servers directly from the client without an LLM round-trip or custom host code. transformData (JMESPath) and updateDataModel handle JSON responses, but 13 of 28 public MCP servers I surveyed return lists only as plain text (usually Key: value blocks like Title: ...\nURL: ...). split and regexCapture are needed convert those blocks into data that can be bound to the UI.

This set of functions does not grow unbounded. The README closes the set at these four functions (JSON and Key: value text). JMESPath already covers length, join, and to_string, and custom formats like CSV or protobuf belong in an app's own catalog.

@wrenj

wrenj commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

I'm with @ditman on this one. What GE or material3 or any other specific app does should not dictate what goes into the standard libraries that every app in the world will be using.

Fair point, @yjbanov — sorry for framing my earlier comment around internal adopters. Agreed that anything in a standard catalog has to stand on its own technical merits.

The short version is that split and regexCapture aren't random string utilities — they're only separate functions because we chose vanilla JMESPath for transformData (the data transformer @ditman mentioned being on board with), and vanilla JMESPath intentionally omits string splitting and regex extraction.

When I originally built this in #2650, the goal was a single declarative transformer so callMcpTool could shape arbitrary third-party MCP responses into the data model on generic hosts without custom client code. I evaluated several expression libraries for running untrusted, LLM-generated transforms on the client:

  • JSONata (what feat(catalogs/mcp): Add jmespath, split, regexCapture, regexReplace, updateDataModel functions to MCP catalog to process MCP results #2650 actually started with): natively includes $split and $match, so we didn't need extra catalog functions at all — but it's Turing-complete (unbounded recursion/loops make it unsafe to evaluate untrusted expressions without custom sandboxing/fuel limits) and lacks good cross-language runtimes for Dart/Swift.
  • JSONPath: too limited on its own (it queries nodes into flat lists rather than projecting/reshaping objects for updateDataModel), and extended dialects like jsonpath-plus that add script filters have a history of JS sandbox-escape/RCE CVEs.
  • jmespath-community: extends JMESPath with built-in split and regex functions, which would have kept everything inside transformData. However, it has much smaller adoption, weaker multi-language parity, and security issues — in particular, its built-in regex functions pass untrusted patterns straight into the host's backtracking RegExp engine with no ReDoS protection.
  • Vanilla JMESPath: non-Turing-complete (no loops or recursion, guaranteed termination, no host access), battle-tested (ships in AWS CLI/SDKs, ~6M weekly npm downloads), and has clean implementations across TS, Python, Dart, Swift, and Kotlin.

The one catch with vanilla JMESPath is that it only operates on structured JSON — it has no way to tokenize strings or extract fields, whereas roughly half of public MCP servers return list data only as plain-text Key: value blocks rather than JSON.

So the cleanest, safest tradeoff was vanilla JMESPath + just the two missing extraction primitives (split and regexCapture), where we can enforce a portable regex subset and redos-detector checks ourselves. That also answers @ditman's question on why this doesn't snowball into length, join, etc.: vanilla JMESPath already handles those natively once text records are turned into arrays, which is why we cut regexReplace and closed the set at these four functions in the README.

WDYT — does that clarify why we landed on this split rather than baking it into a heavier expression engine?

Many MCP servers return list data only as text, usually as records of Key: value lines. split cuts that text into records and regexCapture turns each record into its capture groups, so a payload can read the text field of the callMcpTool payload without client code.

regexCapture patterns use a portable subset (no lookaround, no backreferences) that every renderer can implement, checked by isPortableRegex before isSafeRegex. The READMEs document the closed set of four data functions, why each is included, and how a host adds its own function in a catalog of its own.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants