@typeship-ax/mcp 0.9.1 → 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.
- package/AGENTS.md +10 -4
- package/README.md +16 -5
- package/api.json +6055 -645
- package/api.md +5225 -567
- package/dist/core/http.d.ts +3 -2
- package/dist/core/http.d.ts.map +1 -1
- package/dist/core/http.js +18 -6
- package/dist/errors.d.ts +17 -10
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +24 -15
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- package/dist/mcp-protocol.d.ts +10 -6
- package/dist/mcp-protocol.d.ts.map +1 -1
- package/dist/mcp-protocol.js +108 -37
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +36 -17
- package/dist/ops.d.ts +4 -0
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js +45 -31
- package/dist/resources/account.d.ts +4 -4
- package/dist/resources/account.d.ts.map +1 -1
- package/dist/resources/account.js +10 -5
- package/dist/resources/api-keys.d.ts +40 -14
- package/dist/resources/api-keys.d.ts.map +1 -1
- package/dist/resources/api-keys.js +45 -13
- package/dist/resources/definition-revisions.d.ts +35 -18
- package/dist/resources/definition-revisions.d.ts.map +1 -1
- package/dist/resources/definition-revisions.js +43 -15
- package/dist/resources/definitions.d.ts +21 -6
- package/dist/resources/definitions.d.ts.map +1 -1
- package/dist/resources/definitions.js +16 -3
- package/dist/resources/generate.d.ts +44 -15
- package/dist/resources/generate.d.ts.map +1 -1
- package/dist/resources/generate.js +55 -13
- package/dist/resources/generations.d.ts +14 -6
- package/dist/resources/generations.d.ts.map +1 -1
- package/dist/resources/generations.js +26 -5
- package/dist/resources/projects.d.ts +106 -53
- package/dist/resources/projects.d.ts.map +1 -1
- package/dist/resources/projects.js +78 -31
- package/dist/resources/targets.d.ts +285 -22
- package/dist/resources/targets.d.ts.map +1 -1
- package/dist/resources/targets.js +408 -10
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +138 -73
- package/dist/types.d.ts +2162 -134
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +99 -2
- package/package.json +3 -3
- package/server.json +3 -3
- package/src/core/http.ts +17 -6
- package/src/errors.ts +25 -15
- package/src/index.ts +9 -5
- package/src/mcp-protocol.ts +93 -36
- package/src/mcp.ts +32 -15
- package/src/ops.ts +49 -31
- package/src/resources/account.ts +11 -4
- package/src/resources/api-keys.ts +84 -13
- package/src/resources/definition-revisions.ts +74 -16
- package/src/resources/definitions.ts +28 -4
- package/src/resources/generate.ts +78 -13
- package/src/resources/generations.ts +31 -4
- package/src/resources/projects.ts +141 -39
- package/src/resources/targets.ts +756 -13
- package/src/schemas.ts +138 -73
- package/src/types.ts +2372 -189
package/src/errors.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// typeship — typed error classes.
|
|
2
|
-
// Generated by typeship — https://typeship.dev
|
|
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
|
-
*
|
|
92
|
-
* Raised for HTTP
|
|
101
|
+
* No such resource in this account.
|
|
102
|
+
* Raised for HTTP 404 responses.
|
|
93
103
|
*/
|
|
94
|
-
export class
|
|
104
|
+
export class NotFoundError extends ApiError<404, ErrorModelRead> {
|
|
95
105
|
constructor(body: ErrorModelRead, response: ResponseMeta) {
|
|
96
|
-
super("
|
|
106
|
+
super("No such resource in this account.", 404, body, response);
|
|
97
107
|
}
|
|
98
108
|
}
|
|
99
109
|
|
|
100
110
|
/**
|
|
101
|
-
*
|
|
102
|
-
* Raised for HTTP
|
|
111
|
+
* The plan does not include another project or the requested target configuration.
|
|
112
|
+
* Raised for HTTP 402 responses.
|
|
103
113
|
*/
|
|
104
|
-
export class
|
|
114
|
+
export class PaymentRequiredError extends ApiError<402, ErrorModelRead> {
|
|
105
115
|
constructor(body: ErrorModelRead, response: ResponseMeta) {
|
|
106
|
-
super("
|
|
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
|
-
*
|
|
112
|
-
* Raised for HTTP
|
|
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
|
|
124
|
+
export class PreconditionFailedError extends ApiError<412, ErrorModelRead> {
|
|
115
125
|
constructor(body: ErrorModelRead, response: ResponseMeta) {
|
|
116
|
-
super("
|
|
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
|
-
*
|
|
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("
|
|
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
|
|
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.
|
|
26
|
-
const USER_AGENT = "@typeship-ax/mcp/0.
|
|
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.
|
|
82
|
+
* typeship — v0.19.0
|
|
83
83
|
*
|
|
84
84
|
* Resolve an OpenAPI or GraphQL Definition, diagnose it, and keep every
|
|
85
|
-
* selected
|
|
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;
|
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
|
};
|
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)
|
|
@@ -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.
|
|
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
|
|
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 +
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
//
|
|
454
|
-
|
|
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
|
|
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).
|