@emseepea/server 0.19.1 → 0.21.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 +46 -0
- package/dist/events-webhook.d.ts +8 -0
- package/dist/events-webhook.d.ts.map +1 -0
- package/dist/events-webhook.js +83 -0
- package/dist/events-webhook.js.map +1 -0
- package/dist/events.d.ts +97 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +322 -0
- package/dist/events.js.map +1 -0
- package/dist/external-approval.d.ts +66 -0
- package/dist/external-approval.d.ts.map +1 -0
- package/dist/external-approval.js +215 -0
- package/dist/external-approval.js.map +1 -0
- package/dist/http.d.ts +1 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +1 -1
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +130 -6
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1016,6 +1016,52 @@ The default limits are 16 active streams, 256 events, 8 KiB per event, a
|
|
|
1016
1016
|
`resourceSubscriptions`. Streams and notifications are process-local, with no
|
|
1017
1017
|
replay or reconnect recovery. List-change subscriptions are not supported.
|
|
1018
1018
|
|
|
1019
|
+
## Trigger Work from MCP Events
|
|
1020
|
+
|
|
1021
|
+
MCP Events are separate from the process-local resource subscriptions above.
|
|
1022
|
+
To offer ChatGPT's webhook event mode, pass `events` and OAuth authentication to
|
|
1023
|
+
`createEmseepea`. No event methods or outbound callbacks are enabled by default.
|
|
1024
|
+
|
|
1025
|
+
An event definition has a fixed name, description, Zod object schemas for
|
|
1026
|
+
subscription arguments and event data, and a `matches(arguments, data)` filter.
|
|
1027
|
+
The catalogue is fixed for the life of the server; redeploy to change it.
|
|
1028
|
+
|
|
1029
|
+
The `events` configuration must also provide:
|
|
1030
|
+
|
|
1031
|
+
- `ownerKey(auth)`: a stable, account-specific identifier. Do not use the OAuth
|
|
1032
|
+
client ID or an access token as the subscription owner.
|
|
1033
|
+
- `authorize({ ownerKey, name, arguments, phase })`: return `true` only while
|
|
1034
|
+
that owner may see or receive the event. The framework calls it for listing,
|
|
1035
|
+
subscribing, and again before each delivery.
|
|
1036
|
+
- `health()`: return `true` only while the durable subscription and delivery
|
|
1037
|
+
store and required outbound delivery are operational. A failure makes event
|
|
1038
|
+
methods and readiness fail closed.
|
|
1039
|
+
- `callbackTimeoutMs`: optional deadline for these adopter callbacks, from 1
|
|
1040
|
+
to 10,000 milliseconds (5,000 by default). Timed-out operations fail closed.
|
|
1041
|
+
- `store`: an adopter-owned durable implementation of `McpEventStore`. Persist
|
|
1042
|
+
subscriptions and queued deliveries across restarts. An in-memory map is
|
|
1043
|
+
suitable only for a test, not deployment. Its operations must provide:
|
|
1044
|
+
- Atomic subscription `put` and `delete`.
|
|
1045
|
+
- Durable delivery `enqueue`.
|
|
1046
|
+
- Atomic `claimDue` leases across instances.
|
|
1047
|
+
- Idempotent `complete` and `reschedule` that apply only to the current lease.
|
|
1048
|
+
|
|
1049
|
+
After a domain change, call `await publishMcpEvent(app, name, data)`. It checks
|
|
1050
|
+
the event payload, applies each subscription's filter and current access, then
|
|
1051
|
+
queues a stable event ID for matching subscribers. The background worker checks
|
|
1052
|
+
access again, signs the exact JSON bytes with Standard Webhooks, and retries
|
|
1053
|
+
transient failures with bounded backoff. It does not retry HTTP 410 or 413.
|
|
1054
|
+
|
|
1055
|
+
Callbacks must use HTTPS and public addresses. For each connection, the
|
|
1056
|
+
framework checks DNS, pins a validated address for TLS, and refuses redirects.
|
|
1057
|
+
It verifies a signed challenge before storing a subscription.
|
|
1058
|
+
|
|
1059
|
+
This opt-in supports webhook `events/list`, `events/subscribe`, and
|
|
1060
|
+
`events/unsubscribe` on the authenticated MCP endpoint. It does not implement
|
|
1061
|
+
polling, streaming, replay, or gap/terminated notifications. Local tests cover
|
|
1062
|
+
the protocol boundary and delivery logic; a production ChatGPT event-triggered
|
|
1063
|
+
journey must be verified separately before claiming that outcome.
|
|
1064
|
+
|
|
1019
1065
|
## Tell Clients When They May Reuse Results
|
|
1020
1066
|
|
|
1021
1067
|
By default, clients are told not to reuse lists, discovery details, or resource
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface WebhookResponse {
|
|
2
|
+
readonly status: number;
|
|
3
|
+
readonly body: Buffer;
|
|
4
|
+
}
|
|
5
|
+
export declare function signWebhook(secret: string, id: string, timestamp: number, body: Buffer): string;
|
|
6
|
+
/** Resolve on every request and pin the validated address for the TLS connection. */
|
|
7
|
+
export declare function postCheckedWebhook(url: string, body: Buffer, headers: Readonly<Record<string, string>>): Promise<WebhookResponse>;
|
|
8
|
+
//# sourceMappingURL=events-webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events-webhook.d.ts","sourceRoot":"","sources":["../src/events-webhook.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/F;AAED,qFAAqF;AACrF,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACxC,OAAO,CAAC,eAAe,CAAC,CAmE1B"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { createHmac } from "node:crypto";
|
|
2
|
+
import { lookup } from "node:dns/promises";
|
|
3
|
+
import { request } from "node:https";
|
|
4
|
+
import { isIP } from "node:net";
|
|
5
|
+
import { isAllowedAddress } from "./http.js";
|
|
6
|
+
const MAX_RESPONSE_BYTES = 4096;
|
|
7
|
+
const MAX_HEADER_BYTES = 8192;
|
|
8
|
+
export function signWebhook(secret, id, timestamp, body) {
|
|
9
|
+
const key = Buffer.from(secret.slice("whsec_".length), "base64");
|
|
10
|
+
return `v1,${createHmac("sha256", key).update(`${id}.${timestamp}.`).update(body).digest("base64")}`;
|
|
11
|
+
}
|
|
12
|
+
/** Resolve on every request and pin the validated address for the TLS connection. */
|
|
13
|
+
export async function postCheckedWebhook(url, body, headers) {
|
|
14
|
+
const target = new URL(url);
|
|
15
|
+
if (target.protocol !== "https:" || target.username || target.password || target.hash ||
|
|
16
|
+
isIP(target.hostname.replace(/^\[|\]$/g, "")) !== 0 ||
|
|
17
|
+
target.hostname === "localhost" || target.hostname.endsWith(".localhost") ||
|
|
18
|
+
target.hostname.endsWith(".local") ||
|
|
19
|
+
(target.port && target.port !== "443") || Buffer.byteLength(`${target.pathname}${target.search}`) > 8192) {
|
|
20
|
+
throw new Error("unsafe_callback");
|
|
21
|
+
}
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const timeout = setTimeout(() => controller.abort(), 10_000);
|
|
24
|
+
timeout.unref();
|
|
25
|
+
try {
|
|
26
|
+
const addresses = await Promise.race([
|
|
27
|
+
lookup(target.hostname, { all: true, order: "verbatim" }),
|
|
28
|
+
new Promise((_resolve, reject) => controller.signal.addEventListener("abort", () => reject(new Error("callback_timeout")), { once: true })),
|
|
29
|
+
]);
|
|
30
|
+
if (addresses.length === 0 || addresses.some(({ address, family }) => !isAllowedAddress(address, family))) {
|
|
31
|
+
throw new Error("unsafe_callback");
|
|
32
|
+
}
|
|
33
|
+
const address = addresses[0];
|
|
34
|
+
return await new Promise((resolve, reject) => {
|
|
35
|
+
const requestOptions = {
|
|
36
|
+
agent: false,
|
|
37
|
+
autoSelectFamily: false,
|
|
38
|
+
family: address.family,
|
|
39
|
+
lookup: (_hostname, _options, callback) => callback(null, address.address, address.family),
|
|
40
|
+
servername: target.hostname,
|
|
41
|
+
method: "POST",
|
|
42
|
+
maxHeaderSize: MAX_HEADER_BYTES,
|
|
43
|
+
signal: controller.signal,
|
|
44
|
+
headers: {
|
|
45
|
+
...headers,
|
|
46
|
+
"Content-Type": "application/json",
|
|
47
|
+
"Content-Length": String(body.byteLength),
|
|
48
|
+
Host: target.host,
|
|
49
|
+
"Accept-Encoding": "identity",
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
const outgoing = request(target, requestOptions, (incoming) => {
|
|
53
|
+
const contentLength = incoming.headers["content-length"];
|
|
54
|
+
if (Number(contentLength) > MAX_RESPONSE_BYTES || incoming.headers["content-encoding"] ||
|
|
55
|
+
Number(incoming.headers["content-length"]) < 0) {
|
|
56
|
+
incoming.destroy();
|
|
57
|
+
reject(new Error("invalid_callback_response"));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const chunks = [];
|
|
61
|
+
let bytes = 0;
|
|
62
|
+
incoming.on("data", (chunk) => {
|
|
63
|
+
bytes += chunk.byteLength;
|
|
64
|
+
if (bytes > MAX_RESPONSE_BYTES) {
|
|
65
|
+
incoming.destroy();
|
|
66
|
+
reject(new Error("invalid_callback_response"));
|
|
67
|
+
}
|
|
68
|
+
else
|
|
69
|
+
chunks.push(chunk);
|
|
70
|
+
});
|
|
71
|
+
incoming.once("error", reject);
|
|
72
|
+
incoming.once("aborted", () => reject(new Error("invalid_callback_response")));
|
|
73
|
+
incoming.once("end", () => resolve({ status: incoming.statusCode ?? 0, body: Buffer.concat(chunks) }));
|
|
74
|
+
});
|
|
75
|
+
outgoing.once("error", reject);
|
|
76
|
+
outgoing.end(body);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
clearTimeout(timeout);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=events-webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events-webhook.js","sourceRoot":"","sources":["../src/events-webhook.ts"],"names":[],"mappings":"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"}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { AuthInfo } from "@modelcontextprotocol/server";
|
|
2
|
+
import { type ZodType } from "zod";
|
|
3
|
+
import { postCheckedWebhook } from "./events-webhook.js";
|
|
4
|
+
export interface McpEventDefinition {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly description: string;
|
|
7
|
+
readonly inputSchema: ZodType;
|
|
8
|
+
readonly payloadSchema: ZodType;
|
|
9
|
+
readonly matches: (arguments_: Readonly<Record<string, unknown>>, data: Readonly<Record<string, unknown>>) => boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface McpEventSubscription {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
readonly ownerKey: string;
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly arguments: Readonly<Record<string, unknown>>;
|
|
16
|
+
readonly url: string;
|
|
17
|
+
readonly secret: string;
|
|
18
|
+
readonly expiresAt: number;
|
|
19
|
+
readonly verifiedUntil?: number;
|
|
20
|
+
readonly previousSecret?: string;
|
|
21
|
+
readonly previousSecretUntil?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface McpEventStore {
|
|
24
|
+
/** Implementations must persist records and make put/delete atomic across instances. */
|
|
25
|
+
get(id: string): Promise<McpEventSubscription | null>;
|
|
26
|
+
put(subscription: McpEventSubscription): Promise<void>;
|
|
27
|
+
delete(id: string): Promise<void>;
|
|
28
|
+
list(name: string): Promise<readonly McpEventSubscription[]>;
|
|
29
|
+
/** Atomic, durable queue operations. claimDue must lease jobs across instances. */
|
|
30
|
+
enqueue(delivery: McpEventDelivery): Promise<void>;
|
|
31
|
+
claimDue(limit: number, now: number, leaseMs: number): Promise<readonly McpClaimedEventDelivery[]>;
|
|
32
|
+
/** Apply only if the lease still matches; a stale worker must not resurrect a completed delivery. */
|
|
33
|
+
complete(id: string, leaseId: string): Promise<void>;
|
|
34
|
+
reschedule(delivery: McpClaimedEventDelivery): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
export interface McpEventDelivery {
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly subscriptionId: string;
|
|
39
|
+
readonly eventId: string;
|
|
40
|
+
readonly body: string;
|
|
41
|
+
readonly attempts: number;
|
|
42
|
+
readonly nextAttemptAt: number;
|
|
43
|
+
}
|
|
44
|
+
export interface McpClaimedEventDelivery extends McpEventDelivery {
|
|
45
|
+
readonly leaseId: string;
|
|
46
|
+
}
|
|
47
|
+
export interface McpEventsOptions {
|
|
48
|
+
readonly definitions: readonly McpEventDefinition[];
|
|
49
|
+
/** Maximum time to wait for adopter callbacks, in milliseconds (default 5000). */
|
|
50
|
+
readonly callbackTimeoutMs?: number;
|
|
51
|
+
readonly ownerKey: (auth: AuthInfo) => string | Promise<string>;
|
|
52
|
+
readonly authorize: (request: {
|
|
53
|
+
readonly ownerKey: string;
|
|
54
|
+
readonly name: string;
|
|
55
|
+
readonly arguments: Readonly<Record<string, unknown>>;
|
|
56
|
+
readonly phase: "list" | "subscribe" | "delivery";
|
|
57
|
+
}) => boolean | Promise<boolean>;
|
|
58
|
+
readonly health: () => boolean | Promise<boolean>;
|
|
59
|
+
readonly store: McpEventStore;
|
|
60
|
+
}
|
|
61
|
+
export interface NormalizedEventDefinition extends McpEventDefinition {
|
|
62
|
+
readonly wire: Readonly<{
|
|
63
|
+
name: string;
|
|
64
|
+
description: string;
|
|
65
|
+
delivery: readonly ["webhook"];
|
|
66
|
+
inputSchema: Record<string, unknown>;
|
|
67
|
+
payloadSchema: Record<string, unknown>;
|
|
68
|
+
}>;
|
|
69
|
+
}
|
|
70
|
+
export interface McpEventRuntime {
|
|
71
|
+
readonly definitions: readonly NormalizedEventDefinition[];
|
|
72
|
+
ownerKey(auth: AuthInfo): Promise<string>;
|
|
73
|
+
list(ownerKey: string): Promise<readonly NormalizedEventDefinition["wire"][]>;
|
|
74
|
+
validateSubscription(params: unknown): {
|
|
75
|
+
readonly definition: NormalizedEventDefinition;
|
|
76
|
+
readonly arguments: Readonly<Record<string, unknown>>;
|
|
77
|
+
readonly url: string;
|
|
78
|
+
readonly secret: string;
|
|
79
|
+
};
|
|
80
|
+
subscribe(ownerKey: string, params: unknown): Promise<{
|
|
81
|
+
readonly id: string;
|
|
82
|
+
readonly refreshBefore: string;
|
|
83
|
+
readonly cursor: null;
|
|
84
|
+
readonly truncated: false;
|
|
85
|
+
}>;
|
|
86
|
+
unsubscribe(ownerKey: string, params: unknown): Promise<void>;
|
|
87
|
+
publish(name: string, data: Readonly<Record<string, unknown>>): Promise<void>;
|
|
88
|
+
drain(): Promise<void>;
|
|
89
|
+
ready(): Promise<boolean>;
|
|
90
|
+
}
|
|
91
|
+
export declare class McpEventInputError extends Error {
|
|
92
|
+
readonly code: number;
|
|
93
|
+
readonly reason: string;
|
|
94
|
+
constructor(code: number, reason: string);
|
|
95
|
+
}
|
|
96
|
+
export declare function createMcpEventRuntime(options: McpEventsOptions, webhookPost?: typeof postCheckedWebhook): McpEventRuntime;
|
|
97
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
|
package/dist/events.js
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { isIP } from "node:net";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { postCheckedWebhook, signWebhook } from "./events-webhook.js";
|
|
5
|
+
export class McpEventInputError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
reason;
|
|
8
|
+
constructor(code, reason) {
|
|
9
|
+
super(code === -32015 ? "Callback endpoint rejected" : "Invalid event subscription");
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.reason = reason;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function createMcpEventRuntime(options, webhookPost = postCheckedWebhook) {
|
|
15
|
+
if (!options || typeof options !== "object" || !Array.isArray(options.definitions) ||
|
|
16
|
+
options.definitions.length === 0 || options.definitions.length > 100 ||
|
|
17
|
+
typeof options.ownerKey !== "function" ||
|
|
18
|
+
typeof options.authorize !== "function" || typeof options.health !== "function" ||
|
|
19
|
+
!options.store || typeof options.store !== "object" ||
|
|
20
|
+
["get", "put", "delete", "list", "enqueue", "claimDue", "complete", "reschedule"].some((method) => typeof options.store[method] !== "function")) {
|
|
21
|
+
throw new TypeError("events require definitions, ownerKey, authorize, health, and a durable store");
|
|
22
|
+
}
|
|
23
|
+
const callbackTimeoutMs = options.callbackTimeoutMs ?? 5000;
|
|
24
|
+
if (!Number.isSafeInteger(callbackTimeoutMs) || callbackTimeoutMs < 1 || callbackTimeoutMs > 10_000)
|
|
25
|
+
throw new TypeError("events.callbackTimeoutMs must be between 1 and 10000");
|
|
26
|
+
const bounded = async (callback) => {
|
|
27
|
+
let timer;
|
|
28
|
+
try {
|
|
29
|
+
return await Promise.race([
|
|
30
|
+
Promise.resolve().then(callback),
|
|
31
|
+
new Promise((_resolve, reject) => {
|
|
32
|
+
timer = setTimeout(() => reject(new Error("Event callback deadline exceeded")), callbackTimeoutMs);
|
|
33
|
+
}),
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
if (timer)
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const names = new Set();
|
|
42
|
+
const definitions = Object.freeze(options.definitions.map((definition) => {
|
|
43
|
+
if (!definition || typeof definition.name !== "string" ||
|
|
44
|
+
!/^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)+$/.test(definition.name) ||
|
|
45
|
+
names.has(definition.name) || typeof definition.description !== "string" ||
|
|
46
|
+
!definition.description.trim() || typeof definition.matches !== "function" ||
|
|
47
|
+
!(definition.inputSchema instanceof z.ZodType) ||
|
|
48
|
+
!(definition.payloadSchema instanceof z.ZodType)) {
|
|
49
|
+
throw new TypeError("events require unique named definitions with schemas and a matcher");
|
|
50
|
+
}
|
|
51
|
+
names.add(definition.name);
|
|
52
|
+
const inputSchema = z.toJSONSchema(definition.inputSchema);
|
|
53
|
+
const payloadSchema = z.toJSONSchema(definition.payloadSchema);
|
|
54
|
+
if (!inputSchema || typeof inputSchema !== "object" || Array.isArray(inputSchema) ||
|
|
55
|
+
inputSchema.type !== "object" || !payloadSchema || typeof payloadSchema !== "object" ||
|
|
56
|
+
Array.isArray(payloadSchema) || payloadSchema.type !== "object") {
|
|
57
|
+
throw new TypeError("event schemas must describe objects");
|
|
58
|
+
}
|
|
59
|
+
const wire = Object.freeze({
|
|
60
|
+
name: definition.name,
|
|
61
|
+
description: definition.description,
|
|
62
|
+
delivery: Object.freeze(["webhook"]),
|
|
63
|
+
inputSchema: deepFreeze(inputSchema),
|
|
64
|
+
payloadSchema: deepFreeze(payloadSchema),
|
|
65
|
+
});
|
|
66
|
+
return Object.freeze({ ...definition, wire });
|
|
67
|
+
}));
|
|
68
|
+
const subscriptionIdentity = (ownerKey, name, args, url) => `sub_${createHash("sha256").update(JSON.stringify([ownerKey, name, canonical(args), url])).digest("hex")}`;
|
|
69
|
+
const validateUnsubscribe = (params) => {
|
|
70
|
+
if (!params || typeof params !== "object" || Array.isArray(params))
|
|
71
|
+
throw new McpEventInputError(-32602, "invalid_params");
|
|
72
|
+
const request = params;
|
|
73
|
+
const definition = definitions.find((item) => item.name === request.name);
|
|
74
|
+
if (!definition || !request.arguments || typeof request.arguments !== "object" ||
|
|
75
|
+
Array.isArray(request.arguments))
|
|
76
|
+
throw new McpEventInputError(-32602, "invalid_params");
|
|
77
|
+
const parsed = definition.inputSchema.safeParse(request.arguments);
|
|
78
|
+
if (!parsed.success || !parsed.data || typeof parsed.data !== "object" || Array.isArray(parsed.data))
|
|
79
|
+
throw new McpEventInputError(-32602, "invalid_arguments");
|
|
80
|
+
const delivery = request.delivery;
|
|
81
|
+
if (!delivery || typeof delivery !== "object" || Array.isArray(delivery) ||
|
|
82
|
+
delivery.mode !== "webhook" ||
|
|
83
|
+
typeof delivery.url !== "string")
|
|
84
|
+
throw new McpEventInputError(-32602, "invalid_delivery");
|
|
85
|
+
let url;
|
|
86
|
+
const callbackUrl = delivery.url;
|
|
87
|
+
if (typeof callbackUrl !== "string")
|
|
88
|
+
throw new McpEventInputError(-32602, "invalid_delivery");
|
|
89
|
+
try {
|
|
90
|
+
url = new URL(callbackUrl);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
throw new McpEventInputError(-32602, "invalid_delivery");
|
|
94
|
+
}
|
|
95
|
+
if (url.protocol !== "https:" || url.username || url.password || url.hash ||
|
|
96
|
+
url.port && url.port !== "443" || isIP(url.hostname) !== 0)
|
|
97
|
+
throw new McpEventInputError(-32602, "invalid_delivery");
|
|
98
|
+
return { definition, arguments: parsed.data, url: url.href };
|
|
99
|
+
};
|
|
100
|
+
const runtime = {
|
|
101
|
+
definitions,
|
|
102
|
+
async ownerKey(auth) {
|
|
103
|
+
const ownerKey = await bounded(() => options.ownerKey(auth));
|
|
104
|
+
if (typeof ownerKey !== "string" || !ownerKey.trim() ||
|
|
105
|
+
Buffer.byteLength(ownerKey, "utf8") > 256) {
|
|
106
|
+
throw new TypeError("events.ownerKey must return a stable nonempty key of at most 256 bytes");
|
|
107
|
+
}
|
|
108
|
+
return ownerKey;
|
|
109
|
+
},
|
|
110
|
+
async list(ownerKey) {
|
|
111
|
+
const allowed = [];
|
|
112
|
+
for (const definition of definitions) {
|
|
113
|
+
if (await bounded(() => options.authorize({ ownerKey, name: definition.name,
|
|
114
|
+
arguments: {}, phase: "list" })) === true) {
|
|
115
|
+
allowed.push(definition.wire);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return allowed;
|
|
119
|
+
},
|
|
120
|
+
validateSubscription(params) {
|
|
121
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
122
|
+
throw new McpEventInputError(-32602, "invalid_params");
|
|
123
|
+
}
|
|
124
|
+
const request = params;
|
|
125
|
+
const definition = definitions.find((item) => item.name === request.name);
|
|
126
|
+
if (!definition || !request.arguments || typeof request.arguments !== "object" ||
|
|
127
|
+
Array.isArray(request.arguments) ||
|
|
128
|
+
request.cursor !== null && request.cursor !== undefined) {
|
|
129
|
+
throw new McpEventInputError(-32602, "invalid_params");
|
|
130
|
+
}
|
|
131
|
+
const parsed = definition.inputSchema.safeParse(request.arguments);
|
|
132
|
+
if (!parsed.success || !parsed.data || typeof parsed.data !== "object" ||
|
|
133
|
+
Array.isArray(parsed.data)) {
|
|
134
|
+
throw new McpEventInputError(-32602, "invalid_arguments");
|
|
135
|
+
}
|
|
136
|
+
const delivery = request.delivery;
|
|
137
|
+
if (!delivery || typeof delivery !== "object" || Array.isArray(delivery)) {
|
|
138
|
+
throw new McpEventInputError(-32602, "invalid_delivery");
|
|
139
|
+
}
|
|
140
|
+
const { mode, url, secret } = delivery;
|
|
141
|
+
if (mode !== "webhook" || typeof url !== "string" || typeof secret !== "string" ||
|
|
142
|
+
!/^whsec_[A-Za-z0-9+/]+={0,2}$/.test(secret)) {
|
|
143
|
+
throw new McpEventInputError(-32602, "invalid_delivery");
|
|
144
|
+
}
|
|
145
|
+
const decoded = Buffer.from(secret.slice(6), "base64");
|
|
146
|
+
if (decoded.length < 24 || decoded.length > 64 ||
|
|
147
|
+
decoded.toString("base64") !== secret.slice(6)) {
|
|
148
|
+
throw new McpEventInputError(-32602, "invalid_secret");
|
|
149
|
+
}
|
|
150
|
+
let callback;
|
|
151
|
+
try {
|
|
152
|
+
callback = new URL(url);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
throw new McpEventInputError(-32015, "invalid_url");
|
|
156
|
+
}
|
|
157
|
+
if (callback.protocol !== "https:" || callback.username || callback.password ||
|
|
158
|
+
callback.hash || callback.port && callback.port !== "443" ||
|
|
159
|
+
Buffer.byteLength(callback.href, "utf8") > 8192 ||
|
|
160
|
+
!callback.hostname || isIP(callback.hostname) !== 0 ||
|
|
161
|
+
callback.hostname === "localhost" || callback.hostname.endsWith(".localhost") ||
|
|
162
|
+
callback.hostname.endsWith(".local")) {
|
|
163
|
+
throw new McpEventInputError(-32015, "invalid_url");
|
|
164
|
+
}
|
|
165
|
+
return { definition, arguments: parsed.data, url: callback.href, secret };
|
|
166
|
+
},
|
|
167
|
+
async subscribe(ownerKey, params) {
|
|
168
|
+
const valid = runtime.validateSubscription(params);
|
|
169
|
+
const request = params;
|
|
170
|
+
const ttlMs = request.ttlMs;
|
|
171
|
+
if (ttlMs !== undefined && ttlMs !== null &&
|
|
172
|
+
(!Number.isSafeInteger(ttlMs) || Number(ttlMs) <= 0))
|
|
173
|
+
throw new McpEventInputError(-32602, "invalid_ttl");
|
|
174
|
+
if (!await bounded(() => options.authorize({ ownerKey, name: valid.definition.name,
|
|
175
|
+
arguments: valid.arguments, phase: "subscribe" })))
|
|
176
|
+
throw new McpEventInputError(-32602, "capability_not_found");
|
|
177
|
+
const id = subscriptionIdentity(ownerKey, valid.definition.name, valid.arguments, valid.url);
|
|
178
|
+
const prior = await bounded(() => options.store.get(id));
|
|
179
|
+
const now = Date.now();
|
|
180
|
+
if (!prior || prior.secret !== valid.secret || !prior.verifiedUntil || prior.verifiedUntil < now) {
|
|
181
|
+
const challenge = randomBytes(32).toString("base64url");
|
|
182
|
+
const messageId = `msg_verification_${randomBytes(16).toString("hex")}`;
|
|
183
|
+
const body = Buffer.from(JSON.stringify({ type: "verification", challenge }));
|
|
184
|
+
const timestamp = Math.floor(now / 1000);
|
|
185
|
+
try {
|
|
186
|
+
const response = await webhookPost(valid.url, body, {
|
|
187
|
+
"webhook-id": messageId,
|
|
188
|
+
"webhook-timestamp": String(timestamp),
|
|
189
|
+
"webhook-signature": signWebhook(valid.secret, messageId, timestamp, body),
|
|
190
|
+
"X-MCP-Subscription-Id": id,
|
|
191
|
+
});
|
|
192
|
+
if (response.status < 200 || response.status > 299) {
|
|
193
|
+
throw new McpEventInputError(-32015, "challenge_failed");
|
|
194
|
+
}
|
|
195
|
+
const answer = JSON.parse(response.body.toString("utf8"));
|
|
196
|
+
const actual = answer && typeof answer === "object" && !Array.isArray(answer)
|
|
197
|
+
? answer.challenge : undefined;
|
|
198
|
+
if (typeof actual !== "string" ||
|
|
199
|
+
!timingSafeEqual(createHash("sha256").update(actual).digest(), createHash("sha256").update(challenge).digest())) {
|
|
200
|
+
throw new McpEventInputError(-32015, "challenge_failed");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
if (error instanceof McpEventInputError)
|
|
205
|
+
throw error;
|
|
206
|
+
throw new McpEventInputError(-32015, "challenge_failed");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const expiresAt = now + Math.min(typeof ttlMs === "number" ? ttlMs : 86_400_000, 86_400_000);
|
|
210
|
+
await bounded(() => options.store.put({
|
|
211
|
+
id, ownerKey, name: valid.definition.name, arguments: valid.arguments,
|
|
212
|
+
url: valid.url, secret: valid.secret, expiresAt, verifiedUntil: now + 600_000,
|
|
213
|
+
...(prior && prior.secret !== valid.secret ? {
|
|
214
|
+
previousSecret: prior.secret, previousSecretUntil: now + 300_000,
|
|
215
|
+
} : {}),
|
|
216
|
+
}));
|
|
217
|
+
return { id, refreshBefore: new Date(expiresAt).toISOString(), cursor: null, truncated: false };
|
|
218
|
+
},
|
|
219
|
+
async unsubscribe(ownerKey, params) {
|
|
220
|
+
const valid = validateUnsubscribe(params);
|
|
221
|
+
const id = subscriptionIdentity(ownerKey, valid.definition.name, valid.arguments, valid.url);
|
|
222
|
+
const prior = await bounded(() => options.store.get(id));
|
|
223
|
+
if (prior && prior.ownerKey === ownerKey)
|
|
224
|
+
await bounded(() => options.store.delete(id));
|
|
225
|
+
},
|
|
226
|
+
async publish(name, data) {
|
|
227
|
+
if (!await runtime.ready())
|
|
228
|
+
throw new Error("Events are unavailable");
|
|
229
|
+
const definition = definitions.find((item) => item.name === name);
|
|
230
|
+
if (!definition)
|
|
231
|
+
throw new TypeError("Event name is not registered");
|
|
232
|
+
const checked = definition.payloadSchema.safeParse(data);
|
|
233
|
+
if (!checked.success || !checked.data || typeof checked.data !== "object" || Array.isArray(checked.data))
|
|
234
|
+
throw new TypeError("Event data does not match payload schema");
|
|
235
|
+
const eventId = `evt_${randomUUID()}`;
|
|
236
|
+
const body = JSON.stringify({ eventId, name, timestamp: new Date().toISOString(),
|
|
237
|
+
data: checked.data, cursor: null });
|
|
238
|
+
if (Buffer.byteLength(body, "utf8") > 262_144)
|
|
239
|
+
throw new TypeError("Event exceeds 256 KiB");
|
|
240
|
+
const subscriptions = await bounded(() => options.store.list(name));
|
|
241
|
+
const now = Date.now();
|
|
242
|
+
for (const subscription of subscriptions) {
|
|
243
|
+
if (subscription.expiresAt <= now ||
|
|
244
|
+
!definition.matches(subscription.arguments, checked.data))
|
|
245
|
+
continue;
|
|
246
|
+
if (!await bounded(() => options.authorize({ ownerKey: subscription.ownerKey,
|
|
247
|
+
name, arguments: subscription.arguments, phase: "delivery" })))
|
|
248
|
+
continue;
|
|
249
|
+
await bounded(() => options.store.enqueue({
|
|
250
|
+
id: `${subscription.id}:${eventId}`, subscriptionId: subscription.id,
|
|
251
|
+
eventId, body, attempts: 0, nextAttemptAt: now,
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
async drain() {
|
|
256
|
+
if (!await runtime.ready())
|
|
257
|
+
return;
|
|
258
|
+
const due = await bounded(() => options.store.claimDue(4, Date.now(), 60_000));
|
|
259
|
+
for (const delivery of due) {
|
|
260
|
+
if (!delivery.leaseId)
|
|
261
|
+
throw new Error("Event store returned an unleased delivery");
|
|
262
|
+
const subscription = await bounded(() => options.store.get(delivery.subscriptionId));
|
|
263
|
+
if (!subscription || subscription.expiresAt <= Date.now() ||
|
|
264
|
+
!await bounded(() => options.authorize({ ownerKey: subscription.ownerKey, name: subscription.name,
|
|
265
|
+
arguments: subscription.arguments, phase: "delivery" }))) {
|
|
266
|
+
await bounded(() => options.store.complete(delivery.id, delivery.leaseId));
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const body = Buffer.from(delivery.body);
|
|
270
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
271
|
+
let status = 0;
|
|
272
|
+
try {
|
|
273
|
+
const signature = signWebhook(subscription.secret, delivery.eventId, timestamp, body);
|
|
274
|
+
const oldSignature = subscription.previousSecret && subscription.previousSecretUntil &&
|
|
275
|
+
subscription.previousSecretUntil > Date.now()
|
|
276
|
+
? ` ${signWebhook(subscription.previousSecret, delivery.eventId, timestamp, body)}` : "";
|
|
277
|
+
const response = await webhookPost(subscription.url, body, {
|
|
278
|
+
"webhook-id": delivery.eventId,
|
|
279
|
+
"webhook-timestamp": String(timestamp),
|
|
280
|
+
"webhook-signature": `${signature}${oldSignature}`,
|
|
281
|
+
"X-MCP-Subscription-Id": subscription.id,
|
|
282
|
+
});
|
|
283
|
+
status = response.status;
|
|
284
|
+
}
|
|
285
|
+
catch { /* Retry transient delivery failures below. */ }
|
|
286
|
+
if (status >= 200 && status < 300 || status === 410 || status === 413 || delivery.attempts >= 3) {
|
|
287
|
+
await bounded(() => options.store.complete(delivery.id, delivery.leaseId));
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
await bounded(() => options.store.reschedule({ ...delivery, attempts: delivery.attempts + 1,
|
|
291
|
+
nextAttemptAt: Date.now() + Math.min(60_000, 1000 * 5 ** delivery.attempts) }));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
async ready() {
|
|
296
|
+
try {
|
|
297
|
+
return await bounded(() => options.health()) === true;
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
return Object.freeze(runtime);
|
|
305
|
+
}
|
|
306
|
+
function canonical(value) {
|
|
307
|
+
if (Array.isArray(value))
|
|
308
|
+
return value.map(canonical);
|
|
309
|
+
if (value && typeof value === "object")
|
|
310
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
|
|
311
|
+
.map(([key, item]) => [key, canonical(item)]));
|
|
312
|
+
return value;
|
|
313
|
+
}
|
|
314
|
+
function deepFreeze(value) {
|
|
315
|
+
if (value && typeof value === "object") {
|
|
316
|
+
for (const child of Object.values(value))
|
|
317
|
+
deepFreeze(child);
|
|
318
|
+
Object.freeze(value);
|
|
319
|
+
}
|
|
320
|
+
return value;
|
|
321
|
+
}
|
|
322
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
|