@typeship-ax/mcp 0.22.0 → 0.23.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 (55) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +1 -1
  3. package/api.md +1 -1
  4. package/dist/arguments.d.ts +9 -2
  5. package/dist/arguments.d.ts.map +1 -1
  6. package/dist/arguments.js +17 -6
  7. package/dist/fields.d.ts +8 -1
  8. package/dist/fields.d.ts.map +1 -1
  9. package/dist/fields.js +91 -5
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.js +3 -3
  12. package/dist/mcp-protocol.d.ts +21 -0
  13. package/dist/mcp-protocol.d.ts.map +1 -1
  14. package/dist/mcp-protocol.js +280 -58
  15. package/dist/mcp.d.ts.map +1 -1
  16. package/dist/mcp.js +4 -3
  17. package/dist/ops.d.ts +5 -0
  18. package/dist/ops.d.ts.map +1 -1
  19. package/dist/ops.js +66 -6
  20. package/dist/resources/deliveries.d.ts +1 -1
  21. package/dist/resources/deliveries.d.ts.map +1 -1
  22. package/dist/resources/deliveries.js +1 -1
  23. package/dist/resources/drafts.d.ts +1 -1
  24. package/dist/resources/drafts.d.ts.map +1 -1
  25. package/dist/resources/drafts.js +1 -1
  26. package/dist/resources/generations.d.ts +2 -2
  27. package/dist/resources/generations.d.ts.map +1 -1
  28. package/dist/resources/generations.js +2 -2
  29. package/dist/resources/releases.d.ts +1 -1
  30. package/dist/resources/releases.d.ts.map +1 -1
  31. package/dist/resources/releases.js +1 -1
  32. package/dist/resources/spec-revisions.d.ts +1 -1
  33. package/dist/resources/spec-revisions.d.ts.map +1 -1
  34. package/dist/resources/spec-revisions.js +1 -1
  35. package/dist/resources/targets.d.ts +1 -1
  36. package/dist/resources/targets.d.ts.map +1 -1
  37. package/dist/resources/targets.js +1 -1
  38. package/dist/type-docs.d.ts +61 -0
  39. package/dist/type-docs.d.ts.map +1 -0
  40. package/dist/type-docs.js +174 -0
  41. package/package.json +1 -1
  42. package/server.json +2 -2
  43. package/src/arguments.ts +19 -7
  44. package/src/fields.ts +81 -5
  45. package/src/index.ts +3 -3
  46. package/src/mcp-protocol.ts +279 -58
  47. package/src/mcp.ts +4 -3
  48. package/src/ops.ts +69 -6
  49. package/src/resources/deliveries.ts +2 -2
  50. package/src/resources/drafts.ts +2 -2
  51. package/src/resources/generations.ts +4 -4
  52. package/src/resources/releases.ts +2 -2
  53. package/src/resources/spec-revisions.ts +2 -2
  54. package/src/resources/targets.ts +2 -2
  55. package/src/type-docs.ts +205 -0
@@ -90,7 +90,7 @@ export class DeliveriesResource {
90
90
  query: {
91
91
  limit: params?.limit,
92
92
  cursor: params?.cursor,
93
- target_id: params?.targetId,
93
+ target_id: params?.target_id,
94
94
  },
95
95
  idempotent: true,
96
96
  schemaKey: "deliveries.list",
@@ -239,7 +239,7 @@ export interface DeliveriesListParams {
239
239
  */
240
240
  cursor?: string;
241
241
  /** Only Deliveries of this Target. */
242
- targetId?: TargetId;
242
+ target_id?: TargetId;
243
243
  }
244
244
 
245
245
  /** Typed errors `list` can throw. */
@@ -58,7 +58,7 @@ export class DraftsResource {
58
58
  query: {
59
59
  limit: params?.limit,
60
60
  cursor: params?.cursor,
61
- target_id: params?.targetId,
61
+ target_id: params?.target_id,
62
62
  status: params?.status,
63
63
  },
64
64
  idempotent: true,
@@ -249,7 +249,7 @@ export interface DraftsListParams {
249
249
  */
250
250
  cursor?: string;
251
251
  /** Only Drafts of this Target. */
252
- targetId?: TargetId;
252
+ target_id?: TargetId;
253
253
  /** Only Drafts with this status. */
254
254
  status?: DraftStatus;
255
255
  }
@@ -72,8 +72,8 @@ export class GenerationsResource {
72
72
  query: {
73
73
  limit: params?.limit,
74
74
  cursor: params?.cursor,
75
- project_id: params?.projectId,
76
- target_id: params?.targetId,
75
+ project_id: params?.project_id,
76
+ target_id: params?.target_id,
77
77
  status: params?.status,
78
78
  },
79
79
  idempotent: true,
@@ -173,9 +173,9 @@ export interface GenerationsListParams {
173
173
  */
174
174
  cursor?: string;
175
175
  /** Only Generations in this Project. */
176
- projectId?: ProjectId;
176
+ project_id?: ProjectId;
177
177
  /** Only Generations of this Target. */
178
- targetId?: TargetId;
178
+ target_id?: TargetId;
179
179
  /** Only Generations with this status. */
180
180
  status?: GenerationStatus;
181
181
  }
@@ -45,7 +45,7 @@ export class ReleasesResource {
45
45
  query: {
46
46
  limit: params?.limit,
47
47
  cursor: params?.cursor,
48
- target_id: params?.targetId,
48
+ target_id: params?.target_id,
49
49
  },
50
50
  idempotent: true,
51
51
  schemaKey: "releases.list",
@@ -128,7 +128,7 @@ export interface ReleasesListParams {
128
128
  */
129
129
  cursor?: string;
130
130
  /** Only releases of this Target. */
131
- targetId?: TargetId;
131
+ target_id?: TargetId;
132
132
  }
133
133
 
134
134
  /** Typed errors `list` can throw. */
@@ -54,7 +54,7 @@ export class SpecRevisionsResource {
54
54
  query: {
55
55
  limit: params?.limit,
56
56
  cursor: params?.cursor,
57
- spec_id: params?.specId,
57
+ spec_id: params?.spec_id,
58
58
  },
59
59
  idempotent: true,
60
60
  schemaKey: "specRevisions.list",
@@ -151,7 +151,7 @@ export interface SpecRevisionsListParams {
151
151
  */
152
152
  cursor?: string;
153
153
  /** Only revisions of this Spec. */
154
- specId?: SpecId;
154
+ spec_id?: SpecId;
155
155
  }
156
156
 
157
157
  /** Typed errors `list` can throw. */
@@ -84,7 +84,7 @@ export class TargetsResource {
84
84
  query: {
85
85
  limit: params?.limit,
86
86
  cursor: params?.cursor,
87
- project_id: params?.projectId,
87
+ project_id: params?.project_id,
88
88
  },
89
89
  idempotent: true,
90
90
  schemaKey: "targets.list",
@@ -260,7 +260,7 @@ export interface TargetsListParams {
260
260
  */
261
261
  cursor?: string;
262
262
  /** Only Targets in this Project. */
263
- projectId?: ProjectId;
263
+ project_id?: ProjectId;
264
264
  }
265
265
 
266
266
  /** Typed errors `list` can throw. */
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Named input types for the docs surfaces (MCP read_docs, the CLI's docs
3
+ * command): a type's reference by name, and an argument path such as
4
+ * "filter.team.key" resolved to its type. Each type is described once with
5
+ * its fields typed by name, so a lookup is bounded and every type it
6
+ * mentions can be looked up the same way.
7
+ */
8
+
9
+ export interface InputTypeField {
10
+ /** As an agent writes it: a type name, string[], "a"|"b", object. */
11
+ type: string;
12
+ required?: true;
13
+ description?: string;
14
+ default?: unknown;
15
+ deprecated?: true;
16
+ }
17
+
18
+ export interface InputTypeDoc {
19
+ description?: string;
20
+ /** An input object's fields. */
21
+ fields?: Record<string, InputTypeField>;
22
+ /** An enum's values. */
23
+ values?: unknown[];
24
+ /** A union's member types. */
25
+ variants?: string[];
26
+ }
27
+
28
+ export interface InputTypes {
29
+ types: Record<string, InputTypeDoc>;
30
+ /** Per tool, each argument whose type is (or contains) a named type. */
31
+ args: Record<string, Record<string, string>>;
32
+ }
33
+
34
+ /** Enum values a type page lists before a count. */
35
+ const TYPE_ENUM_VALUES = 200;
36
+
37
+ /** One line of prose: links reduced to their text. */
38
+ function prose(text: string | undefined): string {
39
+ return (text ?? "").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\s+/g, " ").trim();
40
+ }
41
+
42
+ /** The table's type names an expression mentions, in order. */
43
+ export function namedTypesIn(table: InputTypes | undefined, expression: string | undefined): string[] {
44
+ if (!table || !expression) return [];
45
+ return [...new Set(expression.split(/[|()[\]\s]+/).filter((part) => Object.hasOwn(table.types, part)))];
46
+ }
47
+
48
+ /** The one input object an expression names (IssueFilter, IssueFilter[]),
49
+ * whose fields a path continues into. */
50
+ function objectTypeOf(table: InputTypes, expression: string | undefined): string | undefined {
51
+ const objects = namedTypesIn(table, expression).filter((name) => table.types[name]!.fields);
52
+ return objects.length === 1 ? objects[0] : undefined;
53
+ }
54
+
55
+ /** A type by exact name, else by a case-insensitive match. */
56
+ export function findInputType(table: InputTypes | undefined, name: string): string | undefined {
57
+ if (!table) return undefined;
58
+ if (Object.hasOwn(table.types, name)) return name;
59
+ const lower = name.toLowerCase();
60
+ return Object.keys(table.types).find((candidate) => candidate.toLowerCase() === lower);
61
+ }
62
+
63
+ function fieldLine(name: string, field: InputTypeField): string {
64
+ const extras = [
65
+ ...(field.required ? ["required"] : []),
66
+ ...(field.default !== undefined ? ["default " + JSON.stringify(field.default)] : []),
67
+ ...(field.deprecated ? ["deprecated"] : []),
68
+ ];
69
+ const description = prose(field.description);
70
+ return " " + name + " (" + field.type + (extras.length ? ", " + extras.join(", ") : "") + ")" + (description ? ": " + description : "");
71
+ }
72
+
73
+ /** How to read another type, in the caller's own syntax. */
74
+ export type TypeLookupHint = (name: string) => string;
75
+
76
+ /** A named type's reference: description, then its fields, values or
77
+ * members, then how to read the named types it mentions. */
78
+ export function inputTypeText(table: InputTypes, name: string, lookup: TypeLookupHint): string {
79
+ const doc = table.types[name]!;
80
+ const kind = doc.fields ? "input object" : doc.values ? "enum" : "union";
81
+ const lines = [name + " (" + kind + ")"];
82
+ if (doc.description) lines.push(prose(doc.description));
83
+ const mentioned: string[] = [];
84
+ if (doc.fields) {
85
+ const entries = Object.entries(doc.fields);
86
+ lines.push("", entries.length === 0 ? "No fields." : "Fields (" + entries.length + "):");
87
+ for (const [field, spec] of entries) {
88
+ lines.push(fieldLine(field, spec));
89
+ mentioned.push(...namedTypesIn(table, spec.type));
90
+ }
91
+ } else if (doc.values) {
92
+ const values = doc.values.slice(0, TYPE_ENUM_VALUES).map((value) => JSON.stringify(value));
93
+ lines.push("", "Values: " + values.join(", ") + (doc.values.length > TYPE_ENUM_VALUES ? ", … " + (doc.values.length - TYPE_ENUM_VALUES) + " more" : ""));
94
+ } else if (doc.variants) {
95
+ lines.push("", "One of: " + doc.variants.join(" | "));
96
+ for (const variant of doc.variants) mentioned.push(...namedTypesIn(table, variant));
97
+ }
98
+ const others = [...new Set(mentioned)].filter((other) => other !== name);
99
+ if (others.length > 0) {
100
+ lines.push("", "Types used above (" + others.length + "): " + others.join(", ") + ". Read one with " + lookup("<type>") + ".");
101
+ }
102
+ return lines.join("\n");
103
+ }
104
+
105
+ /** A field of an anonymous object in an inline JSON Schema, one level. */
106
+ interface InlineSchema { type?: unknown; properties?: Record<string, InlineSchema>; required?: string[]; items?: InlineSchema; anyOf?: InlineSchema[]; oneOf?: InlineSchema[]; enum?: unknown[]; description?: string }
107
+
108
+ function inlineObject(schema: InlineSchema | undefined): InlineSchema | undefined {
109
+ if (!schema || typeof schema !== "object") return undefined;
110
+ if (schema.properties && typeof schema.properties === "object") return schema;
111
+ if (schema.items) return inlineObject(schema.items);
112
+ const variants = schema.anyOf ?? schema.oneOf;
113
+ if (Array.isArray(variants)) {
114
+ const objects = variants.map(inlineObject).filter((variant) => variant !== undefined);
115
+ if (objects.length === 1) return objects[0];
116
+ }
117
+ return undefined;
118
+ }
119
+
120
+ function inlineType(schema: InlineSchema): string {
121
+ if (Array.isArray(schema.enum)) return schema.enum.filter((value) => value !== null).map((value) => JSON.stringify(value)).join("|");
122
+ const variants = schema.anyOf ?? schema.oneOf;
123
+ if (Array.isArray(variants)) return [...new Set(variants.map(inlineType))].filter((t) => t !== "null").join("|") || "null";
124
+ const types = (Array.isArray(schema.type) ? schema.type : typeof schema.type === "string" ? [schema.type] : []).filter((t) => t !== "null") as string[];
125
+ if (types.includes("array")) {
126
+ const inner = schema.items ? inlineType(schema.items) : "any";
127
+ return (/[| ]/.test(inner) ? "(" + inner + ")" : inner) + "[]";
128
+ }
129
+ return types.join("|") || (schema.properties ? "object" : "any");
130
+ }
131
+
132
+ export type PathLookup =
133
+ | { ok: true; text: string }
134
+ | { ok: false; message: string; available: string[] };
135
+
136
+ /**
137
+ * What one argument path means: the field's type, whether it is required,
138
+ * its description, and, when its type is named, that type's reference in
139
+ * full (a comparator's operators, an enum's values). `root` is an
140
+ * operation (its tool name, inline input schema, and how the caller names
141
+ * it) or a named type.
142
+ */
143
+ export function argumentPathText(
144
+ table: InputTypes | undefined,
145
+ root: { tool: string; inputSchema: Record<string, unknown>; label?: string } | { type: string },
146
+ path: string,
147
+ lookup: TypeLookupHint,
148
+ ): PathLookup {
149
+ const segments = path.split(".").map((segment) => segment.trim()).filter((segment) => segment.length > 0);
150
+ if (segments.length === 0) return { ok: false, message: "The path is empty.", available: [] };
151
+ // Walk the named-type table where the path has a type name, else the
152
+ // operation's inline schema.
153
+ let typeName: string | undefined = "type" in root ? root.type : undefined;
154
+ let inline: InlineSchema | undefined = "type" in root ? undefined : root.inputSchema as InlineSchema;
155
+ let expression: string | undefined;
156
+ let field: InputTypeField | undefined;
157
+ const label = "type" in root ? root.type : root.label ?? root.tool;
158
+ for (let index = 0; index < segments.length; index += 1) {
159
+ const segment = segments[index]!;
160
+ const at = index === 0 ? label : label + " " + segments.slice(0, index).join(".");
161
+ if (typeName && table) {
162
+ const fields = table.types[typeName]!.fields ?? {};
163
+ if (!Object.hasOwn(fields, segment)) return { ok: false, message: at + " (" + typeName + ") has no field \"" + segment + "\".", available: Object.keys(fields) };
164
+ field = fields[segment]!;
165
+ expression = field.type;
166
+ inline = undefined;
167
+ } else {
168
+ const object = inlineObject(inline);
169
+ const properties = object?.properties ?? {};
170
+ if (!object || !Object.hasOwn(properties, segment)) {
171
+ return { ok: false, message: object ? at + " has no field \"" + segment + "\"." : at + " is not an object; the path cannot continue past it.", available: Object.keys(properties) };
172
+ }
173
+ const child = properties[segment]!;
174
+ const topLevel = index === 0 && !("type" in root) ? table?.args[root.tool]?.[segment] : undefined;
175
+ expression = topLevel ?? inlineType(child);
176
+ field = {
177
+ type: expression,
178
+ ...(Array.isArray(object.required) && object.required.includes(segment) ? { required: true as const } : {}),
179
+ ...(typeof child.description === "string" ? { description: child.description } : {}),
180
+ };
181
+ inline = child;
182
+ }
183
+ typeName = table ? objectTypeOf(table, expression) : undefined;
184
+ if (!typeName && index < segments.length - 1 && !inlineObject(inline)) {
185
+ return { ok: false, message: label + " " + segments.slice(0, index + 1).join(".") + " is " + expression + ", not an object; the path cannot continue past it.", available: [] };
186
+ }
187
+ }
188
+ const lines = [label + " " + segments.join(".") + ": " + field!.type + (field!.required ? " (required)" : "")];
189
+ const description = prose(field!.description);
190
+ if (description) lines.push(description);
191
+ const named = namedTypesIn(table, expression);
192
+ if (named.length > 0 && table) {
193
+ for (const name of named) lines.push("", inputTypeText(table, name, lookup));
194
+ } else {
195
+ const object = inlineObject(inline);
196
+ if (object) {
197
+ const required = new Set(object.required ?? []);
198
+ lines.push("", "Fields:");
199
+ for (const [name, child] of Object.entries(object.properties ?? {})) {
200
+ lines.push(fieldLine(name, { type: inlineType(child), ...(required.has(name) ? { required: true } : {}), ...(child.description ? { description: child.description } : {}) }));
201
+ }
202
+ }
203
+ }
204
+ return { ok: true, text: lines.join("\n") };
205
+ }