@typeship-ax/mcp 0.10.0 → 0.20.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/AGENTS.md +11 -5
- package/README.md +16 -5
- package/api.json +8824 -3777
- package/api.md +8359 -3987
- package/dist/api-identity.d.ts.map +1 -1
- package/dist/api-identity.js +6 -1
- package/dist/core/http.d.ts +29 -16
- package/dist/core/http.d.ts.map +1 -1
- package/dist/core/http.js +108 -25
- package/dist/core/pagination.d.ts +8 -8
- package/dist/core/pagination.d.ts.map +1 -1
- package/dist/core/pagination.js +7 -16
- package/dist/errors.d.ts +20 -13
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +29 -20
- package/dist/index.d.ts +37 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +42 -18
- package/dist/mcp-protocol.d.ts +10 -6
- package/dist/mcp-protocol.d.ts.map +1 -1
- package/dist/mcp-protocol.js +110 -39
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +38 -18
- package/dist/ops.d.ts +5 -1
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js +42 -35
- package/dist/resources/api-keys.d.ts +44 -18
- package/dist/resources/api-keys.d.ts.map +1 -1
- package/dist/resources/api-keys.js +46 -14
- package/dist/resources/deliveries.d.ts +46 -0
- package/dist/resources/deliveries.d.ts.map +1 -0
- package/dist/resources/deliveries.js +70 -0
- package/dist/resources/drafts.d.ts +155 -0
- package/dist/resources/drafts.d.ts.map +1 -0
- package/dist/resources/drafts.js +230 -0
- package/dist/resources/files.d.ts +23 -0
- package/dist/resources/files.d.ts.map +1 -0
- package/dist/resources/files.js +38 -0
- package/dist/resources/generate.d.ts +49 -20
- package/dist/resources/generate.d.ts.map +1 -1
- package/dist/resources/generate.js +56 -14
- package/dist/resources/generations.d.ts +70 -18
- package/dist/resources/generations.d.ts.map +1 -1
- package/dist/resources/generations.js +84 -16
- package/dist/resources/organization.d.ts +18 -0
- package/dist/resources/organization.d.ts.map +1 -0
- package/dist/resources/organization.js +32 -0
- package/dist/resources/projects.d.ts +87 -124
- package/dist/resources/projects.d.ts.map +1 -1
- package/dist/resources/projects.js +70 -173
- package/dist/resources/publications.d.ts +46 -0
- package/dist/resources/publications.d.ts.map +1 -0
- package/dist/resources/publications.js +70 -0
- package/dist/resources/releases.d.ts +66 -0
- package/dist/resources/releases.d.ts.map +1 -0
- package/dist/resources/releases.js +101 -0
- package/dist/resources/spec-revisions.d.ts +85 -0
- package/dist/resources/spec-revisions.d.ts.map +1 -0
- package/dist/resources/spec-revisions.js +116 -0
- package/dist/resources/specs.d.ts +72 -0
- package/dist/resources/specs.d.ts.map +1 -0
- package/dist/resources/specs.js +107 -0
- package/dist/resources/targets.d.ts +85 -105
- package/dist/resources/targets.d.ts.map +1 -1
- package/dist/resources/targets.js +70 -163
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +175 -129
- package/dist/types.d.ts +2626 -1141
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +90 -11
- package/package.json +3 -3
- package/server.json +3 -3
- package/src/api-identity.ts +6 -2
- package/src/core/http.ts +106 -30
- package/src/core/pagination.ts +13 -23
- package/src/errors.ts +30 -20
- package/src/index.ts +46 -24
- package/src/mcp-protocol.ts +95 -38
- package/src/mcp.ts +34 -16
- package/src/ops.ts +47 -36
- package/src/resources/api-keys.ts +87 -19
- package/src/resources/deliveries.ts +139 -0
- package/src/resources/drafts.ts +422 -0
- package/src/resources/files.ts +68 -0
- package/src/resources/generate.ts +81 -19
- package/src/resources/generations.ts +167 -29
- package/src/resources/organization.ts +53 -0
- package/src/resources/projects.ts +129 -322
- package/src/resources/publications.ts +139 -0
- package/src/resources/releases.ts +199 -0
- package/src/resources/spec-revisions.ts +237 -0
- package/src/resources/specs.ts +200 -0
- package/src/resources/targets.ts +135 -304
- package/src/schemas.ts +175 -129
- package/src/types.ts +2793 -1177
- package/dist/resources/account.d.ts +0 -18
- package/dist/resources/account.d.ts.map +0 -1
- package/dist/resources/account.js +0 -27
- package/dist/resources/definition-revisions.d.ts +0 -58
- package/dist/resources/definition-revisions.d.ts.map +0 -1
- package/dist/resources/definition-revisions.js +0 -114
- package/dist/resources/definitions.d.ts +0 -35
- package/dist/resources/definitions.d.ts.map +0 -1
- package/dist/resources/definitions.js +0 -60
- package/src/resources/account.ts +0 -46
- package/src/resources/definition-revisions.ts +0 -207
- package/src/resources/definitions.ts +0 -122
package/src/mcp-protocol.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP
|
|
2
|
+
* MCP tools server with a 2025-11-25 handshake adapter and a 2026-07-28 core.
|
|
3
|
+
* Generated by typeship — https://typeship.dev
|
|
3
4
|
*
|
|
4
5
|
* The protocol layer shared by the MCP server in every generated package and
|
|
5
6
|
* by typeship's hosted endpoint: JSON-RPC shape checks, the per-request _meta
|
|
@@ -19,10 +20,9 @@ import { dateKindOf, relativeDate } from "./dates.js";
|
|
|
19
20
|
import { docsReadTarget, resolveDocsContentUrl, searchConnectedGuides } from "./docs.js";
|
|
20
21
|
|
|
21
22
|
export const MCP_PROTOCOL_VERSION = "2026-07-28";
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
export const SUPPORTED_PROTOCOL_VERSIONS: readonly string[] = [MCP_PROTOCOL_VERSION];
|
|
23
|
+
export const LEGACY_PROTOCOL_VERSION = "2025-11-25";
|
|
24
|
+
/** The modern model and the supported initialize-handshake wire revision. */
|
|
25
|
+
export const SUPPORTED_PROTOCOL_VERSIONS: readonly string[] = [MCP_PROTOCOL_VERSION, LEGACY_PROTOCOL_VERSION];
|
|
26
26
|
|
|
27
27
|
export const META_VERSION = "io.modelcontextprotocol/protocolVersion";
|
|
28
28
|
export const META_CLIENT_CAPS = "io.modelcontextprotocol/clientCapabilities";
|
|
@@ -241,7 +241,7 @@ export function checkRequestMeta(request: JsonRpcMessage): RpcOutcome | { versio
|
|
|
241
241
|
if (typeof version !== "string") {
|
|
242
242
|
return rpcError(request.id, -32602, "Invalid params: params._meta[\"" + META_VERSION + "\"] is required on every request (MCP " + MCP_PROTOCOL_VERSION + ").", 400);
|
|
243
243
|
}
|
|
244
|
-
if (
|
|
244
|
+
if (version !== MCP_PROTOCOL_VERSION) {
|
|
245
245
|
return rpcError(request.id, -32022, "Unsupported protocol version", 400, { supported: SUPPORTED_PROTOCOL_VERSIONS, requested: version });
|
|
246
246
|
}
|
|
247
247
|
if (caps === null || typeof caps !== "object" || Array.isArray(caps)) {
|
|
@@ -271,6 +271,16 @@ export function checkRequestHeaders(headers: { get(name: string): string | null
|
|
|
271
271
|
const id = message.id;
|
|
272
272
|
const version = headers.get("mcp-protocol-version");
|
|
273
273
|
const bodyVersion = (message.params?._meta as Record<string, unknown> | undefined)?.[META_VERSION];
|
|
274
|
+
if (version !== null && !SUPPORTED_PROTOCOL_VERSIONS.includes(version)) return rpcError(id, -32022, "Unsupported protocol version", 400, { supported: SUPPORTED_PROTOCOL_VERSIONS, requested: version });
|
|
275
|
+
if (message.method === "initialize" || version === LEGACY_PROTOCOL_VERSION) {
|
|
276
|
+
if (bodyVersion !== undefined || headers.get("mcp-method") !== null || headers.get("mcp-name") !== null) {
|
|
277
|
+
return rpcError(id, -32020, "Header mismatch: initialize-handshake requests cannot carry modern protocol metadata or routing headers.", 400);
|
|
278
|
+
}
|
|
279
|
+
if (version !== null && version !== LEGACY_PROTOCOL_VERSION) {
|
|
280
|
+
return rpcError(id, -32022, "Unsupported initialize-handshake protocol version", 400, { supported: [LEGACY_PROTOCOL_VERSION], requested: version });
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
274
284
|
if (version === null) return rpcError(id, -32020, "Header mismatch: MCP-Protocol-Version header is required", 400);
|
|
275
285
|
if (typeof bodyVersion === "string" && version !== bodyVersion) {
|
|
276
286
|
return rpcError(id, -32020, "Header mismatch: MCP-Protocol-Version header '" + version + "' does not match body value '" + bodyVersion + "'", 400);
|
|
@@ -302,7 +312,34 @@ export function takeCancelled(id: unknown): boolean {
|
|
|
302
312
|
|
|
303
313
|
// ---- dispatch ------------------------------------------------------------------
|
|
304
314
|
|
|
305
|
-
|
|
315
|
+
/** One stdio process owns its handshake state. HTTP instead carries the
|
|
316
|
+
* negotiated revision on each request and never needs a server session. */
|
|
317
|
+
export function createStdioRpcHandler(server: McpServer): (incoming: unknown) => Promise<RpcOutcome> {
|
|
318
|
+
let version: string | undefined;
|
|
319
|
+
let initialized = false;
|
|
320
|
+
let initializing = false;
|
|
321
|
+
return async (incoming) => {
|
|
322
|
+
const request = asJsonRpc(incoming);
|
|
323
|
+
if (isRpcOutcome(request)) return request;
|
|
324
|
+
if (request.method === "initialize") {
|
|
325
|
+
if (version || initializing) return rpcError(request.id, -32600, "This connection has already selected its MCP protocol.", 400, { supported: SUPPORTED_PROTOCOL_VERSIONS });
|
|
326
|
+
initializing = true;
|
|
327
|
+
const result = await handleRpc(server, request);
|
|
328
|
+
initializing = false;
|
|
329
|
+
if (result.status === 200) version = LEGACY_PROTOCOL_VERSION;
|
|
330
|
+
return result;
|
|
331
|
+
}
|
|
332
|
+
if (version === LEGACY_PROTOCOL_VERSION) {
|
|
333
|
+
if (request.method === "notifications/initialized" && request.id === undefined) initialized = true;
|
|
334
|
+
if (!initialized && request.id !== undefined && request.method !== "ping") return rpcError(request.id, -32600, "Send notifications/initialized before calling MCP methods.", 400);
|
|
335
|
+
} else if ((request.params?._meta as Record<string, unknown> | undefined)?.[META_VERSION] === MCP_PROTOCOL_VERSION) {
|
|
336
|
+
version = MCP_PROTOCOL_VERSION;
|
|
337
|
+
}
|
|
338
|
+
return handleRpc(server, request, version);
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export async function handleRpc(server: McpServer, incoming: unknown, protocolVersion?: string | null): Promise<RpcOutcome> {
|
|
306
343
|
const parsed = asJsonRpc(incoming);
|
|
307
344
|
if (isRpcOutcome(parsed)) return parsed;
|
|
308
345
|
const request = parsed;
|
|
@@ -317,24 +354,45 @@ export async function handleRpc(server: McpServer, incoming: unknown): Promise<R
|
|
|
317
354
|
return { message: null, status: 202 };
|
|
318
355
|
}
|
|
319
356
|
|
|
320
|
-
//
|
|
321
|
-
//
|
|
357
|
+
// Only the wire envelope differs. Tool execution and authorization below
|
|
358
|
+
// remain shared with modern clients.
|
|
322
359
|
if (request.method === "initialize") {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
360
|
+
const params = request.params;
|
|
361
|
+
if ((params?._meta as Record<string, unknown> | undefined)?.[META_VERSION] !== undefined) return rpcError(id, -32602, "initialize cannot carry modern protocol metadata.", 400);
|
|
362
|
+
const info = params?.clientInfo as Record<string, unknown> | undefined;
|
|
363
|
+
if (!params || typeof params.protocolVersion !== "string" || !params.capabilities || typeof params.capabilities !== "object" || Array.isArray(params.capabilities) ||
|
|
364
|
+
!info || typeof info !== "object" || Array.isArray(info) || typeof info.name !== "string" || typeof info.version !== "string") {
|
|
365
|
+
return rpcError(id, -32602, "initialize requires protocolVersion, capabilities, and clientInfo with name and version.", 400);
|
|
366
|
+
}
|
|
367
|
+
if (params.protocolVersion !== LEGACY_PROTOCOL_VERSION) return rpcError(id, -32022, "Unsupported initialize-handshake protocol version", 400, { supported: [LEGACY_PROTOCOL_VERSION], requested: params.protocolVersion });
|
|
368
|
+
return { status: 200, message: { jsonrpc: "2.0", id, result: {
|
|
369
|
+
protocolVersion: LEGACY_PROTOCOL_VERSION,
|
|
370
|
+
capabilities: { tools: {} },
|
|
371
|
+
serverInfo: server.serverInfo,
|
|
372
|
+
...(server.instructions ? { instructions: server.instructions } : {}),
|
|
373
|
+
} } };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const legacy = protocolVersion === LEGACY_PROTOCOL_VERSION;
|
|
377
|
+
if (legacy) {
|
|
378
|
+
const meta = request.params?._meta as Record<string, unknown> | undefined;
|
|
379
|
+
if (meta?.[META_VERSION] !== undefined || meta?.[META_CLIENT_CAPS] !== undefined || request.params?.inputResponses !== undefined || request.method === "server/discover") {
|
|
380
|
+
return rpcError(id, -32602, "Modern protocol metadata and methods cannot be used on an initialize-handshake connection.", 400);
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
const meta = checkRequestMeta(request);
|
|
384
|
+
if ("status" in meta) return meta;
|
|
326
385
|
}
|
|
327
386
|
|
|
328
|
-
const meta = checkRequestMeta(request);
|
|
329
|
-
if ("status" in meta) return meta;
|
|
330
|
-
|
|
331
387
|
const complete = (result: Record<string, unknown>): RpcOutcome => ({
|
|
332
|
-
message: { jsonrpc: "2.0", id, result: { resultType: "complete", ...result, _meta: { [META_SERVER_INFO]: server.serverInfo } } },
|
|
388
|
+
message: { jsonrpc: "2.0", id, result: legacy ? result : { resultType: "complete", ...result, _meta: { [META_SERVER_INFO]: server.serverInfo } } },
|
|
333
389
|
status: 200,
|
|
334
390
|
});
|
|
335
391
|
const cacheScope = server.cacheScope ?? "public";
|
|
336
392
|
|
|
337
393
|
switch (request.method) {
|
|
394
|
+
case "ping":
|
|
395
|
+
return legacy ? complete({}) : rpcError(id, -32601, "Method not found: ping", 404);
|
|
338
396
|
case "server/discover":
|
|
339
397
|
return complete({
|
|
340
398
|
supportedVersions: SUPPORTED_PROTOCOL_VERSIONS,
|
|
@@ -344,7 +402,7 @@ export async function handleRpc(server: McpServer, incoming: unknown): Promise<R
|
|
|
344
402
|
cacheScope,
|
|
345
403
|
});
|
|
346
404
|
case "tools/list":
|
|
347
|
-
return complete({ tools: server.listTools(), ttlMs: server.toolsTtlMs, cacheScope });
|
|
405
|
+
return complete({ tools: server.listTools(), ...(!legacy ? { ttlMs: server.toolsTtlMs, cacheScope } : {}) });
|
|
348
406
|
case "tools/call": {
|
|
349
407
|
const name = request.params?.name;
|
|
350
408
|
if (typeof name !== "string") return rpcError(id, -32602, "tools/call requires a tool name", 400);
|
|
@@ -371,7 +429,7 @@ export async function handleRpc(server: McpServer, incoming: unknown): Promise<R
|
|
|
371
429
|
try { outcome = await server.callTool(name, args); }
|
|
372
430
|
catch (error) {
|
|
373
431
|
if (!(error instanceof McpAccountLinkRequired)) throw error;
|
|
374
|
-
const caps = (request.params?._meta as Record<string, unknown>)[META_CLIENT_CAPS] as { elicitation?: { url?: unknown } };
|
|
432
|
+
const caps = legacy ? {} : (request.params?._meta as Record<string, unknown>)[META_CLIENT_CAPS] as { elicitation?: { url?: unknown } };
|
|
375
433
|
const urlMode = caps.elicitation?.url;
|
|
376
434
|
if (urlMode === null || typeof urlMode !== "object" || Array.isArray(urlMode)) {
|
|
377
435
|
const unsupported = textError("Connect your API account using an MCP client that supports browser account-linking prompts (URL-mode elicitation), then retry.", "ACCOUNT_LINK_REQUIRED");
|
|
@@ -1185,26 +1243,24 @@ export function argumentsError(op: { tool: string }, issues: ArgumentIssue[]): T
|
|
|
1185
1243
|
// ---- results: projection, size cap, envelopes ------------------------------------
|
|
1186
1244
|
|
|
1187
1245
|
/** Keep only `paths` of a value: arrays item by item, objects by dotted
|
|
1188
|
-
* path
|
|
1246
|
+
* path, including paths through arrays (`items.id` keeps each item's id);
|
|
1247
|
+
* scalars untouched. Same rule as the CLI's --fields. */
|
|
1189
1248
|
export function projectFields(value: unknown, paths: string[][] | null): unknown {
|
|
1190
1249
|
if (paths === null) return value;
|
|
1191
1250
|
if (Array.isArray(value)) return value.map((v) => projectFields(v, paths));
|
|
1192
1251
|
if (value === null || typeof value !== "object") return value;
|
|
1252
|
+
const groups = new Map<string, string[][]>();
|
|
1253
|
+
for (const [key, ...rest] of paths) {
|
|
1254
|
+
if (key === undefined) continue;
|
|
1255
|
+
const group = groups.get(key);
|
|
1256
|
+
if (group) group.push(rest); else groups.set(key, [rest]);
|
|
1257
|
+
}
|
|
1193
1258
|
const out: Record<string, unknown> = {};
|
|
1194
|
-
for (const
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
}
|
|
1200
|
-
if (cursor === undefined) continue;
|
|
1201
|
-
let target = out;
|
|
1202
|
-
for (const key of path.slice(0, -1)) {
|
|
1203
|
-
const next = target[key];
|
|
1204
|
-
if (next === undefined || next === null || typeof next !== "object" || Array.isArray(next)) target[key] = {};
|
|
1205
|
-
target = target[key] as Record<string, unknown>;
|
|
1206
|
-
}
|
|
1207
|
-
target[path[path.length - 1]!] = cursor;
|
|
1259
|
+
for (const [key, rests] of groups) {
|
|
1260
|
+
const child = (value as Record<string, unknown>)[key];
|
|
1261
|
+
if (child === undefined) continue;
|
|
1262
|
+
if (rests.some((rest) => rest.length === 0)) out[key] = child;
|
|
1263
|
+
else if (child !== null && typeof child === "object") out[key] = projectFields(child, rests);
|
|
1208
1264
|
}
|
|
1209
1265
|
return out;
|
|
1210
1266
|
}
|
|
@@ -1296,8 +1352,9 @@ export function pageOutcome(items: unknown[], nextPage: Record<string, unknown>
|
|
|
1296
1352
|
}
|
|
1297
1353
|
|
|
1298
1354
|
/** Placeholder for a value cut from an oversized object result. */
|
|
1299
|
-
const omittedMarker = (key: string, chars: number) =>
|
|
1300
|
-
"[omitted: " + chars.toLocaleString("en-US") + " characters. Pass fields
|
|
1355
|
+
const omittedMarker = (key: string, chars: number, maxChars: number) => chars > maxChars
|
|
1356
|
+
? "[omitted: " + chars.toLocaleString("en-US") + " characters, more than the cap on its own. Pass fields with dotted paths inside it, such as [\"" + key + ".id\"], or request a smaller page.]"
|
|
1357
|
+
: "[omitted: " + chars.toLocaleString("en-US") + " characters. Pass fields=[\"" + key + "\"] to fetch this key alone.]";
|
|
1301
1358
|
|
|
1302
1359
|
export function dataOutcome(data: unknown, options: ResultOptions = {}): ToolOutcome {
|
|
1303
1360
|
const fields = options.fields ?? null;
|
|
@@ -1336,7 +1393,7 @@ export function dataOutcome(data: unknown, options: ResultOptions = {}): ToolOut
|
|
|
1336
1393
|
for (const e of entries) if (!cut.has(e.key)) out[e.key] = e.v;
|
|
1337
1394
|
out.truncated = {
|
|
1338
1395
|
omitted_keys: [...cut.keys()],
|
|
1339
|
-
omitted: Object.fromEntries([...cut].map(([key, chars]) => [key, omittedMarker(key, chars)])),
|
|
1396
|
+
omitted: Object.fromEntries([...cut].map(([key, chars]) => [key, omittedMarker(key, chars, maxChars)])),
|
|
1340
1397
|
reason: "The result is " + text.length.toLocaleString("en-US") + " characters; results are capped at " + maxChars.toLocaleString("en-US") + ".",
|
|
1341
1398
|
next_steps: [fieldsHint(false)],
|
|
1342
1399
|
};
|
|
@@ -1561,8 +1618,8 @@ function omittedPlanLimit(ops: OpLike[], requested?: string): ToolOutcome {
|
|
|
1561
1618
|
error: "PlanLimitError",
|
|
1562
1619
|
code: "PLAN_LIMIT",
|
|
1563
1620
|
message: requested
|
|
1564
|
-
? "The operation " + requested + " exists in the
|
|
1565
|
-
: "Matching operations exist in the
|
|
1621
|
+
? "The operation " + requested + " exists in the Spec but was omitted from this generated package by its plan limit."
|
|
1622
|
+
: "Matching operations exist in the Spec but were omitted from this generated package by its plan limit.",
|
|
1566
1623
|
omitted_operations: ops.map((op) => ({ tool: op.tool, method: op.httpMethod, path: op.path })),
|
|
1567
1624
|
next_steps: ["Upgrade at https://typeship.dev/pricing and regenerate the package without the operation cap.", "Do not invent or retry an omitted operation against this generated package."],
|
|
1568
1625
|
};
|
package/src/mcp.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// typeship — MCP server. Generated by typeship — https://typeship.dev
|
|
3
|
-
// Speaks MCP 2026-07-28
|
|
4
|
-
//
|
|
3
|
+
// Speaks MCP 2026-07-28 and accepts the 2025-11-25 initialize handshake.
|
|
4
|
+
// HTTP is stateless (no sessions); the protocol layer and tool surface
|
|
5
5
|
// live in ./mcp-protocol.js, shared with typeship's hosted endpoint. Two
|
|
6
6
|
// transports, zero dependencies:
|
|
7
7
|
// node mcp.js stdio (newline-delimited JSON-RPC 2.0)
|
|
@@ -22,10 +22,11 @@ import { homedir, tmpdir } from "node:os";
|
|
|
22
22
|
import { basename, join } from "node:path";
|
|
23
23
|
import { fileURLToPath } from "node:url";
|
|
24
24
|
import { TypeshipClient, formatDebugEvent, type ClientOptions, type DebugEvent } from "./index.js";
|
|
25
|
+
import { asApiResult } from "./core/http.js";
|
|
25
26
|
import { GLOBALS, OMITTED_OPS, OPS, buildArgs, type OpSpec } from "./ops.js";
|
|
26
27
|
import {
|
|
27
28
|
DEFAULT_MAX_RESULT_CHARS, SUPPORTED_PROTOCOL_VERSIONS, McpAccountLinkRequired, argumentsError, asJsonRpc, binaryOutcome, callSharedTool, checkRequestHeaders,
|
|
28
|
-
dataOutcome, errorOutcome, handleRpc, isRpcOutcome, pageOutcome, parseIncludeList, prepareCall, resolveReferences, serverInstructions,
|
|
29
|
+
createStdioRpcHandler, dataOutcome, errorOutcome, handleRpc, isRpcOutcome, pageOutcome, parseIncludeList, prepareCall, resolveReferences, serverInstructions,
|
|
29
30
|
takeCancelled, textError, toolDefinitions, visibleOps,
|
|
30
31
|
type ArgumentIssue, type DocsSource, type McpServer, type OpLike, type RpcOutcome, type ToolOutcome,
|
|
31
32
|
} from "./mcp-protocol.js";
|
|
@@ -42,13 +43,13 @@ export { McpAccountLinkRequired } from "./mcp-protocol.js";
|
|
|
42
43
|
const BIN = "typeship";
|
|
43
44
|
const PKG_NAME = "@typeship-ax/mcp";
|
|
44
45
|
const SERVER_NAME = "typeship-mcp";
|
|
45
|
-
const SERVER_VERSION = "0.
|
|
46
|
+
const SERVER_VERSION = "0.20.0";
|
|
46
47
|
/** The MCP client's announced name (clientInfo in request _meta), for the User-Agent. */
|
|
47
48
|
let MCP_CLIENT_NAME: string | null = null;
|
|
48
49
|
function noteClientInfo(message: unknown): void {
|
|
49
50
|
const meta = (message as { params?: { _meta?: Record<string, { name?: unknown }> } } | null)?.params?._meta;
|
|
50
51
|
const name = meta?.["io.modelcontextprotocol/clientInfo"]?.name;
|
|
51
|
-
if (typeof name === "string" && name
|
|
52
|
+
if (typeof name === "string" && name) { MCP_CLIENT_NAME = name.replace(/[\s;()]+/g, "_").slice(0, 60); }
|
|
52
53
|
}
|
|
53
54
|
const DEFAULT_BASE_URL = "https://typeship.dev/api/v1";
|
|
54
55
|
const NAMED_SCHEMES: CredentialSchemes = {"apiKey":{"kind":"bearer","options":["bearerToken"]}};
|
|
@@ -64,7 +65,7 @@ const TOOL_MODE: "operations" | "meta" = "meta";
|
|
|
64
65
|
/** Project-supplied guidance appended to the server instructions. */
|
|
65
66
|
const CUSTOM_INSTRUCTIONS: string | null = null;
|
|
66
67
|
/** The tool that returns the caller (the CLI's whoami target), named in the instructions. */
|
|
67
|
-
const IDENTITY_TOOL: string | null =
|
|
68
|
+
const IDENTITY_TOOL: string | null = null;
|
|
68
69
|
/** Surface switches: reads only, and/or a subset of resources or tools.
|
|
69
70
|
* Flags win over the environment. */
|
|
70
71
|
const ARGV = process.argv.slice(2);
|
|
@@ -146,7 +147,7 @@ function makeClient(op: OpSpec): TypeshipClient {
|
|
|
146
147
|
options.debug = (event: DebugEvent) => process.stderr.write(formatDebugEvent(BIN + "-mcp", event) + "\n");
|
|
147
148
|
}
|
|
148
149
|
// The local MCP server identifies itself (surface + the client it serves, when announced).
|
|
149
|
-
options.defaultHeaders = { "User-Agent": PKG_NAME + "-mcp/" + SERVER_VERSION +
|
|
150
|
+
options.defaultHeaders = { "User-Agent": PKG_NAME + "-mcp/" + SERVER_VERSION + (MCP_CLIENT_NAME ? " (client=" + MCP_CLIENT_NAME + ")" : "") };
|
|
150
151
|
// Keep the SDK's machine-token cache while re-evaluating local configuration.
|
|
151
152
|
const key = createHash("sha256").update(JSON.stringify([options, config, profile?.name, stored?.oauth?.sessionId, null, process.env["TYPESHIP_DEBUG"]])).digest("hex");
|
|
152
153
|
if (clientInstance && clientKey === key) return clientInstance;
|
|
@@ -176,7 +177,8 @@ async function callOperationRaw(op: OpSpec, rawArgs: Record<string, unknown>, re
|
|
|
176
177
|
const { args, fields, maxChars } = prepared.call;
|
|
177
178
|
const values: Record<string, unknown> = {};
|
|
178
179
|
for (const p of op.params) {
|
|
179
|
-
|
|
180
|
+
const argument = p.kind === "header" && p.name === "If-Match" ? "if_match" : p.name;
|
|
181
|
+
if (args[argument] !== undefined) values[p.name] = args[argument];
|
|
180
182
|
}
|
|
181
183
|
// File arguments are local paths on this machine; unreadable ones are
|
|
182
184
|
// argument errors, reported before anything is sent.
|
|
@@ -206,8 +208,19 @@ async function callOperationRaw(op: OpSpec, rawArgs: Record<string, unknown>, re
|
|
|
206
208
|
const shape = { fields, maxChars, pagination: op.pagination, args };
|
|
207
209
|
try {
|
|
208
210
|
const target = (client as unknown as Record<string, Record<string, (...a: unknown[]) => unknown>>)[op.resource]!;
|
|
209
|
-
|
|
211
|
+
let result = await asApiResult(target[op.method]!(...callArgs) as Promise<unknown>);
|
|
210
212
|
if (!result.ok) return errorOutcome(result.error, errorContext);
|
|
213
|
+
if (op.httpMethod === "POST" && op.path === "/projects/{project_id}/generate") {
|
|
214
|
+
const batch = result.data as { data: Array<{ id: string }> };
|
|
215
|
+
const generations = (client as unknown as { generations: { wait(id: string): Promise<unknown> } }).generations;
|
|
216
|
+
const completed: unknown[] = [];
|
|
217
|
+
for (const generation of batch.data) {
|
|
218
|
+
const waited = await asApiResult(generations.wait(generation.id));
|
|
219
|
+
if (!waited.ok) return errorOutcome(waited.error, errorContext);
|
|
220
|
+
completed.push(waited.data);
|
|
221
|
+
}
|
|
222
|
+
result = { ...result, data: { ...batch, data: completed } };
|
|
223
|
+
}
|
|
211
224
|
if (op.paginated) {
|
|
212
225
|
const page = result.data as { items: unknown[]; nextPageParams(): Record<string, unknown> | null; response: { requestId?: string } };
|
|
213
226
|
return pageOutcome(page.items, page.nextPageParams(), { ...shape, requestId: page.response.requestId });
|
|
@@ -269,7 +282,12 @@ const LOCAL_CREDENTIALS = LOCAL_PROCESS && !ARGV.includes("--http");
|
|
|
269
282
|
* only on a local server (mcpExposed), writes are out under --read-only,
|
|
270
283
|
* and --tools narrows to a subset. A hidden operation is unknown to
|
|
271
284
|
* tools/call and execute alike. */
|
|
272
|
-
const MCP_OPS = visibleOps(OPS as unknown as OpLike[], { readOnly: READ_ONLY, include: INCLUDE, uploads: LOCAL_PROCESS }) as unknown as OpSpec[]
|
|
285
|
+
const MCP_OPS = (visibleOps(OPS as unknown as OpLike[], { readOnly: READ_ONLY, include: INCLUDE, uploads: LOCAL_PROCESS }) as unknown as OpSpec[]).map((op) => {
|
|
286
|
+
const properties = op.inputSchema.properties as Record<string, unknown> | undefined;
|
|
287
|
+
if (!properties || !Object.hasOwn(properties, "If-Match")) return op;
|
|
288
|
+
const { ["If-Match"]: ifMatch, ...rest } = properties;
|
|
289
|
+
return { ...op, inputSchema: { ...op.inputSchema, properties: { ...rest, if_match: ifMatch } } };
|
|
290
|
+
});
|
|
273
291
|
const HIDDEN_WRITES = READ_ONLY ? visibleOps(OPS as unknown as OpLike[], { include: INCLUDE, uploads: LOCAL_PROCESS }).length - MCP_OPS.length : 0;
|
|
274
292
|
const HAS_UPLOADS = MCP_OPS.some((op) => op.bodyKind === "multipart" || op.bodyKind === "binary");
|
|
275
293
|
|
|
@@ -415,7 +433,7 @@ async function authorizedHttp(request: Request, authorizer: ReturnType<typeof cr
|
|
|
415
433
|
if (url.pathname !== resource.pathname) return json(404, { error: "Unknown MCP endpoint." });
|
|
416
434
|
if (request.method !== "POST") {
|
|
417
435
|
// GET streams, DELETE (session end) and Mcp-Session-Id belong to earlier
|
|
418
|
-
// revisions;
|
|
436
|
+
// revisions; this stateless transport answers 405 instead.
|
|
419
437
|
return json(405, { error: "POST JSON-RPC messages to this endpoint (MCP " + SUPPORTED_PROTOCOL_VERSIONS[0] + ", stateless)." }, { Allow: "POST, OPTIONS" });
|
|
420
438
|
}
|
|
421
439
|
|
|
@@ -450,16 +468,15 @@ async function authorizedHttp(request: Request, authorizer: ReturnType<typeof cr
|
|
|
450
468
|
}
|
|
451
469
|
const parsed = asJsonRpc(incoming);
|
|
452
470
|
if (isRpcOutcome(parsed)) return json(parsed.status, parsed.message);
|
|
453
|
-
//
|
|
454
|
-
|
|
455
|
-
if (parsed.id !== undefined && parsed.id !== null && parsed.method !== "initialize") {
|
|
471
|
+
// Validate the selected wire revision before dispatching a request.
|
|
472
|
+
if (parsed.id !== undefined && parsed.id !== null) {
|
|
456
473
|
const mismatch = checkRequestHeaders(request.headers, parsed);
|
|
457
474
|
if (mismatch) return json(mismatch.status, mismatch.message);
|
|
458
475
|
}
|
|
459
476
|
noteClientInfo(parsed);
|
|
460
477
|
let outcome: RpcOutcome;
|
|
461
478
|
try {
|
|
462
|
-
outcome = await handleRpc(serverFor(remote), parsed);
|
|
479
|
+
outcome = await handleRpc(serverFor(remote), parsed, request.headers.get("mcp-protocol-version"));
|
|
463
480
|
} catch (e) {
|
|
464
481
|
return json(500, { jsonrpc: "2.0", id: parsed.id ?? null, error: { code: -32603, message: "Internal error" } });
|
|
465
482
|
}
|
|
@@ -510,6 +527,7 @@ async function startStdio(): Promise<void> {
|
|
|
510
527
|
const rl = createInterface({ input: process.stdin });
|
|
511
528
|
const write = (message: Record<string, unknown>) => process.stdout.write(JSON.stringify(message) + "\n");
|
|
512
529
|
const server = serverFor();
|
|
530
|
+
const dispatch = createStdioRpcHandler(server);
|
|
513
531
|
rl.on("line", (line) => {
|
|
514
532
|
const trimmed = line.trim();
|
|
515
533
|
if (trimmed === "") return;
|
|
@@ -522,7 +540,7 @@ async function startStdio(): Promise<void> {
|
|
|
522
540
|
}
|
|
523
541
|
const id = incoming !== null && typeof incoming === "object" && !Array.isArray(incoming) ? (incoming as { id?: unknown }).id : undefined;
|
|
524
542
|
noteClientInfo(incoming);
|
|
525
|
-
void
|
|
543
|
+
void dispatch(incoming)
|
|
526
544
|
.then((outcome) => {
|
|
527
545
|
if (outcome.message === null) return;
|
|
528
546
|
// A cancelled request gets no response (spec: MUST NOT send further messages).
|