Skip to content

[MCP] Add the v1 MCP catalog with the McpApp component schema - #2945

Merged
josemontespg merged 1 commit into
mainfrom
catalogs-mcp-app-schema
Oct 13, 2026
Merged

josemontespg merged 1 commit into
mainfrom
catalogs-mcp-app-schema

Conversation

@josemontespg

@josemontespg josemontespg commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds catalogs/mcp/v1/catalog.json (https://a2ui.org/specification/v1_0/catalogs/mcp/catalog.json), the v1.0 version of the MCP catalog, on top of the v1.0 protocol schemas. It carries callMcpTool and the five data functions forward from the v0.9 root catalog (catalogs/mcp/catalog.json, kept intact) and adds the McpApp component schema for rendering interactive MCP Apps (io.modelcontextprotocol/ui) inside a double-iframe sandbox.

Changes

  • catalogs/mcp/v1/catalog.json: v1.0 catalog (protocolVersion: "1.0") with McpApp (htmlContent, title, allowedTools, allowedFunctions, data.paths, csp, permissions) and callMcpTool + data functions. The file follows the v1.0 Catalog Schema Rules: McpApp is a flat component (no ComponentCommon allOf, no unevaluatedProperties), helper shapes are inlined into its properties, refs are relative common_types.json#/$defs/..., and $defs holds only anyComponent (with discriminator) and anyFunction. Moving the data functions to a separate transformations catalog is tracked in Split the data transformation functions out of the MCP catalog into a transformations catalog #3153.
  • specification/v1_0/test/run_tests.py: registers catalogs/mcp/v1/catalog.json in the structural, identifier and $ref target checks.
  • catalogs/mcp/v1/mcp_app_specification.md: moves the McpApp component specification from the Angular MCP calculator sample and updates it for v1.0 and @modelcontextprotocol/ext-apps fields.
  • conformance/core/catalog.yaml: registers catalogs/mcp/v1/catalog.json in test_v10_published_mcp_catalog_is_self_contained (required by test_catalog_conformance_covers_published_catalogs for every catalogs/*/v*/catalog.json).
  • .github/workflows/docs.yml, catalogs/mcp/README.md, catalogs/README.md: publishes catalogs/mcp/v1 at /specification/v1_0/catalogs/mcp/ and documents the v1.0 catalog layout.

Verification

  • python3 specification/scripts/validate.py
  • python3 specification/v1_0/test/run_tests.py
  • pytest tests/test_conformance.py in python/a2ui_core
  • ./scripts/fix_format.sh --check and python3 scripts/fix_licenses.py --check

Related to #3157

@github-project-automation github-project-automation Bot moved this to Todo in A2UI Oct 1, 2026
@josemontespg
josemontespg force-pushed the catalogs-mcp-app-schema branch from 062292a to 5aa7661 Compare October 1, 2026 21:56
@josemontespg
josemontespg added this pull request to stack #2946 October 1, 2026 21:56

@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 introduces the A2UI iframe catalog (@a2ui/catalog-iframe), enabling the embedding of sandboxed web applications via the WebAppFrameUrl and WebAppFrameSrcdoc components, and updates the MCP catalog to declare the McpApp component. The review feedback highlights several important improvements and bug fixes: resolving a potential runtime crash in the link interceptor script by checking if e.target is an Element before calling closest(), avoiding intrusive thread-blocking alerts in the security self-test by checking win.top?.location.href instead, ensuring that stale iframe content is cleared when a resource becomes null, and explicitly removing unused src or srcdoc attributes to prevent browser precedence conflicts when switching resource types.

I am having trouble creating individual review comments. Click here to see my feedback.

typescript/catalogs/shared/sandbox/sandbox.ts (327-333)

medium

When updating the resource in the proxy iframe, if the previous resource was loaded via srcdoc and the new resource is loaded via src (or vice versa), the browser's precedence rules for srcdoc over src will prevent the new src from loading. To ensure the new resource loads correctly, we should explicitly remove the unused attribute (src or srcdoc) before setting the new one.

        if (html !== undefined) {
          inner.removeAttribute('src');
          inner.onload = sendInit;
          inner.srcdoc = html;
        } else if (url !== undefined) {
          inner.removeAttribute('srcdoc');
          inner.onload = sendInit;
          inner.src = url;
        }

typescript/catalogs/shared/sandbox/sandbox.ts (284-296)

medium

The security self-test uses win.top?.alert(...) to check if the top window is reachable. This is highly intrusive because if the sandbox is misconfigured during development, it pops up a modal alert dialog that blocks the main thread and requires user interaction to dismiss. We can perform a non-intrusive check by accessing a property on the top window (such as win.top?.location.href), which will throw a SecurityError if securely isolated (cross-origin) without showing any modal dialogs.

  if (!disableSelfTest) {
    // Security self-test: a proxy that can call into the top window is not isolated from it.
    let topReachable = false;
    try {
      // Accessing a property on the top window throws a SecurityError if securely isolated (cross-origin).
      const _ = win.top?.location.href;
      topReachable = true;
    } catch {
      // Cross-origin access threw, which is the expected outcome.
    }
    if (topReachable) {
      throw new Error('The sandbox is not setup securely.');
    }
  }

typescript/catalogs/shared/sandbox/sandboxed_frame_element.ts (248-256)

medium

If resource is null (e.g., because htmlContent or url was cleared), sendResource returns early and does not notify the proxy. This leaves the stale previous content visible in the iframe instead of clearing it. We should send an empty resource (e.g., {html: ''}) to the proxy when resource is null so it can clear the inner iframe.

  private sendResource(resource: SandboxResource | null): void {
    const session = this.session;
    if (!session) {
      return;
    }
    session.resource = resource;
    if (!this.proxyReady) {
      return;
    }
    sendSandboxResourceReady({
      frame: session.frame,
      sandboxOrigin: session.sandboxOrigin,
      protocol: this.sandboxProtocol,
      resource: resource ?? {html: ''},
    });
  }

typescript/catalogs/iframe/src/components/srcdoc_content.ts (43)

medium

In LINK_INTERCEPTOR_SCRIPT, e.target.closest('a') is called directly. However, e.target can be a Text node or Document which does not have the closest method, leading to potential runtime TypeError crashes. We should defensively check if e.target is an Element (e.g., e.target instanceof Element) before calling closest.

        const anchor = (e.target instanceof Element) ? e.target.closest('a') : null;

@josemontespg
josemontespg force-pushed the catalogs-mcp-app-schema branch 2 times, most recently from fcef136 to 8670800 Compare October 1, 2026 22:41
@josemontespg josemontespg changed the title feat(catalogs): add the McpApp component schema to the MCP catalog feat(catalogs): add the v1 MCP catalog with the McpApp component schema Oct 2, 2026
@josemontespg
josemontespg removed this pull request from stack #2946 October 2, 2026 00:35
@josemontespg
josemontespg force-pushed the catalogs-mcp-app-schema branch from 8670800 to ce977a6 Compare October 2, 2026 00:35
@josemontespg
josemontespg force-pushed the catalogs-mcp-app-schema branch from ce977a6 to f12d965 Compare October 2, 2026 00:36
@josemontespg
josemontespg added this pull request to stack #2951 October 2, 2026 00:36
@josemontespg
josemontespg force-pushed the catalogs-mcp-app-schema branch from f12d965 to 17556ef Compare October 2, 2026 00:54
@josemontespg
josemontespg force-pushed the catalogs-mcp-app-schema branch from 17556ef to 2b63402 Compare October 2, 2026 18:26
@josemontespg
josemontespg force-pushed the catalogs-mcp-app-schema branch from 2b63402 to 76a31ee Compare October 2, 2026 18:51
@josemontespg
josemontespg force-pushed the catalogs-mcp-app-schema branch 2 times, most recently from ef8b7cd to b9ef6f2 Compare October 2, 2026 20:50
@josemontespg
josemontespg force-pushed the catalogs-mcp-app-schema branch from b9ef6f2 to 8f4b379 Compare October 5, 2026 18:48
@josemontespg
josemontespg force-pushed the catalogs-mcp-app-schema branch from 8f4b379 to 261d05d Compare October 5, 2026 19:42
@josemontespg
josemontespg removed this pull request from stack #2951 October 5, 2026 20:45
@josemontespg
josemontespg force-pushed the catalogs-mcp-app-schema branch from e566bf4 to 5a5e4d4 Compare October 9, 2026 18:18
@josemontespg
josemontespg removed this pull request from stack #3057 October 9, 2026 18:19
@josemontespg
josemontespg added this pull request to stack #3112 October 9, 2026 18:19
@josemontespg
josemontespg removed this pull request from stack #3112 October 9, 2026 18:40
@josemontespg
josemontespg changed the base branch from main to catalogs-mcp-conformance October 9, 2026 18:43
@josemontespg
josemontespg force-pushed the catalogs-mcp-app-schema branch from 5a5e4d4 to 07944f4 Compare October 9, 2026 18:49
@josemontespg
josemontespg added this pull request to stack #3114 October 9, 2026 18:49
@josemontespg
josemontespg force-pushed the catalogs-mcp-app-schema branch from 07944f4 to 31af1ec Compare October 9, 2026 19:09
@josemontespg
josemontespg deleted the branch main October 9, 2026 19:11
@github-project-automation github-project-automation Bot moved this from Todo to Done in A2UI Oct 9, 2026
@josemontespg
josemontespg removed this pull request from stack #3114 October 9, 2026 19:12
@josemontespg josemontespg reopened this Oct 9, 2026
@josemontespg
josemontespg changed the base branch from catalogs-mcp-conformance to main October 9, 2026 19:12
@josemontespg
josemontespg added this pull request to stack #3115 October 9, 2026 19:12
@josemontespg
josemontespg marked this pull request as ready for review October 9, 2026 19:32
@josemontespg
josemontespg requested a review from ditman October 9, 2026 19:32
@josemontespg
josemontespg force-pushed the catalogs-mcp-app-schema branch from 31af1ec to cc55b19 Compare October 12, 2026 18:57

@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.

Thanks for putting this together! Two main pieces of feedback:

1. Register catalogs/mcp/v1/catalog.json in the v1.0 spec test runner

python3 specification/scripts/validate.py only validates message example files, so catalogs/mcp/v1/catalog.json isn't currently being checked against the v1.0 Catalog Schema Rules (specification/v1_0/docs/a2ui_protocol.md:552-625) or catalog_definition.json.

Please add ("catalogs/mcp/v1/catalog.json", os.path.join(REPO_ROOT, "catalogs/mcp/v1/catalog.json")) to catalogs_to_validate in specification/v1_0/test/run_tests.py inside:

And run:

python3 specification/v1_0/test/run_tests.py

To verify the test setup is working, you should initially see failures like these on the current catalogs/mcp/v1/catalog.json:

Validating catalog structural integrity against catalog_definition.json...
  [FAIL] catalogs/mcp/v1/catalog.json
  - instancePath: '/$defs', params: { additionalProperty: 'JsonSchema' } (and AllowedFunctions, DataPaths, McpAppCsp, McpAppPermissions)
  - instancePath: '/functions/callMcpTool', params: { unevaluatedProperty: 'unevaluatedProperties' }

Validating catalog $ref targets against the allowed target list...
  [FAIL] catalogs/mcp/v1/catalog.json
  - Disallowed $ref target: 'https://a2ui.org/specification/v1_0/common_types.json#/$defs/ComponentCommon'
  - Disallowed $ref target: 'https://a2ui.org/specification/v1_0/common_types.json#/$defs/DynamicString'
  - Disallowed $ref target: '#/$defs/AllowedFunctions'

(Fixing those to follow Rules 2–6—inlining the helper schemas into McpApp.properties, using relative common_types.json#/$defs/... refs, removing the redundant ComponentCommon wrapper and component/function-level unevaluatedProperties: false, and adding discriminator to anyComponent—should also let you drop the PUBLISHED_CATALOG_NOT_SELF_CONTAINED divergence in conformance_test.mjs.)


2. Separate McpApp / MCP catalog responsibilities from sample-specific data transformations

The MCP catalog (catalogs/mcp/v1/catalog.json:91-225) currently mixes MCP functionality (McpApp, callMcpTool) with general string manipulation (split), RE2 regex handling (regexCapture, regexReplace), JMESPath querying (jmespath), and path writes (updateDataModel).

Those five functions are not MCP specific. They were added for the a2ui-over-mcp-filesystem sample to parse raw plain-text CLI responses on the client. Bundling them into the shared MCP catalog:

  • Forces the shared @a2ui/catalog-mcp package (typescript/catalogs/mcp/package.json:31-32) and every consumer (even a2ui-over-mcp-recipe, which doesn't use them) to pull in jmespath and re2js dependencies.
  • Would require every v1.0 SDK/renderer implementing the MCP catalog across languages to ship JMESPath and RE2 engines, while bloating LLM prompts with string-scraping schemas that McpApp (data.paths) and callMcpTool (targetDataPath) don't need.

Proposed split:

  1. In this PR: Keep catalogs/mcp/v1/catalog.json minimal and strictly scoped to MCP (McpApp and callMcpTool only).
  2. In the community sample: Move the string/RE2/JMESPath pipeline (jmespath, split, regexCapture, regexReplace, updateDataModel) and the jmespath / re2js dependencies out of typescript/catalogs/mcp/ and into samples/community/mcp/a2ui-over-mcp-filesystem (which already registers a custom composite catalog), so the shared MCP catalog and McpApp stay clean.

@josemontespg

Copy link
Copy Markdown
Collaborator Author

Thanks for the review.

Spec test registration. catalogs/mcp/v1/catalog.json is now in the three run_tests.py lists and follows rules 2 to 6. McpApp is a flat object without the ComponentCommon allOf and without unevaluatedProperties. The helper schemas are inlined into its properties, and the free-form JsonSchema is a plain {"type": "object"}. All refs are relative (common_types.json#/$defs/...), and $defs only holds anyComponent, with a discriminator, and anyFunction. run_tests.py and validate.py pass. I made the same changes to catalogs/iframe/catalog.json in #2795.

The PUBLISHED_CATALOG_NOT_SELF_CONTAINED entry stays. That test runs on the schema web_core generates, and the only ref that leaves the document is in the copied FunctionCall standard def (standard_defs.ts:306): catalog.json#/$defs/anyFunction, taken verbatim from common_types.json. The catalog cannot fix this itself, since rule 2 allows nothing but anyComponent and anyFunction in $defs; the basic v1 catalog fails the same test for the same reason. The fix is in the generator: rewrite that ref to #/$defs/anyFunction when it embeds the standard defs. I left that out of this stack.

Data functions. Agreed, the five data functions are not MCP specific. They already ship in the v0.9 catalog, hosts register functions one by one, and the Composer vendors all six from this catalog today, so I would move them in a follow-up to a separate transformations catalog (functions for function calling and for processing arguments and results) rather than into one sample. Tracked in #3153. This PR keeps the function set unchanged.

@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.

About the extra functions:

They already ship in the v0.9 catalog, hosts register functions one by one, and the Composer vendors all six from this catalog today [...]

This is not accurate. They ship as part of a "community sample" app, introduced 3 weeks ago in #2650, but this PR makes them "first class citizens" of the "mcp/v1" catalog.

They rrrreally shouldn't land here, especially if we're not planning on addressing the new catalog as a fast follow.

Comment thread catalogs/mcp/v1/mcp_app_specification.md Outdated
Add catalogs/mcp/v1/catalog.json, the MCP catalog for A2UI protocol v1.0: the
existing callMcpTool and data functions plus the McpApp component, whose
allowedFunctions is a map of function name to JSON Schema. Move the component
specification next to it, add examples, validate them in
specification/scripts/validate.py, and publish the catalog at its $id URL from
the docs workflow. The v0.9 catalog at catalogs/mcp/catalog.json is unchanged.
@josemontespg
josemontespg force-pushed the catalogs-mcp-app-schema branch from ba71a3d to 5a87480 Compare October 13, 2026 19:39
@josemontespg

josemontespg commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator Author

You are right, I overstated that: the functions arrived with #2650 three weeks ago for the filesystem sample, and this PR is what would have made them part of a published v1 catalog.

Following your comment on #3153 these extra functions will be extracted in #3171 as part of this stack. This catalog won't be released until then

@josemontespg
josemontespg merged commit 686f383 into main Oct 13, 2026
43 checks passed
@josemontespg
josemontespg deleted the catalogs-mcp-app-schema branch October 13, 2026 19:48
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.

2 participants