typespec-http-mcp 0.1.0 → 0.2.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 +4 -1
- package/dist/src/bodies.d.ts +16 -34
- package/dist/src/bodies.js +13 -78
- package/dist/src/emitter.js +9 -11
- package/dist/src/lib.d.ts +0 -3
- package/dist/src/lib.js +1 -17
- package/dist/src/refusals.d.ts +0 -3
- package/dist/src/refusals.js +7 -18
- package/dist/src/runtime.d.ts +39 -10
- package/dist/src/runtime.js +98 -29
- package/dist/src/select.d.ts +5 -10
- package/dist/src/select.js +5 -31
- package/dist/src/tools.js +85 -71
- package/package.json +16 -15
- package/src/runtime.ts +156 -57
- package/dist/src/statuses.d.ts +0 -35
- package/dist/src/statuses.js +0 -29
package/dist/src/select.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Program } from "@typespec/compiler";
|
|
2
|
-
import type { HttpOperation
|
|
2
|
+
import type { HttpOperation } from "@typespec/http";
|
|
3
3
|
import type { EmittedRoute } from "typespec-http-zod";
|
|
4
4
|
/**
|
|
5
5
|
* Which operations become tools.
|
|
@@ -36,14 +36,9 @@ export interface SelectionOptions {
|
|
|
36
36
|
readonly toolTags: readonly string[] | undefined;
|
|
37
37
|
readonly excludeTags: readonly string[];
|
|
38
38
|
}
|
|
39
|
-
/** The `HttpOperation` behind each emitted route, keyed by verb and path. */
|
|
40
|
-
export declare function operationsById(service: HttpService): ReadonlyMap<string, HttpOperation>;
|
|
41
39
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* which is most of them in a real spec -- a defect this estate has already paid for once. The verb
|
|
46
|
-
* and the path are what both the route and the document agree on.
|
|
40
|
+
* **Each route's operation is read by its operation id**, from the library's `operations`. It was
|
|
41
|
+
* looked up by verb and path, which `@sharedRoute` does not make unique, so every member of a shared
|
|
42
|
+
* route was selected by one member's tags.
|
|
47
43
|
*/
|
|
48
|
-
export declare function
|
|
49
|
-
export declare function selectTools(program: Program, service: HttpService, routes: readonly EmittedRoute[], options: SelectionOptions): Selection;
|
|
44
|
+
export declare function selectTools(program: Program, routes: readonly Pick<EmittedRoute, "operationId">[], operations: ReadonlyMap<string, HttpOperation>, options: SelectionOptions): Selection;
|
package/dist/src/select.js
CHANGED
|
@@ -1,40 +1,14 @@
|
|
|
1
1
|
import { getAllTags } from "@typespec/compiler";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `HttpOperation.verb` is lowercase (`"get"`) -- measured, on this package's own fixture. A
|
|
7
|
-
* case-sensitive key misses every operation, and the failure is silent in the worst possible way:
|
|
8
|
-
* the emitter selects nothing, reports `no-tools-selected`, and the message blames the tag
|
|
9
|
-
* configuration. It was caught here only because a fixture asserted that an unnarrowed selection
|
|
10
|
-
* selects SOMETHING.
|
|
3
|
+
* **Each route's operation is read by its operation id**, from the library's `operations`. It was
|
|
4
|
+
* looked up by verb and path, which `@sharedRoute` does not make unique, so every member of a shared
|
|
5
|
+
* route was selected by one member's tags.
|
|
11
6
|
*/
|
|
12
|
-
function
|
|
13
|
-
return `${verb.toLowerCase()} ${path}`;
|
|
14
|
-
}
|
|
15
|
-
/** The `HttpOperation` behind each emitted route, keyed by verb and path. */
|
|
16
|
-
export function operationsById(service) {
|
|
17
|
-
const byVerbAndPath = new Map();
|
|
18
|
-
for (const operation of service.operations) {
|
|
19
|
-
byVerbAndPath.set(keyOf(operation.verb, operation.path), operation);
|
|
20
|
-
}
|
|
21
|
-
return byVerbAndPath;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Look an operation up by verb and path, never by name.
|
|
25
|
-
*
|
|
26
|
-
* **Matching on operation NAME silently fails for every operation declared inside an `interface`**,
|
|
27
|
-
* which is most of them in a real spec -- a defect this estate has already paid for once. The verb
|
|
28
|
-
* and the path are what both the route and the document agree on.
|
|
29
|
-
*/
|
|
30
|
-
export function httpOperationFor(service, route) {
|
|
31
|
-
return operationsById(service).get(keyOf(route.verb, route.path));
|
|
32
|
-
}
|
|
33
|
-
export function selectTools(program, service, routes, options) {
|
|
7
|
+
export function selectTools(program, routes, operations, options) {
|
|
34
8
|
const available = new Set();
|
|
35
9
|
const selected = [];
|
|
36
10
|
for (const route of routes) {
|
|
37
|
-
const operation =
|
|
11
|
+
const operation = operations.get(route.operationId);
|
|
38
12
|
if (operation === undefined)
|
|
39
13
|
continue;
|
|
40
14
|
const tags = getAllTags(program, operation.operation) ?? [];
|
package/dist/src/tools.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { jsDocComment, objectKey, } from "typespec-http-zod";
|
|
1
|
+
import { jsDocComment, mediaTypeTypeOf, objectKey, statusCodesOf, } from "typespec-http-zod";
|
|
2
2
|
import { annotationsFor } from "./annotations.js";
|
|
3
|
-
import { bodyKindsOf,
|
|
4
|
-
import { CONTENTLESS_STATUS_CODES, STATUS_GROUPS } from "./statuses.js";
|
|
3
|
+
import { bodyKindsOf, isJsonMediaType, membersOf } from "./bodies.js";
|
|
5
4
|
/**
|
|
6
5
|
* **Rendering lives here, with everything else that produces text.**
|
|
7
6
|
*
|
|
@@ -39,10 +38,11 @@ const FIELDS_HELPER = `/**
|
|
|
39
38
|
* **Zod 4 infers \`Record<string, never>\` for \`z.object({})\`.** That is a fair reading on its own -
|
|
40
39
|
* no string key may exist - and lethal in an intersection: \`{ id: string } & Record<string, never>\`
|
|
41
40
|
* makes \`id\` into \`string & never\`, so the input type is uninhabitable and the call site below
|
|
42
|
-
* cannot compile. An empty model contributes nothing to an operation's input,
|
|
43
|
-
* identity of
|
|
41
|
+
* cannot compile. An empty model contributes nothing to an operation's input, so it becomes \`{}\`,
|
|
42
|
+
* the identity of \`&\` over the object types an input is made of - not \`unknown\`, which an input
|
|
43
|
+
* made of nothing else would then BE, and which the contract types' \`{}\` does not accept.
|
|
44
44
|
*/
|
|
45
|
-
type Fields<T> = string extends keyof T ? ([T[string]] extends [never] ?
|
|
45
|
+
type Fields<T> = string extends keyof T ? ([T[string]] extends [never] ? {} : T) : T;
|
|
46
46
|
|
|
47
47
|
`;
|
|
48
48
|
const PRODUCED_HELPER = `/**
|
|
@@ -51,8 +51,11 @@ const PRODUCED_HELPER = `/**
|
|
|
51
51
|
* An index signature and \`readonly\` are true of an inferred type without being obligations on a
|
|
52
52
|
* producer, so both are dropped here - only where the shape has declared keys beside it, because an
|
|
53
53
|
* index signature also arrives as a property's own declared type, where stripping it would destroy
|
|
54
|
-
* the property.
|
|
55
|
-
*
|
|
54
|
+
* the property.
|
|
55
|
+
*
|
|
56
|
+
* It is applied to the schema's INPUT, \`z.input\`, because that is what a result is checked against:
|
|
57
|
+
* a property with a default may be left out, and an optional property is \`?: T\`, because
|
|
58
|
+
* \`.exactOptional()\` refuses an explicit \`undefined\`. Both are what the contract types state.
|
|
56
59
|
*/
|
|
57
60
|
type ProducedKeyOf<T> = keyof {
|
|
58
61
|
[K in keyof T as string extends K
|
|
@@ -110,7 +113,14 @@ export const DEFAULT_RUNTIME_MODULE = "./mcp-runtime.gen.js";
|
|
|
110
113
|
*/
|
|
111
114
|
function intersectedValidators(entry) {
|
|
112
115
|
const { names, route } = entry;
|
|
113
|
-
|
|
116
|
+
/**
|
|
117
|
+
* **A cookie is merged like a header, and for the same reason.** A tool call carries neither, so
|
|
118
|
+
* neither is transport the tool can set - both are inputs the operation needs, and a tool's
|
|
119
|
+
* arguments are one object merged from every location the document declares. The alternative was
|
|
120
|
+
* to refuse the operation, which would remove a working tool over a distinction the MCP surface
|
|
121
|
+
* does not have.
|
|
122
|
+
*/
|
|
123
|
+
const groups = [names.path, names.query, names.header, names.cookie, names.body];
|
|
114
124
|
return groups.filter((name) => name !== undefined && (route.bodyProperty === undefined || name !== names.body));
|
|
115
125
|
}
|
|
116
126
|
/**
|
|
@@ -130,12 +140,30 @@ function shapedValidators(entry) {
|
|
|
130
140
|
function inputTypeOf(entry) {
|
|
131
141
|
const parts = intersectedValidators(entry).map((name) => `Fields<z.infer<typeof ${name}>>`);
|
|
132
142
|
if (entry.route.bodyProperty !== undefined && entry.names.body !== undefined) {
|
|
143
|
+
// `?: T`, as the sibling server types it: an absent body is an absent key.
|
|
133
144
|
const optionalMark = entry.route.optionalBody ? "?" : "";
|
|
134
|
-
|
|
135
|
-
parts.push(`{ ${objectKey(entry.route.bodyProperty)}${optionalMark}: z.infer<typeof ${entry.names.body}>${optionalValue} }`);
|
|
145
|
+
parts.push(`{ ${objectKey(entry.route.bodyProperty)}${optionalMark}: z.infer<typeof ${entry.names.body}> }`);
|
|
136
146
|
}
|
|
147
|
+
const selected = selectedAcceptOf(entry);
|
|
148
|
+
if (selected !== undefined)
|
|
149
|
+
parts.push(`{ ${selected} }`);
|
|
137
150
|
return parts.length === 0 ? EMPTY_INPUT : parts.join(" & ");
|
|
138
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* **The `accept` literal a tool supplies itself**, on a route several operations share, as
|
|
154
|
+
* `key: "literal"`.
|
|
155
|
+
*
|
|
156
|
+
* The server negotiates which operation answers, so its handler receives the one literal that reached
|
|
157
|
+
* it, and the header validator leaves `accept` out. A tool IS one of those operations, so the literal
|
|
158
|
+
* is known exactly here too: the handler receives it, and the caller is never asked for a value with
|
|
159
|
+
* one answer. It was missing, so a handler typed for the server could not be registered as a tool.
|
|
160
|
+
*/
|
|
161
|
+
function selectedAcceptOf(entry) {
|
|
162
|
+
const selected = entry.names.selectedAccept;
|
|
163
|
+
return selected === undefined
|
|
164
|
+
? undefined
|
|
165
|
+
: `${objectKey(selected.name)}: ${JSON.stringify(selected.value)}`;
|
|
166
|
+
}
|
|
139
167
|
/**
|
|
140
168
|
* The tool's ARGUMENT schema: every group the document declares, merged into one object.
|
|
141
169
|
*
|
|
@@ -155,64 +183,27 @@ function inputSchemaOf(entry, sealed) {
|
|
|
155
183
|
const constructor = sealed ? "z.strictObject" : "z.looseObject";
|
|
156
184
|
const spreads = shapedValidators(entry).map((name) => `...${name}.shape`);
|
|
157
185
|
if (entry.route.bodyProperty !== undefined && entry.names.body !== undefined) {
|
|
158
|
-
|
|
186
|
+
/**
|
|
187
|
+
* **Optional where the document lets the body be absent.** It was required here while the
|
|
188
|
+
* handler's input said \`body?:\`, so a tool call omitting a body the document makes optional
|
|
189
|
+
* was refused before it reached the handler.
|
|
190
|
+
*/
|
|
191
|
+
const optional = entry.route.optionalBody ? ".exactOptional()" : "";
|
|
192
|
+
spreads.push(`${objectKey(entry.route.bodyProperty)}: ${entry.names.body}${optional}`);
|
|
159
193
|
}
|
|
160
194
|
if (spreads.length === 0)
|
|
161
195
|
return "z.strictObject({})";
|
|
162
196
|
return `${constructor}({ ${spreads.join(", ")} })`;
|
|
163
197
|
}
|
|
164
|
-
/** The codes of the status group a range key names. */
|
|
165
|
-
function groupCodesOf(status) {
|
|
166
|
-
return STATUS_GROUPS[Number(status.charAt(0))].codes;
|
|
167
|
-
}
|
|
168
198
|
/**
|
|
169
|
-
* The
|
|
199
|
+
* The status a result member is typed with: the codes its response answers with, written out.
|
|
170
200
|
*
|
|
171
|
-
* **
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*/
|
|
175
|
-
function contentTypeTypeOf(mediaType) {
|
|
176
|
-
if (!isMediaRange(mediaType))
|
|
177
|
-
return JSON.stringify(mediaType);
|
|
178
|
-
if (mediaType === "*/*")
|
|
179
|
-
return "string";
|
|
180
|
-
return `\`${mediaType.slice(0, -1)}\${string}\``;
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* A member's status as a TypeScript type: disjoint from every other member's, by construction.
|
|
184
|
-
*
|
|
185
|
-
* **Written as a union of literals, never as Hono's type names.** This package's output imports no
|
|
186
|
-
* Hono, so where the sibling emitter writes `Exclude<ClientErrorStatusCode, 404 | ContentlessStatusCode>`
|
|
187
|
-
* this writes the codes that expression resolves to, from the same sets (`statuses.ts`, held equal to
|
|
188
|
-
* Hono's declaration file by test):
|
|
189
|
-
*
|
|
190
|
-
* - an exact code is itself;
|
|
191
|
-
* - a range is its group, less every code the operation declares exactly, and less the statuses that
|
|
192
|
-
* cannot carry a body where the range has one;
|
|
193
|
-
* - `default` is every official status from 2xx to 5xx, less the exact codes, less every code a
|
|
194
|
-
* declared range covers, and less the contentless statuses where it has a body.
|
|
195
|
-
*
|
|
196
|
-
* **Disjoint is the property that makes a wrong body a compile error**: were `default` allowed to
|
|
197
|
-
* overlap a declared status, a 404 carrying the default arm's body would type-check through it.
|
|
201
|
+
* **Written out, never as Hono's group types**, because this file imports nothing from Hono. The
|
|
202
|
+
* codes are the library's `statusCodesOf`, the same rule the server emitter and the contract types
|
|
203
|
+
* read, so a tool's result and a handler's result agree member by member.
|
|
198
204
|
*/
|
|
199
205
|
function statusTypeOf(response, route) {
|
|
200
|
-
const
|
|
201
|
-
if (typeof status === "number")
|
|
202
|
-
return String(status);
|
|
203
|
-
const exact = new Set(route.responses.flatMap((candidate) => typeof candidate.status === "number" ? [candidate.status] : []));
|
|
204
|
-
const contentless = new Set(response.schema === undefined ? [] : CONTENTLESS_STATUS_CODES);
|
|
205
|
-
const candidates = status === "default"
|
|
206
|
-
? [2, 3, 4, 5].flatMap((digit) => STATUS_GROUPS[digit].codes)
|
|
207
|
-
: groupCodesOf(status);
|
|
208
|
-
const ranged = new Set(status === "default"
|
|
209
|
-
? route.responses.flatMap((candidate) => typeof candidate.status === "string" && candidate.status !== "default"
|
|
210
|
-
? groupCodesOf(candidate.status)
|
|
211
|
-
: [])
|
|
212
|
-
: []);
|
|
213
|
-
const codes = candidates
|
|
214
|
-
.filter((code) => !exact.has(code) && !ranged.has(code) && !contentless.has(code))
|
|
215
|
-
.toSorted((a, b) => a - b);
|
|
206
|
+
const codes = statusCodesOf(response, route.responses, (candidate) => candidate.schema !== undefined);
|
|
216
207
|
return codes.length === 0 ? "never" : codes.join(" | ");
|
|
217
208
|
}
|
|
218
209
|
/**
|
|
@@ -226,23 +217,28 @@ function resultTypeOf(entry) {
|
|
|
226
217
|
const { response } = member;
|
|
227
218
|
const fields = [`readonly status: ${statusTypeOf(response, entry.route)}`];
|
|
228
219
|
if (member.choosesMediaType && member.mediaType !== undefined) {
|
|
229
|
-
fields.push(`readonly contentType: ${
|
|
220
|
+
fields.push(`readonly contentType: ${mediaTypeTypeOf(member.mediaType)}`);
|
|
230
221
|
}
|
|
231
|
-
switch (
|
|
222
|
+
switch (member.serving) {
|
|
232
223
|
case "none":
|
|
233
224
|
fields.push("readonly body?: undefined");
|
|
234
225
|
break;
|
|
235
226
|
case "stream":
|
|
236
|
-
fields.push("readonly body:
|
|
227
|
+
fields.push("readonly body: ByteStream");
|
|
237
228
|
break;
|
|
238
229
|
case "binary":
|
|
239
|
-
|
|
230
|
+
/**
|
|
231
|
+
* **`ByteStream`, not `ReadableStream`, so a backend's contract value fits**, as the sibling
|
|
232
|
+
* server types it. The contract types state a binary body structurally, because they cannot
|
|
233
|
+
* name the platform stream; a platform stream still satisfies it.
|
|
234
|
+
*/
|
|
235
|
+
fields.push("readonly body: ByteStream | Uint8Array<ArrayBuffer> | ArrayBuffer");
|
|
240
236
|
break;
|
|
241
237
|
case "unvalidated":
|
|
242
238
|
fields.push("readonly body: string");
|
|
243
239
|
break;
|
|
244
240
|
default:
|
|
245
|
-
fields.push(`readonly body: Produced<z.
|
|
241
|
+
fields.push(`readonly body: Produced<z.input<typeof ${member.schemaName}>>`);
|
|
246
242
|
}
|
|
247
243
|
if (response.headers.length > 0) {
|
|
248
244
|
/**
|
|
@@ -328,7 +324,7 @@ export function renderTools(entries, declaredOperationIds, options) {
|
|
|
328
324
|
if (entry.names.body !== undefined)
|
|
329
325
|
imported.add(entry.names.body);
|
|
330
326
|
for (const member of membersOf(entry.route, entry.names)) {
|
|
331
|
-
const serving =
|
|
327
|
+
const serving = member.serving;
|
|
332
328
|
if ((serving === "json" || serving === "text") && member.schemaName !== undefined) {
|
|
333
329
|
imported.add(member.schemaName);
|
|
334
330
|
}
|
|
@@ -349,6 +345,12 @@ export function renderTools(entries, declaredOperationIds, options) {
|
|
|
349
345
|
const runtimeTypes = ["McpServer", "ServerContext", "ToolDeps"];
|
|
350
346
|
if (entries.length > 0)
|
|
351
347
|
runtimeTypes.push("Awaitable");
|
|
348
|
+
const usesByteStream = entries.some((entry) => membersOf(entry.route, entry.names).some((member) => {
|
|
349
|
+
const serving = member.serving;
|
|
350
|
+
return serving === "binary" || serving === "stream";
|
|
351
|
+
}));
|
|
352
|
+
if (usesByteStream)
|
|
353
|
+
runtimeTypes.push("ByteStream");
|
|
352
354
|
const from = JSON.stringify(DEFAULT_RUNTIME_MODULE);
|
|
353
355
|
const runtimeImport = [
|
|
354
356
|
...(entries.length > 0 ? [`import { toolResult } from ${from};`] : []),
|
|
@@ -364,7 +366,7 @@ export function renderTools(entries, declaredOperationIds, options) {
|
|
|
364
366
|
*/
|
|
365
367
|
const usesFields = entries.some((entry) => intersectedValidators(entry).length > 0);
|
|
366
368
|
const usesProduced = entries.some((entry) => membersOf(entry.route, entry.names).some((member) => {
|
|
367
|
-
const serving =
|
|
369
|
+
const serving = member.serving;
|
|
368
370
|
return serving === "json" || serving === "text";
|
|
369
371
|
}));
|
|
370
372
|
const helpers = `${usesFields ? FIELDS_HELPER : ""}${usesProduced ? PRODUCED_HELPER : ""}`;
|
|
@@ -418,10 +420,16 @@ export function renderTools(entries, declaredOperationIds, options) {
|
|
|
418
420
|
* The body kinds the runtime cannot read off an arm, only where one applies: a JSON body is
|
|
419
421
|
* known from its media type, and most operations answer nothing else.
|
|
420
422
|
*/
|
|
421
|
-
const kinds =
|
|
422
|
-
const
|
|
423
|
+
const kinds = new Map();
|
|
424
|
+
for (const [status, mediaType, kind] of bodyKindsOf(entry.route, entry.names)) {
|
|
425
|
+
kinds.set(status, [
|
|
426
|
+
...(kinds.get(status) ?? []),
|
|
427
|
+
`${JSON.stringify(mediaType)}: ${JSON.stringify(kind)}`,
|
|
428
|
+
]);
|
|
429
|
+
}
|
|
430
|
+
const bodies = kinds.size === 0
|
|
423
431
|
? ""
|
|
424
|
-
: `, bodies: { ${kinds.map(([status,
|
|
432
|
+
: `, bodies: { ${[...kinds].map(([status, byType]) => `${JSON.stringify(status)}: { ${byType.join(", ")} }`).join(", ")} }`;
|
|
425
433
|
const declared = `{ arms: ${entry.names.responses}, outputSchema: ${published ?? "undefined"}${bodies} }`;
|
|
426
434
|
/**
|
|
427
435
|
* **A parameterless tool must not DECLARE an argument it never reads.**
|
|
@@ -432,7 +440,13 @@ export function renderTools(entries, declaredOperationIds, options) {
|
|
|
432
440
|
* omission is deliberate.
|
|
433
441
|
*/
|
|
434
442
|
const parameterless = intersectedValidators(entry).length === 0 && entry.route.bodyProperty === undefined;
|
|
435
|
-
|
|
443
|
+
// The selected `accept` last, so a caller's own `accept` argument cannot replace it.
|
|
444
|
+
const supplied = selectedAcceptOf(entry);
|
|
445
|
+
const fields = [
|
|
446
|
+
...(parameterless ? [] : ["...args"]),
|
|
447
|
+
...(supplied === undefined ? [] : [supplied]),
|
|
448
|
+
];
|
|
449
|
+
const argument = fields.length === 0 ? "{}" : `{ ${fields.join(", ")} }`;
|
|
436
450
|
const argumentName = parameterless ? "_args" : "args";
|
|
437
451
|
return ` {
|
|
438
452
|
const tool = server.registerTool(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typespec-http-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "TypeSpec emitter: generate an MCP tool surface, and the Zod validators it publishes, from an HTTP service definition, dispatching into the same handlers a generated HTTP server calls.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare-workers",
|
|
@@ -45,29 +45,29 @@
|
|
|
45
45
|
"provenance": true
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"typespec-http-zod": "^0.
|
|
48
|
+
"typespec-http-zod": "^0.28.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@hono/zod-validator": "^0.9.1",
|
|
52
52
|
"@modelcontextprotocol/client": "2.0.0",
|
|
53
53
|
"@modelcontextprotocol/server": "2.0.0",
|
|
54
54
|
"@types/node": "^26.5.1",
|
|
55
|
-
"@typespec/compiler": "1.
|
|
56
|
-
"@typespec/events": "0.
|
|
57
|
-
"@typespec/http": "1.
|
|
58
|
-
"@typespec/http-specs": "0.1.0-alpha.
|
|
59
|
-
"@typespec/openapi": "1.
|
|
60
|
-
"@typespec/openapi3": "1.
|
|
61
|
-
"@typespec/rest": "0.
|
|
62
|
-
"@typespec/sse": "0.
|
|
63
|
-
"@typespec/streams": "0.
|
|
64
|
-
"@typespec/versioning": "0.
|
|
65
|
-
"@typespec/xml": "0.
|
|
55
|
+
"@typespec/compiler": "1.17.0",
|
|
56
|
+
"@typespec/events": "0.87.0",
|
|
57
|
+
"@typespec/http": "1.17.0",
|
|
58
|
+
"@typespec/http-specs": "0.1.0-alpha.44",
|
|
59
|
+
"@typespec/openapi": "1.17.0",
|
|
60
|
+
"@typespec/openapi3": "1.17.0",
|
|
61
|
+
"@typespec/rest": "0.87.0",
|
|
62
|
+
"@typespec/sse": "0.87.0",
|
|
63
|
+
"@typespec/streams": "0.87.0",
|
|
64
|
+
"@typespec/versioning": "0.87.0",
|
|
65
|
+
"@typespec/xml": "0.87.0",
|
|
66
66
|
"hono": "^4.13.7",
|
|
67
67
|
"oxfmt": "^0.67.0",
|
|
68
68
|
"oxlint": "^1.82.0",
|
|
69
69
|
"typescript": "~7.0.2",
|
|
70
|
-
"typespec-hono": "0.
|
|
70
|
+
"typespec-hono": "0.25.0",
|
|
71
71
|
"typespec-http-mcp": "link:.",
|
|
72
72
|
"vitest": "^5.0.0",
|
|
73
73
|
"wrangler": "^4.131.1",
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
78
78
|
"@typespec/compiler": "^1.15.0",
|
|
79
79
|
"@typespec/http": "^1.15.0",
|
|
80
|
+
"@typespec/openapi": "^1.15.0",
|
|
80
81
|
"zod": "^4.5.0"
|
|
81
82
|
},
|
|
82
83
|
"engines": {
|
|
@@ -84,7 +85,7 @@
|
|
|
84
85
|
},
|
|
85
86
|
"tspMain": "lib/main.tsp",
|
|
86
87
|
"scripts": {
|
|
87
|
-
"build": "tsc -p tsconfig.build.json",
|
|
88
|
+
"build": "node --eval \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json",
|
|
88
89
|
"test": "tsc -p tsconfig.build.json && vitest run",
|
|
89
90
|
"test:unit": "tsc -p tsconfig.build.json && vitest run --project unit",
|
|
90
91
|
"typecheck": "tsc -p tsconfig.json",
|