@acid-cat/spec-ts 0.9.0 → 0.10.1
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 +3 -0
- package/dist/index.d.ts +167 -0
- package/dist/index.js +394 -18
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,5 +17,8 @@ pnpm add @acid-cat/spec-ts
|
|
|
17
17
|
| `CALL_FAILURE_KINDS`, `CallFailureError` | The kinds of call failure, and the JavaScript error for one |
|
|
18
18
|
| `AVAILABLE`, `unavailable` | Make [availability](../../spec/invocation.md#availability) values |
|
|
19
19
|
| `viewOf`, `bundle` | The [view](../../spec/catalog.md#views) of a target, and a schema with its shared types |
|
|
20
|
+
| `resolveType` | The type that the `$ref` of a schema refers to, whatever other keys the schema has |
|
|
21
|
+
| `ownMember` | The member of an object with a key, or `undefined`. Inherited names, such as `constructor`, are not members |
|
|
22
|
+
| `byCodePoint` | Order strings by Unicode code point, as the [canonical form](../../spec/catalog.md#canonical-form) sorts keys |
|
|
20
23
|
|
|
21
24
|
The other exports give the names, flags, exit codes and tool shapes of each [binding](../../spec/bindings/README.md).
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* implementation. This package has no dependencies.
|
|
4
4
|
*
|
|
5
5
|
* - Outcomes and call failures: spec/invocation.md
|
|
6
|
+
* - Subscriptions and occurrences: spec/events.md
|
|
6
7
|
* - Names, views and bundling: spec/catalog.md
|
|
7
8
|
* - Exit codes, status codes and MCP results: spec/bindings/
|
|
8
9
|
*/
|
|
@@ -36,6 +37,39 @@ export declare class CallFailureError extends Error {
|
|
|
36
37
|
readonly kind: CallFailureKind;
|
|
37
38
|
constructor(failure: CallFailure);
|
|
38
39
|
}
|
|
40
|
+
/** The kinds of call failure that a subscription can fail with before its first occurrence, in order of precedence. */
|
|
41
|
+
export declare const SUBSCRIBE_FAILURE_KINDS: readonly ["cancelled", "unknown-capability", "invalid-input", "internal"];
|
|
42
|
+
/** One thing that happened, in its JSON form (spec/events.md#occurrences). */
|
|
43
|
+
export interface Occurrence<Payload = unknown> {
|
|
44
|
+
readonly eventId: string;
|
|
45
|
+
readonly name: string;
|
|
46
|
+
readonly timestamp: string;
|
|
47
|
+
readonly data: Payload;
|
|
48
|
+
readonly cursor?: string;
|
|
49
|
+
readonly truncated?: true;
|
|
50
|
+
}
|
|
51
|
+
/** What a provider gives for one occurrence. The binding adds `name`, and makes `eventId` and `timestamp` if they are absent. */
|
|
52
|
+
export interface Emitted<Payload = unknown> {
|
|
53
|
+
readonly data: Payload;
|
|
54
|
+
readonly eventId?: string;
|
|
55
|
+
readonly timestamp?: string;
|
|
56
|
+
readonly cursor?: string;
|
|
57
|
+
readonly truncated?: true;
|
|
58
|
+
}
|
|
59
|
+
/** Where a subscription starts (spec/events.md#resuming). */
|
|
60
|
+
export interface Resume {
|
|
61
|
+
readonly cursor?: string;
|
|
62
|
+
readonly maxAgeMs?: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The occurrence for an emitted value. `makeId` gives an `eventId` that is unique in the subscription, and `now` gives
|
|
66
|
+
* the time, when the provider gives none.
|
|
67
|
+
*/
|
|
68
|
+
export declare function occurrenceOf<Payload>(name: string, emitted: Emitted<Payload>, makeId: () => string, now: () => Date): Occurrence<Payload>;
|
|
69
|
+
/** Tell if a value is an occurrence in its JSON form. `data` can be any JSON object. */
|
|
70
|
+
export declare function isOccurrence(value: unknown): value is Occurrence;
|
|
71
|
+
/** The time with no message on a stream after which a binding sends a keepalive or a heartbeat, in milliseconds. */
|
|
72
|
+
export declare const KEEPALIVE_MS = 15000;
|
|
39
73
|
export type Availability = {
|
|
40
74
|
readonly available: true;
|
|
41
75
|
} | {
|
|
@@ -82,6 +116,19 @@ export declare function cliJsonArray(values: readonly string[]): readonly unknow
|
|
|
82
116
|
* `undefined` when the capability has no input.
|
|
83
117
|
*/
|
|
84
118
|
export declare function cliUsage(program: string, id: string, input: JsonObject | undefined): string;
|
|
119
|
+
/**
|
|
120
|
+
* The `usage` of an event in `list` (spec/bindings/cli.md#events). `input` is the bundled input schema, or `undefined`
|
|
121
|
+
* when the event has no input.
|
|
122
|
+
*/
|
|
123
|
+
export declare function cliEventUsage(program: string, id: string, input: JsonObject | undefined): string;
|
|
124
|
+
/** The flags of `watch` that give the cursor and `maxAgeMs`. No input property of an event can have these names. */
|
|
125
|
+
export declare const CLI_RESUME_FLAGS: readonly ["cursor", "max-age-ms"];
|
|
126
|
+
/** The exit code for each end of a subscription through the CLI (spec/bindings/cli.md#events). */
|
|
127
|
+
export declare const CLI_WATCH_EXIT_CODES: {
|
|
128
|
+
readonly complete: 0;
|
|
129
|
+
readonly cancelled: 130;
|
|
130
|
+
readonly internal: 5;
|
|
131
|
+
};
|
|
85
132
|
export declare const HTTP_STATUS: {
|
|
86
133
|
readonly "unknown-capability": 404;
|
|
87
134
|
readonly "invalid-input": 422;
|
|
@@ -90,6 +137,21 @@ export declare const HTTP_STATUS: {
|
|
|
90
137
|
readonly internal: 500;
|
|
91
138
|
};
|
|
92
139
|
export declare const httpStatus: (outcome: Outcome<unknown, unknown>) => number;
|
|
140
|
+
/** One message of a stream of server-sent events, as text, with the blank line that ends it. */
|
|
141
|
+
export declare function sseMessage(fields: {
|
|
142
|
+
readonly event?: string | undefined;
|
|
143
|
+
readonly id?: string | undefined;
|
|
144
|
+
readonly data: string;
|
|
145
|
+
}): string;
|
|
146
|
+
/** The messages of a subscription through the HTTP binding (spec/bindings/http.md#events). */
|
|
147
|
+
export declare const httpEventMessage: {
|
|
148
|
+
readonly next: (occurrence: Occurrence) => string;
|
|
149
|
+
readonly complete: () => string;
|
|
150
|
+
readonly error: (failure: CallFailure) => string;
|
|
151
|
+
readonly keepalive: () => string;
|
|
152
|
+
};
|
|
153
|
+
/** The media type of a stream of server-sent events. */
|
|
154
|
+
export declare const SSE_MEDIA_TYPE = "text/event-stream";
|
|
93
155
|
export type Rejection = "method" | "origin" | "media-type" | "too-large" | "not-utf8";
|
|
94
156
|
/**
|
|
95
157
|
* The first rule that a request breaks, before its body is read: method, origin, media type, declared length.
|
|
@@ -116,6 +178,85 @@ export declare const MCP_REJECTIONS: {
|
|
|
116
178
|
readonly code?: number;
|
|
117
179
|
};
|
|
118
180
|
};
|
|
181
|
+
/** The identifier of the MCP events extension (spec/bindings/mcp.md#events). */
|
|
182
|
+
export declare const MCP_EVENTS_EXTENSION = "io.modelcontextprotocol/events";
|
|
183
|
+
/** The key in `params._meta` that holds the id of the `events/stream` request. */
|
|
184
|
+
export declare const MCP_SUBSCRIPTION_ID = "io.modelcontextprotocol/subscriptionId";
|
|
185
|
+
/** The JSON-RPC errors of the MCP events extension. Their numbers are not final in SEP-3415. */
|
|
186
|
+
export declare const MCP_EVENT_ERRORS: {
|
|
187
|
+
readonly notFound: -32023;
|
|
188
|
+
readonly forbidden: -32024;
|
|
189
|
+
readonly resourceExhausted: -32025;
|
|
190
|
+
readonly unsupported: -32026;
|
|
191
|
+
readonly callbackEndpoint: -32027;
|
|
192
|
+
readonly invalidParams: -32602;
|
|
193
|
+
readonly internal: -32603;
|
|
194
|
+
};
|
|
195
|
+
/** The JSON-RPC error for a call failure of a subscription before its first occurrence. */
|
|
196
|
+
export declare function mcpSubscribeError(failure: CallFailure): {
|
|
197
|
+
readonly code: number;
|
|
198
|
+
readonly message: string;
|
|
199
|
+
readonly data?: JsonObject;
|
|
200
|
+
};
|
|
201
|
+
/** An entry of `events/list`. `input` and `payload` are the bundled schemas. */
|
|
202
|
+
export declare function mcpEventEntry(id: string, description: string, input: JsonObject | undefined, payload: JsonObject | undefined, webhook?: boolean): JsonObject;
|
|
203
|
+
/** The params of `notifications/events/event` for an occurrence: the occurrence without `truncated`. */
|
|
204
|
+
export declare function mcpEventParams(occurrence: Occurrence, subscriptionId: string | number): JsonObject;
|
|
205
|
+
/**
|
|
206
|
+
* Order strings by Unicode code point. The `<` operator compares UTF-16 code units, which put a character after U+FFFF
|
|
207
|
+
* before a character from U+E000 to U+FFFF.
|
|
208
|
+
*/
|
|
209
|
+
export declare function byCodePoint(left: string, right: string): number;
|
|
210
|
+
/** The canonical JSON of a value (spec/bindings/mcp.md#canonical-json): no spaces, and the keys of each object in code point order. */
|
|
211
|
+
export declare function canonicalJson(value: unknown): string;
|
|
212
|
+
/** The defaults of poll delivery (spec/bindings/mcp.md#poll). */
|
|
213
|
+
export declare const MCP_POLL_DEFAULTS: {
|
|
214
|
+
readonly leaseMs: 300000;
|
|
215
|
+
readonly bufferLimit: 1000;
|
|
216
|
+
readonly maxEvents: 100;
|
|
217
|
+
readonly nextPollMs: 2000;
|
|
218
|
+
};
|
|
219
|
+
/** Where a poll cursor points: a lease, a position in its buffer, and the last cursor of the provider. */
|
|
220
|
+
export interface PollPosition {
|
|
221
|
+
readonly lease: string;
|
|
222
|
+
readonly position: number;
|
|
223
|
+
readonly provider?: string;
|
|
224
|
+
}
|
|
225
|
+
/** The cursor of a poll position. Only the server reads it. */
|
|
226
|
+
export declare function encodePollCursor(position: PollPosition): string;
|
|
227
|
+
/** The position of a poll cursor, or `undefined` when the text is not a poll cursor. */
|
|
228
|
+
export declare function decodePollCursor(cursor: string): PollPosition | undefined;
|
|
229
|
+
/** The defaults of webhook delivery (spec/bindings/mcp.md#webhook). */
|
|
230
|
+
export declare const MCP_WEBHOOK_DEFAULTS: {
|
|
231
|
+
readonly maxTtlMs: 3600000;
|
|
232
|
+
readonly minTtlMs: 60000;
|
|
233
|
+
readonly maxSubscriptions: 100;
|
|
234
|
+
readonly attempts: 5;
|
|
235
|
+
readonly retryWindowMs: 900000;
|
|
236
|
+
readonly timeoutMs: 5000;
|
|
237
|
+
readonly rotationGraceMs: 300000;
|
|
238
|
+
};
|
|
239
|
+
/** The reasons of a `CallbackEndpointError`, and the values of `lastError` (spec/bindings/mcp.md#webhook). */
|
|
240
|
+
export declare const WEBHOOK_ERRORS: readonly ["challenge_failed", "connection_refused", "timeout", "tls_error", "http_4xx", "http_5xx"];
|
|
241
|
+
export type WebhookError = (typeof WEBHOOK_ERRORS)[number];
|
|
242
|
+
/** The bytes of a Standard Webhooks secret: `whsec_` and the base64 of 24 to 64 bytes. `undefined` when it is not valid. */
|
|
243
|
+
export declare function webhookSecretBytes(secret: string): Uint8Array<ArrayBuffer> | undefined;
|
|
244
|
+
/** The lifetime that the server grants for a suggested `ttlMs` (spec/bindings/mcp.md#webhook). */
|
|
245
|
+
export declare function webhookGrantMs(ttlMs: number | null | undefined, limits: {
|
|
246
|
+
readonly maxTtlMs: number;
|
|
247
|
+
readonly minTtlMs: number;
|
|
248
|
+
}): number;
|
|
249
|
+
/** The text that a Standard Webhooks signature covers. */
|
|
250
|
+
export declare const webhookSignedText: (webhookId: string, timestamp: number, body: string) => string;
|
|
251
|
+
/** The `webhook-signature` header: one `v1,` signature for each secret, with a space between them. */
|
|
252
|
+
export declare function webhookSignature(secrets: readonly string[], webhookId: string, timestamp: number, body: string): Promise<string>;
|
|
253
|
+
/** The subscription id of a webhook subscription: `sub_` and 16 hexadecimal digits of the SHA-256 of its key. */
|
|
254
|
+
export declare function webhookSubscriptionId(principal: string, url: string, id: string, input: unknown): Promise<string>;
|
|
255
|
+
/**
|
|
256
|
+
* Tell if an IP address is globally routable: not in a special-purpose range of the IANA IPv4 and IPv6 registries that
|
|
257
|
+
* is not global, for example loopback, private, link-local, shared, documentation or multicast addresses.
|
|
258
|
+
*/
|
|
259
|
+
export declare function isGloballyRoutable(address: string): boolean;
|
|
119
260
|
export interface McpToolResult {
|
|
120
261
|
readonly content: readonly [{
|
|
121
262
|
readonly type: "text";
|
|
@@ -140,6 +281,12 @@ export interface CatalogCapability extends JsonObject {
|
|
|
140
281
|
readonly readOnly?: boolean;
|
|
141
282
|
readonly state?: JsonObject;
|
|
142
283
|
}
|
|
284
|
+
export interface CatalogEvent extends JsonObject {
|
|
285
|
+
readonly description: string;
|
|
286
|
+
readonly audience?: readonly string[];
|
|
287
|
+
readonly input?: JsonObject;
|
|
288
|
+
readonly payload?: JsonObject;
|
|
289
|
+
}
|
|
143
290
|
export interface CatalogDocument extends JsonObject {
|
|
144
291
|
readonly acidcat: string;
|
|
145
292
|
readonly title?: string;
|
|
@@ -155,12 +302,24 @@ export interface CatalogDocument extends JsonObject {
|
|
|
155
302
|
readonly capabilities: {
|
|
156
303
|
readonly [id: string]: CatalogCapability;
|
|
157
304
|
};
|
|
305
|
+
readonly events?: {
|
|
306
|
+
readonly [id: string]: CatalogEvent;
|
|
307
|
+
};
|
|
158
308
|
}
|
|
159
309
|
export declare const REFERENCE: RegExp;
|
|
160
310
|
/** Tell if a value is a JSON object: an object that is not `null` and not an array. */
|
|
161
311
|
export declare const isJsonObject: (value: unknown) => value is JsonObject;
|
|
312
|
+
/**
|
|
313
|
+
* The member of an object with this key, or `undefined` when the object does not have it. Inherited names, such as
|
|
314
|
+
* `constructor` or `toString`, are not members.
|
|
315
|
+
*/
|
|
316
|
+
export declare function ownMember<Value>(object: {
|
|
317
|
+
readonly [key: string]: Value;
|
|
318
|
+
}, key: string): Value | undefined;
|
|
162
319
|
/** The schemas of a capability, each with the member that holds it. */
|
|
163
320
|
export declare function schemasOf(capability: CatalogCapability): readonly (readonly [string, JsonObject])[];
|
|
321
|
+
/** The schemas of an event, each with the member that holds it. */
|
|
322
|
+
export declare function eventSchemasOf(event: CatalogEvent): readonly (readonly [string, JsonObject])[];
|
|
164
323
|
/** Every object in a JSON value, at all levels. */
|
|
165
324
|
export declare function visitObjects(value: unknown, onObject: (node: JsonObject) => void): void;
|
|
166
325
|
/** The types that a schema uses, directly or through other types. */
|
|
@@ -175,6 +334,14 @@ export declare function typesUsedBy(schema: unknown, types: {
|
|
|
175
334
|
export declare function objectInput(schema: JsonObject, types: {
|
|
176
335
|
readonly [name: string]: JsonObject;
|
|
177
336
|
}): JsonObject;
|
|
337
|
+
/**
|
|
338
|
+
* The type that a schema's `$ref` refers to, whatever other keys the schema has, or the schema itself when it has no
|
|
339
|
+
* `$ref` to a type in `types`. A catalog uses it to tell if an input or a payload is an object
|
|
340
|
+
* (spec/catalog.md#capabilities).
|
|
341
|
+
*/
|
|
342
|
+
export declare function resolveType(schema: JsonObject, types: {
|
|
343
|
+
readonly [name: string]: JsonObject;
|
|
344
|
+
}): JsonObject;
|
|
178
345
|
/** Bundle a schema (spec/catalog.md#bundling). */
|
|
179
346
|
export declare function bundle(schema: JsonObject, types: {
|
|
180
347
|
readonly [name: string]: JsonObject;
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
// src/index.ts
|
|
2
2
|
/**
|
|
3
3
|
* The fixed details of the AcidCat specification, as TypeScript data and pure functions, for the TypeScript reference
|
|
4
4
|
* implementation. This package has no dependencies.
|
|
5
5
|
*
|
|
6
6
|
* - Outcomes and call failures: spec/invocation.md
|
|
7
|
+
* - Subscriptions and occurrences: spec/events.md
|
|
7
8
|
* - Names, views and bundling: spec/catalog.md
|
|
8
9
|
* - Exit codes, status codes and MCP results: spec/bindings/
|
|
9
10
|
*/
|
|
@@ -38,6 +39,41 @@ var CallFailureError = class extends Error {
|
|
|
38
39
|
this.name = failure.kind === "cancelled" ? "AbortError" : failure.kind === "invalid-input" ? "TypeError" : "Error";
|
|
39
40
|
}
|
|
40
41
|
};
|
|
42
|
+
/** The kinds of call failure that a subscription can fail with before its first occurrence, in order of precedence. */
|
|
43
|
+
const SUBSCRIBE_FAILURE_KINDS = [
|
|
44
|
+
"cancelled",
|
|
45
|
+
"unknown-capability",
|
|
46
|
+
"invalid-input",
|
|
47
|
+
"internal"
|
|
48
|
+
];
|
|
49
|
+
/**
|
|
50
|
+
* The occurrence for an emitted value. `makeId` gives an `eventId` that is unique in the subscription, and `now` gives
|
|
51
|
+
* the time, when the provider gives none.
|
|
52
|
+
*/
|
|
53
|
+
function occurrenceOf(name, emitted, makeId, now) {
|
|
54
|
+
return {
|
|
55
|
+
eventId: emitted.eventId ?? makeId(),
|
|
56
|
+
name,
|
|
57
|
+
timestamp: emitted.timestamp ?? now().toISOString(),
|
|
58
|
+
data: emitted.data,
|
|
59
|
+
...emitted.cursor === void 0 ? {} : { cursor: emitted.cursor },
|
|
60
|
+
...emitted.truncated === true ? { truncated: true } : {}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Tell if a value is an occurrence in its JSON form. `data` can be any JSON object. */
|
|
64
|
+
function isOccurrence(value) {
|
|
65
|
+
return isJsonObject(value) && typeof value.eventId === "string" && value.eventId.length > 0 && typeof value.name === "string" && isId(value.name) && typeof value.timestamp === "string" && !Number.isNaN(Date.parse(value.timestamp)) && isJsonObject(value.data) && (value.cursor === void 0 || typeof value.cursor === "string") && (value.truncated === void 0 || value.truncated === true) && Object.keys(value).every((key) => OCCURRENCE_MEMBERS.has(key));
|
|
66
|
+
}
|
|
67
|
+
const OCCURRENCE_MEMBERS = /* @__PURE__ */ new Set([
|
|
68
|
+
"eventId",
|
|
69
|
+
"name",
|
|
70
|
+
"timestamp",
|
|
71
|
+
"data",
|
|
72
|
+
"cursor",
|
|
73
|
+
"truncated"
|
|
74
|
+
]);
|
|
75
|
+
/** The time with no message on a stream after which a binding sends a keepalive or a heartbeat, in milliseconds. */
|
|
76
|
+
const KEEPALIVE_MS = 15e3;
|
|
41
77
|
const AVAILABLE = { available: true };
|
|
42
78
|
const unavailable = (reason) => ({
|
|
43
79
|
available: false,
|
|
@@ -65,8 +101,7 @@ function cliFlags(input) {
|
|
|
65
101
|
const properties = objectOrEmpty(input.properties);
|
|
66
102
|
const definitions = objectOrEmpty(input.$defs);
|
|
67
103
|
const required = new Set(stringsOf(input.required));
|
|
68
|
-
const
|
|
69
|
-
const names = Object.keys(properties).sort(byName);
|
|
104
|
+
const names = Object.keys(properties).sort(byCodePoint);
|
|
70
105
|
return [...names.filter((name) => required.has(name)), ...names.filter((name) => !required.has(name))].map((name) => {
|
|
71
106
|
const property = cliResolve(objectOrEmpty(properties[name]), definitions);
|
|
72
107
|
const items = cliRepeatedItems(property, definitions);
|
|
@@ -76,8 +111,8 @@ function cliFlags(input) {
|
|
|
76
111
|
}
|
|
77
112
|
/** A property schema, or the type in `definitions` that its `$ref` refers to. */
|
|
78
113
|
function cliResolve(property, definitions) {
|
|
79
|
-
const target =
|
|
80
|
-
return target === void 0 ? property : objectOrEmpty(definitions
|
|
114
|
+
const target = referenceName(property);
|
|
115
|
+
return target === void 0 ? property : objectOrEmpty(ownMember(definitions, target) ?? property);
|
|
81
116
|
}
|
|
82
117
|
/**
|
|
83
118
|
* The schema of the items of a repeated flag (spec/bindings/cli.md#commands), or `undefined` when the property does
|
|
@@ -122,6 +157,29 @@ function cliUsage(program, id, input) {
|
|
|
122
157
|
...input === void 0 ? [] : cliFlags(input)
|
|
123
158
|
].join(" ");
|
|
124
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* The `usage` of an event in `list` (spec/bindings/cli.md#events). `input` is the bundled input schema, or `undefined`
|
|
162
|
+
* when the event has no input.
|
|
163
|
+
*/
|
|
164
|
+
function cliEventUsage(program, id, input) {
|
|
165
|
+
const resume = "[--cursor <string>] [--max-age-ms <integer>]";
|
|
166
|
+
if (input !== void 0 && input.properties === void 0) return `${program} watch ${id} '<json>' ${resume}`;
|
|
167
|
+
return [
|
|
168
|
+
program,
|
|
169
|
+
"watch",
|
|
170
|
+
id,
|
|
171
|
+
...input === void 0 ? [] : cliFlags(input),
|
|
172
|
+
resume
|
|
173
|
+
].join(" ");
|
|
174
|
+
}
|
|
175
|
+
/** The flags of `watch` that give the cursor and `maxAgeMs`. No input property of an event can have these names. */
|
|
176
|
+
const CLI_RESUME_FLAGS = ["cursor", "max-age-ms"];
|
|
177
|
+
/** The exit code for each end of a subscription through the CLI (spec/bindings/cli.md#events). */
|
|
178
|
+
const CLI_WATCH_EXIT_CODES = {
|
|
179
|
+
complete: 0,
|
|
180
|
+
cancelled: 130,
|
|
181
|
+
internal: 5
|
|
182
|
+
};
|
|
125
183
|
const HTTP_STATUS = {
|
|
126
184
|
"unknown-capability": 404,
|
|
127
185
|
"invalid-input": 422,
|
|
@@ -130,6 +188,36 @@ const HTTP_STATUS = {
|
|
|
130
188
|
internal: 500
|
|
131
189
|
};
|
|
132
190
|
const httpStatus = (outcome) => isCallFailure(outcome) ? HTTP_STATUS[outcome.kind] : 200;
|
|
191
|
+
/** One message of a stream of server-sent events, as text, with the blank line that ends it. */
|
|
192
|
+
function sseMessage(fields) {
|
|
193
|
+
return `${[
|
|
194
|
+
...fields.event === void 0 ? [] : [`event: ${fields.event}`],
|
|
195
|
+
...fields.id === void 0 ? [] : [`id: ${fields.id}`],
|
|
196
|
+
...fields.data.split("\n").map((line) => line === "" ? "data:" : `data: ${line}`)
|
|
197
|
+
].join("\n")}\n\n`;
|
|
198
|
+
}
|
|
199
|
+
/** The messages of a subscription through the HTTP binding (spec/bindings/http.md#events). */
|
|
200
|
+
const httpEventMessage = {
|
|
201
|
+
next: (occurrence) => sseMessage({
|
|
202
|
+
event: "next",
|
|
203
|
+
id: occurrence.cursor,
|
|
204
|
+
data: JSON.stringify(occurrence)
|
|
205
|
+
}),
|
|
206
|
+
complete: () => sseMessage({
|
|
207
|
+
event: "complete",
|
|
208
|
+
data: ""
|
|
209
|
+
}),
|
|
210
|
+
error: (failure) => sseMessage({
|
|
211
|
+
event: "error",
|
|
212
|
+
data: JSON.stringify({
|
|
213
|
+
kind: failure.kind,
|
|
214
|
+
message: failure.message
|
|
215
|
+
})
|
|
216
|
+
}),
|
|
217
|
+
keepalive: () => ":\n\n"
|
|
218
|
+
};
|
|
219
|
+
/** The media type of a stream of server-sent events. */
|
|
220
|
+
const SSE_MEDIA_TYPE = "text/event-stream";
|
|
133
221
|
/**
|
|
134
222
|
* The first rule that a request breaks, before its body is read: method, origin, media type, declared length.
|
|
135
223
|
* The body itself is checked as it arrives, with `readBounded`.
|
|
@@ -187,6 +275,269 @@ const MCP_REJECTIONS = {
|
|
|
187
275
|
code: -32700
|
|
188
276
|
}
|
|
189
277
|
};
|
|
278
|
+
/** The identifier of the MCP events extension (spec/bindings/mcp.md#events). */
|
|
279
|
+
const MCP_EVENTS_EXTENSION = "io.modelcontextprotocol/events";
|
|
280
|
+
/** The key in `params._meta` that holds the id of the `events/stream` request. */
|
|
281
|
+
const MCP_SUBSCRIPTION_ID = "io.modelcontextprotocol/subscriptionId";
|
|
282
|
+
/** The JSON-RPC errors of the MCP events extension. Their numbers are not final in SEP-3415. */
|
|
283
|
+
const MCP_EVENT_ERRORS = {
|
|
284
|
+
notFound: -32023,
|
|
285
|
+
forbidden: -32024,
|
|
286
|
+
resourceExhausted: -32025,
|
|
287
|
+
unsupported: -32026,
|
|
288
|
+
callbackEndpoint: -32027,
|
|
289
|
+
invalidParams: -32602,
|
|
290
|
+
internal: -32603
|
|
291
|
+
};
|
|
292
|
+
/** The JSON-RPC error for a call failure of a subscription before its first occurrence. */
|
|
293
|
+
function mcpSubscribeError(failure) {
|
|
294
|
+
if (failure.kind === "unknown-capability") return {
|
|
295
|
+
code: MCP_EVENT_ERRORS.notFound,
|
|
296
|
+
message: failure.message,
|
|
297
|
+
data: { kind: "event" }
|
|
298
|
+
};
|
|
299
|
+
if (failure.kind === "invalid-input") return {
|
|
300
|
+
code: MCP_EVENT_ERRORS.invalidParams,
|
|
301
|
+
message: failure.message
|
|
302
|
+
};
|
|
303
|
+
return {
|
|
304
|
+
code: MCP_EVENT_ERRORS.internal,
|
|
305
|
+
message: failure.message
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
/** An entry of `events/list`. `input` and `payload` are the bundled schemas. */
|
|
309
|
+
function mcpEventEntry(id, description, input, payload, webhook = false) {
|
|
310
|
+
return {
|
|
311
|
+
name: id,
|
|
312
|
+
description,
|
|
313
|
+
delivery: webhook ? [
|
|
314
|
+
"push",
|
|
315
|
+
"poll",
|
|
316
|
+
"webhook"
|
|
317
|
+
] : ["push", "poll"],
|
|
318
|
+
inputSchema: input ?? { type: "object" },
|
|
319
|
+
payloadSchema: payload ?? { type: "object" }
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
/** The params of `notifications/events/event` for an occurrence: the occurrence without `truncated`. */
|
|
323
|
+
function mcpEventParams(occurrence, subscriptionId) {
|
|
324
|
+
const { truncated: _, ...rest } = occurrence;
|
|
325
|
+
return {
|
|
326
|
+
...rest,
|
|
327
|
+
_meta: { [MCP_SUBSCRIPTION_ID]: subscriptionId }
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Order strings by Unicode code point. The `<` operator compares UTF-16 code units, which put a character after U+FFFF
|
|
332
|
+
* before a character from U+E000 to U+FFFF.
|
|
333
|
+
*/
|
|
334
|
+
function byCodePoint(left, right) {
|
|
335
|
+
const a = Array.from(left, (character) => character.codePointAt(0) ?? 0);
|
|
336
|
+
const b = Array.from(right, (character) => character.codePointAt(0) ?? 0);
|
|
337
|
+
for (let i = 0; i < Math.min(a.length, b.length); i += 1) {
|
|
338
|
+
const difference = (a[i] ?? 0) - (b[i] ?? 0);
|
|
339
|
+
if (difference !== 0) return difference;
|
|
340
|
+
}
|
|
341
|
+
return a.length - b.length;
|
|
342
|
+
}
|
|
343
|
+
/** The canonical JSON of a value (spec/bindings/mcp.md#canonical-json): no spaces, and the keys of each object in code point order. */
|
|
344
|
+
function canonicalJson(value) {
|
|
345
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
346
|
+
if (isJsonObject(value)) return `{${Object.keys(value).sort(byCodePoint).map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
|
|
347
|
+
return JSON.stringify(value) ?? "null";
|
|
348
|
+
}
|
|
349
|
+
/** The defaults of poll delivery (spec/bindings/mcp.md#poll). */
|
|
350
|
+
const MCP_POLL_DEFAULTS = {
|
|
351
|
+
leaseMs: 3e5,
|
|
352
|
+
bufferLimit: 1e3,
|
|
353
|
+
maxEvents: 100,
|
|
354
|
+
nextPollMs: 2e3
|
|
355
|
+
};
|
|
356
|
+
/** The cursor of a poll position. Only the server reads it. */
|
|
357
|
+
function encodePollCursor(position) {
|
|
358
|
+
const parts = position.provider === void 0 ? [position.lease, position.position] : [
|
|
359
|
+
position.lease,
|
|
360
|
+
position.position,
|
|
361
|
+
position.provider
|
|
362
|
+
];
|
|
363
|
+
return `p1.${base64UrlOfText(JSON.stringify(parts))}`;
|
|
364
|
+
}
|
|
365
|
+
/** The position of a poll cursor, or `undefined` when the text is not a poll cursor. */
|
|
366
|
+
function decodePollCursor(cursor) {
|
|
367
|
+
if (!cursor.startsWith("p1.")) return void 0;
|
|
368
|
+
let parts;
|
|
369
|
+
try {
|
|
370
|
+
parts = JSON.parse(textOfBase64Url(cursor.slice(3)));
|
|
371
|
+
} catch {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (!Array.isArray(parts) || parts.length < 2 || parts.length > 3) return void 0;
|
|
375
|
+
const [lease, position, provider] = parts;
|
|
376
|
+
if (typeof lease !== "string" || typeof position !== "number" || !Number.isSafeInteger(position) || position < 0) return void 0;
|
|
377
|
+
if (provider !== void 0 && typeof provider !== "string") return void 0;
|
|
378
|
+
return provider === void 0 ? {
|
|
379
|
+
lease,
|
|
380
|
+
position
|
|
381
|
+
} : {
|
|
382
|
+
lease,
|
|
383
|
+
position,
|
|
384
|
+
provider
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function base64UrlOfText(text) {
|
|
388
|
+
let binary = "";
|
|
389
|
+
for (const byte of new TextEncoder().encode(text)) binary += String.fromCharCode(byte);
|
|
390
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
391
|
+
}
|
|
392
|
+
function textOfBase64Url(text) {
|
|
393
|
+
const binary = atob(text.replaceAll("-", "+").replaceAll("_", "/"));
|
|
394
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(Uint8Array.from(binary, (character) => character.charCodeAt(0)));
|
|
395
|
+
}
|
|
396
|
+
/** The defaults of webhook delivery (spec/bindings/mcp.md#webhook). */
|
|
397
|
+
const MCP_WEBHOOK_DEFAULTS = {
|
|
398
|
+
maxTtlMs: 36e5,
|
|
399
|
+
minTtlMs: 6e4,
|
|
400
|
+
maxSubscriptions: 100,
|
|
401
|
+
attempts: 5,
|
|
402
|
+
retryWindowMs: 9e5,
|
|
403
|
+
timeoutMs: 5e3,
|
|
404
|
+
rotationGraceMs: 3e5
|
|
405
|
+
};
|
|
406
|
+
/** The reasons of a `CallbackEndpointError`, and the values of `lastError` (spec/bindings/mcp.md#webhook). */
|
|
407
|
+
const WEBHOOK_ERRORS = [
|
|
408
|
+
"challenge_failed",
|
|
409
|
+
"connection_refused",
|
|
410
|
+
"timeout",
|
|
411
|
+
"tls_error",
|
|
412
|
+
"http_4xx",
|
|
413
|
+
"http_5xx"
|
|
414
|
+
];
|
|
415
|
+
/** The bytes of a Standard Webhooks secret: `whsec_` and the base64 of 24 to 64 bytes. `undefined` when it is not valid. */
|
|
416
|
+
function webhookSecretBytes(secret) {
|
|
417
|
+
if (!secret.startsWith("whsec_")) return void 0;
|
|
418
|
+
const text = secret.slice(6);
|
|
419
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(text) || text.length % 4 !== 0) return void 0;
|
|
420
|
+
let binary;
|
|
421
|
+
try {
|
|
422
|
+
binary = atob(text);
|
|
423
|
+
} catch {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
return binary.length >= 24 && binary.length <= 64 ? Uint8Array.from(binary, (character) => character.charCodeAt(0)) : void 0;
|
|
427
|
+
}
|
|
428
|
+
/** The lifetime that the server grants for a suggested `ttlMs` (spec/bindings/mcp.md#webhook). */
|
|
429
|
+
function webhookGrantMs(ttlMs, limits) {
|
|
430
|
+
if (ttlMs === null || ttlMs === void 0) return limits.maxTtlMs;
|
|
431
|
+
return Math.min(limits.maxTtlMs, Math.max(limits.minTtlMs, ttlMs));
|
|
432
|
+
}
|
|
433
|
+
/** The text that a Standard Webhooks signature covers. */
|
|
434
|
+
const webhookSignedText = (webhookId, timestamp, body) => `${webhookId}.${timestamp}.${body}`;
|
|
435
|
+
/** The `webhook-signature` header: one `v1,` signature for each secret, with a space between them. */
|
|
436
|
+
async function webhookSignature(secrets, webhookId, timestamp, body) {
|
|
437
|
+
const content = new TextEncoder().encode(webhookSignedText(webhookId, timestamp, body));
|
|
438
|
+
const signatures = [];
|
|
439
|
+
for (const secret of secrets) {
|
|
440
|
+
const bytes = webhookSecretBytes(secret);
|
|
441
|
+
if (bytes === void 0) throw new TypeError("A webhook secret is not valid.");
|
|
442
|
+
const key = await crypto.subtle.importKey("raw", bytes, {
|
|
443
|
+
name: "HMAC",
|
|
444
|
+
hash: "SHA-256"
|
|
445
|
+
}, false, ["sign"]);
|
|
446
|
+
const mac = new Uint8Array(await crypto.subtle.sign("HMAC", key, content));
|
|
447
|
+
let binary = "";
|
|
448
|
+
for (const byte of mac) binary += String.fromCharCode(byte);
|
|
449
|
+
signatures.push(`v1,${btoa(binary)}`);
|
|
450
|
+
}
|
|
451
|
+
return signatures.join(" ");
|
|
452
|
+
}
|
|
453
|
+
/** The subscription id of a webhook subscription: `sub_` and 16 hexadecimal digits of the SHA-256 of its key. */
|
|
454
|
+
async function webhookSubscriptionId(principal, url, id, input) {
|
|
455
|
+
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalJson([
|
|
456
|
+
principal,
|
|
457
|
+
url,
|
|
458
|
+
id,
|
|
459
|
+
input
|
|
460
|
+
]))));
|
|
461
|
+
return `sub_${Array.from(digest.slice(0, 8), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Tell if an IP address is globally routable: not in a special-purpose range of the IANA IPv4 and IPv6 registries that
|
|
465
|
+
* is not global, for example loopback, private, link-local, shared, documentation or multicast addresses.
|
|
466
|
+
*/
|
|
467
|
+
function isGloballyRoutable(address) {
|
|
468
|
+
const ipv4 = ipv4Bytes(address);
|
|
469
|
+
if (ipv4 !== void 0) return isGlobalIpv4(ipv4);
|
|
470
|
+
const ipv6 = ipv6Words(address);
|
|
471
|
+
if (ipv6 === void 0) return false;
|
|
472
|
+
const [a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0] = ipv6;
|
|
473
|
+
if (a === 0 && b === 0 && c === 0 && d === 0 && e === 0 && f === 65535) return isGlobalIpv4([
|
|
474
|
+
g >> 8,
|
|
475
|
+
g & 255,
|
|
476
|
+
h >> 8,
|
|
477
|
+
h & 255
|
|
478
|
+
]);
|
|
479
|
+
if (ipv6.every((word) => word === 0)) return false;
|
|
480
|
+
if (a === 0 && b === 0 && c === 0 && d === 0 && e === 0 && f === 0 && g === 0 && h === 1) return false;
|
|
481
|
+
if (a === 100 && b === 65435) return false;
|
|
482
|
+
if (a === 256 && b === 0 && c === 0 && d === 0) return false;
|
|
483
|
+
if (a === 8193 && b < 512) return false;
|
|
484
|
+
if (a === 8193 && b === 3512) return false;
|
|
485
|
+
if (a === 8194) return false;
|
|
486
|
+
if (a === 16383 && b < 4096) return false;
|
|
487
|
+
if (a === 24320) return false;
|
|
488
|
+
if ((a & 65024) === 64512) return false;
|
|
489
|
+
if ((a & 65472) === 65152) return false;
|
|
490
|
+
if ((a & 65280) === 65280) return false;
|
|
491
|
+
return (a & 57344) === 8192;
|
|
492
|
+
}
|
|
493
|
+
function isGlobalIpv4([a = 0, b = 0, c = 0]) {
|
|
494
|
+
if (a === 0 || a === 10 || a === 127) return false;
|
|
495
|
+
if (a === 100 && b >= 64 && b <= 127) return false;
|
|
496
|
+
if (a === 169 && b === 254) return false;
|
|
497
|
+
if (a === 172 && b >= 16 && b <= 31) return false;
|
|
498
|
+
if (a === 192 && b === 0 && (c === 0 || c === 2)) return false;
|
|
499
|
+
if (a === 192 && b === 88 && c === 99) return false;
|
|
500
|
+
if (a === 192 && b === 168) return false;
|
|
501
|
+
if (a === 198 && (b === 18 || b === 19)) return false;
|
|
502
|
+
if (a === 198 && b === 51 && c === 100) return false;
|
|
503
|
+
if (a === 203 && b === 0 && c === 113) return false;
|
|
504
|
+
return a < 224;
|
|
505
|
+
}
|
|
506
|
+
function ipv4Bytes(address) {
|
|
507
|
+
const parts = address.split(".");
|
|
508
|
+
if (parts.length !== 4 || !parts.every((part) => /^(0|[1-9][0-9]{0,2})$/.test(part))) return void 0;
|
|
509
|
+
const bytes = parts.map(Number);
|
|
510
|
+
return bytes.every((byte) => byte <= 255) ? bytes : void 0;
|
|
511
|
+
}
|
|
512
|
+
function ipv6Words(input) {
|
|
513
|
+
let address = input.startsWith("[") && input.endsWith("]") ? input.slice(1, -1) : input;
|
|
514
|
+
const zone = address.indexOf("%");
|
|
515
|
+
if (zone !== -1) address = address.slice(0, zone);
|
|
516
|
+
const tail = [];
|
|
517
|
+
const lastColon = address.lastIndexOf(":");
|
|
518
|
+
const ipv4 = lastColon === -1 ? void 0 : ipv4Bytes(address.slice(lastColon + 1));
|
|
519
|
+
if (ipv4 !== void 0) {
|
|
520
|
+
const [w = 0, x = 0, y = 0, z = 0] = ipv4;
|
|
521
|
+
tail.push(w << 8 | x, y << 8 | z);
|
|
522
|
+
address = address.slice(0, lastColon + 1);
|
|
523
|
+
if (!address.endsWith("::")) address = address.slice(0, -1);
|
|
524
|
+
}
|
|
525
|
+
const halves = address.split("::");
|
|
526
|
+
if (halves.length > 2) return void 0;
|
|
527
|
+
const words = (text) => text === "" ? [] : text.split(":");
|
|
528
|
+
const head = words(halves[0] ?? "");
|
|
529
|
+
const rest = halves.length === 2 ? words(halves[1] ?? "") : [];
|
|
530
|
+
if (![...head, ...rest].every((word) => /^[0-9a-fA-F]{1,4}$/.test(word))) return void 0;
|
|
531
|
+
const count = head.length + rest.length + tail.length;
|
|
532
|
+
if (halves.length === 1 ? count !== 8 : count > 7) return void 0;
|
|
533
|
+
const zeros = halves.length === 2 ? Array.from({ length: 8 - count }, () => 0) : [];
|
|
534
|
+
return [
|
|
535
|
+
...head.map((word) => parseInt(word, 16)),
|
|
536
|
+
...zeros,
|
|
537
|
+
...rest.map((word) => parseInt(word, 16)),
|
|
538
|
+
...tail
|
|
539
|
+
];
|
|
540
|
+
}
|
|
190
541
|
/** The MCP tool result for an outcome that is not `unknown-capability`. */
|
|
191
542
|
function mcpToolResult(outcome, hasOutputSchema) {
|
|
192
543
|
const text = (value) => [{
|
|
@@ -213,8 +564,17 @@ function mcpToolResult(outcome, hasOutputSchema) {
|
|
|
213
564
|
} : { content: text(outcome.value) };
|
|
214
565
|
}
|
|
215
566
|
const REFERENCE = /^#\/\$defs\/([A-Za-z][A-Za-z0-9_]*)$/;
|
|
567
|
+
/** The name of the type that a schema's `$ref` refers to, or `undefined` when it has no `$ref` of that form. */
|
|
568
|
+
const referenceName = (schema) => typeof schema.$ref === "string" ? REFERENCE.exec(schema.$ref)?.[1] : void 0;
|
|
216
569
|
/** Tell if a value is a JSON object: an object that is not `null` and not an array. */
|
|
217
570
|
const isJsonObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
571
|
+
/**
|
|
572
|
+
* The member of an object with this key, or `undefined` when the object does not have it. Inherited names, such as
|
|
573
|
+
* `constructor` or `toString`, are not members.
|
|
574
|
+
*/
|
|
575
|
+
function ownMember(object, key) {
|
|
576
|
+
return Object.hasOwn(object, key) ? object[key] : void 0;
|
|
577
|
+
}
|
|
218
578
|
/** A value as a JSON object, or an empty object when it is not one. */
|
|
219
579
|
const objectOrEmpty = (value) => isJsonObject(value) ? value : {};
|
|
220
580
|
/** The strings in a value that is an array, or no strings when it is not an array. */
|
|
@@ -228,6 +588,10 @@ function schemasOf(capability) {
|
|
|
228
588
|
...Object.entries(capability.failures ?? {}).map(([tag, schema]) => [`failure ${tag}`, schema])
|
|
229
589
|
].filter((entry) => entry[1] !== void 0);
|
|
230
590
|
}
|
|
591
|
+
/** The schemas of an event, each with the member that holds it. */
|
|
592
|
+
function eventSchemasOf(event) {
|
|
593
|
+
return [["input", event.input], ["payload", event.payload]].filter((entry) => entry[1] !== void 0);
|
|
594
|
+
}
|
|
231
595
|
/** Every object in a JSON value, at all levels. */
|
|
232
596
|
function visitObjects(value, onObject) {
|
|
233
597
|
if (Array.isArray(value)) value.forEach((item) => visitObjects(item, onObject));
|
|
@@ -240,8 +604,8 @@ function visitObjects(value, onObject) {
|
|
|
240
604
|
function typesUsedBy(schema, types) {
|
|
241
605
|
const used = /* @__PURE__ */ new Map();
|
|
242
606
|
const collect = (node) => visitObjects(node, (inner) => {
|
|
243
|
-
const name =
|
|
244
|
-
const type = name === void 0 ? void 0 : types
|
|
607
|
+
const name = referenceName(inner);
|
|
608
|
+
const type = name === void 0 ? void 0 : ownMember(types, name);
|
|
245
609
|
if (name !== void 0 && type !== void 0 && !used.has(name)) {
|
|
246
610
|
used.set(name, type);
|
|
247
611
|
collect(type);
|
|
@@ -258,9 +622,8 @@ function typesUsedBy(schema, types) {
|
|
|
258
622
|
function objectInput(schema, types) {
|
|
259
623
|
const resolve = (node) => {
|
|
260
624
|
if (!isJsonObject(node)) return void 0;
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
return name === void 0 ? object : types[name];
|
|
625
|
+
const name = referenceName(node);
|
|
626
|
+
return name === void 0 ? node : ownMember(types, name);
|
|
264
627
|
};
|
|
265
628
|
const target = resolve(schema);
|
|
266
629
|
if (schema.type !== void 0 || target === void 0 || target.type !== void 0) return schema;
|
|
@@ -271,10 +634,18 @@ function objectInput(schema, types) {
|
|
|
271
634
|
...schema
|
|
272
635
|
} : schema;
|
|
273
636
|
}
|
|
637
|
+
/**
|
|
638
|
+
* The type that a schema's `$ref` refers to, whatever other keys the schema has, or the schema itself when it has no
|
|
639
|
+
* `$ref` to a type in `types`. A catalog uses it to tell if an input or a payload is an object
|
|
640
|
+
* (spec/catalog.md#capabilities).
|
|
641
|
+
*/
|
|
642
|
+
function resolveType(schema, types) {
|
|
643
|
+
const name = referenceName(schema);
|
|
644
|
+
return (name === void 0 ? void 0 : ownMember(types, name)) ?? schema;
|
|
645
|
+
}
|
|
274
646
|
/** Bundle a schema (spec/catalog.md#bundling). */
|
|
275
647
|
function bundle(schema, types) {
|
|
276
|
-
const
|
|
277
|
-
const base = only === void 0 ? schema : types[only] ?? schema;
|
|
648
|
+
const base = Object.keys(schema).length === 1 ? resolveType(schema, types) : schema;
|
|
278
649
|
const used = typesUsedBy(base, types);
|
|
279
650
|
return used.size === 0 ? base : {
|
|
280
651
|
...base,
|
|
@@ -288,16 +659,21 @@ function viewOf(catalog, target) {
|
|
|
288
659
|
...capability,
|
|
289
660
|
audience: [target]
|
|
290
661
|
}]));
|
|
662
|
+
const events = Object.fromEntries(Object.entries(catalog.events ?? {}).filter(([, event]) => (event.audience ?? []).includes(target)).map(([id, event]) => [id, {
|
|
663
|
+
...event,
|
|
664
|
+
audience: [target]
|
|
665
|
+
}]));
|
|
291
666
|
const used = /* @__PURE__ */ new Map();
|
|
292
|
-
|
|
293
|
-
const
|
|
294
|
-
const
|
|
667
|
+
const schemas = [...Object.values(capabilities).flatMap(schemasOf), ...Object.values(events).flatMap(eventSchemasOf)];
|
|
668
|
+
for (const [, schema] of schemas) for (const [name, type] of typesUsedBy(schema, types)) used.set(name, type);
|
|
669
|
+
const { $defs: _, events: __, ...rest } = catalog;
|
|
670
|
+
const declared = ownMember(catalog.targets, target);
|
|
295
671
|
return {
|
|
296
672
|
...rest,
|
|
297
673
|
targets: declared === void 0 ? {} : { [target]: declared },
|
|
298
674
|
...used.size > 0 ? { $defs: Object.fromEntries(used) } : {},
|
|
299
|
-
capabilities
|
|
675
|
+
capabilities,
|
|
676
|
+
...Object.keys(events).length > 0 ? { events } : {}
|
|
300
677
|
};
|
|
301
678
|
}
|
|
302
|
-
|
|
303
|
-
export { AVAILABLE, CALL_FAILURE_KINDS, CLI_EXIT_CODES, CallFailureError, HTTP_STATUS, ID_PATTERN, MCP_PROTOCOL_VERSION, MCP_REJECTIONS, MCP_UNKNOWN_TOOL, REFERENCE, bundle, callFailure, cliExitCode, cliFlags, cliJsonArray, cliRepeatedItems, cliResolve, cliUsage, commandPath, fail, httpStatus, isCallFailure, isId, isJsonObject, mcpToolResult, objectInput, ok, readBounded, schemasOf, screenRequest, typesUsedBy, unavailable, viewOf, visitObjects };
|
|
679
|
+
export { AVAILABLE, CALL_FAILURE_KINDS, CLI_EXIT_CODES, CLI_RESUME_FLAGS, CLI_WATCH_EXIT_CODES, CallFailureError, HTTP_STATUS, ID_PATTERN, KEEPALIVE_MS, MCP_EVENTS_EXTENSION, MCP_EVENT_ERRORS, MCP_POLL_DEFAULTS, MCP_PROTOCOL_VERSION, MCP_REJECTIONS, MCP_SUBSCRIPTION_ID, MCP_UNKNOWN_TOOL, MCP_WEBHOOK_DEFAULTS, REFERENCE, SSE_MEDIA_TYPE, SUBSCRIBE_FAILURE_KINDS, WEBHOOK_ERRORS, bundle, byCodePoint, callFailure, canonicalJson, cliEventUsage, cliExitCode, cliFlags, cliJsonArray, cliRepeatedItems, cliResolve, cliUsage, commandPath, decodePollCursor, encodePollCursor, eventSchemasOf, fail, httpEventMessage, httpStatus, isCallFailure, isGloballyRoutable, isId, isJsonObject, isOccurrence, mcpEventEntry, mcpEventParams, mcpSubscribeError, mcpToolResult, objectInput, occurrenceOf, ok, ownMember, readBounded, resolveType, schemasOf, screenRequest, sseMessage, typesUsedBy, unavailable, viewOf, visitObjects, webhookGrantMs, webhookSecretBytes, webhookSignature, webhookSignedText, webhookSubscriptionId };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acid-cat/spec-ts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "The fixed details of the AcidCat specification as TypeScript data and pure functions, with no dependencies. Part of the TypeScript reference implementation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"acidcat",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"access": "public"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@types/node": "26.6.
|
|
34
|
+
"@types/node": "26.6.6",
|
|
35
35
|
"typescript": "7.0.2"
|
|
36
36
|
}
|
|
37
37
|
}
|