skybridge 0.0.0-next.6d0f0bf → 0.0.0-next.6dd4439
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/telemetry.js +2 -0
- package/dist/cli/telemetry.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.js +5 -0
- package/dist/server/express.js.map +1 -1
- package/dist/server/express.test.js +1 -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/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 +157 -13
- package/dist/web/bridges/adaptor.js.map +1 -1
- package/dist/web/bridges/adaptor.test.js +45 -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/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.js +2 -0
- package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
- package/dist/web/bridges/mcp-app/types.d.ts +17 -2
- package/dist/web/bridges/mcp-app/types.js.map +1 -1
- package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +1 -1
- 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 +5 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<a href="https://docs.skybridge.tech">
|
|
5
5
|
<picture>
|
|
6
6
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/alpic-ai/skybridge/main/docs/images/skybridge-readme-banner-dark.png" />
|
|
7
|
-
<img alt="Skybridge, the full-stack React framework for MCP
|
|
7
|
+
<img alt="Skybridge, the full-stack React framework for MCP Apps and MCP servers" src="https://raw.githubusercontent.com/alpic-ai/skybridge/main/docs/images/skybridge-readme-banner-light.png" width="100%" />
|
|
8
8
|
</picture>
|
|
9
9
|
</a>
|
|
10
10
|
</p>
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
|
|
29
29
|
## About Skybridge
|
|
30
30
|
|
|
31
|
-
Skybridge helps developers build type-safe MCP
|
|
31
|
+
Skybridge helps developers build type-safe MCP Apps for Claude, ChatGPT and other UI-enabled MCP clients, with a complete set of tooling designed for both humans and agents.
|
|
32
32
|
|
|
33
|
-
Why? MCP
|
|
33
|
+
Why? MCP Apps extend the [Model Context Protocol](https://modelcontextprotocol.io/docs/getting-started/intro) with **rich, interactive UI views** rendered from MCP servers. Conversational apps need seamless interaction between the user, the UI, and the model. This means new UX patterns, developer tooling, and abstractions.
|
|
34
34
|
Plus, the raw SDKs are low-level: no hooks, type safety, HMR, etc.
|
|
35
35
|
|
|
36
36
|
That's why we built *Skybridge*.
|
|
@@ -38,13 +38,13 @@ That's why we built *Skybridge*.
|
|
|
38
38
|
Features include:
|
|
39
39
|
|
|
40
40
|
- **Delightful dev environment**: Skybridge provides a dev server with a local emulator, hot module reload, and a permanent tunnel to connect your local app to Claude and ChatGPT.
|
|
41
|
-
- **Write once, run everywhere**: the framework abstracts implementation differences between MCP clients, so your app runs seamlessly in Claude, ChatGPT, VSCode, and any other MCP
|
|
42
|
-
- **Agent-ready**: powerful skills, CLI, and programmatic dev tool APIs, everything your coding agent needs to build MCP
|
|
41
|
+
- **Write once, run everywhere**: the framework abstracts implementation differences between MCP clients, so your app runs seamlessly in Claude, ChatGPT, VSCode, and any other MCP Apps compatible client.
|
|
42
|
+
- **Agent-ready**: powerful skills, CLI, and programmatic dev tool APIs, everything your coding agent needs to build MCP Apps end-to-end.
|
|
43
43
|
- **Type-safe end-to-end**: tRPC-style inference from MCP server tool definition to React view for type safety from server to frontend.
|
|
44
44
|
- **React-first**: Intuitive React Query-style hooks, with advanced state management.
|
|
45
45
|
- **Example library**: get started quickly with ChatGPT- and Claude-ready app examples for ecommerce, travel, SaaS, and more.
|
|
46
46
|
|
|
47
|
-
They chose to build their MCP
|
|
47
|
+
They chose to build their MCP Apps with Skybridge:
|
|
48
48
|
|
|
49
49
|
<p align="center">
|
|
50
50
|
<a href="https://www.datadoghq.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/alpic-ai/skybridge/main/docs/images/user-logos/datadog-dark.svg"><img src="https://raw.githubusercontent.com/alpic-ai/skybridge/main/docs/images/user-logos/datadog-light.svg" alt="Datadog" height="24"></picture></a>
|
|
@@ -62,7 +62,7 @@ They chose to build their MCP apps with Skybridge:
|
|
|
62
62
|
|
|
63
63
|
**For agents**
|
|
64
64
|
|
|
65
|
-
Install our [skill](https://docs.skybridge.tech/guides/migrate) for building MCP
|
|
65
|
+
Install our [skill](https://docs.skybridge.tech/guides/migrate) for building MCP Apps and ChatGPT plugins:
|
|
66
66
|
```bash
|
|
67
67
|
npx skills add alpic-ai/skybridge -s skybridge
|
|
68
68
|
```
|
|
@@ -84,14 +84,14 @@ For full install instructions, read our [**Quickstart guide**](https://docs.skyb
|
|
|
84
84
|
|
|
85
85
|
The [Skybridge documentation](https://docs.skybridge.tech) covers the full lifecycle of building MCP Apps:
|
|
86
86
|
|
|
87
|
-
- [Fundamentals](https://docs.skybridge.tech/get-started/architecture): understand MCP Apps, ChatGPT
|
|
87
|
+
- [Fundamentals](https://docs.skybridge.tech/get-started/architecture): understand MCP Apps, ChatGPT plugins, and how Skybridge bridges both runtimes.
|
|
88
88
|
- [Core concepts](https://docs.skybridge.tech/get-started/architecture): learn about server <> model <> UI data flows, LLM context sync, type safety, and instant local iteration with our devtools.
|
|
89
89
|
- [Guides](https://docs.skybridge.tech/build/tools): build real app behavior with tools, views, state, and model communication.
|
|
90
90
|
- [API Reference](https://docs.skybridge.tech/api-reference): browse our MCP server APIs, React hooks, CLI commands, and runtime compatibility.
|
|
91
91
|
|
|
92
92
|
## Deploy
|
|
93
93
|
|
|
94
|
-
Deploy Skybridge apps instantly on [Alpic](https://alpic.ai) for scalable hosting, MCP-specific analytics, permanent tunneling,
|
|
94
|
+
Deploy Skybridge apps instantly on [Alpic](https://alpic.ai) for scalable hosting, MCP-specific analytics, permanent tunneling, plugin directory compliance auditing and submission help. You can also self-host on any Node.js-compatible platform.
|
|
95
95
|
|
|
96
96
|
See our [deployment guide](https://docs.skybridge.tech/ship/deploy) for the full production path.
|
|
97
97
|
|
package/dist/cli/telemetry.js
CHANGED
|
@@ -3,6 +3,7 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import ci from "ci-info";
|
|
5
5
|
import { PostHog } from "posthog-node";
|
|
6
|
+
import { agent } from "std-env";
|
|
6
7
|
const POSTHOG_API_KEY = "phc_rQdkCYr0DO4NcZBQXZnUwsHAbau9zuNwKIpil9FQP6v";
|
|
7
8
|
const POSTHOG_HOST = "https://us.i.posthog.com";
|
|
8
9
|
const ENV_TELEMETRY_DISABLED = "SKYBRIDGE_TELEMETRY_DISABLED";
|
|
@@ -98,6 +99,7 @@ const hook = async ({ id: command, config: { version }, error, }) => {
|
|
|
98
99
|
machineId: getMachineId(),
|
|
99
100
|
sessionId: crypto.randomUUID(),
|
|
100
101
|
isCI: ci.isCI,
|
|
102
|
+
agent,
|
|
101
103
|
nodeVersion: process.version,
|
|
102
104
|
platform: process.platform,
|
|
103
105
|
outcome: error ? "failure" : "success",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/cli/telemetry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/cli/telemetry.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Hook } from \"@oclif/core\";\nimport ci from \"ci-info\";\nimport { PostHog } from \"posthog-node\";\nimport { agent } from \"std-env\";\n\nconst POSTHOG_API_KEY = \"phc_rQdkCYr0DO4NcZBQXZnUwsHAbau9zuNwKIpil9FQP6v\";\nconst POSTHOG_HOST = \"https://us.i.posthog.com\";\n\nconst ENV_TELEMETRY_DISABLED = \"SKYBRIDGE_TELEMETRY_DISABLED\";\nconst ENV_TELEMETRY_DEBUG = \"SKYBRIDGE_TELEMETRY_DEBUG\";\nconst ENV_DO_NOT_TRACK = \"DO_NOT_TRACK\";\n\nconst GLOBAL_CONFIG_DIR = join(homedir(), \".skybridge\");\nconst GLOBAL_CONFIG_FILE = join(GLOBAL_CONFIG_DIR, \"config.json\");\n\ninterface GlobalConfig {\n machineId: string;\n telemetry: {\n enabled: boolean;\n };\n}\n\ninterface TelemetryEvent {\n version: string;\n machineId: string;\n sessionId: string;\n isCI: boolean;\n agent?: string;\n nodeVersion: string;\n platform: NodeJS.Platform;\n outcome: \"success\" | \"failure\";\n error?: string;\n}\n\nlet posthogClient: PostHog | null = null;\n\nfunction getPostHogClient(): PostHog {\n if (!posthogClient) {\n posthogClient = new PostHog(POSTHOG_API_KEY, {\n host: POSTHOG_HOST,\n flushAt: 1,\n flushInterval: 0,\n });\n }\n\n return posthogClient;\n}\n\nfunction readJsonFile<T>(filePath: string): T | null {\n try {\n if (existsSync(filePath)) {\n const content = readFileSync(filePath, \"utf-8\");\n return JSON.parse(content) as T;\n }\n } catch {\n // Ignore errors reading config\n }\n return null;\n}\n\nfunction writeJsonFile(filePath: string, data: unknown): void {\n try {\n const dir = join(filePath, \"..\");\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(filePath, JSON.stringify(data, null, 2), \"utf-8\");\n } catch {\n // Ignore errors writing config\n }\n}\n\nfunction getGlobalConfig(): GlobalConfig {\n const existing = readJsonFile<GlobalConfig>(GLOBAL_CONFIG_FILE);\n if (existing?.machineId && existing?.telemetry !== undefined) {\n return existing;\n }\n\n const config: GlobalConfig = {\n machineId: existing?.machineId || crypto.randomUUID(),\n telemetry: {\n enabled: existing?.telemetry?.enabled ?? true,\n },\n };\n\n writeJsonFile(GLOBAL_CONFIG_FILE, config);\n return config;\n}\n\nexport function isEnabled(): boolean {\n if (\n process.env[ENV_TELEMETRY_DISABLED] === \"1\" ||\n process.env[ENV_TELEMETRY_DISABLED]?.toLowerCase() === \"true\"\n ) {\n return false;\n }\n\n if (\n process.env[ENV_DO_NOT_TRACK] === \"1\" ||\n process.env[ENV_DO_NOT_TRACK]?.toLowerCase() === \"true\"\n ) {\n return false;\n }\n\n if (ci.isCI) {\n return true;\n }\n\n const config = getGlobalConfig();\n return config.telemetry.enabled;\n}\n\nexport function isDebugMode(): boolean {\n return (\n process.env[ENV_TELEMETRY_DEBUG] === \"1\" ||\n process.env[ENV_TELEMETRY_DEBUG]?.toLowerCase() === \"true\"\n );\n}\n\nexport function setEnabled(enabled: boolean): void {\n const config = getGlobalConfig();\n config.telemetry.enabled = enabled;\n writeJsonFile(GLOBAL_CONFIG_FILE, config);\n}\n\nexport function getMachineId(): string {\n if (ci.isCI) {\n return ci.name ?? \"unknown-ci\";\n }\n\n return getGlobalConfig().machineId;\n}\n\nconst hook: Hook<\"finally\"> = async ({\n id: command,\n config: { version },\n error,\n}) => {\n if (!isEnabled()) {\n return;\n }\n\n const event: TelemetryEvent = {\n version,\n machineId: getMachineId(),\n sessionId: crypto.randomUUID(),\n isCI: ci.isCI,\n agent,\n nodeVersion: process.version,\n platform: process.platform,\n outcome: error ? \"failure\" : \"success\",\n error: error?.message,\n };\n\n if (isDebugMode()) {\n console.error(\n \"[Telemetry Debug] Would send event:\",\n JSON.stringify(event, null, 2),\n );\n return;\n }\n\n try {\n const client = getPostHogClient();\n client.capture({\n distinctId: event.machineId,\n event: command,\n properties: event,\n });\n } catch {\n // Silently ignore telemetry errors - never block CLI operation\n }\n};\n\nexport default hook;\n"]}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { type McpServer as SdkMcpServer, type StandardSchemaV1, type StandardSchemaWithJSON } from "@modelcontextprotocol/server";
|
|
2
|
+
/**
|
|
3
|
+
* A webhook subscription, as passed to `onSubscribe`. Whatever delivers the
|
|
4
|
+
* events POSTs them to `url`, signed with `secret`, until `expiresAt`.
|
|
5
|
+
*/
|
|
6
|
+
export interface EventSubscription {
|
|
7
|
+
/** Stable across refreshes: the same user subscribing to the same event with the same arguments and callback gets the same `id`. */
|
|
8
|
+
id: string;
|
|
9
|
+
/** Callback URL deliveries are POSTed to. */
|
|
10
|
+
url: string;
|
|
11
|
+
/** Standard Webhooks signing secret (`whsec_…`). A refresh may carry a new one. */
|
|
12
|
+
secret: string;
|
|
13
|
+
/** When the subscription lapses unless the host refreshes it. */
|
|
14
|
+
expiresAt: Date;
|
|
15
|
+
}
|
|
16
|
+
/** An event occurrence, as passed to {@link deliverEvent}. */
|
|
17
|
+
export interface EventOccurrence {
|
|
18
|
+
/** Event type name, as registered with `registerEvent`. */
|
|
19
|
+
name: string;
|
|
20
|
+
/** Stable identifier the host uses to drop duplicates, ideally the upstream's. Reuse it when retrying. */
|
|
21
|
+
id: string;
|
|
22
|
+
/** When the event happened. Defaults to now. */
|
|
23
|
+
timestamp?: Date;
|
|
24
|
+
/** Payload matching the event's `payloadSchema`. */
|
|
25
|
+
data: unknown;
|
|
26
|
+
}
|
|
27
|
+
/** Outcome of {@link deliverEvent}. Retry with the same event `id` when `retryable`. */
|
|
28
|
+
export type DeliveryResult = {
|
|
29
|
+
ok: true;
|
|
30
|
+
} | {
|
|
31
|
+
ok: false;
|
|
32
|
+
reason: string;
|
|
33
|
+
retryable: boolean;
|
|
34
|
+
status?: number;
|
|
35
|
+
};
|
|
36
|
+
type EventSchema = Record<string, StandardSchemaWithJSON> | StandardSchemaWithJSON;
|
|
37
|
+
export type InferEventSchema<T extends EventSchema> = T extends StandardSchemaWithJSON ? StandardSchemaV1.InferOutput<T> : T extends Record<string, StandardSchemaWithJSON> ? {
|
|
38
|
+
[K in keyof T as undefined extends StandardSchemaV1.InferOutput<T[K]> ? never : K]: StandardSchemaV1.InferOutput<T[K]>;
|
|
39
|
+
} & {
|
|
40
|
+
[K in keyof T as undefined extends StandardSchemaV1.InferOutput<T[K]> ? K : never]?: StandardSchemaV1.InferOutput<T[K]>;
|
|
41
|
+
} : never;
|
|
42
|
+
/** The `registerEvent` config: how the event type is described in `events/list`. */
|
|
43
|
+
export interface EventConfig<TInput extends EventSchema> {
|
|
44
|
+
/** Stable, specific name such as `comment.created`. */
|
|
45
|
+
name: string;
|
|
46
|
+
/** What the event is and when it fires. */
|
|
47
|
+
description?: string;
|
|
48
|
+
/** Subscription arguments (filters): a Zod shape or a Standard Schema object. */
|
|
49
|
+
inputSchema?: TInput;
|
|
50
|
+
/** Shape of the delivered `data`: a Zod shape or a Standard Schema object. */
|
|
51
|
+
payloadSchema: EventSchema;
|
|
52
|
+
_meta?: Record<string, unknown>;
|
|
53
|
+
}
|
|
54
|
+
/** What `registerEvent` does when the host subscribes and unsubscribes. */
|
|
55
|
+
export interface EventHooks<TArguments, TExtra> {
|
|
56
|
+
/**
|
|
57
|
+
* Runs on every `events/subscribe`, including the host's refreshes, once
|
|
58
|
+
* the callback URL is verified. Hand the subscription to whatever delivers
|
|
59
|
+
* the events, keyed by `subscription.id`. Throw (e.g. a `ProtocolError`) to
|
|
60
|
+
* reject it, for instance when the user may not access what the arguments
|
|
61
|
+
* point to.
|
|
62
|
+
*/
|
|
63
|
+
onSubscribe(args: TArguments, context: {
|
|
64
|
+
subscription: EventSubscription;
|
|
65
|
+
extra: TExtra;
|
|
66
|
+
}): void | Promise<void>;
|
|
67
|
+
/** Runs on `events/unsubscribe`. Stop delivering to `subscription.id`. */
|
|
68
|
+
onUnsubscribe(args: TArguments, context: {
|
|
69
|
+
subscription: Pick<EventSubscription, "id">;
|
|
70
|
+
extra: TExtra;
|
|
71
|
+
}): void | Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* @experimental POST one event to a subscription handed out by `onSubscribe`,
|
|
75
|
+
* as MCP Events expects: the event envelope, Standard Webhooks signature
|
|
76
|
+
* headers, a 10 second deadline and, outside development, no requests to
|
|
77
|
+
* private addresses. Pass the old and new secrets during a rotation so both
|
|
78
|
+
* sign the delivery. A subscription past `expiresAt` is not posted to.
|
|
79
|
+
* Retrying is up to the caller. API may change.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* const result = await deliverEvent(subscription, {
|
|
84
|
+
* name: "comment.created",
|
|
85
|
+
* id: comment.id,
|
|
86
|
+
* data: { documentId: comment.documentId, excerpt: comment.text.slice(0, 280) },
|
|
87
|
+
* });
|
|
88
|
+
* if (!result.ok && result.retryable) {
|
|
89
|
+
* await queue.retryLater(subscription, comment);
|
|
90
|
+
* }
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @see https://docs.skybridge.tech/api-reference/register-event
|
|
94
|
+
*/
|
|
95
|
+
export declare function deliverEvent(subscription: Pick<EventSubscription, "id" | "url"> & {
|
|
96
|
+
secret: string | string[];
|
|
97
|
+
expiresAt: Date | string;
|
|
98
|
+
}, event: EventOccurrence): Promise<DeliveryResult>;
|
|
99
|
+
/**
|
|
100
|
+
* The event types registered on one server, and the `events/*` handlers that
|
|
101
|
+
* serve them.
|
|
102
|
+
*
|
|
103
|
+
* @internal
|
|
104
|
+
*/
|
|
105
|
+
export declare class EventRegistry {
|
|
106
|
+
private readonly server;
|
|
107
|
+
private readonly events;
|
|
108
|
+
constructor(server: Pick<SdkMcpServer["server"], "setRequestHandler">);
|
|
109
|
+
add(config: EventConfig<EventSchema>, hooks: EventHooks<never, never>): void;
|
|
110
|
+
private install;
|
|
111
|
+
private resolve;
|
|
112
|
+
}
|
|
113
|
+
export {};
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import http from "node:http";
|
|
3
|
+
import https from "node:https";
|
|
4
|
+
import { INVALID_PARAMS, ProtocolError, } from "@modelcontextprotocol/server";
|
|
5
|
+
import { globalHttpAgent, globalHttpsAgent } from "request-filtering-agent";
|
|
6
|
+
import { Webhook } from "standardwebhooks";
|
|
7
|
+
import { z } from "zod/v4";
|
|
8
|
+
const EVENTS_ERROR = {
|
|
9
|
+
NotFound: -32011,
|
|
10
|
+
Forbidden: -32012,
|
|
11
|
+
Unsupported: -32014,
|
|
12
|
+
CallbackEndpointError: -32015,
|
|
13
|
+
};
|
|
14
|
+
const MAX_PAYLOAD_BYTES = 256 * 1024;
|
|
15
|
+
const DELIVERY_TIMEOUT_MS = 10_000;
|
|
16
|
+
const VERIFICATION_CACHE_MS = 60 * 60_000;
|
|
17
|
+
const MIN_TTL_MS = 60_000;
|
|
18
|
+
const DEFAULT_TTL_MS = 30 * 60_000;
|
|
19
|
+
const MAX_TTL_MS = 24 * 60 * 60_000;
|
|
20
|
+
function toStandardSchema(schema) {
|
|
21
|
+
return "~standard" in schema
|
|
22
|
+
? schema
|
|
23
|
+
: z.object(schema);
|
|
24
|
+
}
|
|
25
|
+
async function validate(schema, value) {
|
|
26
|
+
const result = await schema["~standard"].validate(value);
|
|
27
|
+
if (result.issues) {
|
|
28
|
+
throw new ProtocolError(INVALID_PARAMS, `Invalid arguments: ${result.issues.map((issue) => issue.message).join(", ")}`);
|
|
29
|
+
}
|
|
30
|
+
return result.value;
|
|
31
|
+
}
|
|
32
|
+
function canonicalJson(value) {
|
|
33
|
+
if (Array.isArray(value)) {
|
|
34
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
35
|
+
}
|
|
36
|
+
if (value && typeof value === "object") {
|
|
37
|
+
return `{${Object.keys(value)
|
|
38
|
+
.sort()
|
|
39
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
|
40
|
+
.join(",")}}`;
|
|
41
|
+
}
|
|
42
|
+
return JSON.stringify(value);
|
|
43
|
+
}
|
|
44
|
+
function subscriberOf(ctx) {
|
|
45
|
+
const authInfo = ctx.http?.authInfo;
|
|
46
|
+
const subject = authInfo?.extra?.subject;
|
|
47
|
+
const subscriber = typeof subject === "string" ? subject : authInfo?.clientId;
|
|
48
|
+
if (!subscriber) {
|
|
49
|
+
throw new ProtocolError(EVENTS_ERROR.Forbidden, "Webhook subscriptions require an authenticated caller");
|
|
50
|
+
}
|
|
51
|
+
return subscriber;
|
|
52
|
+
}
|
|
53
|
+
function isValidSecret(secret) {
|
|
54
|
+
const encoded = secret.startsWith("whsec_") ? secret.slice(6) : "";
|
|
55
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(encoded)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
const length = Buffer.from(encoded, "base64").length;
|
|
59
|
+
return length >= 24 && length <= 64;
|
|
60
|
+
}
|
|
61
|
+
const insecureCallbacksAllowed = () => process.env.NODE_ENV !== "production";
|
|
62
|
+
function checkCallbackUrl(raw) {
|
|
63
|
+
let url;
|
|
64
|
+
try {
|
|
65
|
+
url = new URL(raw);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
throw new ProtocolError(INVALID_PARAMS, "delivery.url is not a valid URL");
|
|
69
|
+
}
|
|
70
|
+
const allowed = url.protocol === "https:" ||
|
|
71
|
+
(url.protocol === "http:" && insecureCallbacksAllowed());
|
|
72
|
+
if (!allowed) {
|
|
73
|
+
throw new ProtocolError(INVALID_PARAMS, "delivery.url must use https");
|
|
74
|
+
}
|
|
75
|
+
return url.href;
|
|
76
|
+
}
|
|
77
|
+
function post(url, headers, body) {
|
|
78
|
+
const signal = AbortSignal.timeout(DELIVERY_TIMEOUT_MS);
|
|
79
|
+
const failure = (error) => ({
|
|
80
|
+
ok: false,
|
|
81
|
+
reason: signal.aborted
|
|
82
|
+
? "timeout"
|
|
83
|
+
: error?.code?.startsWith("ERR_TLS") || error?.code?.includes("CERT")
|
|
84
|
+
? "tls_error"
|
|
85
|
+
: "connection_refused",
|
|
86
|
+
});
|
|
87
|
+
const [client, agent] = url.startsWith("https:")
|
|
88
|
+
? [https, globalHttpsAgent]
|
|
89
|
+
: [http, globalHttpAgent];
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
const request = client.request(url, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: { ...headers, "content-length": Buffer.byteLength(body) },
|
|
94
|
+
signal,
|
|
95
|
+
...(!insecureCallbacksAllowed() && { agent }),
|
|
96
|
+
}, (response) => {
|
|
97
|
+
const status = response.statusCode ?? 0;
|
|
98
|
+
const chunks = [];
|
|
99
|
+
let size = 0;
|
|
100
|
+
response.on("data", (chunk) => {
|
|
101
|
+
size += chunk.length;
|
|
102
|
+
if (size <= 64 * 1024) {
|
|
103
|
+
chunks.push(chunk);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
response.on("end", () => {
|
|
107
|
+
if (status >= 200 && status < 300) {
|
|
108
|
+
resolve({ ok: true, body: Buffer.concat(chunks).toString() });
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
resolve({
|
|
112
|
+
ok: false,
|
|
113
|
+
status,
|
|
114
|
+
reason: status >= 500 ? "http_5xx" : "http_4xx",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
response.on("error", (error) => resolve(failure(error)));
|
|
119
|
+
response.on("close", () => resolve(failure()));
|
|
120
|
+
});
|
|
121
|
+
request.on("error", (error) => resolve(failure(error)));
|
|
122
|
+
request.end(body);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function signedPost(subscription, messageId, body) {
|
|
126
|
+
const signedAt = new Date();
|
|
127
|
+
const secrets = [subscription.secret].flat();
|
|
128
|
+
return post(subscription.url, {
|
|
129
|
+
"content-type": "application/json",
|
|
130
|
+
"webhook-id": messageId,
|
|
131
|
+
"webhook-timestamp": String(Math.floor(signedAt.getTime() / 1000)),
|
|
132
|
+
"webhook-signature": secrets
|
|
133
|
+
.map((secret) => new Webhook(secret).sign(messageId, signedAt, body))
|
|
134
|
+
.join(" "),
|
|
135
|
+
"x-mcp-subscription-id": subscription.id,
|
|
136
|
+
}, body);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* @experimental POST one event to a subscription handed out by `onSubscribe`,
|
|
140
|
+
* as MCP Events expects: the event envelope, Standard Webhooks signature
|
|
141
|
+
* headers, a 10 second deadline and, outside development, no requests to
|
|
142
|
+
* private addresses. Pass the old and new secrets during a rotation so both
|
|
143
|
+
* sign the delivery. A subscription past `expiresAt` is not posted to.
|
|
144
|
+
* Retrying is up to the caller. API may change.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* const result = await deliverEvent(subscription, {
|
|
149
|
+
* name: "comment.created",
|
|
150
|
+
* id: comment.id,
|
|
151
|
+
* data: { documentId: comment.documentId, excerpt: comment.text.slice(0, 280) },
|
|
152
|
+
* });
|
|
153
|
+
* if (!result.ok && result.retryable) {
|
|
154
|
+
* await queue.retryLater(subscription, comment);
|
|
155
|
+
* }
|
|
156
|
+
* ```
|
|
157
|
+
*
|
|
158
|
+
* @see https://docs.skybridge.tech/api-reference/register-event
|
|
159
|
+
*/
|
|
160
|
+
export async function deliverEvent(subscription, event) {
|
|
161
|
+
if (!(new Date(subscription.expiresAt).getTime() > Date.now())) {
|
|
162
|
+
return { ok: false, reason: "expired", retryable: false };
|
|
163
|
+
}
|
|
164
|
+
const body = JSON.stringify({
|
|
165
|
+
eventId: event.id,
|
|
166
|
+
name: event.name,
|
|
167
|
+
timestamp: (event.timestamp ?? new Date()).toISOString(),
|
|
168
|
+
data: event.data,
|
|
169
|
+
cursor: null,
|
|
170
|
+
});
|
|
171
|
+
if (Buffer.byteLength(body) > MAX_PAYLOAD_BYTES) {
|
|
172
|
+
throw new Error(`Event "${event.name}" payload exceeds ${MAX_PAYLOAD_BYTES} bytes; send a summary and expose a tool to fetch the rest.`);
|
|
173
|
+
}
|
|
174
|
+
const outcome = await signedPost(subscription, event.id, body);
|
|
175
|
+
if (outcome.ok) {
|
|
176
|
+
return { ok: true };
|
|
177
|
+
}
|
|
178
|
+
const status = outcome.status ?? 0;
|
|
179
|
+
return {
|
|
180
|
+
...outcome,
|
|
181
|
+
retryable: status !== 410 && status !== 413 && (status < 300 || status >= 400),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const verifiedCallbacks = new Map();
|
|
185
|
+
async function verifyCallback(subscriber, subscription) {
|
|
186
|
+
const key = `${subscriber}\n${subscription.url}`;
|
|
187
|
+
const now = Date.now();
|
|
188
|
+
if ((verifiedCallbacks.get(key) ?? 0) > now) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const challenge = randomBytes(24).toString("base64url");
|
|
192
|
+
const outcome = await signedPost(subscription, `msg_verification_${randomBytes(12).toString("hex")}`, JSON.stringify({ type: "verification", challenge }));
|
|
193
|
+
let echoed;
|
|
194
|
+
if (outcome.ok) {
|
|
195
|
+
try {
|
|
196
|
+
echoed = JSON.parse(outcome.body)?.challenge;
|
|
197
|
+
}
|
|
198
|
+
catch { }
|
|
199
|
+
}
|
|
200
|
+
const matched = typeof echoed === "string" &&
|
|
201
|
+
echoed.length === challenge.length &&
|
|
202
|
+
timingSafeEqual(Buffer.from(echoed), Buffer.from(challenge));
|
|
203
|
+
if (!matched) {
|
|
204
|
+
throw new ProtocolError(EVENTS_ERROR.CallbackEndpointError, "Callback endpoint verification failed", { reason: outcome.ok ? "challenge_failed" : outcome.reason });
|
|
205
|
+
}
|
|
206
|
+
for (const [cached, until] of verifiedCallbacks) {
|
|
207
|
+
if (until <= now) {
|
|
208
|
+
verifiedCallbacks.delete(cached);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
verifiedCallbacks.set(key, now + VERIFICATION_CACHE_MS);
|
|
212
|
+
}
|
|
213
|
+
const SubscriptionKeySchema = z.object({
|
|
214
|
+
name: z.string(),
|
|
215
|
+
arguments: z.record(z.string(), z.unknown()).optional(),
|
|
216
|
+
delivery: z.object({ url: z.string() }),
|
|
217
|
+
});
|
|
218
|
+
const SubscribeParamsSchema = SubscriptionKeySchema.extend({
|
|
219
|
+
delivery: z.object({
|
|
220
|
+
mode: z.string().optional(),
|
|
221
|
+
url: z.string(),
|
|
222
|
+
secret: z.string(),
|
|
223
|
+
}),
|
|
224
|
+
cursor: z.string().nullable().optional(),
|
|
225
|
+
ttlMs: z.number().nullable().optional(),
|
|
226
|
+
});
|
|
227
|
+
/**
|
|
228
|
+
* The event types registered on one server, and the `events/*` handlers that
|
|
229
|
+
* serve them.
|
|
230
|
+
*
|
|
231
|
+
* @internal
|
|
232
|
+
*/
|
|
233
|
+
export class EventRegistry {
|
|
234
|
+
server;
|
|
235
|
+
events = new Map();
|
|
236
|
+
constructor(server) {
|
|
237
|
+
this.server = server;
|
|
238
|
+
}
|
|
239
|
+
add(config, hooks) {
|
|
240
|
+
if (this.events.has(config.name)) {
|
|
241
|
+
throw new Error(`Event "${config.name}" is already registered.`);
|
|
242
|
+
}
|
|
243
|
+
if (this.events.size === 0) {
|
|
244
|
+
this.install();
|
|
245
|
+
}
|
|
246
|
+
this.events.set(config.name, {
|
|
247
|
+
name: config.name,
|
|
248
|
+
description: config.description,
|
|
249
|
+
inputSchema: toStandardSchema(config.inputSchema ?? {}),
|
|
250
|
+
payloadSchema: toStandardSchema(config.payloadSchema),
|
|
251
|
+
_meta: config._meta,
|
|
252
|
+
hooks,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
install() {
|
|
256
|
+
this.server.setRequestHandler("events/list", { params: z.object({ cursor: z.string().optional() }).optional() }, () => ({
|
|
257
|
+
events: [...this.events.values()].map((event) => ({
|
|
258
|
+
name: event.name,
|
|
259
|
+
...(event.description && { description: event.description }),
|
|
260
|
+
delivery: ["webhook"],
|
|
261
|
+
inputSchema: event.inputSchema["~standard"].jsonSchema.input({
|
|
262
|
+
target: "draft-2020-12",
|
|
263
|
+
}),
|
|
264
|
+
payloadSchema: event.payloadSchema["~standard"].jsonSchema.output({
|
|
265
|
+
target: "draft-2020-12",
|
|
266
|
+
}),
|
|
267
|
+
...(event._meta && { _meta: event._meta }),
|
|
268
|
+
})),
|
|
269
|
+
}));
|
|
270
|
+
this.server.setRequestHandler("events/subscribe", { params: SubscribeParamsSchema }, async (params, ctx) => {
|
|
271
|
+
if (params.delivery.mode && params.delivery.mode !== "webhook") {
|
|
272
|
+
throw new ProtocolError(EVENTS_ERROR.Unsupported, `Unsupported delivery mode: ${params.delivery.mode}`, { feature: "deliveryMode", value: params.delivery.mode });
|
|
273
|
+
}
|
|
274
|
+
if (!isValidSecret(params.delivery.secret)) {
|
|
275
|
+
throw new ProtocolError(INVALID_PARAMS, "delivery.secret must be whsec_ followed by base64 of 24 to 64 bytes");
|
|
276
|
+
}
|
|
277
|
+
const { event, subscriber, args, id, url } = await this.resolve(params, ctx);
|
|
278
|
+
const ttl = params.ttlMs === null ? MAX_TTL_MS : (params.ttlMs ?? DEFAULT_TTL_MS);
|
|
279
|
+
const subscription = {
|
|
280
|
+
id,
|
|
281
|
+
url,
|
|
282
|
+
secret: params.delivery.secret,
|
|
283
|
+
expiresAt: new Date(Date.now() + Math.min(Math.max(ttl, MIN_TTL_MS), MAX_TTL_MS)),
|
|
284
|
+
};
|
|
285
|
+
await verifyCallback(subscriber, subscription);
|
|
286
|
+
await event.hooks.onSubscribe(args, { subscription, extra: ctx });
|
|
287
|
+
return {
|
|
288
|
+
id,
|
|
289
|
+
refreshBefore: subscription.expiresAt.toISOString(),
|
|
290
|
+
cursor: null,
|
|
291
|
+
truncated: false,
|
|
292
|
+
};
|
|
293
|
+
});
|
|
294
|
+
this.server.setRequestHandler("events/unsubscribe", { params: SubscriptionKeySchema }, async (params, ctx) => {
|
|
295
|
+
const { event, args, id } = await this.resolve(params, ctx);
|
|
296
|
+
await event.hooks.onUnsubscribe(args, {
|
|
297
|
+
subscription: { id },
|
|
298
|
+
extra: ctx,
|
|
299
|
+
});
|
|
300
|
+
return {};
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
async resolve(params, ctx) {
|
|
304
|
+
const event = this.events.get(params.name);
|
|
305
|
+
if (!event) {
|
|
306
|
+
throw new ProtocolError(EVENTS_ERROR.NotFound, `Unknown event: ${params.name}`, { kind: "event" });
|
|
307
|
+
}
|
|
308
|
+
const url = checkCallbackUrl(params.delivery.url);
|
|
309
|
+
const subscriber = subscriberOf(ctx);
|
|
310
|
+
const rawArguments = params.arguments ?? {};
|
|
311
|
+
const args = await validate(event.inputSchema, rawArguments);
|
|
312
|
+
const hash = createHash("sha256")
|
|
313
|
+
.update(canonicalJson([subscriber, url, event.name, rawArguments]))
|
|
314
|
+
.digest("hex");
|
|
315
|
+
return { event, subscriber, args, url, id: `sub_${hash.slice(0, 32)}` };
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/server/events.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomBytes, timingSafeEqual } from \"node:crypto\";\nimport http from \"node:http\";\nimport https from \"node:https\";\nimport {\n INVALID_PARAMS,\n ProtocolError,\n type McpServer as SdkMcpServer,\n type ServerContext,\n type StandardSchemaV1,\n type StandardSchemaWithJSON,\n} from \"@modelcontextprotocol/server\";\nimport { globalHttpAgent, globalHttpsAgent } from \"request-filtering-agent\";\nimport { Webhook } from \"standardwebhooks\";\nimport { z } from \"zod/v4\";\n\nconst EVENTS_ERROR = {\n NotFound: -32011,\n Forbidden: -32012,\n Unsupported: -32014,\n CallbackEndpointError: -32015,\n} as const;\n\nconst MAX_PAYLOAD_BYTES = 256 * 1024;\nconst DELIVERY_TIMEOUT_MS = 10_000;\nconst VERIFICATION_CACHE_MS = 60 * 60_000;\nconst MIN_TTL_MS = 60_000;\nconst DEFAULT_TTL_MS = 30 * 60_000;\nconst MAX_TTL_MS = 24 * 60 * 60_000;\n\n/**\n * A webhook subscription, as passed to `onSubscribe`. Whatever delivers the\n * events POSTs them to `url`, signed with `secret`, until `expiresAt`.\n */\nexport interface EventSubscription {\n /** Stable across refreshes: the same user subscribing to the same event with the same arguments and callback gets the same `id`. */\n id: string;\n /** Callback URL deliveries are POSTed to. */\n url: string;\n /** Standard Webhooks signing secret (`whsec_…`). A refresh may carry a new one. */\n secret: string;\n /** When the subscription lapses unless the host refreshes it. */\n expiresAt: Date;\n}\n\n/** An event occurrence, as passed to {@link deliverEvent}. */\nexport interface EventOccurrence {\n /** Event type name, as registered with `registerEvent`. */\n name: string;\n /** Stable identifier the host uses to drop duplicates, ideally the upstream's. Reuse it when retrying. */\n id: string;\n /** When the event happened. Defaults to now. */\n timestamp?: Date;\n /** Payload matching the event's `payloadSchema`. */\n data: unknown;\n}\n\n/** Outcome of {@link deliverEvent}. Retry with the same event `id` when `retryable`. */\nexport type DeliveryResult =\n | { ok: true }\n | { ok: false; reason: string; retryable: boolean; status?: number };\n\ntype EventSchema =\n | Record<string, StandardSchemaWithJSON>\n | StandardSchemaWithJSON;\n\nexport type InferEventSchema<T extends EventSchema> =\n T extends StandardSchemaWithJSON\n ? StandardSchemaV1.InferOutput<T>\n : T extends Record<string, StandardSchemaWithJSON>\n ? {\n [K in keyof T as undefined extends StandardSchemaV1.InferOutput<T[K]>\n ? never\n : K]: StandardSchemaV1.InferOutput<T[K]>;\n } & {\n [K in keyof T as undefined extends StandardSchemaV1.InferOutput<T[K]>\n ? K\n : never]?: StandardSchemaV1.InferOutput<T[K]>;\n }\n : never;\n\n/** The `registerEvent` config: how the event type is described in `events/list`. */\nexport interface EventConfig<TInput extends EventSchema> {\n /** Stable, specific name such as `comment.created`. */\n name: string;\n /** What the event is and when it fires. */\n description?: string;\n /** Subscription arguments (filters): a Zod shape or a Standard Schema object. */\n inputSchema?: TInput;\n /** Shape of the delivered `data`: a Zod shape or a Standard Schema object. */\n payloadSchema: EventSchema;\n _meta?: Record<string, unknown>;\n}\n\n/** What `registerEvent` does when the host subscribes and unsubscribes. */\nexport interface EventHooks<TArguments, TExtra> {\n /**\n * Runs on every `events/subscribe`, including the host's refreshes, once\n * the callback URL is verified. Hand the subscription to whatever delivers\n * the events, keyed by `subscription.id`. Throw (e.g. a `ProtocolError`) to\n * reject it, for instance when the user may not access what the arguments\n * point to.\n */\n onSubscribe(\n args: TArguments,\n context: { subscription: EventSubscription; extra: TExtra },\n ): void | Promise<void>;\n /** Runs on `events/unsubscribe`. Stop delivering to `subscription.id`. */\n onUnsubscribe(\n args: TArguments,\n context: { subscription: Pick<EventSubscription, \"id\">; extra: TExtra },\n ): void | Promise<void>;\n}\n\ntype RegisteredEvent = {\n name: string;\n description?: string;\n inputSchema: StandardSchemaWithJSON;\n payloadSchema: StandardSchemaWithJSON;\n _meta?: Record<string, unknown>;\n hooks: EventHooks<Record<string, unknown>, ServerContext>;\n};\n\nfunction toStandardSchema(schema: EventSchema): StandardSchemaWithJSON {\n return \"~standard\" in schema\n ? (schema as StandardSchemaWithJSON)\n : z.object(schema as unknown as z.ZodRawShape);\n}\n\nasync function validate(\n schema: StandardSchemaWithJSON,\n value: unknown,\n): Promise<Record<string, unknown>> {\n const result = await schema[\"~standard\"].validate(value);\n if (result.issues) {\n throw new ProtocolError(\n INVALID_PARAMS,\n `Invalid arguments: ${result.issues.map((issue) => issue.message).join(\", \")}`,\n );\n }\n return result.value as Record<string, unknown>;\n}\n\nfunction canonicalJson(value: unknown): string {\n if (Array.isArray(value)) {\n return `[${value.map(canonicalJson).join(\",\")}]`;\n }\n if (value && typeof value === \"object\") {\n return `{${Object.keys(value)\n .sort()\n .map(\n (key) =>\n `${JSON.stringify(key)}:${canonicalJson((value as Record<string, unknown>)[key])}`,\n )\n .join(\",\")}}`;\n }\n return JSON.stringify(value);\n}\n\nfunction subscriberOf(ctx: ServerContext): string {\n const authInfo = ctx.http?.authInfo;\n const subject = authInfo?.extra?.subject;\n const subscriber = typeof subject === \"string\" ? subject : authInfo?.clientId;\n if (!subscriber) {\n throw new ProtocolError(\n EVENTS_ERROR.Forbidden,\n \"Webhook subscriptions require an authenticated caller\",\n );\n }\n return subscriber;\n}\n\nfunction isValidSecret(secret: string): boolean {\n const encoded = secret.startsWith(\"whsec_\") ? secret.slice(6) : \"\";\n if (!/^[A-Za-z0-9+/]+={0,2}$/.test(encoded)) {\n return false;\n }\n const length = Buffer.from(encoded, \"base64\").length;\n return length >= 24 && length <= 64;\n}\n\nconst insecureCallbacksAllowed = () => process.env.NODE_ENV !== \"production\";\n\nfunction checkCallbackUrl(raw: string): string {\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n throw new ProtocolError(INVALID_PARAMS, \"delivery.url is not a valid URL\");\n }\n const allowed =\n url.protocol === \"https:\" ||\n (url.protocol === \"http:\" && insecureCallbacksAllowed());\n if (!allowed) {\n throw new ProtocolError(INVALID_PARAMS, \"delivery.url must use https\");\n }\n return url.href;\n}\n\ntype PostOutcome =\n | { ok: true; body: string }\n | { ok: false; status?: number; reason: string };\n\nfunction post(\n url: string,\n headers: Record<string, string>,\n body: string,\n): Promise<PostOutcome> {\n const signal = AbortSignal.timeout(DELIVERY_TIMEOUT_MS);\n const failure = (error?: NodeJS.ErrnoException): PostOutcome => ({\n ok: false,\n reason: signal.aborted\n ? \"timeout\"\n : error?.code?.startsWith(\"ERR_TLS\") || error?.code?.includes(\"CERT\")\n ? \"tls_error\"\n : \"connection_refused\",\n });\n const [client, agent] = url.startsWith(\"https:\")\n ? [https, globalHttpsAgent]\n : [http, globalHttpAgent];\n return new Promise((resolve) => {\n const request = client.request(\n url,\n {\n method: \"POST\",\n headers: { ...headers, \"content-length\": Buffer.byteLength(body) },\n signal,\n ...(!insecureCallbacksAllowed() && { agent }),\n },\n (response) => {\n const status = response.statusCode ?? 0;\n const chunks: Buffer[] = [];\n let size = 0;\n response.on(\"data\", (chunk: Buffer) => {\n size += chunk.length;\n if (size <= 64 * 1024) {\n chunks.push(chunk);\n }\n });\n response.on(\"end\", () => {\n if (status >= 200 && status < 300) {\n resolve({ ok: true, body: Buffer.concat(chunks).toString() });\n } else {\n resolve({\n ok: false,\n status,\n reason: status >= 500 ? \"http_5xx\" : \"http_4xx\",\n });\n }\n });\n response.on(\"error\", (error) => resolve(failure(error)));\n response.on(\"close\", () => resolve(failure()));\n },\n );\n request.on(\"error\", (error) => resolve(failure(error)));\n request.end(body);\n });\n}\n\nfunction signedPost(\n subscription: { id: string; url: string; secret: string | string[] },\n messageId: string,\n body: string,\n): Promise<PostOutcome> {\n const signedAt = new Date();\n const secrets = [subscription.secret].flat();\n return post(\n subscription.url,\n {\n \"content-type\": \"application/json\",\n \"webhook-id\": messageId,\n \"webhook-timestamp\": String(Math.floor(signedAt.getTime() / 1000)),\n \"webhook-signature\": secrets\n .map((secret) => new Webhook(secret).sign(messageId, signedAt, body))\n .join(\" \"),\n \"x-mcp-subscription-id\": subscription.id,\n },\n body,\n );\n}\n\n/**\n * @experimental POST one event to a subscription handed out by `onSubscribe`,\n * as MCP Events expects: the event envelope, Standard Webhooks signature\n * headers, a 10 second deadline and, outside development, no requests to\n * private addresses. Pass the old and new secrets during a rotation so both\n * sign the delivery. A subscription past `expiresAt` is not posted to.\n * Retrying is up to the caller. API may change.\n *\n * @example\n * ```ts\n * const result = await deliverEvent(subscription, {\n * name: \"comment.created\",\n * id: comment.id,\n * data: { documentId: comment.documentId, excerpt: comment.text.slice(0, 280) },\n * });\n * if (!result.ok && result.retryable) {\n * await queue.retryLater(subscription, comment);\n * }\n * ```\n *\n * @see https://docs.skybridge.tech/api-reference/register-event\n */\nexport async function deliverEvent(\n subscription: Pick<EventSubscription, \"id\" | \"url\"> & {\n secret: string | string[];\n expiresAt: Date | string;\n },\n event: EventOccurrence,\n): Promise<DeliveryResult> {\n if (!(new Date(subscription.expiresAt).getTime() > Date.now())) {\n return { ok: false, reason: \"expired\", retryable: false };\n }\n const body = JSON.stringify({\n eventId: event.id,\n name: event.name,\n timestamp: (event.timestamp ?? new Date()).toISOString(),\n data: event.data,\n cursor: null,\n });\n if (Buffer.byteLength(body) > MAX_PAYLOAD_BYTES) {\n throw new Error(\n `Event \"${event.name}\" payload exceeds ${MAX_PAYLOAD_BYTES} bytes; send a summary and expose a tool to fetch the rest.`,\n );\n }\n const outcome = await signedPost(subscription, event.id, body);\n if (outcome.ok) {\n return { ok: true };\n }\n const status = outcome.status ?? 0;\n return {\n ...outcome,\n retryable:\n status !== 410 && status !== 413 && (status < 300 || status >= 400),\n };\n}\n\nconst verifiedCallbacks = new Map<string, number>();\n\nasync function verifyCallback(\n subscriber: string,\n subscription: EventSubscription,\n): Promise<void> {\n const key = `${subscriber}\\n${subscription.url}`;\n const now = Date.now();\n if ((verifiedCallbacks.get(key) ?? 0) > now) {\n return;\n }\n const challenge = randomBytes(24).toString(\"base64url\");\n const outcome = await signedPost(\n subscription,\n `msg_verification_${randomBytes(12).toString(\"hex\")}`,\n JSON.stringify({ type: \"verification\", challenge }),\n );\n let echoed: unknown;\n if (outcome.ok) {\n try {\n echoed = JSON.parse(outcome.body)?.challenge;\n } catch {}\n }\n const matched =\n typeof echoed === \"string\" &&\n echoed.length === challenge.length &&\n timingSafeEqual(Buffer.from(echoed), Buffer.from(challenge));\n if (!matched) {\n throw new ProtocolError(\n EVENTS_ERROR.CallbackEndpointError,\n \"Callback endpoint verification failed\",\n { reason: outcome.ok ? \"challenge_failed\" : outcome.reason },\n );\n }\n for (const [cached, until] of verifiedCallbacks) {\n if (until <= now) {\n verifiedCallbacks.delete(cached);\n }\n }\n verifiedCallbacks.set(key, now + VERIFICATION_CACHE_MS);\n}\n\nconst SubscriptionKeySchema = z.object({\n name: z.string(),\n arguments: z.record(z.string(), z.unknown()).optional(),\n delivery: z.object({ url: z.string() }),\n});\n\nconst SubscribeParamsSchema = SubscriptionKeySchema.extend({\n delivery: z.object({\n mode: z.string().optional(),\n url: z.string(),\n secret: z.string(),\n }),\n cursor: z.string().nullable().optional(),\n ttlMs: z.number().nullable().optional(),\n});\n\n/**\n * The event types registered on one server, and the `events/*` handlers that\n * serve them.\n *\n * @internal\n */\nexport class EventRegistry {\n private readonly events = new Map<string, RegisteredEvent>();\n\n constructor(\n private readonly server: Pick<SdkMcpServer[\"server\"], \"setRequestHandler\">,\n ) {}\n\n add(config: EventConfig<EventSchema>, hooks: EventHooks<never, never>): void {\n if (this.events.has(config.name)) {\n throw new Error(`Event \"${config.name}\" is already registered.`);\n }\n if (this.events.size === 0) {\n this.install();\n }\n this.events.set(config.name, {\n name: config.name,\n description: config.description,\n inputSchema: toStandardSchema(config.inputSchema ?? {}),\n payloadSchema: toStandardSchema(config.payloadSchema),\n _meta: config._meta,\n hooks,\n });\n }\n\n private install(): void {\n this.server.setRequestHandler(\n \"events/list\",\n { params: z.object({ cursor: z.string().optional() }).optional() },\n () => ({\n events: [...this.events.values()].map((event) => ({\n name: event.name,\n ...(event.description && { description: event.description }),\n delivery: [\"webhook\"],\n inputSchema: event.inputSchema[\"~standard\"].jsonSchema.input({\n target: \"draft-2020-12\",\n }),\n payloadSchema: event.payloadSchema[\"~standard\"].jsonSchema.output({\n target: \"draft-2020-12\",\n }),\n ...(event._meta && { _meta: event._meta }),\n })),\n }),\n );\n\n this.server.setRequestHandler(\n \"events/subscribe\",\n { params: SubscribeParamsSchema },\n async (params, ctx) => {\n if (params.delivery.mode && params.delivery.mode !== \"webhook\") {\n throw new ProtocolError(\n EVENTS_ERROR.Unsupported,\n `Unsupported delivery mode: ${params.delivery.mode}`,\n { feature: \"deliveryMode\", value: params.delivery.mode },\n );\n }\n if (!isValidSecret(params.delivery.secret)) {\n throw new ProtocolError(\n INVALID_PARAMS,\n \"delivery.secret must be whsec_ followed by base64 of 24 to 64 bytes\",\n );\n }\n const { event, subscriber, args, id, url } = await this.resolve(\n params,\n ctx,\n );\n const ttl =\n params.ttlMs === null ? MAX_TTL_MS : (params.ttlMs ?? DEFAULT_TTL_MS);\n const subscription: EventSubscription = {\n id,\n url,\n secret: params.delivery.secret,\n expiresAt: new Date(\n Date.now() + Math.min(Math.max(ttl, MIN_TTL_MS), MAX_TTL_MS),\n ),\n };\n await verifyCallback(subscriber, subscription);\n await event.hooks.onSubscribe(args, { subscription, extra: ctx });\n return {\n id,\n refreshBefore: subscription.expiresAt.toISOString(),\n cursor: null,\n truncated: false,\n };\n },\n );\n\n this.server.setRequestHandler(\n \"events/unsubscribe\",\n { params: SubscriptionKeySchema },\n async (params, ctx) => {\n const { event, args, id } = await this.resolve(params, ctx);\n await event.hooks.onUnsubscribe(args, {\n subscription: { id },\n extra: ctx,\n });\n return {};\n },\n );\n }\n\n private async resolve(\n params: z.infer<typeof SubscriptionKeySchema>,\n ctx: ServerContext,\n ) {\n const event = this.events.get(params.name);\n if (!event) {\n throw new ProtocolError(\n EVENTS_ERROR.NotFound,\n `Unknown event: ${params.name}`,\n { kind: \"event\" },\n );\n }\n const url = checkCallbackUrl(params.delivery.url);\n const subscriber = subscriberOf(ctx);\n const rawArguments = params.arguments ?? {};\n const args = await validate(event.inputSchema, rawArguments);\n const hash = createHash(\"sha256\")\n .update(canonicalJson([subscriber, url, event.name, rawArguments]))\n .digest(\"hex\");\n return { event, subscriber, args, url, id: `sub_${hash.slice(0, 32)}` };\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|