@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.
Files changed (107) hide show
  1. package/AGENTS.md +11 -5
  2. package/README.md +16 -5
  3. package/api.json +8824 -3777
  4. package/api.md +8359 -3987
  5. package/dist/api-identity.d.ts.map +1 -1
  6. package/dist/api-identity.js +6 -1
  7. package/dist/core/http.d.ts +29 -16
  8. package/dist/core/http.d.ts.map +1 -1
  9. package/dist/core/http.js +108 -25
  10. package/dist/core/pagination.d.ts +8 -8
  11. package/dist/core/pagination.d.ts.map +1 -1
  12. package/dist/core/pagination.js +7 -16
  13. package/dist/errors.d.ts +20 -13
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/errors.js +29 -20
  16. package/dist/index.d.ts +37 -19
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +42 -18
  19. package/dist/mcp-protocol.d.ts +10 -6
  20. package/dist/mcp-protocol.d.ts.map +1 -1
  21. package/dist/mcp-protocol.js +110 -39
  22. package/dist/mcp.d.ts.map +1 -1
  23. package/dist/mcp.js +38 -18
  24. package/dist/ops.d.ts +5 -1
  25. package/dist/ops.d.ts.map +1 -1
  26. package/dist/ops.js +42 -35
  27. package/dist/resources/api-keys.d.ts +44 -18
  28. package/dist/resources/api-keys.d.ts.map +1 -1
  29. package/dist/resources/api-keys.js +46 -14
  30. package/dist/resources/deliveries.d.ts +46 -0
  31. package/dist/resources/deliveries.d.ts.map +1 -0
  32. package/dist/resources/deliveries.js +70 -0
  33. package/dist/resources/drafts.d.ts +155 -0
  34. package/dist/resources/drafts.d.ts.map +1 -0
  35. package/dist/resources/drafts.js +230 -0
  36. package/dist/resources/files.d.ts +23 -0
  37. package/dist/resources/files.d.ts.map +1 -0
  38. package/dist/resources/files.js +38 -0
  39. package/dist/resources/generate.d.ts +49 -20
  40. package/dist/resources/generate.d.ts.map +1 -1
  41. package/dist/resources/generate.js +56 -14
  42. package/dist/resources/generations.d.ts +70 -18
  43. package/dist/resources/generations.d.ts.map +1 -1
  44. package/dist/resources/generations.js +84 -16
  45. package/dist/resources/organization.d.ts +18 -0
  46. package/dist/resources/organization.d.ts.map +1 -0
  47. package/dist/resources/organization.js +32 -0
  48. package/dist/resources/projects.d.ts +87 -124
  49. package/dist/resources/projects.d.ts.map +1 -1
  50. package/dist/resources/projects.js +70 -173
  51. package/dist/resources/publications.d.ts +46 -0
  52. package/dist/resources/publications.d.ts.map +1 -0
  53. package/dist/resources/publications.js +70 -0
  54. package/dist/resources/releases.d.ts +66 -0
  55. package/dist/resources/releases.d.ts.map +1 -0
  56. package/dist/resources/releases.js +101 -0
  57. package/dist/resources/spec-revisions.d.ts +85 -0
  58. package/dist/resources/spec-revisions.d.ts.map +1 -0
  59. package/dist/resources/spec-revisions.js +116 -0
  60. package/dist/resources/specs.d.ts +72 -0
  61. package/dist/resources/specs.d.ts.map +1 -0
  62. package/dist/resources/specs.js +107 -0
  63. package/dist/resources/targets.d.ts +85 -105
  64. package/dist/resources/targets.d.ts.map +1 -1
  65. package/dist/resources/targets.js +70 -163
  66. package/dist/schemas.d.ts.map +1 -1
  67. package/dist/schemas.js +175 -129
  68. package/dist/types.d.ts +2626 -1141
  69. package/dist/types.d.ts.map +1 -1
  70. package/dist/types.js +90 -11
  71. package/package.json +3 -3
  72. package/server.json +3 -3
  73. package/src/api-identity.ts +6 -2
  74. package/src/core/http.ts +106 -30
  75. package/src/core/pagination.ts +13 -23
  76. package/src/errors.ts +30 -20
  77. package/src/index.ts +46 -24
  78. package/src/mcp-protocol.ts +95 -38
  79. package/src/mcp.ts +34 -16
  80. package/src/ops.ts +47 -36
  81. package/src/resources/api-keys.ts +87 -19
  82. package/src/resources/deliveries.ts +139 -0
  83. package/src/resources/drafts.ts +422 -0
  84. package/src/resources/files.ts +68 -0
  85. package/src/resources/generate.ts +81 -19
  86. package/src/resources/generations.ts +167 -29
  87. package/src/resources/organization.ts +53 -0
  88. package/src/resources/projects.ts +129 -322
  89. package/src/resources/publications.ts +139 -0
  90. package/src/resources/releases.ts +199 -0
  91. package/src/resources/spec-revisions.ts +237 -0
  92. package/src/resources/specs.ts +200 -0
  93. package/src/resources/targets.ts +135 -304
  94. package/src/schemas.ts +175 -129
  95. package/src/types.ts +2793 -1177
  96. package/dist/resources/account.d.ts +0 -18
  97. package/dist/resources/account.d.ts.map +0 -1
  98. package/dist/resources/account.js +0 -27
  99. package/dist/resources/definition-revisions.d.ts +0 -58
  100. package/dist/resources/definition-revisions.d.ts.map +0 -1
  101. package/dist/resources/definition-revisions.js +0 -114
  102. package/dist/resources/definitions.d.ts +0 -35
  103. package/dist/resources/definitions.d.ts.map +0 -1
  104. package/dist/resources/definitions.js +0 -60
  105. package/src/resources/account.ts +0 -46
  106. package/src/resources/definition-revisions.ts +0 -207
  107. package/src/resources/definitions.ts +0 -122
@@ -1,5 +1,6 @@
1
1
  /**
2
- * MCP 2026-07-28 for a tools-only server. Generated by typeship — https://typeship.dev
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
- /** Revisions served. Legacy (initialize-handshake) revisions are not; an
23
- * initialize request gets an error naming this list, as the spec asks of
24
- * modern-only servers. */
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 (!SUPPORTED_PROTOCOL_VERSIONS.includes(version)) {
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
- export async function handleRpc(server: McpServer, incoming: unknown): Promise<RpcOutcome> {
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
- // Legacy clients open with initialize; name what we do speak so their one
321
- // visible error is actionable (spec: modern-only servers SHOULD do this).
357
+ // Only the wire envelope differs. Tool execution and authorization below
358
+ // remain shared with modern clients.
322
359
  if (request.method === "initialize") {
323
- return rpcError(id, -32601,
324
- "This server speaks MCP " + SUPPORTED_PROTOCOL_VERSIONS.join(", ") + " only (stateless, no initialize handshake). Upgrade the client, or send requests with params._meta[\"" + META_VERSION + "\"].",
325
- 400, { supported: SUPPORTED_PROTOCOL_VERSIONS });
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; scalars untouched. Same rule as the CLI's --fields. */
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 path of paths) {
1195
- let cursor: unknown = value;
1196
- for (const key of path) {
1197
- if (cursor === null || typeof cursor !== "object" || Array.isArray(cursor)) { cursor = undefined; break; }
1198
- cursor = (cursor as Record<string, unknown>)[key];
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=[\"" + key + "\"] to fetch this key alone.]";
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 API Definition but was omitted from this generated package by its plan limit."
1565
- : "Matching operations exist in the API Definition but were omitted from this generated package by its plan limit.",
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 (stateless: per-request _meta, server/discover; no
4
- // initialize handshake, no sessions); the protocol layer and tool surface
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.10.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 && name !== MCP_CLIENT_NAME) { MCP_CLIENT_NAME = name.slice(0, 60); }
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 = "account_retrieve";
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 + " (typeship" + (MCP_CLIENT_NAME ? "; client=" + MCP_CLIENT_NAME : "") + ")" };
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
- if (args[p.name] !== undefined) values[p.name] = args[p.name];
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
- const result = await (target[op.method]!(...callArgs) as Promise<{ ok: boolean; data?: unknown; error?: unknown; response?: { requestId?: string } }>);
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; the spec asks modern-only servers to answer 405.
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
- // Header/body agreement is checked for requests; the spec defines no
454
- // header requirements for notification POSTs.
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 handleRpc(server, incoming)
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).