skybridge 0.0.0-dev.ed005a8 → 0.0.0-dev.ed38f16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/cli/run-plain.js +2 -1
  2. package/dist/cli/run-plain.js.map +1 -1
  3. package/dist/cli/telemetry.js +2 -0
  4. package/dist/cli/telemetry.js.map +1 -1
  5. package/dist/cli/use-nodemon.d.ts +11 -1
  6. package/dist/cli/use-nodemon.js +19 -5
  7. package/dist/cli/use-nodemon.js.map +1 -1
  8. package/dist/commands/dev.js +3 -3
  9. package/dist/commands/dev.js.map +1 -1
  10. package/dist/server/auth/providers/custom.d.ts +4 -2
  11. package/dist/server/auth/providers/custom.js.map +1 -1
  12. package/dist/server/auth/providers/workos.d.ts +3 -2
  13. package/dist/server/auth/providers/workos.js +2 -1
  14. package/dist/server/auth/providers/workos.js.map +1 -1
  15. package/dist/server/auth/verify.d.ts +5 -3
  16. package/dist/server/auth/verify.js.map +1 -1
  17. package/dist/server/auth/verify.test.js +16 -0
  18. package/dist/server/auth/verify.test.js.map +1 -1
  19. package/dist/server/index.d.ts +1 -1
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/register-tool.test.js +50 -0
  22. package/dist/server/register-tool.test.js.map +1 -1
  23. package/dist/server/server.d.ts +76 -1
  24. package/dist/server/server.js +45 -6
  25. package/dist/server/server.js.map +1 -1
  26. package/dist/web/bridges/adaptor.d.ts +6 -2
  27. package/dist/web/bridges/adaptor.js +47 -10
  28. package/dist/web/bridges/adaptor.js.map +1 -1
  29. package/dist/web/bridges/mcp-app/bridge.d.ts +1 -1
  30. package/dist/web/bridges/mcp-app/bridge.js +9 -2
  31. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  32. package/dist/web/bridges/mcp-app/types.d.ts +26 -3
  33. package/dist/web/bridges/mcp-app/types.js.map +1 -1
  34. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +1 -1
  35. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  36. package/dist/web/bridges/types.d.ts +16 -2
  37. package/dist/web/bridges/types.js.map +1 -1
  38. package/dist/web/data-llm.js +3 -2
  39. package/dist/web/data-llm.js.map +1 -1
  40. package/dist/web/data-llm.test.js +6 -0
  41. package/dist/web/data-llm.test.js.map +1 -1
  42. package/dist/web/generate-helpers.d.ts +2 -2
  43. package/dist/web/generate-helpers.js +2 -2
  44. package/dist/web/generate-helpers.js.map +1 -1
  45. package/dist/web/hooks/index.d.ts +3 -1
  46. package/dist/web/hooks/index.js +3 -1
  47. package/dist/web/hooks/index.js.map +1 -1
  48. package/dist/web/hooks/openai-extensions.test.d.ts +1 -0
  49. package/dist/web/hooks/openai-extensions.test.js +104 -0
  50. package/dist/web/hooks/openai-extensions.test.js.map +1 -0
  51. package/dist/web/hooks/use-call-tool.d.ts +2 -2
  52. package/dist/web/hooks/use-call-tool.js +15 -10
  53. package/dist/web/hooks/use-call-tool.js.map +1 -1
  54. package/dist/web/hooks/use-call-tool.test.js +7 -0
  55. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  56. package/dist/web/hooks/use-deep-link.d.ts +22 -0
  57. package/dist/web/hooks/use-deep-link.js +60 -0
  58. package/dist/web/hooks/use-deep-link.js.map +1 -0
  59. package/dist/web/hooks/use-host.d.ts +27 -5
  60. package/dist/web/hooks/use-host.js +20 -5
  61. package/dist/web/hooks/use-host.js.map +1 -1
  62. package/dist/web/hooks/use-model-context.d.ts +42 -0
  63. package/dist/web/hooks/use-model-context.js +63 -0
  64. package/dist/web/hooks/use-model-context.js.map +1 -0
  65. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"verify.test.js","sourceRoot":"","sources":["../../../src/server/auth/verify.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @vitest-environment node\nimport http from \"node:http\";\nimport * as jose from \"jose\";\nimport { afterEach, describe, expect, it } from \"vitest\";\nimport { createJwksVerifier } from \"./verify.js\";\n\nconst ISSUER = \"https://issuer.test\";\nconst AUDIENCE = \"api://default\";\n\nlet jwksServer: http.Server | undefined;\nafterEach(() => jwksServer?.close());\n\nasync function startJwks() {\n const { publicKey, privateKey } = await jose.generateKeyPair(\"RS256\");\n const jwk = {\n ...(await jose.exportJWK(publicKey)),\n kid: \"test-key\",\n alg: \"RS256\",\n use: \"sig\",\n };\n const server = http.createServer((_req, res) => {\n res.setHeader(\"content-type\", \"application/json\");\n res.end(JSON.stringify({ keys: [jwk] }));\n });\n await new Promise<void>((resolve) => server.listen(0, resolve));\n jwksServer = server;\n const port = (server.address() as { port: number }).port;\n return { privateKey, jwksUri: `http://localhost:${port}/jwks` };\n}\n\nfunction sign(\n key: CryptoKey,\n claims: Record<string, unknown>,\n opts: { issuer?: string; audience?: string; expiresAt?: string } = {},\n) {\n return new jose.SignJWT(claims)\n .setProtectedHeader({ alg: \"RS256\", kid: \"test-key\" })\n .setIssuer(opts.issuer ?? ISSUER)\n .setAudience(opts.audience ?? AUDIENCE)\n .setExpirationTime(opts.expiresAt ?? \"1h\")\n .sign(key);\n}\n\nit(\"rejects a config with no issuer\", () => {\n expect(() => createJwksVerifier({ issuer: \"\" })).toThrow(\n /requires an `issuer`/,\n );\n});\n\ndescribe(\"createJwksVerifier\", () => {\n it(\"verifies a valid token and maps claims to AuthInfo\", async () => {\n const { privateKey, jwksUri } = await startJwks();\n const verifier = createJwksVerifier({\n issuer: ISSUER,\n audience: AUDIENCE,\n jwksUri,\n });\n const token = await sign(privateKey, {\n client_id: \"client-1\",\n scope: \"openid email\",\n sub: \"user-1\",\n email: \"a@b.test\",\n });\n\n const auth = await verifier.verifyAccessToken(token);\n\n expect(auth.token).toBe(token);\n expect(auth.clientId).toBe(\"client-1\");\n expect(auth.scopes).toEqual([\"openid\", \"email\"]);\n expect(auth.extra?.subject).toBe(\"user-1\");\n expect(auth.extra?.email).toBe(\"a@b.test\");\n });\n\n it(\"rejects a token with the wrong audience\", async () => {\n const { privateKey, jwksUri } = await startJwks();\n const verifier = createJwksVerifier({\n issuer: ISSUER,\n audience: AUDIENCE,\n jwksUri,\n });\n const token = await sign(\n privateKey,\n { client_id: \"c\" },\n { audience: \"api://other\" },\n );\n\n await expect(verifier.verifyAccessToken(token)).rejects.toThrow(\n /Token verification failed/,\n );\n });\n\n // jose quotes claim names — `\"exp\" claim timestamp check failed`, `unexpected\n // \"aud\" claim value`. Those quotes would close `error_description=\"…\"` early,\n // dropping the `resource_metadata` that MCP clients start discovery from.\n it.each([\n [\n \"an expired token\",\n (key: CryptoKey) => sign(key, {}, { expiresAt: \"-1h\" }),\n // jose: `\"exp\" claim timestamp check failed`\n \"Token verification failed: exp claim timestamp check failed\",\n ],\n [\n \"a token issued for another resource\",\n (key: CryptoKey) => sign(key, {}, { audience: \"api://other\" }),\n // jose: `unexpected \"aud\" claim value`\n \"Token verification failed: unexpected aud claim value\",\n ],\n [\n \"a token from another issuer\",\n (key: CryptoKey) => sign(key, {}, { issuer: \"https://other.test\" }),\n // jose: `unexpected \"iss\" claim value`\n \"Token verification failed: unexpected iss claim value\",\n ],\n [\n \"a token carrying no aud claim\",\n (key: CryptoKey) =>\n new jose.SignJWT({})\n .setProtectedHeader({ alg: \"RS256\", kid: \"test-key\" })\n .setIssuer(ISSUER)\n .setExpirationTime(\"1h\")\n .sign(key),\n // jose: `missing required \"aud\" claim`\n \"Token verification failed: missing required aud claim\",\n ],\n ])(\n \"keeps the message safe inside the challenge for %s\",\n async (_, mint, expected) => {\n const { privateKey, jwksUri } = await startJwks();\n const verifier = createJwksVerifier({\n issuer: ISSUER,\n audience: AUDIENCE,\n jwksUri,\n });\n\n const error = await verifier\n .verifyAccessToken(await mint(privateKey))\n .then(\n () => undefined,\n (e: Error) => e,\n );\n\n expect(error?.message).toBe(expected);\n // OAuth 2.1 §5.3.1: %x20-21 / %x23-5B / %x5D-7E and nothing else.\n expect(error?.message).not.toMatch(/[^\\x20-\\x21\\x23-\\x5B\\x5D-\\x7E]/);\n },\n );\n\n it(\"skips the aud check when no audience is configured (e.g. Clerk)\", async () => {\n const { privateKey, jwksUri } = await startJwks();\n const verifier = createJwksVerifier({ issuer: ISSUER, jwksUri });\n // Clerk-shaped access token: no `aud` claim at all.\n const token = await new jose.SignJWT({ client_id: \"c\", sub: \"u\" })\n .setProtectedHeader({ alg: \"RS256\", kid: \"test-key\" })\n .setIssuer(ISSUER)\n .setExpirationTime(\"1h\")\n .sign(privateKey);\n\n const auth = await verifier.verifyAccessToken(token);\n expect(auth.clientId).toBe(\"c\");\n expect(auth.extra?.subject).toBe(\"u\");\n });\n\n it(\"parses array scope claims and trims extra whitespace\", async () => {\n const { privateKey, jwksUri } = await startJwks();\n const verifier = createJwksVerifier({\n issuer: ISSUER,\n audience: AUDIENCE,\n jwksUri,\n });\n\n const arrayToken = await sign(privateKey, {\n scope: [\"openid\", \"email\"],\n });\n expect((await verifier.verifyAccessToken(arrayToken)).scopes).toEqual([\n \"openid\",\n \"email\",\n ]);\n\n const messyToken = await sign(privateKey, { scope: \" openid email \" });\n expect((await verifier.verifyAccessToken(messyToken)).scopes).toEqual([\n \"openid\",\n \"email\",\n ]);\n });\n});\n"]}
1
+ {"version":3,"file":"verify.test.js","sourceRoot":"","sources":["../../../src/server/auth/verify.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @vitest-environment node\nimport http from \"node:http\";\nimport * as jose from \"jose\";\nimport { afterEach, describe, expect, it } from \"vitest\";\nimport { createJwksVerifier } from \"./verify.js\";\n\nconst ISSUER = \"https://issuer.test\";\nconst AUDIENCE = \"api://default\";\n\nlet jwksServer: http.Server | undefined;\nafterEach(() => jwksServer?.close());\n\nasync function startJwks() {\n const { publicKey, privateKey } = await jose.generateKeyPair(\"RS256\");\n const jwk = {\n ...(await jose.exportJWK(publicKey)),\n kid: \"test-key\",\n alg: \"RS256\",\n use: \"sig\",\n };\n const server = http.createServer((_req, res) => {\n res.setHeader(\"content-type\", \"application/json\");\n res.end(JSON.stringify({ keys: [jwk] }));\n });\n await new Promise<void>((resolve) => server.listen(0, resolve));\n jwksServer = server;\n const port = (server.address() as { port: number }).port;\n return { privateKey, jwksUri: `http://localhost:${port}/jwks` };\n}\n\nfunction sign(\n key: CryptoKey,\n claims: Record<string, unknown>,\n opts: { issuer?: string; audience?: string; expiresAt?: string } = {},\n) {\n return new jose.SignJWT(claims)\n .setProtectedHeader({ alg: \"RS256\", kid: \"test-key\" })\n .setIssuer(opts.issuer ?? ISSUER)\n .setAudience(opts.audience ?? AUDIENCE)\n .setExpirationTime(opts.expiresAt ?? \"1h\")\n .sign(key);\n}\n\nit(\"rejects a config with no issuer\", () => {\n expect(() => createJwksVerifier({ issuer: \"\" })).toThrow(\n /requires an `issuer`/,\n );\n});\n\ndescribe(\"createJwksVerifier\", () => {\n it(\"verifies a valid token and maps claims to AuthInfo\", async () => {\n const { privateKey, jwksUri } = await startJwks();\n const verifier = createJwksVerifier({\n issuer: ISSUER,\n audience: AUDIENCE,\n jwksUri,\n });\n const token = await sign(privateKey, {\n client_id: \"client-1\",\n scope: \"openid email\",\n sub: \"user-1\",\n email: \"a@b.test\",\n });\n\n const auth = await verifier.verifyAccessToken(token);\n\n expect(auth.token).toBe(token);\n expect(auth.clientId).toBe(\"client-1\");\n expect(auth.scopes).toEqual([\"openid\", \"email\"]);\n expect(auth.extra?.subject).toBe(\"user-1\");\n expect(auth.extra?.email).toBe(\"a@b.test\");\n });\n\n it(\"rejects a token with the wrong audience\", async () => {\n const { privateKey, jwksUri } = await startJwks();\n const verifier = createJwksVerifier({\n issuer: ISSUER,\n audience: AUDIENCE,\n jwksUri,\n });\n const token = await sign(\n privateKey,\n { client_id: \"c\" },\n { audience: \"api://other\" },\n );\n\n await expect(verifier.verifyAccessToken(token)).rejects.toThrow(\n /Token verification failed/,\n );\n });\n\n // jose quotes claim names — `\"exp\" claim timestamp check failed`, `unexpected\n // \"aud\" claim value`. Those quotes would close `error_description=\"…\"` early,\n // dropping the `resource_metadata` that MCP clients start discovery from.\n it.each([\n [\n \"an expired token\",\n (key: CryptoKey) => sign(key, {}, { expiresAt: \"-1h\" }),\n // jose: `\"exp\" claim timestamp check failed`\n \"Token verification failed: exp claim timestamp check failed\",\n ],\n [\n \"a token issued for another resource\",\n (key: CryptoKey) => sign(key, {}, { audience: \"api://other\" }),\n // jose: `unexpected \"aud\" claim value`\n \"Token verification failed: unexpected aud claim value\",\n ],\n [\n \"a token from another issuer\",\n (key: CryptoKey) => sign(key, {}, { issuer: \"https://other.test\" }),\n // jose: `unexpected \"iss\" claim value`\n \"Token verification failed: unexpected iss claim value\",\n ],\n [\n \"a token carrying no aud claim\",\n (key: CryptoKey) =>\n new jose.SignJWT({})\n .setProtectedHeader({ alg: \"RS256\", kid: \"test-key\" })\n .setIssuer(ISSUER)\n .setExpirationTime(\"1h\")\n .sign(key),\n // jose: `missing required \"aud\" claim`\n \"Token verification failed: missing required aud claim\",\n ],\n ])(\n \"keeps the message safe inside the challenge for %s\",\n async (_, mint, expected) => {\n const { privateKey, jwksUri } = await startJwks();\n const verifier = createJwksVerifier({\n issuer: ISSUER,\n audience: AUDIENCE,\n jwksUri,\n });\n\n const error = await verifier\n .verifyAccessToken(await mint(privateKey))\n .then(\n () => undefined,\n (e: Error) => e,\n );\n\n expect(error?.message).toBe(expected);\n // OAuth 2.1 §5.3.1: %x20-21 / %x23-5B / %x5D-7E and nothing else.\n expect(error?.message).not.toMatch(/[^\\x20-\\x21\\x23-\\x5B\\x5D-\\x7E]/);\n },\n );\n\n it(\"accepts a token minted for any of several audiences\", async () => {\n const { privateKey, jwksUri } = await startJwks();\n // A server reached at a local port and through a dev tunnel: tokens carry\n // whichever resource the client discovered.\n const verifier = createJwksVerifier({\n issuer: ISSUER,\n audience: [AUDIENCE, \"https://tunnel.test\"],\n jwksUri,\n });\n\n const tunnelToken = await sign(\n privateKey,\n { client_id: \"c\" },\n { audience: \"https://tunnel.test\" },\n );\n expect((await verifier.verifyAccessToken(tunnelToken)).clientId).toBe(\"c\");\n\n const localToken = await sign(privateKey, { client_id: \"c\" });\n expect((await verifier.verifyAccessToken(localToken)).clientId).toBe(\"c\");\n\n const otherToken = await sign(privateKey, {}, { audience: \"api://other\" });\n await expect(verifier.verifyAccessToken(otherToken)).rejects.toThrow(\n /unexpected {2}aud {2}claim value/,\n );\n });\n\n it(\"skips the aud check when no audience is configured (e.g. Clerk)\", async () => {\n const { privateKey, jwksUri } = await startJwks();\n const verifier = createJwksVerifier({ issuer: ISSUER, jwksUri });\n // Clerk-shaped access token: no `aud` claim at all.\n const token = await new jose.SignJWT({ client_id: \"c\", sub: \"u\" })\n .setProtectedHeader({ alg: \"RS256\", kid: \"test-key\" })\n .setIssuer(ISSUER)\n .setExpirationTime(\"1h\")\n .sign(privateKey);\n\n const auth = await verifier.verifyAccessToken(token);\n expect(auth.clientId).toBe(\"c\");\n expect(auth.extra?.subject).toBe(\"u\");\n });\n\n it(\"parses array scope claims and trims extra whitespace\", async () => {\n const { privateKey, jwksUri } = await startJwks();\n const verifier = createJwksVerifier({\n issuer: ISSUER,\n audience: AUDIENCE,\n jwksUri,\n });\n\n const arrayToken = await sign(privateKey, {\n scope: [\"openid\", \"email\"],\n });\n expect((await verifier.verifyAccessToken(arrayToken)).scopes).toEqual([\n \"openid\",\n \"email\",\n ]);\n\n const messyToken = await sign(privateKey, { scope: \" openid email \" });\n expect((await verifier.verifyAccessToken(messyToken)).scopes).toEqual([\n \"openid\",\n \"email\",\n ]);\n });\n});\n"]}
@@ -16,7 +16,7 @@ export { FileRef } from "./file-ref.js";
16
16
  export type { AnyToolRegistry, InferTools, ToolInput, ToolNames, ToolOutput, ToolResponseMetadata, } from "./inferUtilityTypes.js";
17
17
  export type { McpExtra, McpMethodString, McpMiddlewareFilter, McpMiddlewareFn, McpResultFor, McpTypedMiddlewareFn, McpWildcard, } from "./middleware.js";
18
18
  export { getToolError } from "./middleware.js";
19
- export type { HandlerContent, JsonOptions, KnownToolMeta, McpServerTypes, SecurityScheme, SkybridgeServerOptions, ToolDef, ToolMeta, ViewConfig, ViewCsp, ViewName, ViewNameRegistry, } from "./server.js";
19
+ export type { HandlerContent, JsonOptions, KnownToolMeta, McpServerTypes, OpenAIDisplayMode, OpenAIEntrypoint, OpenAIQuickAction, OpenAIToolConfig, SecurityScheme, SkybridgeServerOptions, ToolDef, ToolMeta, ViewConfig, ViewCsp, ViewName, ViewNameRegistry, } from "./server.js";
20
20
  export { __setBuildManifest, __setSkillsManifest, McpServer, normalizeContent, } from "./server.js";
21
21
  export { discoverSkills, type Skill, type SkillsManifest, } from "./skills.js";
22
22
  export { viewsDevServer } from "./viewsDevServer.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,aAAa,EACb,iBAAiB,GAElB,MAAM,8BAA8B,CAAC;AAMtC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,OAAO,EAAoB,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAoB,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC5E,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAEL,eAAe,GAChB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAqB,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,EAAqB,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,EACL,kBAAkB,GAEnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAKL,qBAAqB,EACrB,UAAU,EACV,cAAc,EACd,kBAAkB,EAClB,iBAAiB,GAElB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,EACL,gBAAgB,EAChB,KAAK,EACL,YAAY,EACZ,IAAI,GACL,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAkBxC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAe/C,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,SAAS,EACT,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,cAAc,GAGf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["export {\n inputRequired,\n ProtocolError,\n ProtocolErrorCode,\n type StandardSchemaWithJSON,\n} from \"@modelcontextprotocol/server\";\nexport type {\n SkybridgeConfig,\n SkybridgeHandler,\n SkybridgeOAuthInput,\n} from \"./app.js\";\nexport { Skybridge } from \"./app.js\";\nexport type { OAuthConfig, OAuthProvider } from \"./auth/index.js\";\nexport { type Auth0Claims, auth0Provider } from \"./auth/providers/auth0.js\";\nexport { authplaneProvider } from \"./auth/providers/authplane.js\";\nexport { type ClerkClaims, clerkProvider } from \"./auth/providers/clerk.js\";\nexport { customProvider } from \"./auth/providers/custom.js\";\nexport {\n type DescopeClaims,\n descopeProvider,\n} from \"./auth/providers/descope.js\";\nexport { type StytchClaims, stytchProvider } from \"./auth/providers/stytch.js\";\nexport { type WorkosClaims, workosProvider } from \"./auth/providers/workos.js\";\nexport {\n createJwksVerifier,\n type JwksVerifyConfig,\n} from \"./auth/verify.js\";\nexport {\n type AuthInfo,\n type AuthMetadataOptions,\n type BearerAuthMiddlewareOptions,\n type ExtraClaims,\n mcpAuthMetadataRouter,\n OAuthError,\n OAuthErrorCode,\n optionalBearerAuth,\n requireBearerAuth,\n type TokenVerifier,\n} from \"./auth.js\";\nexport {\n audio,\n embeddedResource,\n image,\n resourceLink,\n text,\n} from \"./content-helpers.js\";\nexport { FileRef } from \"./file-ref.js\";\nexport type {\n AnyToolRegistry,\n InferTools,\n ToolInput,\n ToolNames,\n ToolOutput,\n ToolResponseMetadata,\n} from \"./inferUtilityTypes.js\";\nexport type {\n McpExtra,\n McpMethodString,\n McpMiddlewareFilter,\n McpMiddlewareFn,\n McpResultFor,\n McpTypedMiddlewareFn,\n McpWildcard,\n} from \"./middleware.js\";\nexport { getToolError } from \"./middleware.js\";\nexport type {\n HandlerContent,\n JsonOptions,\n KnownToolMeta,\n McpServerTypes,\n SecurityScheme,\n SkybridgeServerOptions,\n ToolDef,\n ToolMeta,\n ViewConfig,\n ViewCsp,\n ViewName,\n ViewNameRegistry,\n} from \"./server.js\";\nexport {\n __setBuildManifest,\n __setSkillsManifest,\n McpServer,\n normalizeContent,\n} from \"./server.js\";\nexport {\n discoverSkills,\n type Skill,\n type SkillsManifest,\n} from \"./skills.js\";\nexport { viewsDevServer } from \"./viewsDevServer.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,aAAa,EACb,iBAAiB,GAElB,MAAM,8BAA8B,CAAC;AAMtC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,OAAO,EAAoB,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAoB,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC5E,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAEL,eAAe,GAChB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAqB,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,EAAqB,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,EACL,kBAAkB,GAEnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAKL,qBAAqB,EACrB,UAAU,EACV,cAAc,EACd,kBAAkB,EAClB,iBAAiB,GAElB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,EACL,gBAAgB,EAChB,KAAK,EACL,YAAY,EACZ,IAAI,GACL,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAkBxC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAmB/C,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,SAAS,EACT,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,cAAc,GAGf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["export {\n inputRequired,\n ProtocolError,\n ProtocolErrorCode,\n type StandardSchemaWithJSON,\n} from \"@modelcontextprotocol/server\";\nexport type {\n SkybridgeConfig,\n SkybridgeHandler,\n SkybridgeOAuthInput,\n} from \"./app.js\";\nexport { Skybridge } from \"./app.js\";\nexport type { OAuthConfig, OAuthProvider } from \"./auth/index.js\";\nexport { type Auth0Claims, auth0Provider } from \"./auth/providers/auth0.js\";\nexport { authplaneProvider } from \"./auth/providers/authplane.js\";\nexport { type ClerkClaims, clerkProvider } from \"./auth/providers/clerk.js\";\nexport { customProvider } from \"./auth/providers/custom.js\";\nexport {\n type DescopeClaims,\n descopeProvider,\n} from \"./auth/providers/descope.js\";\nexport { type StytchClaims, stytchProvider } from \"./auth/providers/stytch.js\";\nexport { type WorkosClaims, workosProvider } from \"./auth/providers/workos.js\";\nexport {\n createJwksVerifier,\n type JwksVerifyConfig,\n} from \"./auth/verify.js\";\nexport {\n type AuthInfo,\n type AuthMetadataOptions,\n type BearerAuthMiddlewareOptions,\n type ExtraClaims,\n mcpAuthMetadataRouter,\n OAuthError,\n OAuthErrorCode,\n optionalBearerAuth,\n requireBearerAuth,\n type TokenVerifier,\n} from \"./auth.js\";\nexport {\n audio,\n embeddedResource,\n image,\n resourceLink,\n text,\n} from \"./content-helpers.js\";\nexport { FileRef } from \"./file-ref.js\";\nexport type {\n AnyToolRegistry,\n InferTools,\n ToolInput,\n ToolNames,\n ToolOutput,\n ToolResponseMetadata,\n} from \"./inferUtilityTypes.js\";\nexport type {\n McpExtra,\n McpMethodString,\n McpMiddlewareFilter,\n McpMiddlewareFn,\n McpResultFor,\n McpTypedMiddlewareFn,\n McpWildcard,\n} from \"./middleware.js\";\nexport { getToolError } from \"./middleware.js\";\nexport type {\n HandlerContent,\n JsonOptions,\n KnownToolMeta,\n McpServerTypes,\n OpenAIDisplayMode,\n OpenAIEntrypoint,\n OpenAIQuickAction,\n OpenAIToolConfig,\n SecurityScheme,\n SkybridgeServerOptions,\n ToolDef,\n ToolMeta,\n ViewConfig,\n ViewCsp,\n ViewName,\n ViewNameRegistry,\n} from \"./server.js\";\nexport {\n __setBuildManifest,\n __setSkillsManifest,\n McpServer,\n normalizeContent,\n} from \"./server.js\";\nexport {\n discoverSkills,\n type Skill,\n type SkillsManifest,\n} from \"./skills.js\";\nexport { viewsDevServer } from \"./viewsDevServer.js\";\n"]}
@@ -1,5 +1,6 @@
1
1
  import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
2
2
  import { describe, expect, it } from "vitest";
3
+ import { z } from "zod";
3
4
  import { Skybridge } from "./app.js";
4
5
  describe("registerTool handler invocation", () => {
5
6
  it("passes empty args and a usable extra to a schema-less tool handler", async () => {
@@ -23,5 +24,54 @@ describe("registerTool handler invocation", () => {
23
24
  await client.close();
24
25
  await instance.close();
25
26
  });
27
+ it("emits openai options as openai/ui metadata on the tool and its view", async () => {
28
+ const app = new Skybridge({
29
+ name: "test",
30
+ version: "1.0.0",
31
+ handler: (server) => server.registerTool({
32
+ name: "library",
33
+ description: "library",
34
+ view: { component: "widget" },
35
+ openai: {
36
+ entrypoints: [
37
+ { type: "global" },
38
+ { type: "file", extensions: [".stl"] },
39
+ ],
40
+ availableDisplayModes: ["inline", "fullscreen"],
41
+ preferredDisplayMode: "fullscreen",
42
+ },
43
+ }, async () => ({ content: "ok" })),
44
+ });
45
+ const instance = await app.createServerInstance();
46
+ const client = new Client({ name: "client", version: "1.0.0" });
47
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
48
+ await instance.connect(serverTransport);
49
+ await client.connect(clientTransport);
50
+ const { tools } = await client.listTools();
51
+ const { resources } = await client.listResources();
52
+ expect(tools[0]?._meta?.["openai/ui"]).toEqual({
53
+ entrypoints: [{ type: "global" }, { type: "file", extensions: [".stl"] }],
54
+ });
55
+ expect(resources[0]?._meta?.["openai/ui"]).toEqual({
56
+ availableDisplayModes: ["inline", "fullscreen"],
57
+ preferredDisplayMode: "fullscreen",
58
+ });
59
+ await client.close();
60
+ await instance.close();
61
+ });
62
+ it("rejects a global entrypoint on a tool that requires input", async () => {
63
+ const app = new Skybridge({
64
+ name: "test",
65
+ version: "1.0.0",
66
+ handler: (server) => server.registerTool({
67
+ name: "search",
68
+ description: "search",
69
+ inputSchema: { query: z.string() },
70
+ view: { component: "widget" },
71
+ openai: { entrypoints: [{ type: "global" }] },
72
+ }, async () => ({ content: "ok" })),
73
+ });
74
+ await expect(app.createServerInstance()).rejects.toThrow(/rejects `\{\}`/);
75
+ });
26
76
  });
27
77
  //# sourceMappingURL=register-tool.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"register-tool.test.js","sourceRoot":"","sources":["../../src/server/register-tool.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,EAAE,CAAC,oEAAoE,EAAE,KAAK,IAAI,EAAE;QAClF,IAAI,QAAuD,CAAC;QAC5D,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC;YACxB,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,OAAO;YAChB,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAClB,MAAM,CAAC,YAAY,CACjB,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,EAC7C,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;gBACpB,QAAQ,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;gBAC3B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YACrD,CAAC,CACF;SACJ,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,oBAAoB,EAAE,CAAC;QAElD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QAChE,MAAM,CAAC,eAAe,EAAE,eAAe,CAAC,GACtC,iBAAiB,CAAC,gBAAgB,EAAE,CAAC;QACvC,MAAM,QAAQ,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QACxC,MAAM,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QAEtC,MAAM,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;QAE5C,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACnC,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;QAEtD,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { Client, InMemoryTransport } from \"@modelcontextprotocol/client\";\nimport { describe, expect, it } from \"vitest\";\nimport { Skybridge } from \"./app.js\";\n\ndescribe(\"registerTool handler invocation\", () => {\n it(\"passes empty args and a usable extra to a schema-less tool handler\", async () => {\n let received: { args: unknown; extra: unknown } | undefined;\n const app = new Skybridge({\n name: \"test\",\n version: \"1.0.0\",\n handler: (server) =>\n server.registerTool(\n { name: \"no-input\", description: \"no-input\" },\n async (args, extra) => {\n received = { args, extra };\n return { content: [{ type: \"text\", text: \"ok\" }] };\n },\n ),\n });\n const instance = await app.createServerInstance();\n\n const client = new Client({ name: \"client\", version: \"1.0.0\" });\n const [clientTransport, serverTransport] =\n InMemoryTransport.createLinkedPair();\n await instance.connect(serverTransport);\n await client.connect(clientTransport);\n\n await client.callTool({ name: \"no-input\" });\n\n expect(received?.args).toEqual({});\n expect(received?.extra).toHaveProperty(\"mcpReq.send\");\n\n await client.close();\n await instance.close();\n });\n});\n"]}
1
+ {"version":3,"file":"register-tool.test.js","sourceRoot":"","sources":["../../src/server/register-tool.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { Client, InMemoryTransport } from \"@modelcontextprotocol/client\";\nimport { describe, expect, it } from \"vitest\";\nimport { z } from \"zod\";\nimport { Skybridge } from \"./app.js\";\n\ndescribe(\"registerTool handler invocation\", () => {\n it(\"passes empty args and a usable extra to a schema-less tool handler\", async () => {\n let received: { args: unknown; extra: unknown } | undefined;\n const app = new Skybridge({\n name: \"test\",\n version: \"1.0.0\",\n handler: (server) =>\n server.registerTool(\n { name: \"no-input\", description: \"no-input\" },\n async (args, extra) => {\n received = { args, extra };\n return { content: [{ type: \"text\", text: \"ok\" }] };\n },\n ),\n });\n const instance = await app.createServerInstance();\n\n const client = new Client({ name: \"client\", version: \"1.0.0\" });\n const [clientTransport, serverTransport] =\n InMemoryTransport.createLinkedPair();\n await instance.connect(serverTransport);\n await client.connect(clientTransport);\n\n await client.callTool({ name: \"no-input\" });\n\n expect(received?.args).toEqual({});\n expect(received?.extra).toHaveProperty(\"mcpReq.send\");\n\n await client.close();\n await instance.close();\n });\n\n it(\"emits openai options as openai/ui metadata on the tool and its view\", async () => {\n const app = new Skybridge({\n name: \"test\",\n version: \"1.0.0\",\n handler: (server) =>\n server.registerTool(\n {\n name: \"library\",\n description: \"library\",\n view: { component: \"widget\" },\n openai: {\n entrypoints: [\n { type: \"global\" },\n { type: \"file\", extensions: [\".stl\"] },\n ],\n availableDisplayModes: [\"inline\", \"fullscreen\"],\n preferredDisplayMode: \"fullscreen\",\n },\n },\n async () => ({ content: \"ok\" }),\n ),\n });\n const instance = await app.createServerInstance();\n const client = new Client({ name: \"client\", version: \"1.0.0\" });\n const [clientTransport, serverTransport] =\n InMemoryTransport.createLinkedPair();\n await instance.connect(serverTransport);\n await client.connect(clientTransport);\n\n const { tools } = await client.listTools();\n const { resources } = await client.listResources();\n\n expect(tools[0]?._meta?.[\"openai/ui\"]).toEqual({\n entrypoints: [{ type: \"global\" }, { type: \"file\", extensions: [\".stl\"] }],\n });\n expect(resources[0]?._meta?.[\"openai/ui\"]).toEqual({\n availableDisplayModes: [\"inline\", \"fullscreen\"],\n preferredDisplayMode: \"fullscreen\",\n });\n\n await client.close();\n await instance.close();\n });\n\n it(\"rejects a global entrypoint on a tool that requires input\", async () => {\n const app = new Skybridge({\n name: \"test\",\n version: \"1.0.0\",\n handler: (server) =>\n server.registerTool(\n {\n name: \"search\",\n description: \"search\",\n inputSchema: { query: z.string() },\n view: { component: \"widget\" },\n openai: { entrypoints: [{ type: \"global\" }] },\n },\n async () => ({ content: \"ok\" }),\n ),\n });\n\n await expect(app.createServerInstance()).rejects.toThrow(/rejects `\\{\\}`/);\n });\n});\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { McpUiToolMeta } from "@modelcontextprotocol/ext-apps";
2
- import { type ContentBlock, type Implementation, McpServer as McpServerBase, type RequestMeta, type ServerOptions, type ServerResult, type StandardSchemaV1, type StandardSchemaWithJSON, type ToolAnnotations } from "@modelcontextprotocol/server";
2
+ import { type ContentBlock, type Icon, type Implementation, McpServer as McpServerBase, type RequestMeta, type ServerOptions, type ServerResult, type StandardSchemaV1, type StandardSchemaWithJSON, type ToolAnnotations } from "@modelcontextprotocol/server";
3
3
  import type express from "express";
4
4
  import type { ResourceMetadataUrlResolver } from "./auth/setup.js";
5
5
  import type { ExtraClaims } from "./auth.js";
@@ -75,6 +75,64 @@ export type SecurityScheme = {
75
75
  type: "oauth2";
76
76
  scopes?: string[];
77
77
  };
78
+ /** A display mode ChatGPT can render a view in. ChatGPT doesn't support `pip`. */
79
+ export type OpenAIDisplayMode = "inline" | "fullscreen";
80
+ /** A sidebar shortcut that calls another tool of the same server. */
81
+ export interface OpenAIQuickAction {
82
+ title: string;
83
+ icons: Icon[];
84
+ target: {
85
+ type: "tool";
86
+ name: string;
87
+ arguments?: Record<string, unknown>;
88
+ };
89
+ }
90
+ /**
91
+ * A place in ChatGPT where users can open the tool's view without the model.
92
+ *
93
+ * - `global`: an entry in the sidebar, opened fullscreen.
94
+ * - `thread`: a tab in a conversation's side panel.
95
+ * - `file`: a viewer for files with these extensions, such as `".stl"`.
96
+ * - `settings`: an entry in the plugin settings.
97
+ *
98
+ * ChatGPT calls the tool with `{}` for global and thread entrypoints, so the
99
+ * tool must not require any input.
100
+ */
101
+ export type OpenAIEntrypoint = {
102
+ type: "global";
103
+ quickAction?: OpenAIQuickAction;
104
+ } | {
105
+ type: "thread";
106
+ } | {
107
+ type: "file";
108
+ extensions: string[];
109
+ } | {
110
+ type: "settings";
111
+ searchTerms?: string[];
112
+ };
113
+ /**
114
+ * ChatGPT-only tool options, emitted as `_meta["openai/ui"]` on the tool and
115
+ * on its view resource. Every field is optional: ChatGPT applies the defaults
116
+ * of the OpenAI MCP extensions spec when one is omitted. Requires `view`.
117
+ *
118
+ * @see https://github.com/openai/mcp-extensions/blob/main/docs/spec.md
119
+ */
120
+ export interface OpenAIToolConfig {
121
+ /** Places where users can open the view directly. */
122
+ entrypoints?: OpenAIEntrypoint[];
123
+ /**
124
+ * Display modes the view supports, read before the view loads. Defaults to
125
+ * `[preferredDisplayMode]` when that is set, otherwise to both modes.
126
+ */
127
+ availableDisplayModes?: OpenAIDisplayMode[];
128
+ /**
129
+ * Display mode the view prefers to open in. Setting it alone makes it the
130
+ * only available mode, so also set `availableDisplayModes` to keep the other.
131
+ */
132
+ preferredDisplayMode?: OpenAIDisplayMode;
133
+ /** Display mode the view opens in first when the model calls the tool. */
134
+ preferredModelDisplayMode?: OpenAIDisplayMode;
135
+ }
78
136
  /**
79
137
  * Declarative per-tool auth. Enforced when the server has an `oauth` provider:
80
138
  * anonymous or under-scoped calls are rejected before the handler runs. Omit
@@ -175,7 +233,15 @@ interface ToolConfigBase<TInput extends Record<string, StandardSchemaWithJSON> |
175
233
  inputSchema?: TInput;
176
234
  outputSchema?: Record<string, StandardSchemaWithJSON> | StandardSchemaWithJSON;
177
235
  annotations?: ToolAnnotations;
236
+ /**
237
+ * Icons for the tool. The server's `icons` already cover every entrypoint,
238
+ * so set these only to tell several entrypoints apart. ChatGPT expects a
239
+ * monochrome SVG on a 20x20 viewport that uses `currentColor`.
240
+ */
241
+ icons?: Icon[];
178
242
  view?: ViewConfig;
243
+ /** ChatGPT-only options. See {@link OpenAIToolConfig}. */
244
+ openai?: OpenAIToolConfig;
179
245
  _meta?: ToolMeta;
180
246
  }
181
247
  /**
@@ -225,6 +291,15 @@ export interface ClientHintsMeta {
225
291
  "openai/organization"?: string;
226
292
  /** Stable id for the currently mounted widget instance. */
227
293
  "openai/widgetSessionId"?: string;
294
+ /**
295
+ * Set by ChatGPT desktop on tool calls from a view opened through a file
296
+ * entrypoint. `path` is the absolute path of the opened file. Any MCP client
297
+ * can send this key: never use it for authorization, and resolve the path
298
+ * and keep it inside an allowed directory before touching the filesystem.
299
+ */
300
+ "openai/resource"?: {
301
+ path?: string;
302
+ };
228
303
  }
229
304
  type ToolHandlerExtra<TAuthExtra extends ExtraClaims = ExtraClaims> = Omit<McpExtra<TAuthExtra>, "mcpReq"> & {
230
305
  mcpReq: Omit<McpExtra<TAuthExtra>["mcpReq"], "_meta"> & {
@@ -10,6 +10,18 @@ import { resolveServerOrigin } from "./requestOrigin.js";
10
10
  import { discoverSkills, registerSkills, SKILLS_EXTENSION_KEY, } from "./skills.js";
11
11
  import { templateHelper } from "./templateHelper.js";
12
12
  const unionOf = (base, extra) => extra ? [...new Set([...base, ...extra])] : base;
13
+ function definedEntries(values) {
14
+ return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
15
+ }
16
+ function acceptsEmptyInput(inputSchema) {
17
+ if (inputSchema === undefined) {
18
+ return true;
19
+ }
20
+ const checks = "~standard" in inputSchema
21
+ ? [inputSchema["~standard"].validate({})]
22
+ : Object.values(inputSchema).map((schema) => schema["~standard"].validate(undefined));
23
+ return checks.every((result) => result instanceof Promise || result.issues === undefined);
24
+ }
13
25
  const SKILLS_DIR = "src/skills";
14
26
  /**
15
27
  * Normalize an `x-forwarded-prefix` value into a leading-slash, no-trailing-slash
@@ -331,7 +343,7 @@ export class McpServer extends McpServerBaseOmitted {
331
343
  }
332
344
  return { serverUrl, assetsBasePath, connectDomains, contentMetaOverrides };
333
345
  }
334
- registerViewResources(toolName, view, toolMeta) {
346
+ registerViewResources(toolName, view, toolMeta, openaiResourceMeta) {
335
347
  // Append a content-derived version param so hosts (e.g. ChatGPT) bust
336
348
  // their cache when the bundle changes, but keep the URI stable across
337
349
  // `tools/list` calls when the bundle hasn't changed.
@@ -366,10 +378,14 @@ export class McpServer extends McpServerBaseOmitted {
366
378
  "openai/widgetCSP": { redirect_domains: view.csp.redirectDomains },
367
379
  }),
368
380
  };
369
- if (view._meta) {
370
- return { ...base, ...view._meta };
381
+ const meta = { ...base, ...view._meta };
382
+ if (openaiResourceMeta && Object.keys(openaiResourceMeta).length > 0) {
383
+ meta["openai/ui"] = {
384
+ ...view._meta?.["openai/ui"],
385
+ ...openaiResourceMeta,
386
+ };
371
387
  }
372
- return base;
388
+ return meta;
373
389
  },
374
390
  };
375
391
  this.registerViewResource({ name: toolName, viewResource, view });
@@ -508,7 +524,7 @@ export class McpServer extends McpServerBaseOmitted {
508
524
  const baseFn = McpServerBase.prototype.registerTool;
509
525
  const config = rawConfig;
510
526
  const cb = rawCb;
511
- const { name, view, auth, securitySchemes: rawSecuritySchemes, _meta: userToolMeta, ...toolFields } = config;
527
+ const { name, view, auth, securitySchemes: rawSecuritySchemes, openai, _meta: userToolMeta, ...toolFields } = config;
512
528
  const authNeedsProvider = auth !== undefined &&
513
529
  (!auth.allowsAnonymous || Boolean(auth.scopes?.length));
514
530
  if (rawSecuritySchemes === undefined &&
@@ -528,9 +544,32 @@ export class McpServer extends McpServerBaseOmitted {
528
544
  // Apps SDK reference until SEP-1488 lands in the spec.
529
545
  toolMeta.securitySchemes = securitySchemes;
530
546
  }
547
+ if (openai) {
548
+ if (!view) {
549
+ throw new Error(`skybridge: tool "${name}" sets \`openai\` options but has no \`view\`.`);
550
+ }
551
+ const opensWithoutInput = openai.entrypoints?.some(({ type }) => type === "global" || type === "thread");
552
+ if (opensWithoutInput && !acceptsEmptyInput(toolFields.inputSchema)) {
553
+ throw new Error(`skybridge: tool "${name}" has a global or thread entrypoint, so ChatGPT calls it with \`{}\`, but its input schema rejects \`{}\`. Make every input optional.`);
554
+ }
555
+ const openaiUi = definedEntries({
556
+ entrypoints: openai.entrypoints,
557
+ preferredModelDisplayMode: openai.preferredModelDisplayMode,
558
+ });
559
+ if (Object.keys(openaiUi).length > 0) {
560
+ toolMeta["openai/ui"] = {
561
+ ...userToolMeta?.["openai/ui"],
562
+ ...openaiUi,
563
+ };
564
+ }
565
+ }
531
566
  if (view) {
532
567
  this.enforceOneToolPerView(view.component, name);
533
- this.registerViewResources(name, view, toolMeta);
568
+ this.registerViewResources(name, view, toolMeta, openai &&
569
+ definedEntries({
570
+ availableDisplayModes: openai.availableDisplayModes,
571
+ preferredDisplayMode: openai.preferredDisplayMode,
572
+ }));
534
573
  }
535
574
  const wrappedCb = this.decorateToolHandler(cb, {
536
575
  attachViewUUID: Boolean(view),