@dokebi/mcp 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,43 @@
1
+ /** A JSON Schema object (as tools declare it). */
2
+ export type JsonSchema = Readonly<Record<string, unknown>>;
3
+ /** One validation problem: where (`input.notes[2].title`) and what, never the value. */
4
+ export interface SchemaProblem {
5
+ readonly path: string;
6
+ readonly message: string;
7
+ }
8
+ /** Most problems one validation reports. */
9
+ export declare const MAX_PROBLEMS = 20;
10
+ /**
11
+ * Deepest nesting of an input value (arrays and objects). Deeper input is refused before the
12
+ * schema is applied: comparisons (enum, const, uniqueItems) and serialization recurse.
13
+ */
14
+ export declare const MAX_INPUT_DEPTH = 64;
15
+ /** Largest schema text (a schema is sent with every tools/list). */
16
+ export declare const MAX_SCHEMA_CHARS = 65536;
17
+ /**
18
+ * Whether a pattern repeats a group that itself repeats without bound (`(a+)+`, `(\w*)*`,
19
+ * `(x{2,})+`): the shape whose backtracking grows exponentially. Alternation ambiguity such as
20
+ * `(a|aa)+` is not detected: keep patterns linear.
21
+ */
22
+ export declare function nestedQuantifier(pattern: string): boolean;
23
+ /**
24
+ * A checked, frozen copy of a tool's schema and its validator. `check` collects the start-time
25
+ * problems (unknown keywords, a property without a description, a pattern that does not compile).
26
+ */
27
+ export declare class InputSchema {
28
+ #private;
29
+ readonly schema: JsonSchema;
30
+ private constructor();
31
+ /**
32
+ * The schema copied (as JSON, so no function, getter or cycle survives) and checked. `root`
33
+ * says whether this is a tool input (an object) or an output schema.
34
+ */
35
+ static check(value: unknown, root: 'input' | 'output'): {
36
+ schema?: InputSchema;
37
+ problems: string[];
38
+ };
39
+ /** The problems of `value` (at most MAX_PROBLEMS); empty when it is valid. */
40
+ validate(value: unknown): SchemaProblem[];
41
+ }
42
+ /** A frozen JSON copy (descriptions, schemas): nothing a tool changes later leaks into a list. */
43
+ export declare function frozenJson<T>(value: T): T;
package/dist/schema.js ADDED
@@ -0,0 +1,482 @@
1
+ /*
2
+ * The JSON Schema subset of tool inputs (D-0094 §1.5): checked once at start, then used to
3
+ * validate every call before the tool runs. A keyword this validator does not apply is refused at
4
+ * start instead of being ignored at run time (a constraint the model sees must also be enforced).
5
+ *
6
+ * Supported (draft 2020-12 meaning): type, enum, const, properties, required,
7
+ * additionalProperties, minProperties, maxProperties, items, minItems, maxItems, uniqueItems,
8
+ * minLength, maxLength, pattern (ECMAScript with the `u` flag), minimum, maximum,
9
+ * exclusiveMinimum, exclusiveMaximum, multipleOf, anyOf, oneOf. Annotations, kept and not checked:
10
+ * $schema, $comment, title, description, default, examples, deprecated, readOnly, writeOnly,
11
+ * format. Every property needs a non-empty `description`: it is the only text the model reads
12
+ * about the argument.
13
+ */
14
+ const ANNOTATIONS = new Set([
15
+ '$schema',
16
+ '$comment',
17
+ 'title',
18
+ 'description',
19
+ 'default',
20
+ 'examples',
21
+ 'deprecated',
22
+ 'readOnly',
23
+ 'writeOnly',
24
+ 'format',
25
+ ]);
26
+ const ASSERTIONS = new Set([
27
+ 'type',
28
+ 'enum',
29
+ 'const',
30
+ 'properties',
31
+ 'required',
32
+ 'additionalProperties',
33
+ 'minProperties',
34
+ 'maxProperties',
35
+ 'items',
36
+ 'minItems',
37
+ 'maxItems',
38
+ 'uniqueItems',
39
+ 'minLength',
40
+ 'maxLength',
41
+ 'pattern',
42
+ 'minimum',
43
+ 'maximum',
44
+ 'exclusiveMinimum',
45
+ 'exclusiveMaximum',
46
+ 'multipleOf',
47
+ 'anyOf',
48
+ 'oneOf',
49
+ ]);
50
+ const TYPES = new Set(['string', 'number', 'integer', 'boolean', 'object', 'array', 'null']);
51
+ const COUNTS = [
52
+ 'minProperties',
53
+ 'maxProperties',
54
+ 'minItems',
55
+ 'maxItems',
56
+ 'minLength',
57
+ 'maxLength',
58
+ ];
59
+ const BOUNDS = ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum'];
60
+ /** Most problems one validation reports. */
61
+ export const MAX_PROBLEMS = 20;
62
+ /**
63
+ * Deepest nesting of an input value (arrays and objects). Deeper input is refused before the
64
+ * schema is applied: comparisons (enum, const, uniqueItems) and serialization recurse.
65
+ */
66
+ export const MAX_INPUT_DEPTH = 64;
67
+ /** Largest schema text (a schema is sent with every tools/list). */
68
+ export const MAX_SCHEMA_CHARS = 65_536;
69
+ const MAX_DESCRIPTION = 4_096;
70
+ function plain(value) {
71
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
72
+ }
73
+ /** A key that reads as a path segment: `a.b`, else `a["b c"]`. */
74
+ function child(path, key) {
75
+ return /^[A-Za-z_$][\w$]*$/.test(key) ? `${path}.${key}` : `${path}[${JSON.stringify(key)}]`;
76
+ }
77
+ /** Stable text of a JSON value (object keys sorted): equality for enum, const, uniqueItems. */
78
+ function canonical(value) {
79
+ if (Array.isArray(value))
80
+ return `[${value.map(canonical).join(',')}]`;
81
+ if (plain(value))
82
+ return `{${Object.keys(value)
83
+ .sort()
84
+ .map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
85
+ .join(',')}}`;
86
+ return JSON.stringify(value) ?? 'null';
87
+ }
88
+ /**
89
+ * The first part of `value` that is nested deeper than MAX_INPUT_DEPTH or is a number that is not
90
+ * finite, walked without recursion (a 200,000-deep array fits in a 1 MiB body).
91
+ */
92
+ function shapeProblem(value) {
93
+ const stack = [
94
+ { value, path: 'input', depth: 0 },
95
+ ];
96
+ while (stack.length) {
97
+ const { value: one, path, depth, } = stack.pop();
98
+ if (typeof one === 'number' && !Number.isFinite(one))
99
+ return { path, message: 'must be a finite number' };
100
+ if (one === null || typeof one !== 'object')
101
+ continue;
102
+ if (depth >= MAX_INPUT_DEPTH)
103
+ return { path, message: `is nested deeper than ${MAX_INPUT_DEPTH} levels` };
104
+ if (Array.isArray(one))
105
+ for (let index = one.length - 1; index >= 0; index--)
106
+ stack.push({ value: one[index], path: `${path}[${index}]`, depth: depth + 1 });
107
+ else
108
+ for (const key of Object.keys(one))
109
+ stack.push({
110
+ value: one[key],
111
+ path: child(path, key),
112
+ depth: depth + 1,
113
+ });
114
+ }
115
+ return undefined;
116
+ }
117
+ /**
118
+ * Whether a pattern repeats a group that itself repeats without bound (`(a+)+`, `(\w*)*`,
119
+ * `(x{2,})+`): the shape whose backtracking grows exponentially. Alternation ambiguity such as
120
+ * `(a|aa)+` is not detected: keep patterns linear.
121
+ */
122
+ export function nestedQuantifier(pattern) {
123
+ const frames = [false];
124
+ let index = 0;
125
+ const unboundedAt = (at) => {
126
+ const char = pattern[at];
127
+ if (char === '+' || char === '*')
128
+ return 1;
129
+ if (char === '{') {
130
+ const match = /^\{\d*,\}/.exec(pattern.slice(at));
131
+ if (match)
132
+ return match[0].length;
133
+ }
134
+ return 0;
135
+ };
136
+ const quantifiedAt = (at) => {
137
+ const char = pattern[at];
138
+ return (char === '+' || char === '*' || (char === '{' && /^\{\d*(,\d*)?\}/.test(pattern.slice(at))));
139
+ };
140
+ while (index < pattern.length) {
141
+ const char = pattern[index];
142
+ if (char === '\\') {
143
+ index += 2;
144
+ continue;
145
+ }
146
+ if (char === '[') {
147
+ index++;
148
+ while (index < pattern.length && pattern[index] !== ']')
149
+ index += pattern[index] === '\\' ? 2 : 1;
150
+ index++;
151
+ continue;
152
+ }
153
+ if (char === '(') {
154
+ frames.push(false);
155
+ index++;
156
+ continue;
157
+ }
158
+ if (char === ')') {
159
+ const inner = frames.length > 1 ? frames.pop() : false;
160
+ index++;
161
+ if (inner && quantifiedAt(index))
162
+ return true;
163
+ const step = unboundedAt(index);
164
+ frames[frames.length - 1] = frames[frames.length - 1] || inner || step > 0;
165
+ continue;
166
+ }
167
+ const step = unboundedAt(index);
168
+ if (step) {
169
+ frames[frames.length - 1] = true;
170
+ index += step;
171
+ continue;
172
+ }
173
+ index++;
174
+ }
175
+ return false;
176
+ }
177
+ function typeOf(value) {
178
+ if (value === null)
179
+ return 'null';
180
+ if (Array.isArray(value))
181
+ return 'array';
182
+ if (typeof value === 'number')
183
+ return Number.isInteger(value) ? 'integer' : 'number';
184
+ return typeof value;
185
+ }
186
+ function matchesType(value, type) {
187
+ const actual = typeOf(value);
188
+ if (type === 'number')
189
+ return (actual === 'number' || actual === 'integer') && Number.isFinite(value);
190
+ // An integer beyond 2^53 has lost digits already: refused rather than taken as exact.
191
+ if (type === 'integer')
192
+ return actual === 'integer' && Number.isSafeInteger(value);
193
+ return actual === type;
194
+ }
195
+ /** Code points, as JSON Schema counts string length. */
196
+ function length(text) {
197
+ let count = 0;
198
+ for (const _ of text)
199
+ count++;
200
+ return count;
201
+ }
202
+ /**
203
+ * A checked, frozen copy of a tool's schema and its validator. `check` collects the start-time
204
+ * problems (unknown keywords, a property without a description, a pattern that does not compile).
205
+ */
206
+ export class InputSchema {
207
+ schema;
208
+ #patterns = new Map();
209
+ constructor(schema) {
210
+ this.schema = schema;
211
+ }
212
+ /**
213
+ * The schema copied (as JSON, so no function, getter or cycle survives) and checked. `root`
214
+ * says whether this is a tool input (an object) or an output schema.
215
+ */
216
+ static check(value, root) {
217
+ const problems = [];
218
+ let copy;
219
+ try {
220
+ const text = JSON.stringify(value);
221
+ if (text === undefined)
222
+ problems.push(`${root}Schema is not a JSON value`);
223
+ else if (text.length > MAX_SCHEMA_CHARS)
224
+ problems.push(`${root}Schema is longer than ${MAX_SCHEMA_CHARS} characters`);
225
+ else
226
+ copy = JSON.parse(text);
227
+ }
228
+ catch (error) {
229
+ // A cycle or a BigInt: reported as the schema's problem (the start fails with it).
230
+ problems.push(`${root}Schema cannot be written as JSON (${error.message})`);
231
+ }
232
+ if (problems.length)
233
+ return { problems };
234
+ if (!plain(copy) || copy.type !== 'object') {
235
+ problems.push(`${root}Schema must be an object schema ({ type: 'object', properties })`);
236
+ return { problems };
237
+ }
238
+ const schema = new InputSchema(deepFreeze(copy));
239
+ schema.#walk(schema.schema, root === 'input' ? 'inputSchema' : 'outputSchema', problems);
240
+ return problems.length ? { problems } : { schema, problems };
241
+ }
242
+ #walk(node, where, problems) {
243
+ if (typeof node === 'boolean')
244
+ return;
245
+ if (!plain(node)) {
246
+ problems.push(`${where} must be a schema object`);
247
+ return;
248
+ }
249
+ for (const key of Object.keys(node))
250
+ if (!ANNOTATIONS.has(key) && !ASSERTIONS.has(key))
251
+ problems.push(`${where} uses ${key}, which this server does not apply (supported: ${[...ASSERTIONS].join(', ')})`);
252
+ if (node.description !== undefined && typeof node.description !== 'string')
253
+ problems.push(`${where}.description must be a string`);
254
+ if (typeof node.description === 'string' && node.description.length > MAX_DESCRIPTION)
255
+ problems.push(`${where}.description is longer than ${MAX_DESCRIPTION} characters`);
256
+ const type = node.type;
257
+ if (type !== undefined) {
258
+ const list = Array.isArray(type) ? type : [type];
259
+ if (!list.length || list.some((one) => typeof one !== 'string' || !TYPES.has(one)))
260
+ problems.push(`${where}.type must be one of ${[...TYPES].join(', ')} (or a list of them)`);
261
+ }
262
+ if (node.enum !== undefined && (!Array.isArray(node.enum) || node.enum.length === 0))
263
+ problems.push(`${where}.enum must be a non-empty list`);
264
+ for (const key of COUNTS) {
265
+ const count = node[key];
266
+ if (count !== undefined && !(Number.isSafeInteger(count) && count >= 0))
267
+ problems.push(`${where}.${key} must be a whole number of at least 0`);
268
+ }
269
+ for (const key of BOUNDS)
270
+ if (node[key] !== undefined && !Number.isFinite(node[key]))
271
+ problems.push(`${where}.${key} must be a number`);
272
+ if (node.multipleOf !== undefined &&
273
+ !(typeof node.multipleOf === 'number' && node.multipleOf > 0))
274
+ problems.push(`${where}.multipleOf must be a number above 0`);
275
+ if (node.uniqueItems !== undefined && typeof node.uniqueItems !== 'boolean')
276
+ problems.push(`${where}.uniqueItems must be a boolean`);
277
+ if (node.pattern !== undefined) {
278
+ if (typeof node.pattern !== 'string')
279
+ problems.push(`${where}.pattern must be a string`);
280
+ else
281
+ try {
282
+ this.#patterns.set(node.pattern, new RegExp(node.pattern, 'u'));
283
+ if (nestedQuantifier(node.pattern))
284
+ problems.push(`${where}.pattern repeats a group that repeats without bound (catastrophic backtracking): write it linearly`);
285
+ }
286
+ catch (error) {
287
+ problems.push(`${where}.pattern does not compile: ${error.message}`);
288
+ }
289
+ }
290
+ if (node.properties !== undefined) {
291
+ if (!plain(node.properties))
292
+ problems.push(`${where}.properties must be an object`);
293
+ else
294
+ for (const [name, property] of Object.entries(node.properties)) {
295
+ const at = `${where}.properties.${name}`;
296
+ if (!plain(property) ||
297
+ typeof property.description !== 'string' ||
298
+ property.description.trim() === '')
299
+ problems.push(`${at} has no description (the model reads only that about it)`);
300
+ this.#walk(property, at, problems);
301
+ }
302
+ }
303
+ if (node.required !== undefined) {
304
+ const required = node.required;
305
+ if (!Array.isArray(required) || required.some((name) => typeof name !== 'string'))
306
+ problems.push(`${where}.required must be a list of property names`);
307
+ else if (plain(node.properties)) {
308
+ const declared = node.properties;
309
+ for (const name of required)
310
+ if (!Object.hasOwn(declared, name))
311
+ problems.push(`${where}.required names ${name}, which is not in properties`);
312
+ }
313
+ }
314
+ if (node.additionalProperties !== undefined)
315
+ this.#walk(node.additionalProperties, `${where}.additionalProperties`, problems);
316
+ if (node.items !== undefined) {
317
+ if (Array.isArray(node.items))
318
+ problems.push(`${where}.items must be one schema (tuple items are not supported)`);
319
+ else
320
+ this.#walk(node.items, `${where}.items`, problems);
321
+ }
322
+ for (const key of ['anyOf', 'oneOf']) {
323
+ const branches = node[key];
324
+ if (branches === undefined)
325
+ continue;
326
+ if (!Array.isArray(branches) || branches.length === 0)
327
+ problems.push(`${where}.${key} must be a non-empty list of schemas`);
328
+ else
329
+ for (const [index, branch] of branches.entries())
330
+ this.#walk(branch, `${where}.${key}[${index}]`, problems);
331
+ }
332
+ }
333
+ /** The problems of `value` (at most MAX_PROBLEMS); empty when it is valid. */
334
+ validate(value) {
335
+ const shape = shapeProblem(value);
336
+ if (shape)
337
+ return [shape];
338
+ const problems = [];
339
+ this.#check(this.schema, value, 'input', problems);
340
+ return problems.slice(0, MAX_PROBLEMS);
341
+ }
342
+ #check(node, value, path, problems) {
343
+ if (problems.length >= MAX_PROBLEMS)
344
+ return;
345
+ if (node === true || node === undefined)
346
+ return;
347
+ if (node === false || !plain(node)) {
348
+ problems.push({ path, message: 'is not allowed' });
349
+ return;
350
+ }
351
+ const add = (message, at = path) => problems.push({ path: at, message });
352
+ if (node.type !== undefined) {
353
+ const types = (Array.isArray(node.type) ? node.type : [node.type]);
354
+ if (!types.some((type) => matchesType(value, type))) {
355
+ add(`must be ${types.join(' or ')}`);
356
+ return;
357
+ }
358
+ }
359
+ if (node.const !== undefined && canonical(node.const) !== canonical(value))
360
+ add(`must be ${JSON.stringify(node.const)}`);
361
+ if (Array.isArray(node.enum)) {
362
+ const text = canonical(value);
363
+ if (!node.enum.some((option) => canonical(option) === text))
364
+ add(`must be one of ${node.enum
365
+ .slice(0, 20)
366
+ .map((option) => JSON.stringify(option))
367
+ .join(', ')}`);
368
+ }
369
+ if (typeof value === 'string')
370
+ this.#string(node, value, add);
371
+ else if (typeof value === 'number')
372
+ this.#number(node, value, add);
373
+ else if (Array.isArray(value))
374
+ this.#array(node, value, path, problems, add);
375
+ else if (plain(value))
376
+ this.#object(node, value, path, problems, add);
377
+ for (const key of ['anyOf', 'oneOf']) {
378
+ const branches = node[key];
379
+ if (!Array.isArray(branches))
380
+ continue;
381
+ let matched = 0;
382
+ for (const branch of branches) {
383
+ const inner = [];
384
+ this.#check(branch, value, path, inner);
385
+ if (inner.length === 0)
386
+ matched++;
387
+ }
388
+ if (key === 'anyOf' && matched === 0)
389
+ add('matches none of the allowed forms');
390
+ if (key === 'oneOf' && matched !== 1)
391
+ add(matched === 0 ? 'matches none of the allowed forms' : 'matches more than one form');
392
+ }
393
+ }
394
+ #string(node, value, add) {
395
+ const count = node.minLength !== undefined || node.maxLength !== undefined ? length(value) : 0;
396
+ let fits = true;
397
+ if (typeof node.minLength === 'number' && count < node.minLength) {
398
+ add(`must be at least ${node.minLength} characters`);
399
+ fits = false;
400
+ }
401
+ if (typeof node.maxLength === 'number' && count > node.maxLength) {
402
+ add(`must be at most ${node.maxLength} characters`);
403
+ fits = false;
404
+ }
405
+ // The pattern runs only on a string whose length is allowed (a long one never reaches it).
406
+ if (fits && typeof node.pattern === 'string') {
407
+ const pattern = this.#patterns.get(node.pattern);
408
+ if (!pattern.test(value))
409
+ add(`must match the pattern ${node.pattern}`);
410
+ }
411
+ }
412
+ #number(node, value, add) {
413
+ if (typeof node.minimum === 'number' && value < node.minimum)
414
+ add(`must be at least ${node.minimum}`);
415
+ if (typeof node.maximum === 'number' && value > node.maximum)
416
+ add(`must be at most ${node.maximum}`);
417
+ if (typeof node.exclusiveMinimum === 'number' && value <= node.exclusiveMinimum)
418
+ add(`must be above ${node.exclusiveMinimum}`);
419
+ if (typeof node.exclusiveMaximum === 'number' && value >= node.exclusiveMaximum)
420
+ add(`must be below ${node.exclusiveMaximum}`);
421
+ if (typeof node.multipleOf === 'number') {
422
+ const quotient = value / node.multipleOf;
423
+ if (Math.abs(quotient - Math.round(quotient)) > 1e-9)
424
+ add(`must be a multiple of ${node.multipleOf}`);
425
+ }
426
+ }
427
+ #array(node, value, path, problems, add) {
428
+ if (typeof node.minItems === 'number' && value.length < node.minItems)
429
+ add(`must have at least ${node.minItems} items`);
430
+ if (typeof node.maxItems === 'number' && value.length > node.maxItems) {
431
+ add(`must have at most ${node.maxItems} items`);
432
+ return;
433
+ }
434
+ if (node.uniqueItems === true) {
435
+ const seen = new Set();
436
+ for (const item of value) {
437
+ const text = canonical(item);
438
+ if (seen.has(text)) {
439
+ add('must not repeat an item');
440
+ break;
441
+ }
442
+ seen.add(text);
443
+ }
444
+ }
445
+ if (node.items !== undefined)
446
+ value.forEach((item, index) => {
447
+ this.#check(node.items, item, `${path}[${index}]`, problems);
448
+ });
449
+ }
450
+ #object(node, value, path, problems, add) {
451
+ const keys = Object.keys(value);
452
+ if (typeof node.minProperties === 'number' && keys.length < node.minProperties)
453
+ add(`must have at least ${node.minProperties} properties`);
454
+ if (typeof node.maxProperties === 'number' && keys.length > node.maxProperties)
455
+ add(`must have at most ${node.maxProperties} properties`);
456
+ const properties = plain(node.properties) ? node.properties : {};
457
+ if (Array.isArray(node.required))
458
+ for (const name of node.required)
459
+ if (!Object.hasOwn(value, name))
460
+ add('is required', child(path, name));
461
+ for (const key of keys) {
462
+ if (Object.hasOwn(properties, key))
463
+ this.#check(properties[key], value[key], child(path, key), problems);
464
+ else if (node.additionalProperties === false)
465
+ add('is not a known property', child(path, key));
466
+ else if (node.additionalProperties !== undefined)
467
+ this.#check(node.additionalProperties, value[key], child(path, key), problems);
468
+ }
469
+ }
470
+ }
471
+ function deepFreeze(value) {
472
+ if (value !== null && typeof value === 'object') {
473
+ for (const inner of Object.values(value))
474
+ deepFreeze(inner);
475
+ Object.freeze(value);
476
+ }
477
+ return value;
478
+ }
479
+ /** A frozen JSON copy (descriptions, schemas): nothing a tool changes later leaks into a list. */
480
+ export function frozenJson(value) {
481
+ return deepFreeze(JSON.parse(JSON.stringify(value)));
482
+ }
@@ -0,0 +1,43 @@
1
+ import type { Logger } from '@dokebi/core';
2
+ import { type Headers } from './era.js';
3
+ import { type CompiledPrompt, type CompiledResource, type CompiledTool } from './registry.js';
4
+ import type { CallSettings, McpRunner } from './runner.js';
5
+ /** What the route sends: a status, a JSON body (undefined: empty) and the 403 challenge. */
6
+ export interface McpReply {
7
+ readonly status: number;
8
+ readonly body: unknown;
9
+ readonly challenge?: string;
10
+ }
11
+ export interface ServerInfo {
12
+ readonly name: string;
13
+ readonly version: string;
14
+ readonly title?: string;
15
+ }
16
+ export interface ServerSettings {
17
+ readonly runner: McpRunner;
18
+ readonly tools: readonly CompiledTool[];
19
+ readonly prompts: readonly CompiledPrompt[];
20
+ readonly resources: readonly CompiledResource[];
21
+ readonly serverInfo: ServerInfo;
22
+ readonly instructions: string | undefined;
23
+ /** The protected resource metadata URL (the 403 challenge). */
24
+ readonly metadataUrl: string;
25
+ readonly logger: Logger;
26
+ /** `maxBatch` (absent: MAX_BATCH, D-0114). */
27
+ readonly maxBatch?: number;
28
+ /** `listTtlMs` (absent: LIST_TTL_MS, D-0114). */
29
+ readonly listTtlMs?: number;
30
+ /** `uriMaxChars` (absent: URI_MAX_CHARS): the longest URI `resources/read` matches. */
31
+ readonly uriMaxChars?: number;
32
+ }
33
+ /** A call without its version: the route knows the grant, the request and the client signal. */
34
+ export type RequestCall = Omit<CallSettings, 'version'>;
35
+ export declare class McpServer {
36
+ #private;
37
+ readonly capabilities: Readonly<Record<string, unknown>>;
38
+ /** D-0154: the reloaded batch size, list cache time and URI limit, for the next request. */
39
+ update(next: Pick<ServerSettings, 'maxBatch' | 'listTtlMs' | 'uriMaxChars'>): void;
40
+ constructor(settings: ServerSettings);
41
+ /** One POST: the body bytes, the headers and who calls. */
42
+ handle(raw: Uint8Array | undefined, headers: Headers, call: RequestCall): Promise<McpReply>;
43
+ }