From 8b789d3ea42e15f549ad913152b0217a436fdac2 Mon Sep 17 00:00:00 2001 From: Alec Gutman <44984861+Chipagosfinest@users.noreply.github.com> Date: Mon, 17 Aug 2026 09:29:52 -0700 Subject: [PATCH] feat: add app homepage resource metadata --- specification/draft/apps.mdx | 18 +++++++++++++++++- src/generated/schema.json | 4 ++++ src/generated/schema.ts | 21 +++++++++++++++++++++ src/server/index.test.ts | 7 ++++++- src/spec.types.ts | 16 ++++++++++++++++ 5 files changed, 64 insertions(+), 2 deletions(-) diff --git a/specification/draft/apps.mdx b/specification/draft/apps.mdx index f2523f25e..85750f299 100644 --- a/specification/draft/apps.mdx +++ b/specification/draft/apps.mdx @@ -214,6 +214,19 @@ interface UIResourceMeta { * "www-example-com.oaiusercontent.com" */ domain?: string, + /** + * Public homepage URL for the app represented by this UI resource. + * + * This is distinct from `domain`, which configures the host-assigned sandbox + * origin. Hosts MAY expose this URL to help users learn more about or return + * to the app, but MUST treat it as an untrusted external link and apply their + * normal link validation, policy, and consent behavior. Its presence does not + * grant navigation permission or establish trust in the destination. + * + * @example + * "https://weather.example.com" + */ + homepage?: string, /** * Visual boundary preference * @@ -253,6 +266,7 @@ The resource content is returned via `resources/read`: clipboardWrite?: {}; // Request clipboard write access }; domain?: string; + homepage?: string; prefersBorder?: boolean; }; }; @@ -262,7 +276,7 @@ The resource content is returned via `resources/read`: #### Metadata Location -`UIResourceMeta` (CSP, permissions, domain, prefersBorder) may be provided on either or both: +`UIResourceMeta` (CSP, permissions, domain, homepage, prefersBorder) may be provided on either or both: - **`resources/list`:** On the resource entry's `_meta.ui` field. Useful as a static default that hosts can review at connection time. - **`resources/read`:** On each content item's `_meta.ui` field. Useful for per-response overrides or dynamic metadata that is only known at read time. @@ -311,6 +325,7 @@ Example: "connectDomains": ["https://api.openweathermap.org"], "resourceDomains": ["https://cdn.jsdelivr.net"] }, + "homepage": "https://weather.example.com", "prefersBorder": true } } @@ -328,6 +343,7 @@ Example: "connectDomains": ["https://api.openweathermap.org"], "resourceDomains": ["https://cdn.jsdelivr.net"] }, + "homepage": "https://weather.example.com", "prefersBorder": true } } diff --git a/src/generated/schema.json b/src/generated/schema.json index 6b66482c4..0ffd38d78 100644 --- a/src/generated/schema.json +++ b/src/generated/schema.json @@ -4268,6 +4268,10 @@ "description": "Dedicated origin for view sandbox.\n\nUseful when views need stable, dedicated origins for OAuth callbacks, CORS policies, or API key allowlists.\n\n**Host-dependent:** The format and validation rules for this field are determined by each host. Servers MUST consult host-specific documentation for the expected domain format. Common patterns include:\n- Hash-based subdomains (e.g., `{hash}.claudemcpcontent.com`)\n- URL-derived subdomains (e.g., `www-example-com.oaiusercontent.com`)\n\nIf omitted, host uses default sandbox origin (typically per-conversation).", "type": "string" }, + "homepage": { + "description": "Public homepage URL for the app represented by this UI resource.\n\nThis is distinct from `domain`, which configures the host-assigned sandbox\norigin. Hosts MAY expose this URL to\nhelp users learn more about or return to the app, but MUST treat it as an\nuntrusted external link and apply their normal link validation, policy,\nand consent behavior. Its presence does not grant navigation permission\nor establish trust in the destination.", + "type": "string" + }, "prefersBorder": { "description": "Visual boundary preference - true if view prefers a visible border.\n\nBoolean requesting whether a visible border and background is provided by the host. Specifying an explicit value for this is recommended because hosts' defaults may vary.\n\n- `true`: request visible border + background\n- `false`: request no visible border + background\n- omitted: host decides border", "type": "boolean" diff --git a/src/generated/schema.ts b/src/generated/schema.ts index a8bc1b6f5..db0f2f46b 100644 --- a/src/generated/schema.ts +++ b/src/generated/schema.ts @@ -664,6 +664,27 @@ export const McpUiResourceMetaSchema = z.object({ .describe( "Dedicated origin for view sandbox.\n\nUseful when views need stable, dedicated origins for OAuth callbacks, CORS policies, or API key allowlists.\n\n**Host-dependent:** The format and validation rules for this field are determined by each host. Servers MUST consult host-specific documentation for the expected domain format. Common patterns include:\n- Hash-based subdomains (e.g., `{hash}.claudemcpcontent.com`)\n- URL-derived subdomains (e.g., `www-example-com.oaiusercontent.com`)\n\nIf omitted, host uses default sandbox origin (typically per-conversation).", ), + /** + * @description Public homepage URL for the app represented by this UI resource. + * + * This is distinct from `domain`, which configures the host-assigned sandbox + * origin. Hosts MAY expose this URL to + * help users learn more about or return to the app, but MUST treat it as an + * untrusted external link and apply their normal link validation, policy, + * and consent behavior. Its presence does not grant navigation permission + * or establish trust in the destination. + * + * @example + * ```ts + * "https://weather.example.com" + * ``` + */ + homepage: z + .string() + .optional() + .describe( + "Public homepage URL for the app represented by this UI resource.\n\nThis is distinct from `domain`, which configures the host-assigned sandbox\norigin. Hosts MAY expose this URL to\nhelp users learn more about or return to the app, but MUST treat it as an\nuntrusted external link and apply their normal link validation, policy,\nand consent behavior. Its presence does not grant navigation permission\nor establish trust in the destination.", + ), /** * @description Visual boundary preference - true if view prefers a visible border. * diff --git a/src/server/index.test.ts b/src/server/index.test.ts index 1853cc05a..7f1fd5bbf 100644 --- a/src/server/index.test.ts +++ b/src/server/index.test.ts @@ -230,7 +230,9 @@ describe("registerAppResource", () => { "ui://test/view.html", { description: "A test resource", - _meta: { ui: {} }, + _meta: { + ui: { homepage: "https://example.com/apps/my-resource" }, + }, }, callback, ); @@ -240,6 +242,9 @@ describe("registerAppResource", () => { expect(capturedUri).toBe("ui://test/view.html"); expect(capturedConfig?.mimeType).toBe(RESOURCE_MIME_TYPE); expect(capturedConfig?.description).toBe("A test resource"); + expect(capturedConfig?._meta).toEqual({ + ui: { homepage: "https://example.com/apps/my-resource" }, + }); }); it("should allow custom MIME type to override default", () => { diff --git a/src/spec.types.ts b/src/spec.types.ts index bedc1f5c6..382f9d484 100644 --- a/src/spec.types.ts +++ b/src/spec.types.ts @@ -718,6 +718,22 @@ export interface McpUiResourceMeta { * ``` */ domain?: string; + /** + * @description Public homepage URL for the app represented by this UI resource. + * + * This is distinct from `domain`, which configures the host-assigned sandbox + * origin. Hosts MAY expose this URL to + * help users learn more about or return to the app, but MUST treat it as an + * untrusted external link and apply their normal link validation, policy, + * and consent behavior. Its presence does not grant navigation permission + * or establish trust in the destination. + * + * @example + * ```ts + * "https://weather.example.com" + * ``` + */ + homepage?: string; /** * @description Visual boundary preference - true if view prefers a visible border. *