@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
package/src/fields.ts CHANGED
@@ -42,24 +42,34 @@ export interface UnmatchedField {
42
42
  * at least one item has it; reaching null or an empty array counts as a
43
43
  * match, because the path may be right and the values merely empty. An
44
44
  * empty result matches everything.
45
+ *
46
+ * `schema` is the JSON Schema of `value` (the operation's declared
47
+ * response, or its item schema for a list). A key the schema declares may
48
+ * be absent from every item, as optional fields are: the path matches, and
49
+ * projects to nothing. What the schema cannot vouch for (no schema, an
50
+ * object without properties, a key it does not list) still has to be in
51
+ * the data.
45
52
  */
46
- export function unmatchedFields(value: unknown, paths: string[][]): UnmatchedField[] {
53
+ export function unmatchedFields(value: unknown, paths: string[][], schema?: unknown): UnmatchedField[] {
47
54
  const out: UnmatchedField[] = [];
48
55
  for (const path of paths) {
49
56
  const miss = { depth: -1, keys: new Set<string>(), segment: null as string | null };
50
- if (!pathMatches(value, path, 0, miss)) {
57
+ if (!pathMatches(value, path, 0, miss, schema)) {
51
58
  out.push({ path: path.join("."), available: [...miss.keys].slice(0, 40), missing: miss.segment });
52
59
  }
53
60
  }
54
61
  return out;
55
62
  }
56
63
 
57
- function pathMatches(value: unknown, path: string[], index: number, miss: { depth: number; keys: Set<string>; segment: string | null }): boolean {
64
+ type Miss = { depth: number; keys: Set<string>; segment: string | null };
65
+
66
+ function pathMatches(value: unknown, path: string[], index: number, miss: Miss, schema: unknown): boolean {
58
67
  if (value === null || value === undefined) return true;
59
68
  if (Array.isArray(value)) {
60
69
  if (value.length === 0) return true;
70
+ const items = arrayItems(schema);
61
71
  let any = false;
62
- for (const item of value) if (pathMatches(item, path, index, miss)) any = true;
72
+ for (const item of value) if (pathMatches(item, path, index, miss, items)) any = true;
63
73
  return any;
64
74
  }
65
75
  if (index === path.length) return true;
@@ -70,11 +80,77 @@ function pathMatches(value: unknown, path: string[], index: number, miss: { dept
70
80
  const key = path[index]!;
71
81
  const record = value as Record<string, unknown>;
72
82
  if (!Object.hasOwn(record, key)) {
83
+ const declared = declaredProperty(schema, key);
84
+ if (declared !== undefined) return declaredPath(declared, path, index + 1, miss);
73
85
  if (miss.depth < index) { miss.depth = index; miss.keys = new Set(); miss.segment = key; }
74
86
  if (miss.depth === index) for (const k of Object.keys(record)) miss.keys.add(k);
75
87
  return false;
76
88
  }
77
- return pathMatches(record[key], path, index + 1, miss);
89
+ return pathMatches(record[key], path, index + 1, miss, declaredProperty(schema, key));
90
+ }
91
+
92
+ /** The rest of a path below a declared key no item has: nothing in the data
93
+ * can contradict it, so it matches unless the schema lists that level's
94
+ * keys and the next segment is not one of them. */
95
+ function declaredPath(schema: unknown, path: string[], index: number, miss: Miss): boolean {
96
+ if (index === path.length) return true;
97
+ const key = path[index]!;
98
+ const declared = declaredProperty(schema, key);
99
+ if (declared !== undefined) return declaredPath(declared, path, index + 1, miss);
100
+ const listed = listedProperties(schema);
101
+ if (listed === null) return true;
102
+ if (miss.depth < index) { miss.depth = index; miss.keys = new Set(); miss.segment = key; }
103
+ if (miss.depth === index) for (const k of listed) miss.keys.add(k);
104
+ return false;
105
+ }
106
+
107
+ /** A schema with its allOf, anyOf and oneOf branches flattened, arrays
108
+ * seen through to their items. */
109
+ function variants(schema: unknown, out: Record<string, unknown>[] = []): Record<string, unknown>[] {
110
+ if (schema === null || typeof schema !== "object" || Array.isArray(schema)) return out;
111
+ const node = schema as Record<string, unknown>;
112
+ out.push(node);
113
+ for (const key of ["allOf", "anyOf", "oneOf"]) {
114
+ const branches = node[key];
115
+ if (Array.isArray(branches)) for (const branch of branches) variants(branch, out);
116
+ }
117
+ return out;
118
+ }
119
+
120
+ function arrayItems(schema: unknown): unknown {
121
+ const items = variants(schema).map((node) => node.items).filter((item) => item !== undefined && item !== null);
122
+ return items.length === 0 ? undefined : items.length === 1 ? items[0] : { anyOf: items };
123
+ }
124
+
125
+ /** The schema of `key` when some branch declares it, else undefined. */
126
+ function declaredProperty(schema: unknown, key: string): unknown {
127
+ const found = variants(schema).flatMap((node) => {
128
+ const properties = node.properties as Record<string, unknown> | undefined;
129
+ return properties && typeof properties === "object" && Object.hasOwn(properties, key) ? [properties[key] ?? {}] : [];
130
+ });
131
+ return found.length === 0 ? undefined : found.length === 1 ? found[0] : { anyOf: found };
132
+ }
133
+
134
+ /** Every key the schema declares, or null when it cannot say which keys
135
+ * exist: no schema, an object without properties, or one that admits more. */
136
+ function listedProperties(schema: unknown): string[] | null {
137
+ const nodes = variants(schema);
138
+ const keys: string[] = [];
139
+ let closed = false;
140
+ for (const node of nodes) {
141
+ const properties = node.properties as Record<string, unknown> | undefined;
142
+ if (properties && typeof properties === "object") {
143
+ closed = true;
144
+ keys.push(...Object.keys(properties));
145
+ }
146
+ if (node.additionalProperties !== undefined && node.additionalProperties !== false) return null;
147
+ if (node.patternProperties !== undefined) return null;
148
+ // Tool schemas keep the first eight variants of a union; more may exist.
149
+ if (Array.isArray(node.anyOf) && node.anyOf.length >= 8) return null;
150
+ }
151
+ // A branch that is an object without listed properties is open.
152
+ if (nodes.some((node) => node.properties === undefined && (node.type === "object" || (Array.isArray(node.type) && node.type.includes("object"))))) return null;
153
+ return closed ? [...new Set(keys)] : null;
78
154
  }
79
155
 
80
156
  /** The error message: the call went through, and for each unmatched path,
package/src/index.ts CHANGED
@@ -25,8 +25,8 @@ import { OrganizationResource } from "./resources/organization.js";
25
25
  import { ApiKeysResource } from "./resources/api-keys.js";
26
26
 
27
27
  /** This package's version, also sent as the `User-Agent`. */
28
- export const VERSION = "0.22.0";
29
- const USER_AGENT = "@typeship-ax/mcp/0.22.0";
28
+ export const VERSION = "0.23.0";
29
+ const USER_AGENT = "@typeship-ax/mcp/0.23.0";
30
30
 
31
31
  export interface ClientOptions {
32
32
  /** Override the server URL. Default: `https://typeship.dev/api/v1` */
@@ -87,7 +87,7 @@ export interface ClientOptions {
87
87
  }
88
88
 
89
89
  /**
90
- * Typeship — v0.22.0
90
+ * Typeship — v0.23.0
91
91
  *
92
92
  * Resolve an OpenAPI or GraphQL Spec, diagnose it, and keep every
93
93
  * selected CLI, MCP, and SDK Target current.