skybridge 2.0.4 → 2.1.0
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.
- package/README.md +9 -9
- package/dist/cli/run-plain.js +2 -1
- package/dist/cli/run-plain.js.map +1 -1
- package/dist/cli/use-nodemon.d.ts +11 -1
- package/dist/cli/use-nodemon.js +19 -5
- package/dist/cli/use-nodemon.js.map +1 -1
- package/dist/commands/dev.js +3 -3
- package/dist/commands/dev.js.map +1 -1
- package/dist/server/app.d.ts +22 -5
- package/dist/server/app.js +24 -13
- package/dist/server/app.js.map +1 -1
- package/dist/server/app.test.js +81 -0
- package/dist/server/app.test.js.map +1 -1
- package/dist/server/auth/providers/custom.d.ts +4 -2
- package/dist/server/auth/providers/custom.js.map +1 -1
- package/dist/server/auth/providers/workos.d.ts +3 -2
- package/dist/server/auth/providers/workos.js +2 -1
- package/dist/server/auth/providers/workos.js.map +1 -1
- package/dist/server/auth/verify.d.ts +5 -3
- package/dist/server/auth/verify.js.map +1 -1
- package/dist/server/auth/verify.test.js +16 -0
- package/dist/server/auth/verify.test.js.map +1 -1
- package/dist/server/events.d.ts +113 -0
- package/dist/server/events.js +318 -0
- package/dist/server/events.js.map +1 -0
- package/dist/server/events.test.d.ts +1 -0
- package/dist/server/events.test.js +172 -0
- package/dist/server/events.test.js.map +1 -0
- package/dist/server/express.d.ts +3 -2
- package/dist/server/express.js +17 -11
- package/dist/server/express.js.map +1 -1
- package/dist/server/express.test.js +45 -0
- package/dist/server/express.test.js.map +1 -1
- package/dist/server/form-input.d.ts +120 -0
- package/dist/server/form-input.js +89 -0
- package/dist/server/form-input.js.map +1 -0
- package/dist/server/index.d.ts +4 -2
- package/dist/server/index.js +3 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp-errors.test.js +188 -0
- package/dist/server/mcp-errors.test.js.map +1 -1
- package/dist/server/metric.d.ts +5 -3
- package/dist/server/metric.js +5 -5
- package/dist/server/metric.js.map +1 -1
- package/dist/server/register-tool.test.js +203 -0
- package/dist/server/register-tool.test.js.map +1 -1
- package/dist/server/server.d.ts +254 -10
- package/dist/server/server.js +262 -9
- package/dist/server/server.js.map +1 -1
- package/dist/server/tool-extra.test.js +79 -0
- package/dist/server/tool-extra.test.js.map +1 -1
- package/dist/web/bridges/adaptor.d.ts +14 -3
- package/dist/web/bridges/adaptor.js +164 -14
- package/dist/web/bridges/adaptor.js.map +1 -1
- package/dist/web/bridges/adaptor.test.js +62 -6
- package/dist/web/bridges/adaptor.test.js.map +1 -1
- package/dist/web/bridges/apps-sdk/bridge.js +1 -1
- package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
- package/dist/web/bridges/apps-sdk/types.d.ts +2 -3
- package/dist/web/bridges/apps-sdk/types.js.map +1 -1
- package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +3 -3
- package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +3 -3
- package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
- package/dist/web/bridges/mcp-app/bridge.d.ts +1 -1
- package/dist/web/bridges/mcp-app/bridge.js +9 -2
- package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
- package/dist/web/bridges/mcp-app/types.d.ts +18 -3
- package/dist/web/bridges/mcp-app/types.js.map +1 -1
- package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +2 -2
- package/dist/web/bridges/mcp-app/use-mcp-app-context.js +1 -1
- package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
- package/dist/web/bridges/types.d.ts +57 -4
- package/dist/web/bridges/types.js +1 -1
- package/dist/web/bridges/types.js.map +1 -1
- package/dist/web/data-llm.js +3 -2
- package/dist/web/data-llm.js.map +1 -1
- package/dist/web/data-llm.test.js +6 -0
- package/dist/web/data-llm.test.js.map +1 -1
- package/dist/web/generate-helpers.d.ts +2 -2
- package/dist/web/generate-helpers.js +2 -2
- package/dist/web/generate-helpers.js.map +1 -1
- package/dist/web/hooks/index.d.ts +5 -1
- package/dist/web/hooks/index.js +5 -1
- package/dist/web/hooks/index.js.map +1 -1
- package/dist/web/hooks/openai-extensions.test.d.ts +1 -0
- package/dist/web/hooks/openai-extensions.test.js +107 -0
- package/dist/web/hooks/openai-extensions.test.js.map +1 -0
- package/dist/web/hooks/use-call-tool.d.ts +2 -2
- package/dist/web/hooks/use-call-tool.js +15 -10
- package/dist/web/hooks/use-call-tool.js.map +1 -1
- package/dist/web/hooks/use-call-tool.test.js +13 -2
- package/dist/web/hooks/use-call-tool.test.js.map +1 -1
- package/dist/web/hooks/use-deep-link.d.ts +22 -0
- package/dist/web/hooks/use-deep-link.js +34 -0
- package/dist/web/hooks/use-deep-link.js.map +1 -0
- package/dist/web/hooks/use-file-resource.d.ts +37 -0
- package/dist/web/hooks/use-file-resource.js +98 -0
- package/dist/web/hooks/use-file-resource.js.map +1 -0
- package/dist/web/hooks/use-file-resource.test.d.ts +1 -0
- package/dist/web/hooks/use-file-resource.test.js +65 -0
- package/dist/web/hooks/use-file-resource.test.js.map +1 -0
- package/dist/web/hooks/use-host.d.ts +27 -5
- package/dist/web/hooks/use-host.js +20 -5
- package/dist/web/hooks/use-host.js.map +1 -1
- package/dist/web/hooks/use-model-context.d.ts +44 -0
- package/dist/web/hooks/use-model-context.js +63 -0
- package/dist/web/hooks/use-model-context.js.map +1 -0
- package/dist/web/hooks/use-open-file.d.ts +20 -0
- package/dist/web/hooks/use-open-file.js +25 -0
- package/dist/web/hooks/use-open-file.js.map +1 -0
- package/dist/web/hooks/use-register-view-tool.d.ts +1 -1
- package/dist/web/hooks/use-register-view-tool.js +1 -1
- package/dist/web/hooks/use-register-view-tool.js.map +1 -1
- package/dist/web/hooks/use-send-follow-up-message.d.ts +8 -2
- package/dist/web/hooks/use-send-follow-up-message.js +6 -0
- package/dist/web/hooks/use-send-follow-up-message.js.map +1 -1
- package/package.json +6 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-input.js","sourceRoot":"","sources":["../../src/server/form-input.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type ElicitRequestFormParams,\n type ElicitResult,\n fromJsonSchema,\n type Icon,\n type InputRequiredResult,\n type PrimitiveSchemaDefinition,\n type Resource,\n type StringSchema,\n} from \"@modelcontextprotocol/server\";\n\n/** A titled choice, with an optional description and thumbnail. */\nexport interface OpenAIFormOption {\n const: string;\n title: string;\n description?: string;\n /** An HTTPS URL or a base64 data URI. */\n \"x-openai-thumbnail\"?: Icon;\n}\n\n/** A string field that accepts a `pattern` and suggested values. */\nexport type OpenAIFormStringField = StringSchema & {\n pattern?: string;\n \"x-openai-suggestions\"?: OpenAIFormOption[];\n};\n\n/** Resources the user picks from, or adds, in a resource field. */\nexport interface OpenAIResourceInput {\n type: \"resource\";\n /** Multi-select only. Defaults to `\"explicit\"`. */\n selection?: \"explicit\" | \"implicit\";\n options: Resource[];\n userOptions?: { kind?: \"file\" | \"directory\"; accept?: string[] };\n}\n\ntype FieldText = { title?: string; description?: string };\ntype ArrayBounds = FieldText & { minItems?: number; maxItems?: number };\n\n/** One field of an {@link OpenAIForm}. */\nexport type OpenAIFormField =\n | PrimitiveSchemaDefinition\n | OpenAIFormStringField\n | (FieldText & {\n type: \"string\";\n oneOf: OpenAIFormOption[];\n default?: string;\n })\n | (ArrayBounds & {\n type: \"array\";\n items: { anyOf: OpenAIFormOption[] };\n default?: string[];\n })\n | (ArrayBounds & {\n type: \"array\";\n uniqueItems?: boolean;\n items: OpenAIFormStringField;\n default?: string[];\n })\n | (FieldText & {\n type: \"string\";\n format: \"uri\";\n \"x-openai-input\": Omit<OpenAIResourceInput, \"selection\">;\n default?: string;\n })\n | (ArrayBounds & {\n type: \"array\";\n items: { type: \"string\"; format: \"uri\" };\n \"x-openai-input\": OpenAIResourceInput;\n default?: string[];\n });\n\n/** A form for ChatGPT, from the OpenAI MCP extensions. */\nexport interface OpenAIForm {\n type: \"object\";\n properties: Record<string, OpenAIFormField>;\n required?: string[];\n}\n\nexport type OpenAIFormInputParams = Omit<\n ElicitRequestFormParams,\n \"mode\" | \"requestedSchema\"\n> & {\n /** Identifies the form across the two calls of the tool. */\n key: string;\n requestedSchema: OpenAIForm;\n requestState?: string;\n};\n\n/** The user's answer to an {@link OpenAIForm}. */\nexport type OpenAIFormResult =\n | {\n action: \"accept\";\n content: Record<string, string | number | boolean | string[]>;\n }\n | { action: \"decline\" | \"cancel\" };\n\ntype FormInputExtra = {\n mcpReq: {\n envelope?: object;\n inputResponses?: Record<string, unknown>;\n };\n};\n\nfunction supportsOpenAIForms(envelope: object | undefined): boolean {\n const {\n \"io.modelcontextprotocol/protocolVersion\": version,\n \"io.modelcontextprotocol/clientCapabilities\": capabilities,\n } = (envelope ?? {}) as {\n \"io.modelcontextprotocol/protocolVersion\"?: string;\n \"io.modelcontextprotocol/clientCapabilities\"?: {\n elicitation?: { form?: object };\n extensions?: { \"openai/elicitation\"?: { form?: object } };\n };\n };\n return (\n version !== undefined &&\n version >= \"2026-07-28\" &&\n capabilities?.elicitation?.form !== undefined &&\n capabilities.extensions?.[\"openai/elicitation\"]?.form !== undefined\n );\n}\n\nfunction picksOfferedResources(field: OpenAIFormField, value: unknown) {\n if (!(\"x-openai-input\" in field) || value === undefined) {\n return true;\n }\n const input = field[\"x-openai-input\"];\n if (\n input.userOptions !== undefined ||\n (\"selection\" in input && input.selection === \"implicit\")\n ) {\n return true;\n }\n return [value]\n .flat()\n .every((uri) => input.options.some((option) => option.uri === uri));\n}\n\n/**\n * Ask the user to fill a form in ChatGPT, from the OpenAI MCP extensions.\n * Return the result when it is an `input_required` result: ChatGPT shows the\n * form, then calls the tool again, and the same call returns the user's answer.\n *\n * @example\n * ```ts\n * server.registerTool({ name: \"pick-part\" }, (_args, extra) => {\n * const result = requestFormInput(extra, {\n * key: \"part\",\n * message: \"Choose a part\",\n * requestedSchema: {\n * type: \"object\",\n * properties: {\n * part: { type: \"string\", oneOf: [{ const: \"bolt\", title: \"M6 bolt\" }] },\n * },\n * required: [\"part\"],\n * },\n * });\n * if (\"resultType\" in result) {\n * return result;\n * }\n * return {\n * content: result.action === \"accept\" ? `Picked ${result.content.part}` : \"Nothing picked\",\n * };\n * });\n * ```\n *\n * @see https://docs.skybridge.tech/api-reference/request-form-input\n */\nexport function requestFormInput(\n extra: FormInputExtra,\n {\n key,\n requestedSchema,\n requestState,\n _meta,\n ...params\n }: OpenAIFormInputParams,\n): InputRequiredResult | OpenAIFormResult {\n if (!supportsOpenAIForms(extra.mcpReq.envelope)) {\n throw new Error(\n \"OpenAI forms need MCP 2026-07-28 or newer and a client that supports openai/elicitation forms.\",\n );\n }\n const responses = extra.mcpReq.inputResponses;\n if (responses !== undefined && Object.hasOwn(responses, key)) {\n const response = responses[key] as Partial<ElicitResult> | null;\n if (response?.action === \"decline\" || response?.action === \"cancel\") {\n return { action: response.action };\n }\n if (response?.action !== \"accept\") {\n throw new Error(`The answer to form \"${key}\" has no valid action.`);\n }\n const validated = fromJsonSchema<\n Extract<OpenAIFormResult, { action: \"accept\" }>[\"content\"]\n >(requestedSchema as object)[\"~standard\"].validate(response.content);\n if (\n \"then\" in validated ||\n validated.issues ||\n !Object.entries(requestedSchema.properties).every(([name, field]) =>\n picksOfferedResources(field, validated.value?.[name]),\n )\n ) {\n throw new Error(`The answer to form \"${key}\" does not match its schema.`);\n }\n return { action: \"accept\", content: validated.value };\n }\n return {\n resultType: \"input_required\",\n inputRequests: {\n [key]: {\n method: \"elicitation/create\",\n params: {\n ...params,\n mode: \"form\",\n requestedSchema: { type: \"object\", properties: {} },\n _meta: { ..._meta, \"openai/elicitation\": { requestedSchema } },\n },\n },\n },\n ...(requestState !== undefined && { requestState }),\n };\n}\n"]}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { inputRequired, ProtocolError, ProtocolErrorCode, type StandardSchemaWithJSON, } from "@modelcontextprotocol/server";
|
|
1
|
+
export { inputRequired, ProtocolError, ProtocolErrorCode, ResourceTemplate, type StandardSchemaWithJSON, } from "@modelcontextprotocol/server";
|
|
2
2
|
export type { SkybridgeConfig, SkybridgeHandler, SkybridgeOAuthInput, } from "./app.js";
|
|
3
3
|
export { Skybridge } from "./app.js";
|
|
4
4
|
export type { OAuthConfig, OAuthProvider } from "./auth/index.js";
|
|
@@ -12,11 +12,13 @@ export { type WorkosClaims, workosProvider } from "./auth/providers/workos.js";
|
|
|
12
12
|
export { createJwksVerifier, type JwksVerifyConfig, } from "./auth/verify.js";
|
|
13
13
|
export { type AuthInfo, type AuthMetadataOptions, type BearerAuthMiddlewareOptions, type ExtraClaims, mcpAuthMetadataRouter, OAuthError, OAuthErrorCode, optionalBearerAuth, requireBearerAuth, type TokenVerifier, } from "./auth.js";
|
|
14
14
|
export { audio, embeddedResource, image, resourceLink, text, } from "./content-helpers.js";
|
|
15
|
+
export { type DeliveryResult, deliverEvent, type EventConfig, type EventHooks, type EventOccurrence, type EventSubscription, } from "./events.js";
|
|
15
16
|
export { FileRef } from "./file-ref.js";
|
|
17
|
+
export { type OpenAIForm, type OpenAIFormField, type OpenAIFormInputParams, type OpenAIFormOption, type OpenAIFormResult, type OpenAIResourceInput, requestFormInput, } from "./form-input.js";
|
|
16
18
|
export type { AnyToolRegistry, InferTools, ToolInput, ToolNames, ToolOutput, ToolResponseMetadata, } from "./inferUtilityTypes.js";
|
|
17
19
|
export type { McpExtra, McpMethodString, McpMiddlewareFilter, McpMiddlewareFn, McpResultFor, McpTypedMiddlewareFn, McpWildcard, } from "./middleware.js";
|
|
18
20
|
export { getToolError } from "./middleware.js";
|
|
19
|
-
export type { HandlerContent, JsonOptions, KnownToolMeta, McpServerTypes, SecurityScheme, SkybridgeServerOptions, ToolDef, ToolMeta, ViewConfig, ViewCsp, ViewName, ViewNameRegistry, } from "./server.js";
|
|
21
|
+
export type { HandlerContent, JsonOptions, KnownToolMeta, McpServerTypes, OpenAIDisplayMode, OpenAIEntrypoint, OpenAIFileViewerConfig, OpenAIMentionsConfig, OpenAIQuickAction, OpenAISettingsConfig, OpenAISettingsValues, OpenAIToolConfig, SecurityScheme, SkybridgeServerOptions, ToolDef, ToolMeta, ViewConfig, ViewCsp, ViewName, ViewNameRegistry, } from "./server.js";
|
|
20
22
|
export { __setBuildManifest, __setSkillsManifest, McpServer, normalizeContent, } from "./server.js";
|
|
21
23
|
export { discoverSkills, type Skill, type SkillsManifest, } from "./skills.js";
|
|
22
24
|
export { viewsDevServer } from "./viewsDevServer.js";
|
package/dist/server/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { inputRequired, ProtocolError, ProtocolErrorCode, } from "@modelcontextprotocol/server";
|
|
1
|
+
export { inputRequired, ProtocolError, ProtocolErrorCode, ResourceTemplate, } from "@modelcontextprotocol/server";
|
|
2
2
|
export { Skybridge } from "./app.js";
|
|
3
3
|
export { auth0Provider } from "./auth/providers/auth0.js";
|
|
4
4
|
export { authplaneProvider } from "./auth/providers/authplane.js";
|
|
@@ -10,7 +10,9 @@ export { workosProvider } from "./auth/providers/workos.js";
|
|
|
10
10
|
export { createJwksVerifier, } from "./auth/verify.js";
|
|
11
11
|
export { mcpAuthMetadataRouter, OAuthError, OAuthErrorCode, optionalBearerAuth, requireBearerAuth, } from "./auth.js";
|
|
12
12
|
export { audio, embeddedResource, image, resourceLink, text, } from "./content-helpers.js";
|
|
13
|
+
export { deliverEvent, } from "./events.js";
|
|
13
14
|
export { FileRef } from "./file-ref.js";
|
|
15
|
+
export { requestFormInput, } from "./form-input.js";
|
|
14
16
|
export { getToolError } from "./middleware.js";
|
|
15
17
|
export { __setBuildManifest, __setSkillsManifest, McpServer, normalizeContent, } from "./server.js";
|
|
16
18
|
export { discoverSkills, } from "./skills.js";
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,aAAa,EACb,iBAAiB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,gBAAgB,GAEjB,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,EAEL,YAAY,GAKb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAOL,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAkBzB,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAuB/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 ResourceTemplate,\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 {\n type DeliveryResult,\n deliverEvent,\n type EventConfig,\n type EventHooks,\n type EventOccurrence,\n type EventSubscription,\n} from \"./events.js\";\nexport { FileRef } from \"./file-ref.js\";\nexport {\n type OpenAIForm,\n type OpenAIFormField,\n type OpenAIFormInputParams,\n type OpenAIFormOption,\n type OpenAIFormResult,\n type OpenAIResourceInput,\n requestFormInput,\n} from \"./form-input.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 OpenAIFileViewerConfig,\n OpenAIMentionsConfig,\n OpenAIQuickAction,\n OpenAISettingsConfig,\n OpenAISettingsValues,\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,18 +1,71 @@
|
|
|
1
1
|
// @vitest-environment node
|
|
2
2
|
import http from "node:http";
|
|
3
|
+
import { CLIENT_CAPABILITIES_META_KEY, PROTOCOL_VERSION_META_KEY, UnsupportedProtocolVersionError, } from "@modelcontextprotocol/server";
|
|
3
4
|
import { afterEach, expect, it, vi } from "vitest";
|
|
4
5
|
import { Skybridge } from "./app.js";
|
|
6
|
+
import { buildMcpHandler, createApp } from "./express.js";
|
|
5
7
|
vi.mock("@skybridge/devtools", () => ({
|
|
6
8
|
devtoolsStaticServer: () => ((_r, _s, n) => n()),
|
|
7
9
|
}));
|
|
8
10
|
vi.mock("./viewsDevServer.js", () => ({
|
|
9
11
|
viewsDevServer: () => ((_r, _s, n) => n()),
|
|
10
12
|
}));
|
|
13
|
+
const REJECTED = {
|
|
14
|
+
headers: {
|
|
15
|
+
"content-type": "application/json",
|
|
16
|
+
accept: "application/json, text/event-stream",
|
|
17
|
+
"mcp-protocol-version": "client-supplied-text",
|
|
18
|
+
},
|
|
19
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }),
|
|
20
|
+
};
|
|
21
|
+
const UNSUPPORTED = {
|
|
22
|
+
headers: {
|
|
23
|
+
"content-type": "application/json",
|
|
24
|
+
accept: "application/json, text/event-stream",
|
|
25
|
+
},
|
|
26
|
+
body: JSON.stringify({
|
|
27
|
+
jsonrpc: "2.0",
|
|
28
|
+
id: 1,
|
|
29
|
+
method: "tools/list",
|
|
30
|
+
params: {
|
|
31
|
+
_meta: {
|
|
32
|
+
[PROTOCOL_VERSION_META_KEY]: "2099-01-01",
|
|
33
|
+
[CLIENT_CAPABILITIES_META_KEY]: {},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
11
38
|
let openServer;
|
|
12
39
|
afterEach(() => {
|
|
13
40
|
openServer?.close();
|
|
14
41
|
vi.restoreAllMocks();
|
|
15
42
|
});
|
|
43
|
+
function createTestApp() {
|
|
44
|
+
return new Skybridge({
|
|
45
|
+
name: "t",
|
|
46
|
+
version: "0.0.0",
|
|
47
|
+
handler: (server) => server,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async function serve(options) {
|
|
51
|
+
const expressApp = await createApp({
|
|
52
|
+
app: options.app ?? createTestApp(),
|
|
53
|
+
httpServer: http.createServer(),
|
|
54
|
+
mcpHandler: options.mcpHandler,
|
|
55
|
+
onMcpError: options.onMcpError,
|
|
56
|
+
});
|
|
57
|
+
const listening = http.createServer(expressApp);
|
|
58
|
+
await new Promise((r) => listening.listen(0, r));
|
|
59
|
+
openServer = listening;
|
|
60
|
+
return listening.address().port;
|
|
61
|
+
}
|
|
62
|
+
function post(port, request) {
|
|
63
|
+
return fetch(`http://localhost:${port}/mcp`, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: request.headers,
|
|
66
|
+
body: request.body,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
16
69
|
it("reports a failure inside the MCP leg instead of swallowing it", async () => {
|
|
17
70
|
const { createApp } = await import("./express.js");
|
|
18
71
|
const app = new Skybridge({
|
|
@@ -38,4 +91,139 @@ it("reports a failure inside the MCP leg instead of swallowing it", async () =>
|
|
|
38
91
|
expect(res.status).toBeGreaterThanOrEqual(500);
|
|
39
92
|
expect(errors.mock.calls.some(([msg]) => String(msg).includes("Error handling MCP request"))).toBe(true);
|
|
40
93
|
});
|
|
94
|
+
it("logs a rejected request by default", async () => {
|
|
95
|
+
const errors = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
96
|
+
const port = await serve({});
|
|
97
|
+
const res = await post(port, REJECTED);
|
|
98
|
+
expect(res.status).toBe(400);
|
|
99
|
+
expect(errors).toHaveBeenCalledTimes(1);
|
|
100
|
+
const [prefix, error] = errors.mock.calls[0] ?? [];
|
|
101
|
+
expect(prefix).toBe("Error handling MCP request:");
|
|
102
|
+
expect(error).toBeInstanceOf(Error);
|
|
103
|
+
expect(error.message).toMatch(/^Rejected inbound request \(modern-header-without-claim\)/);
|
|
104
|
+
});
|
|
105
|
+
it("skips the unsupported protocol version by default", async () => {
|
|
106
|
+
const errors = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
107
|
+
const port = await serve({});
|
|
108
|
+
const res = await post(port, UNSUPPORTED);
|
|
109
|
+
expect(res.status).toBe(400);
|
|
110
|
+
expect((await res.json()).error.code).toBe(-32022);
|
|
111
|
+
expect(errors).not.toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
it("passes a rejected request to onMcpError instead of logging it", async () => {
|
|
114
|
+
const errors = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
115
|
+
const onMcpError = vi.fn();
|
|
116
|
+
const port = await serve({ onMcpError });
|
|
117
|
+
const res = await post(port, REJECTED);
|
|
118
|
+
expect(res.status).toBe(400);
|
|
119
|
+
expect(onMcpError).toHaveBeenCalledTimes(1);
|
|
120
|
+
const [error] = onMcpError.mock.calls[0] ?? [];
|
|
121
|
+
expect(error).toBeInstanceOf(Error);
|
|
122
|
+
expect(error.message).toMatch(/^Rejected inbound request/);
|
|
123
|
+
expect(errors).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
it("passes the unsupported protocol version to onMcpError", async () => {
|
|
126
|
+
const errors = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
127
|
+
const onMcpError = vi.fn();
|
|
128
|
+
const port = await serve({ onMcpError });
|
|
129
|
+
const res = await post(port, UNSUPPORTED);
|
|
130
|
+
expect(res.status).toBe(400);
|
|
131
|
+
expect((await res.json()).error.code).toBe(-32022);
|
|
132
|
+
expect(onMcpError).toHaveBeenCalledTimes(1);
|
|
133
|
+
expect(onMcpError.mock.calls[0]?.[0]).toBeInstanceOf(UnsupportedProtocolVersionError);
|
|
134
|
+
expect(errors).not.toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
it("passes a server factory failure to onMcpError as the same error", async () => {
|
|
137
|
+
const errors = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
138
|
+
const onMcpError = vi.fn();
|
|
139
|
+
const app = createTestApp();
|
|
140
|
+
const boom = new Error("boom");
|
|
141
|
+
vi.spyOn(app, "createServerInstance").mockImplementation(() => {
|
|
142
|
+
throw boom;
|
|
143
|
+
});
|
|
144
|
+
const port = await serve({ app, onMcpError });
|
|
145
|
+
const res = await post(port, {
|
|
146
|
+
headers: { "content-type": "application/json" },
|
|
147
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize" }),
|
|
148
|
+
});
|
|
149
|
+
expect(res.status).toBe(500);
|
|
150
|
+
expect(onMcpError).toHaveBeenCalledTimes(1);
|
|
151
|
+
expect(onMcpError.mock.calls[0]?.[0]).toBe(boom);
|
|
152
|
+
expect(errors).not.toHaveBeenCalled();
|
|
153
|
+
});
|
|
154
|
+
it("passes a Node adapter failure to onMcpError", async () => {
|
|
155
|
+
const errors = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
156
|
+
const onMcpError = vi.fn();
|
|
157
|
+
const app = createTestApp();
|
|
158
|
+
const mcpHandler = buildMcpHandler(app);
|
|
159
|
+
await mcpHandler.close();
|
|
160
|
+
const port = await serve({ app, mcpHandler, onMcpError });
|
|
161
|
+
const res = await post(port, REJECTED);
|
|
162
|
+
expect(res.status).toBe(500);
|
|
163
|
+
expect(onMcpError).toHaveBeenCalledTimes(1);
|
|
164
|
+
expect(onMcpError.mock.calls[0]?.[0].message).toBe("This MCP handler has been closed");
|
|
165
|
+
expect(errors).not.toHaveBeenCalled();
|
|
166
|
+
});
|
|
167
|
+
it("does not log a Node adapter failure by default", async () => {
|
|
168
|
+
const errors = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
169
|
+
const app = createTestApp();
|
|
170
|
+
const mcpHandler = buildMcpHandler(app);
|
|
171
|
+
await mcpHandler.close();
|
|
172
|
+
const port = await serve({ app, mcpHandler });
|
|
173
|
+
const res = await post(port, REJECTED);
|
|
174
|
+
expect(res.status).toBe(500);
|
|
175
|
+
expect(errors).not.toHaveBeenCalled();
|
|
176
|
+
});
|
|
177
|
+
it("wires onMcpError through run() on Node", async () => {
|
|
178
|
+
const errors = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
179
|
+
const onMcpError = vi.fn();
|
|
180
|
+
const prevPort = process.env.__PORT;
|
|
181
|
+
const prevSend = process.send;
|
|
182
|
+
const sigterm = process.listeners("SIGTERM");
|
|
183
|
+
const sigint = process.listeners("SIGINT");
|
|
184
|
+
let runServer;
|
|
185
|
+
const realCreateServer = http.createServer.bind(http);
|
|
186
|
+
vi.spyOn(http, "createServer").mockImplementation(((...args) => {
|
|
187
|
+
runServer = realCreateServer(...args);
|
|
188
|
+
return runServer;
|
|
189
|
+
}));
|
|
190
|
+
process.env.__PORT = "0";
|
|
191
|
+
if (process.send) {
|
|
192
|
+
process.send = vi.fn();
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const app = new Skybridge({
|
|
196
|
+
name: "t",
|
|
197
|
+
version: "0.0.0",
|
|
198
|
+
onMcpError,
|
|
199
|
+
handler: (server) => server,
|
|
200
|
+
});
|
|
201
|
+
await app.run();
|
|
202
|
+
const { port } = runServer.address();
|
|
203
|
+
const res = await post(port, REJECTED);
|
|
204
|
+
expect(res.status).toBe(400);
|
|
205
|
+
expect(onMcpError).toHaveBeenCalledTimes(1);
|
|
206
|
+
expect(errors).not.toHaveBeenCalled();
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
runServer?.close();
|
|
210
|
+
for (const [signal, saved] of [
|
|
211
|
+
["SIGTERM", sigterm],
|
|
212
|
+
["SIGINT", sigint],
|
|
213
|
+
]) {
|
|
214
|
+
for (const listener of process.listeners(signal)) {
|
|
215
|
+
if (!saved.includes(listener)) {
|
|
216
|
+
process.off(signal, listener);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (prevPort === undefined) {
|
|
221
|
+
delete process.env.__PORT;
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
process.env.__PORT = prevPort;
|
|
225
|
+
}
|
|
226
|
+
process.send = prevSend;
|
|
227
|
+
}
|
|
228
|
+
});
|
|
41
229
|
//# sourceMappingURL=mcp-errors.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-errors.test.js","sourceRoot":"","sources":["../../src/server/mcp-errors.test.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,EAAE,CAAC,IAAI,CAAC,qBAAqB,EAAE,GAAG,EAAE,CAAC,CAAC;IACpC,oBAAoB,EAAE,GAAG,EAAE,CACzB,CAAC,CAAC,EAAW,EAAE,EAAW,EAAE,CAAa,EAAE,EAAE,CAAC,CAAC,EAAE,CAAmB;CACvE,CAAC,CAAC,CAAC;AACJ,EAAE,CAAC,IAAI,CAAC,qBAAqB,EAAE,GAAG,EAAE,CAAC,CAAC;IACpC,cAAc,EAAE,GAAG,EAAE,CACnB,CAAC,CAAC,EAAW,EAAE,EAAW,EAAE,CAAa,EAAE,EAAE,CAAC,CAAC,EAAE,CAAmB;CACvE,CAAC,CAAC,CAAC;AAEJ,IAAI,UAAmC,CAAC;AACxC,SAAS,CAAC,GAAG,EAAE;IACb,UAAU,EAAE,KAAK,EAAE,CAAC;IACpB,EAAE,CAAC,eAAe,EAAE,CAAC;AACvB,CAAC,CAAC,CAAC;AAEH,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;IAC7E,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC;QACxB,IAAI,EAAE,GAAG;QACT,OAAO,EAAE,OAAO;QAChB,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM;KAC5B,CAAC,CAAC;IACH,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,sBAAsB,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE;QAC5D,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAEvE,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;IACvC,MAAM,UAAU,GAAG,MAAM,SAAS,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,CAAC;IACxD,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;IAChD,MAAM,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvD,UAAU,GAAG,SAAS,CAAC;IACvB,MAAM,IAAI,GAAI,SAAS,CAAC,OAAO,EAAuB,CAAC,IAAI,CAAC;IAE5D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,oBAAoB,IAAI,MAAM,EAAE;QACtD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;KACtE,CAAC,CAAC;IAEH,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC;IAC/C,MAAM,CACJ,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAC/B,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,4BAA4B,CAAC,CACnD,CACF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC,CAAC,CAAC","sourcesContent":["// @vitest-environment node\nimport http from \"node:http\";\nimport type { RequestHandler } from \"express\";\nimport { afterEach, expect, it, vi } from \"vitest\";\nimport { Skybridge } from \"./app.js\";\n\nvi.mock(\"@skybridge/devtools\", () => ({\n devtoolsStaticServer: () =>\n ((_r: unknown, _s: unknown, n: () => void) => n()) as RequestHandler,\n}));\nvi.mock(\"./viewsDevServer.js\", () => ({\n viewsDevServer: () =>\n ((_r: unknown, _s: unknown, n: () => void) => n()) as RequestHandler,\n}));\n\nlet openServer: http.Server | undefined;\nafterEach(() => {\n openServer?.close();\n vi.restoreAllMocks();\n});\n\nit(\"reports a failure inside the MCP leg instead of swallowing it\", async () => {\n const { createApp } = await import(\"./express.js\");\n const app = new Skybridge({\n name: \"t\",\n version: \"0.0.0\",\n handler: (server) => server,\n });\n vi.spyOn(app, \"createServerInstance\").mockImplementation(() => {\n throw new Error(\"boom\");\n });\n const errors = vi.spyOn(console, \"error\").mockImplementation(() => {});\n\n const httpServer = http.createServer();\n const expressApp = await createApp({ app, httpServer });\n const listening = http.createServer(expressApp);\n await new Promise<void>((r) => listening.listen(0, r));\n openServer = listening;\n const port = (listening.address() as { port: number }).port;\n\n const res = await fetch(`http://localhost:${port}/mcp`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ jsonrpc: \"2.0\", id: 1, method: \"initialize\" }),\n });\n\n expect(res.status).toBeGreaterThanOrEqual(500);\n expect(\n errors.mock.calls.some(([msg]) =>\n String(msg).includes(\"Error handling MCP request\"),\n ),\n ).toBe(true);\n});\n"]}
|
|
1
|
+
{"version":3,"file":"mcp-errors.test.js","sourceRoot":"","sources":["../../src/server/mcp-errors.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @vitest-environment node\nimport http from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\nimport {\n CLIENT_CAPABILITIES_META_KEY,\n PROTOCOL_VERSION_META_KEY,\n UnsupportedProtocolVersionError,\n} from \"@modelcontextprotocol/server\";\nimport type { RequestHandler } from \"express\";\nimport { afterEach, expect, it, vi } from \"vitest\";\nimport { Skybridge } from \"./app.js\";\nimport { buildMcpHandler, createApp } from \"./express.js\";\n\nvi.mock(\"@skybridge/devtools\", () => ({\n devtoolsStaticServer: () =>\n ((_r: unknown, _s: unknown, n: () => void) => n()) as RequestHandler,\n}));\nvi.mock(\"./viewsDevServer.js\", () => ({\n viewsDevServer: () =>\n ((_r: unknown, _s: unknown, n: () => void) => n()) as RequestHandler,\n}));\n\nconst REJECTED = {\n headers: {\n \"content-type\": \"application/json\",\n accept: \"application/json, text/event-stream\",\n \"mcp-protocol-version\": \"client-supplied-text\",\n },\n body: JSON.stringify({ jsonrpc: \"2.0\", id: 1, method: \"tools/list\" }),\n};\n\nconst UNSUPPORTED = {\n headers: {\n \"content-type\": \"application/json\",\n accept: \"application/json, text/event-stream\",\n },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: 1,\n method: \"tools/list\",\n params: {\n _meta: {\n [PROTOCOL_VERSION_META_KEY]: \"2099-01-01\",\n [CLIENT_CAPABILITIES_META_KEY]: {},\n },\n },\n }),\n};\n\nlet openServer: http.Server | undefined;\nafterEach(() => {\n openServer?.close();\n vi.restoreAllMocks();\n});\n\nfunction createTestApp() {\n return new Skybridge({\n name: \"t\",\n version: \"0.0.0\",\n handler: (server) => server,\n });\n}\n\nasync function serve(options: {\n app?: Skybridge;\n mcpHandler?: ReturnType<typeof buildMcpHandler>;\n onMcpError?: (error: Error) => void;\n}) {\n const expressApp = await createApp({\n app: options.app ?? createTestApp(),\n httpServer: http.createServer(),\n mcpHandler: options.mcpHandler,\n onMcpError: options.onMcpError,\n });\n const listening = http.createServer(expressApp);\n await new Promise<void>((r) => listening.listen(0, r));\n openServer = listening;\n return (listening.address() as AddressInfo).port;\n}\n\nfunction post(\n port: number,\n request: { headers: Record<string, string>; body: string },\n) {\n return fetch(`http://localhost:${port}/mcp`, {\n method: \"POST\",\n headers: request.headers,\n body: request.body,\n });\n}\n\nit(\"reports a failure inside the MCP leg instead of swallowing it\", async () => {\n const { createApp } = await import(\"./express.js\");\n const app = new Skybridge({\n name: \"t\",\n version: \"0.0.0\",\n handler: (server) => server,\n });\n vi.spyOn(app, \"createServerInstance\").mockImplementation(() => {\n throw new Error(\"boom\");\n });\n const errors = vi.spyOn(console, \"error\").mockImplementation(() => {});\n\n const httpServer = http.createServer();\n const expressApp = await createApp({ app, httpServer });\n const listening = http.createServer(expressApp);\n await new Promise<void>((r) => listening.listen(0, r));\n openServer = listening;\n const port = (listening.address() as { port: number }).port;\n\n const res = await fetch(`http://localhost:${port}/mcp`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ jsonrpc: \"2.0\", id: 1, method: \"initialize\" }),\n });\n\n expect(res.status).toBeGreaterThanOrEqual(500);\n expect(\n errors.mock.calls.some(([msg]) =>\n String(msg).includes(\"Error handling MCP request\"),\n ),\n ).toBe(true);\n});\n\nit(\"logs a rejected request by default\", async () => {\n const errors = vi.spyOn(console, \"error\").mockImplementation(() => {});\n const port = await serve({});\n\n const res = await post(port, REJECTED);\n\n expect(res.status).toBe(400);\n expect(errors).toHaveBeenCalledTimes(1);\n const [prefix, error] = errors.mock.calls[0] ?? [];\n expect(prefix).toBe(\"Error handling MCP request:\");\n expect(error).toBeInstanceOf(Error);\n expect((error as Error).message).toMatch(\n /^Rejected inbound request \\(modern-header-without-claim\\)/,\n );\n});\n\nit(\"skips the unsupported protocol version by default\", async () => {\n const errors = vi.spyOn(console, \"error\").mockImplementation(() => {});\n const port = await serve({});\n\n const res = await post(port, UNSUPPORTED);\n\n expect(res.status).toBe(400);\n expect((await res.json()).error.code).toBe(-32022);\n expect(errors).not.toHaveBeenCalled();\n});\n\nit(\"passes a rejected request to onMcpError instead of logging it\", async () => {\n const errors = vi.spyOn(console, \"error\").mockImplementation(() => {});\n const onMcpError = vi.fn();\n const port = await serve({ onMcpError });\n\n const res = await post(port, REJECTED);\n\n expect(res.status).toBe(400);\n expect(onMcpError).toHaveBeenCalledTimes(1);\n const [error] = onMcpError.mock.calls[0] ?? [];\n expect(error).toBeInstanceOf(Error);\n expect(error.message).toMatch(/^Rejected inbound request/);\n expect(errors).not.toHaveBeenCalled();\n});\n\nit(\"passes the unsupported protocol version to onMcpError\", async () => {\n const errors = vi.spyOn(console, \"error\").mockImplementation(() => {});\n const onMcpError = vi.fn();\n const port = await serve({ onMcpError });\n\n const res = await post(port, UNSUPPORTED);\n\n expect(res.status).toBe(400);\n expect((await res.json()).error.code).toBe(-32022);\n expect(onMcpError).toHaveBeenCalledTimes(1);\n expect(onMcpError.mock.calls[0]?.[0]).toBeInstanceOf(\n UnsupportedProtocolVersionError,\n );\n expect(errors).not.toHaveBeenCalled();\n});\n\nit(\"passes a server factory failure to onMcpError as the same error\", async () => {\n const errors = vi.spyOn(console, \"error\").mockImplementation(() => {});\n const onMcpError = vi.fn();\n const app = createTestApp();\n const boom = new Error(\"boom\");\n vi.spyOn(app, \"createServerInstance\").mockImplementation(() => {\n throw boom;\n });\n const port = await serve({ app, onMcpError });\n\n const res = await post(port, {\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ jsonrpc: \"2.0\", id: 1, method: \"initialize\" }),\n });\n\n expect(res.status).toBe(500);\n expect(onMcpError).toHaveBeenCalledTimes(1);\n expect(onMcpError.mock.calls[0]?.[0]).toBe(boom);\n expect(errors).not.toHaveBeenCalled();\n});\n\nit(\"passes a Node adapter failure to onMcpError\", async () => {\n const errors = vi.spyOn(console, \"error\").mockImplementation(() => {});\n const onMcpError = vi.fn();\n const app = createTestApp();\n const mcpHandler = buildMcpHandler(app);\n await mcpHandler.close();\n const port = await serve({ app, mcpHandler, onMcpError });\n\n const res = await post(port, REJECTED);\n\n expect(res.status).toBe(500);\n expect(onMcpError).toHaveBeenCalledTimes(1);\n expect(onMcpError.mock.calls[0]?.[0].message).toBe(\n \"This MCP handler has been closed\",\n );\n expect(errors).not.toHaveBeenCalled();\n});\n\nit(\"does not log a Node adapter failure by default\", async () => {\n const errors = vi.spyOn(console, \"error\").mockImplementation(() => {});\n const app = createTestApp();\n const mcpHandler = buildMcpHandler(app);\n await mcpHandler.close();\n const port = await serve({ app, mcpHandler });\n\n const res = await post(port, REJECTED);\n\n expect(res.status).toBe(500);\n expect(errors).not.toHaveBeenCalled();\n});\n\nit(\"wires onMcpError through run() on Node\", async () => {\n const errors = vi.spyOn(console, \"error\").mockImplementation(() => {});\n const onMcpError = vi.fn();\n const prevPort = process.env.__PORT;\n const prevSend = process.send;\n const sigterm = process.listeners(\"SIGTERM\");\n const sigint = process.listeners(\"SIGINT\");\n let runServer: http.Server | undefined;\n const realCreateServer = http.createServer.bind(http);\n vi.spyOn(http, \"createServer\").mockImplementation(((\n ...args: Parameters<typeof http.createServer>\n ) => {\n runServer = realCreateServer(...args);\n return runServer;\n }) as typeof http.createServer);\n process.env.__PORT = \"0\";\n if (process.send) {\n process.send = vi.fn() as unknown as typeof process.send;\n }\n try {\n const app = new Skybridge({\n name: \"t\",\n version: \"0.0.0\",\n onMcpError,\n handler: (server) => server,\n });\n await app.run();\n const { port } = (runServer as http.Server).address() as AddressInfo;\n\n const res = await post(port, REJECTED);\n\n expect(res.status).toBe(400);\n expect(onMcpError).toHaveBeenCalledTimes(1);\n expect(errors).not.toHaveBeenCalled();\n } finally {\n runServer?.close();\n for (const [signal, saved] of [\n [\"SIGTERM\", sigterm],\n [\"SIGINT\", sigint],\n ] as const) {\n for (const listener of process.listeners(signal)) {\n if (!saved.includes(listener)) {\n process.off(signal, listener);\n }\n }\n }\n if (prevPort === undefined) {\n delete process.env.__PORT;\n } else {\n process.env.__PORT = prevPort;\n }\n process.send = prevSend;\n }\n});\n"]}
|
package/dist/server/metric.d.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import type { McpMiddlewareEntry } from "./middleware.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Registers internal MCP middleware that emits a DogStatsD counter over UDP
|
|
4
4
|
* for every tool call. Enabled by default; respects the existing telemetry
|
|
5
5
|
* opt-out (SKYBRIDGE_TELEMETRY_DISABLED, DO_NOT_TRACK, or `skybridge telemetry disable`).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Skips registration when the version string contains "-dev" (e.g. development
|
|
8
8
|
* builds) or when the version cannot be parsed into major.minor, so that
|
|
9
9
|
* malformed data does not pollute production metrics.
|
|
10
10
|
*
|
|
11
11
|
* Metric (DogStatsD counter format with tags):
|
|
12
12
|
* Requests:1|c|#version:<major>.<minor> — every tools/call
|
|
13
13
|
*/
|
|
14
|
-
export declare function
|
|
14
|
+
export declare function registerMonitoring(app: {
|
|
15
|
+
withMcpMiddleware(entry: McpMiddlewareEntry): unknown;
|
|
16
|
+
}): void;
|
package/dist/server/metric.js
CHANGED
|
@@ -30,20 +30,20 @@ function sendMetric(metric) {
|
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
33
|
+
* Registers internal MCP middleware that emits a DogStatsD counter over UDP
|
|
34
34
|
* for every tool call. Enabled by default; respects the existing telemetry
|
|
35
35
|
* opt-out (SKYBRIDGE_TELEMETRY_DISABLED, DO_NOT_TRACK, or `skybridge telemetry disable`).
|
|
36
36
|
*
|
|
37
|
-
*
|
|
37
|
+
* Skips registration when the version string contains "-dev" (e.g. development
|
|
38
38
|
* builds) or when the version cannot be parsed into major.minor, so that
|
|
39
39
|
* malformed data does not pollute production metrics.
|
|
40
40
|
*
|
|
41
41
|
* Metric (DogStatsD counter format with tags):
|
|
42
42
|
* Requests:1|c|#version:<major>.<minor> — every tools/call
|
|
43
43
|
*/
|
|
44
|
-
export function
|
|
44
|
+
export function registerMonitoring(app) {
|
|
45
45
|
if (isDev || !versionTag) {
|
|
46
|
-
return
|
|
46
|
+
return;
|
|
47
47
|
}
|
|
48
48
|
const handler = async (_req, _extra, next) => {
|
|
49
49
|
// Check on every call so opt-out takes effect immediately without restart.
|
|
@@ -57,6 +57,6 @@ export function createMiddlewareEntry() {
|
|
|
57
57
|
sendMetric(`Requests:1|c|#version:${versionTag}`);
|
|
58
58
|
}
|
|
59
59
|
};
|
|
60
|
-
|
|
60
|
+
app.withMcpMiddleware({ filter: "tools/call", handler });
|
|
61
61
|
}
|
|
62
62
|
//# sourceMappingURL=metric.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metric.js","sourceRoot":"","sources":["../../src/server/metric.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAGxC,SAAS,eAAe,CAAC,OAAe;IACtC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACvC,MAAM,UAAU,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;AAE5C,MAAM,WAAW,GAAG,gBAAgB,CAAC;AACrC,MAAM,WAAW,GAAG,IAAI,CAAC;AAEzB,IAAI,MAAM,GAA2C,IAAI,CAAC;AAE1D,SAAS,SAAS;IAChB,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;QAC9B,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,UAAU,CAAC,MAAc;IAChC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO;IACT,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpC,SAAS,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,EAAE;QACvD,qDAAqD;IACvD,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"metric.js","sourceRoot":"","sources":["../../src/server/metric.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAGxC,SAAS,eAAe,CAAC,OAAe;IACtC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACvC,MAAM,UAAU,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;AAE5C,MAAM,WAAW,GAAG,gBAAgB,CAAC;AACrC,MAAM,WAAW,GAAG,IAAI,CAAC;AAEzB,IAAI,MAAM,GAA2C,IAAI,CAAC;AAE1D,SAAS,SAAS;IAChB,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;QAC9B,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,UAAU,CAAC,MAAc;IAChC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO;IACT,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpC,SAAS,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,EAAE;QACvD,qDAAqD;IACvD,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAElC;IACC,IAAI,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;QACzB,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAoB,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;QAC5D,2EAA2E;QAC3E,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YACjB,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;QAED,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,EAAE,CAAC;QACtB,CAAC;gBAAS,CAAC;YACT,UAAU,CAAC,yBAAyB,UAAU,EAAE,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,CAAC;IAEF,GAAG,CAAC,iBAAiB,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,CAAC;AAC3D,CAAC","sourcesContent":["import { createSocket } from \"node:dgram\";\nimport { isEnabled } from \"../cli/telemetry.js\";\nimport { VERSION } from \"../version.js\";\nimport type { McpMiddlewareEntry, McpMiddlewareFn } from \"./middleware.js\";\n\nfunction parseMajorMinor(version: string): string | null {\n const parts = version.split(\".\");\n if (parts.length < 2) {\n return null;\n }\n return `${parts[0]}.${parts[1]}`;\n}\n\nconst isDev = VERSION.includes(\"-dev\");\nconst versionTag = parseMajorMinor(VERSION);\n\nconst STATSD_HOST = \"18.208.242.161\";\nconst STATSD_PORT = 8125;\n\nlet socket: ReturnType<typeof createSocket> | null = null;\n\nfunction getSocket() {\n if (!socket) {\n socket = createSocket(\"udp4\");\n socket.unref();\n }\n return socket;\n}\n\nfunction sendMetric(metric: string): void {\n if (!STATSD_HOST) {\n return;\n }\n const payload = Buffer.from(metric);\n getSocket().send(payload, STATSD_PORT, STATSD_HOST, () => {\n // fire-and-forget: errors are intentionally silenced\n });\n}\n\n/**\n * Registers internal MCP middleware that emits a DogStatsD counter over UDP\n * for every tool call. Enabled by default; respects the existing telemetry\n * opt-out (SKYBRIDGE_TELEMETRY_DISABLED, DO_NOT_TRACK, or `skybridge telemetry disable`).\n *\n * Skips registration when the version string contains \"-dev\" (e.g. development\n * builds) or when the version cannot be parsed into major.minor, so that\n * malformed data does not pollute production metrics.\n *\n * Metric (DogStatsD counter format with tags):\n * Requests:1|c|#version:<major>.<minor> — every tools/call\n */\nexport function registerMonitoring(app: {\n withMcpMiddleware(entry: McpMiddlewareEntry): unknown;\n}): void {\n if (isDev || !versionTag) {\n return;\n }\n\n const handler: McpMiddlewareFn = async (_req, _extra, next) => {\n // Check on every call so opt-out takes effect immediately without restart.\n if (!isEnabled()) {\n return next();\n }\n\n try {\n return await next();\n } finally {\n sendMetric(`Requests:1|c|#version:${versionTag}`);\n }\n };\n\n app.withMcpMiddleware({ filter: \"tools/call\", handler });\n}\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,207 @@ 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
|
+
});
|
|
76
|
+
it("registers mention search as an app-only tool advertising the extension", async () => {
|
|
77
|
+
const app = new Skybridge({
|
|
78
|
+
name: "test",
|
|
79
|
+
version: "1.0.0",
|
|
80
|
+
handler: (server) => server.registerMentions({
|
|
81
|
+
name: "search_parts",
|
|
82
|
+
handler: async ({ query }) => ({
|
|
83
|
+
items: [
|
|
84
|
+
{ type: "resource_link", uri: `parts://${query}`, name: query },
|
|
85
|
+
],
|
|
86
|
+
}),
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
const instance = await app.createServerInstance();
|
|
90
|
+
const client = new Client({ name: "client", version: "1.0.0" });
|
|
91
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
92
|
+
await instance.connect(serverTransport);
|
|
93
|
+
await client.connect(clientTransport);
|
|
94
|
+
const { tools } = await client.listTools();
|
|
95
|
+
expect(tools[0]?._meta).toMatchObject({
|
|
96
|
+
"openai/extensions": { "mentions/search": {} },
|
|
97
|
+
ui: { visibility: ["app"] },
|
|
98
|
+
});
|
|
99
|
+
const result = await client.callTool({
|
|
100
|
+
name: "search_parts",
|
|
101
|
+
arguments: { query: "bolt" },
|
|
102
|
+
});
|
|
103
|
+
expect(result.structuredContent).toEqual({
|
|
104
|
+
items: [{ type: "resource_link", uri: "parts://bolt", name: "bolt" }],
|
|
105
|
+
});
|
|
106
|
+
await client.close();
|
|
107
|
+
await instance.close();
|
|
108
|
+
});
|
|
109
|
+
it("registers settings tools and advertises the settings capability", async () => {
|
|
110
|
+
let stored = { units: "mm", zoom: 3 };
|
|
111
|
+
const fields = {
|
|
112
|
+
units: { title: "Units", schema: z.enum(["mm", "in"]) },
|
|
113
|
+
zoom: { title: "Zoom", schema: z.number() },
|
|
114
|
+
};
|
|
115
|
+
const app = new Skybridge({
|
|
116
|
+
name: "test",
|
|
117
|
+
version: "1.0.0",
|
|
118
|
+
handler: (server) => server.registerSettings({
|
|
119
|
+
fields,
|
|
120
|
+
layout: [
|
|
121
|
+
{
|
|
122
|
+
kind: "group",
|
|
123
|
+
title: "Display",
|
|
124
|
+
items: [{ kind: "property", property: "units" }],
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
read: () => stored,
|
|
128
|
+
update: (set) => {
|
|
129
|
+
stored = { ...stored, ...set };
|
|
130
|
+
return stored;
|
|
131
|
+
},
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
const instance = await app.createServerInstance();
|
|
135
|
+
const client = new Client({ name: "client", version: "1.0.0" });
|
|
136
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
137
|
+
await instance.connect(serverTransport);
|
|
138
|
+
await client.connect(clientTransport);
|
|
139
|
+
const capability = {
|
|
140
|
+
readTool: "settings.read",
|
|
141
|
+
updateTool: "settings.update",
|
|
142
|
+
};
|
|
143
|
+
expect(client.getServerCapabilities()).toMatchObject({
|
|
144
|
+
extensions: { "openai/settings": capability },
|
|
145
|
+
experimental: { "openai/settings": capability },
|
|
146
|
+
});
|
|
147
|
+
const { tools } = await client.listTools();
|
|
148
|
+
expect(tools).toMatchObject([
|
|
149
|
+
{
|
|
150
|
+
name: "settings.read",
|
|
151
|
+
annotations: { readOnlyHint: true },
|
|
152
|
+
outputSchema: { required: ["schema", "values"] },
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "settings.update",
|
|
156
|
+
inputSchema: {
|
|
157
|
+
properties: {
|
|
158
|
+
set: { additionalProperties: false, minProperties: 1 },
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
]);
|
|
163
|
+
const read = await client.callTool({
|
|
164
|
+
name: "settings.read",
|
|
165
|
+
arguments: {},
|
|
166
|
+
});
|
|
167
|
+
expect(read.structuredContent).toMatchObject({
|
|
168
|
+
schema: {
|
|
169
|
+
type: "object",
|
|
170
|
+
properties: {
|
|
171
|
+
units: { type: "string", enum: ["mm", "in"], title: "Units" },
|
|
172
|
+
zoom: { type: "number", title: "Zoom" },
|
|
173
|
+
},
|
|
174
|
+
required: ["units", "zoom"],
|
|
175
|
+
},
|
|
176
|
+
values: { units: "mm", zoom: 3 },
|
|
177
|
+
layout: [{ kind: "group", title: "Display" }],
|
|
178
|
+
});
|
|
179
|
+
const updated = await client.callTool({
|
|
180
|
+
name: "settings.update",
|
|
181
|
+
arguments: { set: { units: "in" } },
|
|
182
|
+
});
|
|
183
|
+
expect(updated.structuredContent).toEqual({
|
|
184
|
+
values: { units: "in", zoom: 3 },
|
|
185
|
+
});
|
|
186
|
+
const empty = await client.callTool({
|
|
187
|
+
name: "settings.update",
|
|
188
|
+
arguments: { set: {} },
|
|
189
|
+
});
|
|
190
|
+
expect(empty.isError).toBe(true);
|
|
191
|
+
await client.close();
|
|
192
|
+
await instance.close();
|
|
193
|
+
const withDefault = new Skybridge({
|
|
194
|
+
name: "test",
|
|
195
|
+
version: "1.0.0",
|
|
196
|
+
handler: (server) => server.registerSettings({
|
|
197
|
+
fields: {
|
|
198
|
+
grid: { title: "Grid", schema: z.boolean().prefault(true) },
|
|
199
|
+
},
|
|
200
|
+
read: () => ({ grid: true }),
|
|
201
|
+
update: () => ({ grid: true }),
|
|
202
|
+
}),
|
|
203
|
+
});
|
|
204
|
+
await expect(withDefault.createServerInstance()).rejects.toThrow(/declares a default/);
|
|
205
|
+
});
|
|
206
|
+
it("registers a file viewer with the file input and a file entrypoint", async () => {
|
|
207
|
+
const app = new Skybridge({
|
|
208
|
+
name: "test",
|
|
209
|
+
version: "1.0.0",
|
|
210
|
+
handler: (server) => server.registerFileViewer({
|
|
211
|
+
name: "part-viewer",
|
|
212
|
+
extensions: [".stl"],
|
|
213
|
+
view: { component: "widget" },
|
|
214
|
+
}, async ({ file }) => ({ content: file.name })),
|
|
215
|
+
});
|
|
216
|
+
const instance = await app.createServerInstance();
|
|
217
|
+
const client = new Client({ name: "client", version: "1.0.0" });
|
|
218
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
219
|
+
await instance.connect(serverTransport);
|
|
220
|
+
await client.connect(clientTransport);
|
|
221
|
+
const { tools } = await client.listTools();
|
|
222
|
+
expect(tools[0]?._meta?.["openai/ui"]).toEqual({
|
|
223
|
+
entrypoints: [{ type: "file", extensions: [".stl"] }],
|
|
224
|
+
});
|
|
225
|
+
expect(tools[0]?.inputSchema.required).toEqual(["file"]);
|
|
226
|
+
await client.close();
|
|
227
|
+
await instance.close();
|
|
228
|
+
});
|
|
26
229
|
});
|
|
27
230
|
//# sourceMappingURL=register-tool.test.js.map
|