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.

Comment thread typescript/catalogs/mcp/src/functions/split.ts Outdated
Comment thread typescript/catalogs/mcp/src/functions/regex_support.ts
@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 force-pushed the catalogs-mcp-text-functions branch 2 times, most recently from 23ea44c to de9379a Compare October 26, 2026 20:31
@josemontespg
josemontespg removed this pull request from stack #3629 October 26, 2026 20:34
@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 on lines +50 to +98
"variant": "primary",
"action": {
"functionCall": {
"@call": "updateDataModel",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json",
"args": {
"updates": {
"@call": "transformData",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json",
"args": {
"jmespath": "{\"/results\": [?@ != null].{title: [0], url: [1]}}",
"data": {
"@call": "regexCapture",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json",
"args": {
"value": {
"@call": "split",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json",
"args": {
"value": {
"@call": "transformData",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json",
"args": {
"jmespath": "text",
"data": {
"@call": "callMcpTool",
"catalogId": "https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json",
"args": {
"name": "tavily-search",
"arguments": {
"query": {
"@path": "/query"
}
}
}
}
}
},
"separator": "\n\n"
}
},
"pattern": "^Title: (.*)\nURL: (.*)"
}
}
}
}
}
}
}

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.

wouldn't this be much, much more ergonomic if users could inject a "handleSearchResults" function that encapsulates all this code-as-JSON in actual code?

Comment thread catalogs/mcp/README.md

`regexCapture` patterns use a portable subset that every renderer can implement: JavaScript regular expression syntax without lookahead or lookbehind assertions and without backreferences. Implementations also reject patterns that allow catastrophic backtracking. To test whether a string matches a pattern, use the basic catalog's `regex` function.

The set of data functions is closed, because each one has to be specified, tested and implemented in every renderer. The four cover the JSON and text output of most MCP servers: `split` and `regexCapture` exist because many servers return list data only as text, mostly as records of `Key: value` lines. A host that needs another parser defines it in a catalog of its own, with its own catalog ID, and payloads call it with that `catalogId`. The [TypeScript package README](../../typescript/catalogs/mcp/README.md#data-functions) explains the choice of each function and shows how to add one.

@ditman ditman Oct 28, 2026 •

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.

Ah thanks for adding this note, I missed something like this in the prior PR!

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.

3 participants