Repository navigation
feat(catalog-mcp): add split and regexCapture - #3574
josemontespg wants to merge 1 commit into
Conversation
83bda22 to
ec0060f
Compare
There was a problem hiding this comment.
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.
ec0060f to
d704a18
Compare
d704a18 to
d240425
Compare
d240425 to
f39d1eb
Compare
f39d1eb to
5d4e7a0
Compare
5d4e7a0 to
cf4a5d3
Compare
cf4a5d3 to
3f832f4
Compare
3f832f4 to
d697a44
Compare
d697a44 to
9e014b8
Compare
9e014b8 to
b21fcd3
Compare
29b381c to
3053580
Compare
23ea44c to
de9379a
Compare
de9379a to
9adad6e
Compare
9adad6e to
b2313d8
Compare
b2313d8 to
ccb6d2d
Compare
ditman
left a comment
There was a problem hiding this comment.
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 ( |
ccb6d2d to
32ed83c
Compare
32ed83c to
fbfe474
Compare
@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?
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.
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
left a comment
There was a problem hiding this comment.
Disagree and commit! Ship it!
| "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: (.*)" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
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?
|
|
||
| `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. |
There was a problem hiding this comment.
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.
Summary
Many MCP servers return list data only as text, usually as records of
Key: valuelines separated by blank lines. This PR adds two data functions for that output.splitcuts thetextof thecallMcpToolpayload into records.regexCaptureturns each record into its capture groups, ornullwhen it does not match. Both take a single string or an array of strings, so a chain liketransformData(regexCapture(split(...)))turns a text result into rows without any client code.regexCapturepatterns use a portable subset that every renderer can implement: JavaScript syntax without lookahead, lookbehind or backreferences.isPortableRegexchecks this before theisSafeRegexcheck for catastrophic backtracking, and any pattern that fails either check raises anA2uiExpressionError. The catalog ships its own copy ofisSafeRegex(src/functions/safe_regex.ts, backed byredos-detector, the same check as the basic catalog'sregexfunction), so@a2ui/web_coredoes 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,RegexCaptureApiandRegexCaptureImplementation. Thepatterndescription states the portable subset.src/functions/regex_support.ts:isPortableRegexskips escaped characters and the contents of character classes.compileSafeRegexruns both checks and caches compiled patterns.mapTextValuedoes the string-or-array mapping.src/functions/safe_regex.ts,package.json: theisSafeRegexcopy and itsredos-detectordependency (already inyarn.lockthrough@a2ui/web_core).src/catalog.ts,src/index.ts,catalogs/mcp/v1/catalog.json: register, export and declaresplitandregexCapture.catalogs/mcp/v1/examples/mcp-text-tool-result.json: a Tavily-style search. The button callsspliton\n\nandregexCapturewith^Title: (.*)\nURL: (.*), thentransformDatadrops the records that did not match, names the fields and writes them to/results.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 incatalog.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 customparseCsvfunction in a host catalog.catalogs/mcp/README.mdhas a short version.CHANGELOG.md: addssplitandregexCaptureto 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.@a2ui/web_core../scripts/fix_format.sh: clean.