@typeship-ax/mcp 0.10.0 → 0.19.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 (68) hide show
  1. package/AGENTS.md +10 -4
  2. package/README.md +16 -5
  3. package/api.json +4539 -616
  4. package/api.md +3827 -673
  5. package/dist/core/http.d.ts +3 -2
  6. package/dist/core/http.d.ts.map +1 -1
  7. package/dist/core/http.js +18 -6
  8. package/dist/errors.d.ts +17 -10
  9. package/dist/errors.d.ts.map +1 -1
  10. package/dist/errors.js +24 -15
  11. package/dist/index.d.ts +7 -3
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +9 -5
  14. package/dist/mcp-protocol.d.ts +10 -6
  15. package/dist/mcp-protocol.d.ts.map +1 -1
  16. package/dist/mcp-protocol.js +108 -37
  17. package/dist/mcp.d.ts.map +1 -1
  18. package/dist/mcp.js +36 -17
  19. package/dist/ops.d.ts +4 -0
  20. package/dist/ops.d.ts.map +1 -1
  21. package/dist/ops.js +45 -35
  22. package/dist/resources/account.d.ts +4 -4
  23. package/dist/resources/account.d.ts.map +1 -1
  24. package/dist/resources/account.js +10 -5
  25. package/dist/resources/api-keys.d.ts +40 -14
  26. package/dist/resources/api-keys.d.ts.map +1 -1
  27. package/dist/resources/api-keys.js +45 -13
  28. package/dist/resources/definition-revisions.d.ts +35 -18
  29. package/dist/resources/definition-revisions.d.ts.map +1 -1
  30. package/dist/resources/definition-revisions.js +43 -15
  31. package/dist/resources/definitions.d.ts +21 -6
  32. package/dist/resources/definitions.d.ts.map +1 -1
  33. package/dist/resources/definitions.js +16 -3
  34. package/dist/resources/generate.d.ts +44 -15
  35. package/dist/resources/generate.d.ts.map +1 -1
  36. package/dist/resources/generate.js +55 -13
  37. package/dist/resources/generations.d.ts +14 -6
  38. package/dist/resources/generations.d.ts.map +1 -1
  39. package/dist/resources/generations.js +26 -5
  40. package/dist/resources/projects.d.ts +106 -53
  41. package/dist/resources/projects.d.ts.map +1 -1
  42. package/dist/resources/projects.js +78 -31
  43. package/dist/resources/targets.d.ts +240 -40
  44. package/dist/resources/targets.d.ts.map +1 -1
  45. package/dist/resources/targets.js +307 -21
  46. package/dist/schemas.d.ts.map +1 -1
  47. package/dist/schemas.js +130 -78
  48. package/dist/types.d.ts +1969 -154
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/types.js +87 -3
  51. package/package.json +3 -3
  52. package/server.json +3 -3
  53. package/src/core/http.ts +17 -6
  54. package/src/errors.ts +25 -15
  55. package/src/index.ts +9 -5
  56. package/src/mcp-protocol.ts +93 -36
  57. package/src/mcp.ts +32 -15
  58. package/src/ops.ts +49 -35
  59. package/src/resources/account.ts +11 -4
  60. package/src/resources/api-keys.ts +84 -13
  61. package/src/resources/definition-revisions.ts +74 -16
  62. package/src/resources/definitions.ts +28 -4
  63. package/src/resources/generate.ts +78 -13
  64. package/src/resources/generations.ts +31 -4
  65. package/src/resources/projects.ts +141 -39
  66. package/src/resources/targets.ts +561 -27
  67. package/src/schemas.ts +130 -78
  68. package/src/types.ts +2105 -154
package/src/errors.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // typeship — typed error classes.
2
- // Generated by typeship — https://typeship.dev — do not edit by hand.
2
+ // Generated by typeship — https://typeship.dev
3
3
 
4
4
  import { ApiError, type ResponseMeta } from "./core/http.js";
5
5
  import type { ErrorModel, ErrorModelRead } from "./types.js";
@@ -77,6 +77,16 @@ export class RateLimitedError extends ApiError<429, ErrorModelRead> {
77
77
  }
78
78
  }
79
79
 
80
+ /**
81
+ * An unexpected error prevented the request from completing.
82
+ * Raised for HTTP 500 responses.
83
+ */
84
+ export class InternalServerError extends ApiError<500, ErrorModelRead> {
85
+ constructor(body: ErrorModelRead, response: ResponseMeta) {
86
+ super("An unexpected error prevented the request from completing.", 500, body, response);
87
+ }
88
+ }
89
+
80
90
  /**
81
91
  * Unexpected error.
82
92
  * Raised for "default" responses.
@@ -88,41 +98,41 @@ export class ApiResponseError extends ApiError<number, ErrorModelRead> {
88
98
  }
89
99
 
90
100
  /**
91
- * The plan does not include another project or the requested target configuration.
92
- * Raised for HTTP 402 responses.
101
+ * No such resource in this account.
102
+ * Raised for HTTP 404 responses.
93
103
  */
94
- export class PaymentRequiredError extends ApiError<402, ErrorModelRead> {
104
+ export class NotFoundError extends ApiError<404, ErrorModelRead> {
95
105
  constructor(body: ErrorModelRead, response: ResponseMeta) {
96
- super("The plan does not include another project or the requested target configuration.", 402, body, response);
106
+ super("No such resource in this account.", 404, body, response);
97
107
  }
98
108
  }
99
109
 
100
110
  /**
101
- * Project setup failed unexpectedly; the key reservation is released.
102
- * Raised for HTTP 500 responses.
111
+ * The plan does not include another project or the requested target configuration.
112
+ * Raised for HTTP 402 responses.
103
113
  */
104
- export class InternalServerError extends ApiError<500, ErrorModelRead> {
114
+ export class PaymentRequiredError extends ApiError<402, ErrorModelRead> {
105
115
  constructor(body: ErrorModelRead, response: ResponseMeta) {
106
- super("Project setup failed unexpectedly; the key reservation is released.", 500, body, response);
116
+ super("The plan does not include another project or the requested target configuration.", 402, body, response);
107
117
  }
108
118
  }
109
119
 
110
120
  /**
111
- * No such resource in this account.
112
- * Raised for HTTP 404 responses.
121
+ * The resource changed since the ETag supplied in If-Match. No write was applied.
122
+ * Raised for HTTP 412 responses.
113
123
  */
114
- export class NotFoundError extends ApiError<404, ErrorModelRead> {
124
+ export class PreconditionFailedError extends ApiError<412, ErrorModelRead> {
115
125
  constructor(body: ErrorModelRead, response: ResponseMeta) {
116
- super("No such resource in this account.", 404, body, response);
126
+ super("The resource changed since the ETag supplied in If-Match. No write was applied.", 412, body, response);
117
127
  }
118
128
  }
119
129
 
120
130
  /**
121
- * The Project was saved, but an obsolete release pull request could not be retired.
131
+ * Dependent work failed while completing the request.
122
132
  * Raised for HTTP 502 responses.
123
133
  */
124
134
  export class BadGatewayError extends ApiError<502, ErrorModelRead> {
125
135
  constructor(body: ErrorModelRead, response: ResponseMeta) {
126
- super("The Project was saved, but an obsolete release pull request could not be retired.", 502, body, response);
136
+ super("Dependent work failed while completing the request.", 502, body, response);
127
137
  }
128
138
  }
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // typeship — client entry point.
2
- // Generated by typeship — https://typeship.dev — do not edit by hand.
2
+ // Generated by typeship — https://typeship.dev
3
3
 
4
4
  import {
5
5
  HttpCore,
@@ -22,8 +22,8 @@ import { AccountResource } from "./resources/account.js";
22
22
  import { ApiKeysResource } from "./resources/api-keys.js";
23
23
 
24
24
  /** This package's version, also sent as the `User-Agent`. */
25
- export const VERSION = "0.10.0";
26
- const USER_AGENT = "@typeship-ax/mcp/0.10.0 (typeship)";
25
+ export const VERSION = "0.19.0";
26
+ const USER_AGENT = "@typeship-ax/mcp/0.19.0";
27
27
 
28
28
  export interface ClientOptions {
29
29
  /** Override the server URL. Default: `https://typeship.dev/api/v1` */
@@ -79,10 +79,10 @@ export interface ClientOptions {
79
79
  }
80
80
 
81
81
  /**
82
- * typeship — v0.10.0
82
+ * typeship — v0.19.0
83
83
  *
84
84
  * Resolve an OpenAPI or GraphQL Definition, diagnose it, and keep every
85
- * selected SDK, CLI, and MCP Target current.
85
+ * selected CLI, MCP, and SDK Target current.
86
86
  *
87
87
  * Every operation but one requires a bearer credential: an organization
88
88
  * API key from the console, or an OAuth access token carrying the operation's
@@ -90,6 +90,10 @@ export interface ClientOptions {
90
90
  * consent. OAuth grants cannot switch organizations after consent. A browser
91
91
  * session is not a credential for this API. The exception is POST /generate,
92
92
  * which works anonymously with the free plan's limits.
93
+ *
94
+ * Examples use Parcel, a fictional delivery service. Replace its domains,
95
+ * repository names, and resource identifiers with your own. The hosted
96
+ * petstore Definition is a runnable sample.
93
97
  */
94
98
  export class TypeshipClient {
95
99
  readonly generate: GenerateResource;
@@ -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
  };
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)
@@ -25,7 +25,7 @@ import { TypeshipClient, formatDebugEvent, type ClientOptions, type DebugEvent }
25
25
  import { GLOBALS, OMITTED_OPS, OPS, buildArgs, type OpSpec } from "./ops.js";
26
26
  import {
27
27
  DEFAULT_MAX_RESULT_CHARS, SUPPORTED_PROTOCOL_VERSIONS, McpAccountLinkRequired, argumentsError, asJsonRpc, binaryOutcome, callSharedTool, checkRequestHeaders,
28
- dataOutcome, errorOutcome, handleRpc, isRpcOutcome, pageOutcome, parseIncludeList, prepareCall, resolveReferences, serverInstructions,
28
+ createStdioRpcHandler, dataOutcome, errorOutcome, handleRpc, isRpcOutcome, pageOutcome, parseIncludeList, prepareCall, resolveReferences, serverInstructions,
29
29
  takeCancelled, textError, toolDefinitions, visibleOps,
30
30
  type ArgumentIssue, type DocsSource, type McpServer, type OpLike, type RpcOutcome, type ToolOutcome,
31
31
  } from "./mcp-protocol.js";
@@ -42,13 +42,13 @@ export { McpAccountLinkRequired } from "./mcp-protocol.js";
42
42
  const BIN = "typeship";
43
43
  const PKG_NAME = "@typeship-ax/mcp";
44
44
  const SERVER_NAME = "typeship-mcp";
45
- const SERVER_VERSION = "0.10.0";
45
+ const SERVER_VERSION = "0.19.0";
46
46
  /** The MCP client's announced name (clientInfo in request _meta), for the User-Agent. */
47
47
  let MCP_CLIENT_NAME: string | null = null;
48
48
  function noteClientInfo(message: unknown): void {
49
49
  const meta = (message as { params?: { _meta?: Record<string, { name?: unknown }> } } | null)?.params?._meta;
50
50
  const name = meta?.["io.modelcontextprotocol/clientInfo"]?.name;
51
- if (typeof name === "string" && name && name !== MCP_CLIENT_NAME) { MCP_CLIENT_NAME = name.slice(0, 60); }
51
+ if (typeof name === "string" && name) { MCP_CLIENT_NAME = name.replace(/[\s;()]+/g, "_").slice(0, 60); }
52
52
  }
53
53
  const DEFAULT_BASE_URL = "https://typeship.dev/api/v1";
54
54
  const NAMED_SCHEMES: CredentialSchemes = {"apiKey":{"kind":"bearer","options":["bearerToken"]}};
@@ -146,7 +146,7 @@ function makeClient(op: OpSpec): TypeshipClient {
146
146
  options.debug = (event: DebugEvent) => process.stderr.write(formatDebugEvent(BIN + "-mcp", event) + "\n");
147
147
  }
148
148
  // 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 : "") + ")" };
149
+ options.defaultHeaders = { "User-Agent": PKG_NAME + "-mcp/" + SERVER_VERSION + (MCP_CLIENT_NAME ? " (client=" + MCP_CLIENT_NAME + ")" : "") };
150
150
  // Keep the SDK's machine-token cache while re-evaluating local configuration.
151
151
  const key = createHash("sha256").update(JSON.stringify([options, config, profile?.name, stored?.oauth?.sessionId, null, process.env["TYPESHIP_DEBUG"]])).digest("hex");
152
152
  if (clientInstance && clientKey === key) return clientInstance;
@@ -176,7 +176,8 @@ async function callOperationRaw(op: OpSpec, rawArgs: Record<string, unknown>, re
176
176
  const { args, fields, maxChars } = prepared.call;
177
177
  const values: Record<string, unknown> = {};
178
178
  for (const p of op.params) {
179
- if (args[p.name] !== undefined) values[p.name] = args[p.name];
179
+ const argument = p.kind === "header" && p.name === "If-Match" ? "if_match" : p.name;
180
+ if (args[argument] !== undefined) values[p.name] = args[argument];
180
181
  }
181
182
  // File arguments are local paths on this machine; unreadable ones are
182
183
  // argument errors, reported before anything is sent.
@@ -206,8 +207,19 @@ async function callOperationRaw(op: OpSpec, rawArgs: Record<string, unknown>, re
206
207
  const shape = { fields, maxChars, pagination: op.pagination, args };
207
208
  try {
208
209
  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 } }>);
210
+ let result = await (target[op.method]!(...callArgs) as Promise<{ ok: boolean; data?: unknown; error?: unknown; response?: { requestId?: string } }>);
210
211
  if (!result.ok) return errorOutcome(result.error, errorContext);
212
+ if (op.httpMethod === "POST" && op.path === "/projects/{project_id}/generations") {
213
+ const batch = result.data as { data: Array<{ id: string }> };
214
+ const generations = (client as unknown as { generations: { wait(id: string): Promise<{ ok: boolean; data?: unknown; error?: unknown }> } }).generations;
215
+ const completed: unknown[] = [];
216
+ for (const generation of batch.data) {
217
+ const waited = await generations.wait(generation.id);
218
+ if (!waited.ok) return errorOutcome(waited.error, errorContext);
219
+ completed.push(waited.data);
220
+ }
221
+ result = { ...result, data: { ...batch, data: completed } };
222
+ }
211
223
  if (op.paginated) {
212
224
  const page = result.data as { items: unknown[]; nextPageParams(): Record<string, unknown> | null; response: { requestId?: string } };
213
225
  return pageOutcome(page.items, page.nextPageParams(), { ...shape, requestId: page.response.requestId });
@@ -269,7 +281,12 @@ const LOCAL_CREDENTIALS = LOCAL_PROCESS && !ARGV.includes("--http");
269
281
  * only on a local server (mcpExposed), writes are out under --read-only,
270
282
  * and --tools narrows to a subset. A hidden operation is unknown to
271
283
  * 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[];
284
+ const MCP_OPS = (visibleOps(OPS as unknown as OpLike[], { readOnly: READ_ONLY, include: INCLUDE, uploads: LOCAL_PROCESS }) as unknown as OpSpec[]).map((op) => {
285
+ const properties = op.inputSchema.properties as Record<string, unknown> | undefined;
286
+ if (!properties || !Object.hasOwn(properties, "If-Match")) return op;
287
+ const { ["If-Match"]: ifMatch, ...rest } = properties;
288
+ return { ...op, inputSchema: { ...op.inputSchema, properties: { ...rest, if_match: ifMatch } } };
289
+ });
273
290
  const HIDDEN_WRITES = READ_ONLY ? visibleOps(OPS as unknown as OpLike[], { include: INCLUDE, uploads: LOCAL_PROCESS }).length - MCP_OPS.length : 0;
274
291
  const HAS_UPLOADS = MCP_OPS.some((op) => op.bodyKind === "multipart" || op.bodyKind === "binary");
275
292
 
@@ -415,7 +432,7 @@ async function authorizedHttp(request: Request, authorizer: ReturnType<typeof cr
415
432
  if (url.pathname !== resource.pathname) return json(404, { error: "Unknown MCP endpoint." });
416
433
  if (request.method !== "POST") {
417
434
  // GET streams, DELETE (session end) and Mcp-Session-Id belong to earlier
418
- // revisions; the spec asks modern-only servers to answer 405.
435
+ // revisions; this stateless transport answers 405 instead.
419
436
  return json(405, { error: "POST JSON-RPC messages to this endpoint (MCP " + SUPPORTED_PROTOCOL_VERSIONS[0] + ", stateless)." }, { Allow: "POST, OPTIONS" });
420
437
  }
421
438
 
@@ -450,16 +467,15 @@ async function authorizedHttp(request: Request, authorizer: ReturnType<typeof cr
450
467
  }
451
468
  const parsed = asJsonRpc(incoming);
452
469
  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") {
470
+ // Validate the selected wire revision before dispatching a request.
471
+ if (parsed.id !== undefined && parsed.id !== null) {
456
472
  const mismatch = checkRequestHeaders(request.headers, parsed);
457
473
  if (mismatch) return json(mismatch.status, mismatch.message);
458
474
  }
459
475
  noteClientInfo(parsed);
460
476
  let outcome: RpcOutcome;
461
477
  try {
462
- outcome = await handleRpc(serverFor(remote), parsed);
478
+ outcome = await handleRpc(serverFor(remote), parsed, request.headers.get("mcp-protocol-version"));
463
479
  } catch (e) {
464
480
  return json(500, { jsonrpc: "2.0", id: parsed.id ?? null, error: { code: -32603, message: "Internal error" } });
465
481
  }
@@ -510,6 +526,7 @@ async function startStdio(): Promise<void> {
510
526
  const rl = createInterface({ input: process.stdin });
511
527
  const write = (message: Record<string, unknown>) => process.stdout.write(JSON.stringify(message) + "\n");
512
528
  const server = serverFor();
529
+ const dispatch = createStdioRpcHandler(server);
513
530
  rl.on("line", (line) => {
514
531
  const trimmed = line.trim();
515
532
  if (trimmed === "") return;
@@ -522,7 +539,7 @@ async function startStdio(): Promise<void> {
522
539
  }
523
540
  const id = incoming !== null && typeof incoming === "object" && !Array.isArray(incoming) ? (incoming as { id?: unknown }).id : undefined;
524
541
  noteClientInfo(incoming);
525
- void handleRpc(server, incoming)
542
+ void dispatch(incoming)
526
543
  .then((outcome) => {
527
544
  if (outcome.message === null) return;
528
545
  // A cancelled request gets no response (spec: MUST NOT send further messages).