@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
@@ -15,10 +15,11 @@
15
15
  *
16
16
  * Spec: https://modelcontextprotocol.io/specification/2026-07-28
17
17
  */
18
- import { checkValue, closestName, isMeReference, normalizeName, userShapedReference } from "./arguments.js";
18
+ import { checkValue, closestName, isMeReference, meIdentityField, normalizeName, userShapedReference } from "./arguments.js";
19
19
  import { docsReadTarget, resolveDocsContentUrl, searchConnectedGuides } from "./docs.js";
20
20
  import { projectFields, unmatchedFields, unmatchedFieldsMessage } from "./fields.js";
21
21
  import { SEARCH_PAGE_SIZE, rankOperations } from "./search.js";
22
+ import { argumentPathText, findInputType, inputTypeText, namedTypesIn } from "./type-docs.js";
22
23
  export { projectFields } from "./fields.js";
23
24
  export const MCP_PROTOCOL_VERSION = "2026-07-28";
24
25
  export const LEGACY_PROTOCOL_VERSION = "2025-11-25";
@@ -437,8 +438,8 @@ export const SEARCH_DOCS_TOOL = {
437
438
  };
438
439
  export const READ_DOCS_TOOL = {
439
440
  name: "read_docs",
440
- description: 'Read a documentation page: an operation reference (a tool name, or dotted "resource.method") or a docs-site guide page by name or URL. Long pages come in parts; pass the offset a part ends with to continue.',
441
- inputSchema: { type: "object", properties: { page: { type: "string" }, schema: { type: "boolean", description: "Also return the operation's complete input and output JSON Schemas, default false" }, offset: { type: "integer", minimum: 0, description: "Character offset to continue a long page from, default 0" } }, required: ["page"] },
441
+ description: 'Read a documentation page: an operation reference (a tool name, or dotted "resource.method"), a named input type such as a filter, or a docs-site guide page by name or URL. Long pages come in parts; pass the offset a part ends with to continue.',
442
+ inputSchema: { type: "object", properties: { page: { type: "string" }, path: { type: "string", description: "A dotted argument path within the operation or type, e.g. \"filter.team.key\": that field's type, with its named type's fields or values" }, schema: { type: "boolean", description: "Also return the operation's complete input and output JSON Schemas, default false" }, offset: { type: "integer", minimum: 0, description: "Character offset to continue a long page from, default 0" } }, required: ["page"] },
442
443
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
443
444
  };
444
445
  export const EXECUTE_TOOL = {
@@ -531,7 +532,7 @@ export function toolDefinitions(ops, mode, omittedOps = []) {
531
532
  : "";
532
533
  return [
533
534
  { ...SEARCH_DOCS_TOOL, description: "Search this API's " + ops.length + " generated operations and, when a docs site is configured, its guides. Start here to find the operation you need." + coverage },
534
- { ...READ_DOCS_TOOL, description: "Read an operation's reference (arguments, authentication, safety, an example and the result's shape) by tool name, or a docs-site guide page. schema: true adds the complete JSON Schemas." },
535
+ { ...READ_DOCS_TOOL, description: "Read an operation's reference (arguments, authentication, safety, an example and the result's shape) by tool name, a named input type, or a docs-site guide page. path narrows to one nested argument, e.g. \"filter.team.key\"; schema: true adds the complete JSON Schemas." },
535
536
  execute,
536
537
  ];
537
538
  }
@@ -546,6 +547,58 @@ export function findOperation(ops, wanted) {
546
547
  export function missingArguments(op, args) {
547
548
  return op.params.filter((p) => p.required && args[p.name] === undefined).map((p) => p.name);
548
549
  }
550
+ // ---- server instructions ----------------------------------------------------------
551
+ /** Identity fields that name the caller for a login-shaped "me" (GitHub's
552
+ * users_get_authenticated returns login). */
553
+ const IDENTITY_LOGIN_FIELDS = ["login", "username", "handle"];
554
+ const MAX_ME_SCHEMA_DEPTH = 6;
555
+ function acceptsString(schema) {
556
+ if (!schema || typeof schema !== "object")
557
+ return false;
558
+ const record = schema;
559
+ const type = record.type;
560
+ if (type === "string" || (Array.isArray(type) && type.includes("string")))
561
+ return true;
562
+ const variants = record.anyOf ?? record.oneOf;
563
+ return Array.isArray(variants) && variants.some(acceptsString);
564
+ }
565
+ function objectVariants(schema) {
566
+ if (!schema || typeof schema !== "object")
567
+ return [];
568
+ const record = schema;
569
+ const variants = record.anyOf ?? record.oneOf;
570
+ return [record, ...(Array.isArray(variants) ? variants.flatMap(objectVariants) : [])]
571
+ .filter((candidate) => candidate.properties && typeof candidate.properties === "object");
572
+ }
573
+ /** Every argument path where the server resolves "me": a user-shaped string
574
+ * (or a list or union that takes one) at the top level or inside an object
575
+ * argument. The instructions name exactly these. */
576
+ export function meReferenceArguments(ops) {
577
+ const found = new Set();
578
+ const visit = (name, schema, path, depth) => {
579
+ if (!schema || typeof schema !== "object" || depth > MAX_ME_SCHEMA_DEPTH)
580
+ return;
581
+ const record = schema;
582
+ const items = record.items ?? (Array.isArray(record.anyOf ?? record.oneOf) ? (record.anyOf ?? record.oneOf).find((variant) => variant && variant.items)?.items : undefined);
583
+ if (userShapedReference(name) && (acceptsString(record) || acceptsString(items)))
584
+ found.add(path);
585
+ const nested = [[objectVariants(record), path], [objectVariants(items), path + "[]"]];
586
+ for (const [variants, prefix] of nested) {
587
+ for (const variant of variants) {
588
+ for (const [key, child] of Object.entries(variant.properties))
589
+ visit(key, child, prefix + "." + key, depth + 1);
590
+ }
591
+ }
592
+ };
593
+ for (const op of ops) {
594
+ const properties = (op.inputSchema.properties ?? {});
595
+ for (const param of op.params) {
596
+ if (param.resolve !== false)
597
+ visit(param.name, properties[param.name], param.name, 0);
598
+ }
599
+ }
600
+ return [...found].sort((a, b) => a.split(".").length - b.split(".").length || a.localeCompare(b));
601
+ }
549
602
  /** The server/discover instructions: what the tools are, how arguments and
550
603
  * results behave, where credentials come from, plus whatever the project
551
604
  * adds. One paragraph; agents read it once per session. */
@@ -561,8 +614,9 @@ export function serverInstructions(input) {
561
614
  parts.push("Arguments use the API's wire names; an unknown, mistyped or missing argument returns an isError result listing each problem (nothing is dropped silently), and obvious forms are coerced (\"true\" to boolean, \"3\" to number, enum case).");
562
615
  if (input.referenceResolution)
563
616
  parts.push("Reference arguments marked in their schema accept either an ID or an exact case-insensitive name, slug, key or email; the server resolves one match through the named list tool, reports multiple candidates, and never guesses fuzzily.");
564
- if (input.identityTool && input.ops.some((op) => op.params.some((param) => param.type === "string" && param.resolve !== false && userShapedReference(param.name)))) {
565
- parts.push("User-shaped reference arguments also accept \"me\", resolved through " + input.identityTool + ".");
617
+ const meArguments = input.identityTool ? meReferenceArguments(input.ops) : [];
618
+ if (meArguments.length > 0) {
619
+ parts.push("These arguments also accept \"me\" for the caller, resolved through " + input.identityTool + " (ID arguments take its id, others its login when it has one): " + meArguments.join(", ") + ".");
566
620
  }
567
621
  parts.push("Paginated tools return items, hasMore and nextPage (the exact arguments for the following page). Pass fields (dotted paths) to keep only the result keys you need; oversized results are cut to whole items or keys with a truncated note saying how to ask for less.");
568
622
  parts.push("Errors carry error, code, message, status, the API's body and next_steps.");
@@ -656,7 +710,7 @@ export function prepareCall(op, rawArgs, options = {}) {
656
710
  // Patterns apply inside objects and arrays. A top-level argument's own
657
711
  // pattern is left to the API: its documented example values do not all
658
712
  // satisfy it yet, and a name or "me" is only resolved to an ID later.
659
- args[name] = checkValue(value, propSchema, name, issues, { skipPattern: true });
713
+ args[name] = checkValue(value, propSchema, name, issues, { skipPattern: true, meName: name });
660
714
  }
661
715
  // A path argument that is the configured credential's username (Twilio's
662
716
  // AccountSid) defaults to it; without one it is an ordinary missing argument
@@ -763,30 +817,39 @@ export async function resolveReferences(op, preparedArgs, options) {
763
817
  const args = { ...preparedArgs };
764
818
  const maxPages = Math.max(1, Math.floor(options.maxPages ?? 5));
765
819
  const identity = options.identityTool ? findOperation(options.ops, options.identityTool) : undefined;
766
- /** The caller's ID for "me", fetched once per cache. */
767
- const callerId = async (argument) => {
768
- const cacheKey = "me:" + identity.tool;
820
+ let identityBody;
821
+ /** The caller's ID, or login for a login-shaped name (GitHub's owner and
822
+ * assignees), for "me": one identity call per cache. */
823
+ const callerId = async (argument, key) => {
824
+ const field = meIdentityField(key);
825
+ const cacheKey = "me:" + identity.tool + (field === "id" ? "" : ":" + field);
769
826
  const cached = options.cache.get(cacheKey);
770
827
  if (cached !== undefined)
771
828
  return { id: cached };
772
- const outcome = await options.runOperation(identity, {});
773
- if (outcome.isError)
774
- return { outcome };
775
- const body = outcomeValue(outcome);
776
- const id = body && typeof body === "object" && !Array.isArray(body) ? body.id : undefined;
829
+ if (!identityBody) {
830
+ const outcome = await options.runOperation(identity, {});
831
+ if (outcome.isError)
832
+ return { outcome };
833
+ const body = outcomeValue(outcome);
834
+ identityBody = body && typeof body === "object" && !Array.isArray(body) ? body : {};
835
+ }
836
+ const login = IDENTITY_LOGIN_FIELDS.map((name) => identityBody[name]).find((value) => typeof value === "string" && value !== "");
837
+ const id = field === "login" && login !== undefined ? login : identityBody.id;
777
838
  if (typeof id !== "string" && typeof id !== "number") {
778
- return { outcome: referenceError("NOT_AVAILABLE", `${identity.tool} did not return a top-level id, so "me" cannot be resolved for ${argument}.`, argument, ["Pass the caller's exact ID instead."]) };
839
+ const expected = field === "id" ? "a top-level id" : "a top-level " + IDENTITY_LOGIN_FIELDS.join(", ") + " or id";
840
+ return { outcome: referenceError("NOT_AVAILABLE", `${identity.tool} did not return ${expected}, so "me" cannot be resolved for ${argument}.`, argument, [field === "id" ? "Pass the caller's exact ID instead." : "Pass the caller's exact login instead."]) };
779
841
  }
780
842
  putReferenceCache(options.cache, cacheKey, id);
781
843
  return { id };
782
844
  };
783
- /** "me" inside an object argument (a GraphQL input's assigneeId, or each
784
- * of its subscriberIds): the same resolution, keyed by property name. */
845
+ /** "me" inside an object or array argument (a GraphQL input's assigneeId,
846
+ * each of its subscriberIds, each of GitHub's assignees): the same
847
+ * resolution, keyed by property name. */
785
848
  const resolveNestedMe = async (value, key, path) => {
786
849
  if (typeof value === "string") {
787
850
  if (!isMeReference(key, value))
788
851
  return { value };
789
- const caller = await callerId(path);
852
+ const caller = await callerId(path, key);
790
853
  return "outcome" in caller ? caller : { value: caller.id };
791
854
  }
792
855
  if (Array.isArray(value)) {
@@ -824,7 +887,7 @@ export async function resolveReferences(op, preparedArgs, options) {
824
887
  continue;
825
888
  const value = raw.trim();
826
889
  if (identity && isMeReference(param.name, value)) {
827
- const caller = await callerId(param.name);
890
+ const caller = await callerId(param.name, param.name);
828
891
  if ("outcome" in caller)
829
892
  return { ok: false, outcome: caller.outcome };
830
893
  args[param.name] = caller.id;
@@ -907,32 +970,59 @@ export function argumentsError(op, issues) {
907
970
  return { text: JSON.stringify(structured), isError: true, structured };
908
971
  }
909
972
  /** A fields path that selects nothing is an error that names the keys
910
- * that exist, not a silent {}. The API call already happened, so the error
911
- * says so, and a write's unprojected result comes back with it. */
912
- function unmatchedFieldsOutcome(value, fields, perItem, options) {
973
+ * that exist, not a silent {}. A path the response schema declares is not
974
+ * one: an optional key no item has is simply absent. The API call already
975
+ * happened, so the error says so and carries the unprojected result (as
976
+ * much as fits under half the cap), so neither a read nor a write has to
977
+ * run again to see it. */
978
+ function unmatchedFieldsOutcome(value, fields, perItem, options, schema, page) {
913
979
  if (fields === null)
914
980
  return null;
915
981
  const optional = new Set(options.optionalFields ?? []);
916
- const unmatched = unmatchedFields(value, fields).filter((u) => !optional.has(u.path));
982
+ const unmatched = unmatchedFields(value, fields, schema).filter((u) => !optional.has(u.path));
917
983
  if (unmatched.length === 0)
918
984
  return null;
919
985
  const write = options.safety !== "read";
920
986
  const maxChars = options.maxChars ?? DEFAULT_MAX_RESULT_CHARS;
921
- const full = write ? JSON.stringify(value) : undefined;
987
+ const retained = retainedResult(value, Math.floor(maxChars / 2));
988
+ const steps = [];
989
+ if (write)
990
+ steps.push("This operation has already run; do not call it again to change fields." + (retained.whole ? " Its full result is in result." : retained.result !== undefined ? " The first items of its result are in result." : ""));
991
+ else if (retained.whole)
992
+ steps.push("The full result is in result; use it rather than calling again.");
993
+ else if (retained.result !== undefined)
994
+ steps.push("result holds the first " + retained.result.length + " of " + value.length + " items; the rest are over the size cap.");
995
+ steps.push((write ? "Next time, use" : "To project a later call, use") + " fields from the available keys" + (perItem ? " (fields apply to each item)" : "") + ", or omit fields for the whole result.");
922
996
  const structured = {
923
997
  error: "UnmatchedFields",
924
998
  code: "FIELDS_UNMATCHED",
925
999
  message: unmatchedFieldsMessage(unmatched, perItem),
926
1000
  unmatched,
927
- ...(full !== undefined && full.length <= maxChars / 2 ? { result: value } : {}),
928
- next_steps: [
929
- ...(write
930
- ? ["This operation has already run; do not call it again to change fields." + (full !== undefined && full.length <= maxChars / 2 ? " Its full result is in result." : "")]
931
- : ["Call again with fields from the available keys" + (perItem ? " (fields apply to each item)" : "") + ", or omit fields for the whole result."]),
932
- ],
1001
+ ...(retained.result !== undefined ? { result: retained.result } : {}),
1002
+ ...(retained.omitted ? { result_omitted: retained.omitted } : {}),
1003
+ ...(page ? { hasMore: page.nextPage !== null, ...(page.nextPage !== null ? { nextPage: page.nextPage } : {}) } : {}),
1004
+ ...(options.requestId ? { request_id: options.requestId } : {}),
1005
+ next_steps: steps,
933
1006
  };
934
1007
  return { text: JSON.stringify(structured), isError: true, structured };
935
1008
  }
1009
+ /** The unprojected result an unmatched-fields error carries: whole when it
1010
+ * fits the budget, the leading items of a list when only some do, and
1011
+ * nothing for an object over the budget. */
1012
+ function retainedResult(value, budget) {
1013
+ const text = JSON.stringify(value);
1014
+ if (text === undefined)
1015
+ return { whole: false };
1016
+ if (text.length <= budget)
1017
+ return { result: value, whole: true };
1018
+ if (!Array.isArray(value))
1019
+ return { whole: false };
1020
+ const k = itemsThatFit(value, budget);
1021
+ const head = value.slice(0, k);
1022
+ if (JSON.stringify(head).length > budget)
1023
+ return { whole: false };
1024
+ return { result: head, whole: false, omitted: value.length - k };
1025
+ }
936
1026
  const fieldsHint = (perItem) => "Pass fields (dotted paths" + (perItem ? ", applied per item" : "") + ") to keep only the keys you need.";
937
1027
  /** How many leading items fit under `budget` characters once serialized
938
1028
  * compactly with commas between them. At least one. */
@@ -966,7 +1056,8 @@ export function pageOutcome(items, nextPage, options = {}) {
966
1056
  }
967
1057
  const fields = options.fields ?? null;
968
1058
  const maxChars = options.maxChars ?? DEFAULT_MAX_RESULT_CHARS;
969
- const unmatched = unmatchedFieldsOutcome(items, fields, true, options);
1059
+ const pageSchema = options.outputSchema?.properties;
1060
+ const unmatched = unmatchedFieldsOutcome(items, fields, true, options, pageSchema?.items, { nextPage });
970
1061
  if (unmatched)
971
1062
  return unmatched;
972
1063
  const shown = projectFields(items, fields);
@@ -1026,7 +1117,7 @@ const omittedMarker = (key, chars, maxChars) => chars > maxChars
1026
1117
  export function dataOutcome(data, options = {}) {
1027
1118
  const fields = options.fields ?? null;
1028
1119
  const maxChars = options.maxChars ?? DEFAULT_MAX_RESULT_CHARS;
1029
- const unmatched = data !== undefined && data !== null ? unmatchedFieldsOutcome(data, fields, Array.isArray(data), options) : null;
1120
+ const unmatched = data !== undefined && data !== null ? unmatchedFieldsOutcome(data, fields, Array.isArray(data), options, options.outputSchema) : null;
1030
1121
  if (unmatched)
1031
1122
  return unmatched;
1032
1123
  const value = data !== undefined && data !== null ? projectFields(data, fields) : { ok: true };
@@ -1156,6 +1247,31 @@ function payloadFailureCode(code) {
1156
1247
  return "RATE_LIMITED";
1157
1248
  return undefined;
1158
1249
  }
1250
+ /** The vendor's own code on an in-band GraphQL error: the first error's
1251
+ * extensions.code, or its type (GitHub). */
1252
+ function graphqlVendorCode(error) {
1253
+ const first = error?.errors?.[0];
1254
+ const code = first?.extensions?.code ?? first?.type;
1255
+ return typeof code === "string" && code !== "" ? code : undefined;
1256
+ }
1257
+ /** GraphQL error codes whose meaning is settled (Apollo's standard codes,
1258
+ * GitHub's types, Linear's codes), by recovery class. Any other code is
1259
+ * passed through as is, with no guessed next step. The CLI's
1260
+ * classification (cli-agent.ts) uses the same table. */
1261
+ function graphqlErrorClass(code) {
1262
+ const c = code.toUpperCase();
1263
+ if (c === "NOT_FOUND")
1264
+ return "not_found";
1265
+ if (c === "UNAUTHENTICATED" || c === "AUTHENTICATION_ERROR")
1266
+ return "unauthenticated";
1267
+ if (c === "FORBIDDEN")
1268
+ return "forbidden";
1269
+ if (c === "BAD_USER_INPUT" || c === "GRAPHQL_VALIDATION_FAILED" || c === "GRAPHQL_PARSE_FAILED" || c === "INPUT_ERROR")
1270
+ return "bad_input";
1271
+ if (c === "RATE_LIMITED" || c === "RATELIMITED")
1272
+ return "rate_limited";
1273
+ return undefined;
1274
+ }
1159
1275
  /** Classify an SDK error result by status: the code and what to do next. */
1160
1276
  export function classifyError(error, context = {}) {
1161
1277
  const e = (error ?? {});
@@ -1184,15 +1300,26 @@ export function classifyError(error, context = {}) {
1184
1300
  return { code: "RATE_LIMITED", nextSteps: [rateLimitNextStep(undefined)] };
1185
1301
  return { code: "CALL_FAILED", nextSteps: ["The API reported a failure in a successful response; body says why. Do not treat the call as done."] };
1186
1302
  }
1187
- if (status === 401) {
1188
- return context.hadCredential
1189
- ? { code: "AUTH_INVALID", nextSteps: ["The credential was rejected; it may be expired or for another environment." + (auth ? " " + auth : "")] }
1190
- : { code: "NO_AUTH", nextSteps: ["No credential was sent." + (auth ? " " + auth : "")] };
1303
+ const unauthenticated = () => context.hadCredential
1304
+ ? { code: "AUTH_INVALID", nextSteps: ["The credential was rejected; it may be expired or for another environment." + (auth ? " " + auth : "")] }
1305
+ : { code: "NO_AUTH", nextSteps: ["No credential was sent." + (auth ? " " + auth : "")] };
1306
+ const forbidden = () => scopes.length ? scopeFailure : { code: "AUTH_INVALID", nextSteps: ["The credential lacks access to this operation; it is not a retryable error."] };
1307
+ // An in-band GraphQL error (HTTP 200): classified by the vendor's code.
1308
+ if (e.name === "GraphQLRequestError") {
1309
+ const vendor = graphqlVendorCode(error);
1310
+ switch (vendor === undefined ? undefined : graphqlErrorClass(vendor)) {
1311
+ case "not_found": return { code: "NOT_FOUND", nextSteps: ["Check the id in the arguments; list the resource first to find the right one."] };
1312
+ case "unauthenticated": return unauthenticated();
1313
+ case "forbidden": return forbidden();
1314
+ case "bad_input": return { code: "INVALID_REQUEST", nextSteps: ["The API rejected an argument or the selection; the errors in body name it (message, path). Fix that and call again."] };
1315
+ case "rate_limited": return { code: "RATE_LIMITED", nextSteps: [rateLimitNextStep(undefined)] };
1316
+ default: return { code: "CALL_FAILED", nextSteps: [] };
1317
+ }
1191
1318
  }
1192
- if (status === 403 && scopes.length)
1193
- return scopeFailure;
1319
+ if (status === 401)
1320
+ return unauthenticated();
1194
1321
  if (status === 403)
1195
- return { code: "AUTH_INVALID", nextSteps: ["The credential lacks access to this operation; it is not a retryable error."] };
1322
+ return forbidden();
1196
1323
  if (status === 402)
1197
1324
  return { code: "PLAN_LIMIT", nextSteps: ["The account's plan stops here; the body may name where to lift the limit. Do not retry the same call as is."] };
1198
1325
  if (status === 404)
@@ -1251,9 +1378,12 @@ export function errorOutcome(error, context = {}) {
1251
1378
  ? e.body.request_id ?? e.body.requestId
1252
1379
  : undefined;
1253
1380
  const requestId = e?.response?.requestId ?? (typeof bodyRequestId === "string" ? bodyRequestId : undefined);
1381
+ // The vendor's identity for the failure, next to the normalized code.
1382
+ const vendorCode = e?.name === "GraphQLRequestError" ? graphqlVendorCode(error) : undefined;
1254
1383
  const structured = {
1255
1384
  error: e?.name ?? "Error",
1256
1385
  code,
1386
+ ...(vendorCode ? { vendor_code: vendorCode } : {}),
1257
1387
  message: e?.message,
1258
1388
  ...(typeof e?.status === "number" ? { status: e.status } : {}),
1259
1389
  ...(requestId ? { request_id: requestId } : {}),
@@ -1312,11 +1442,55 @@ const REFERENCE_DEPTH = 3;
1312
1442
  const REFERENCE_ARGUMENT_BUDGET = 6_000;
1313
1443
  /** Enum values listed inline; longer enums are cut with a count. */
1314
1444
  const REFERENCE_ENUM_VALUES = 30;
1315
- /** Prose for one description: one line, markdown links reduced to their text. */
1316
- function referenceProse(text) {
1445
+ /** HTML that API descriptions carry for formatting only. Anything else
1446
+ * in angle brackets (a <placeholder>) is text and stays. */
1447
+ const FORMATTING_TAGS = /<\/?(?:a|abbr|b|br|code|div|em|i|li|ol|p|pre|small|span|strong|sub|sup|u|ul)(?:\s[^<>]*)?\/?>/gi;
1448
+ /** A Markdown link or image: [text](url "title"), where the URL may hold
1449
+ * one level of parentheses (Wikipedia's Foo_(bar)). */
1450
+ const MARKDOWN_LINK = /!?\[([^\[\]]*(?:\[[^\[\]]*\][^\[\]]*)*)\]\((?:[^()\s]|\([^()\s]*\))*(?:\s+"[^"]*")?\)/g;
1451
+ /** Prose for one description, as one line: Markdown and HTML links reduced
1452
+ * to their visible text, formatting tags and emphasis dropped, <code> as
1453
+ * inline code (kept, because agents copy it). */
1454
+ export function referenceProse(text) {
1317
1455
  if (typeof text !== "string")
1318
1456
  return "";
1319
- return text.replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\s+/g, " ").trim();
1457
+ return text
1458
+ .replace(/<code>([\s\S]*?)<\/code>/gi, "`$1`")
1459
+ .replace(/<br\s*\/?>/gi, " ")
1460
+ .replace(FORMATTING_TAGS, "")
1461
+ .replace(MARKDOWN_LINK, "$1")
1462
+ .replace(/\[([^\[\]]+)\]\[[^\[\]]*\]/g, "$1")
1463
+ .replace(/(\*\*|__)(?=\S)([^*_]*?\S)\1/g, "$2")
1464
+ .replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"").replace(/&#39;/g, "'").replace(/&amp;/g, "&")
1465
+ .replace(/\s+/g, " ")
1466
+ .trim();
1467
+ }
1468
+ /** Abbreviations whose period does not end a sentence. */
1469
+ const ABBREVIATION = /(?:^|[\s(])(?:e\.g|i\.e|etc|vs|approx|incl|cf|no|min|max)\.$/i;
1470
+ /** A description's sentences, every character kept: a sentence ends at
1471
+ * . ! or ? (closing quotes and brackets included) before whitespace, not
1472
+ * after an abbreviation, and never inside inline code. */
1473
+ function sentencesOf(prose) {
1474
+ const sentences = [];
1475
+ let current = "";
1476
+ for (const part of prose.split(/(?<=[.!?]["')\]]?)\s+/)) {
1477
+ current = current ? current + " " + part : part;
1478
+ const openCode = (current.match(/`/g) ?? []).length % 2 === 1;
1479
+ if (!openCode && !ABBREVIATION.test(current)) {
1480
+ sentences.push(current);
1481
+ current = "";
1482
+ }
1483
+ }
1484
+ if (current)
1485
+ sentences.push(current);
1486
+ return sentences;
1487
+ }
1488
+ /** Cut one long sentence at a word boundary, never inside inline code. */
1489
+ function cutSentence(sentence, limit) {
1490
+ let cut = sentence.slice(0, limit).replace(/\s+\S*$/, "");
1491
+ if ((cut.match(/`/g) ?? []).length % 2 === 1)
1492
+ cut = cut.slice(0, cut.lastIndexOf("`")).trimEnd();
1493
+ return cut.replace(/[\s,;:(]+$/, "") + "…";
1320
1494
  }
1321
1495
  /** Sentences the generator appends to an argument's description because a
1322
1496
  * call depends on them: enum meanings, defaults, deprecation, reference
@@ -1327,22 +1501,20 @@ const REFERENCE_ARGUMENT_PROSE = 160;
1327
1501
  /** An argument's description, cut to its leading sentences within the
1328
1502
  * budget plus every note the generator added. The full text is in the
1329
1503
  * schema (schema: true). */
1330
- function argumentProse(text) {
1504
+ export function argumentProse(text) {
1331
1505
  const prose = referenceProse(text);
1332
1506
  if (prose.length <= REFERENCE_ARGUMENT_PROSE)
1333
1507
  return prose;
1334
- const sentences = prose.match(/[^.!?]+(?:[.!?]+(?=\s|$)|$)\s*/g) ?? [prose];
1335
1508
  const kept = [];
1336
1509
  let used = 0;
1337
1510
  let cut = false;
1338
- for (const raw of sentences) {
1339
- const sentence = raw.trim();
1511
+ for (const sentence of sentencesOf(prose)) {
1340
1512
  if (REFERENCE_NOTE.test(sentence)) {
1341
1513
  kept.push(sentence);
1342
1514
  continue;
1343
1515
  }
1344
1516
  if (kept.length === 0 || used + sentence.length <= REFERENCE_ARGUMENT_PROSE) {
1345
- kept.push(sentence.length > REFERENCE_ARGUMENT_PROSE * 2 ? sentence.slice(0, REFERENCE_ARGUMENT_PROSE * 2).replace(/\s+\S*$/, "") + "…" : sentence);
1517
+ kept.push(sentence.length > REFERENCE_ARGUMENT_PROSE * 2 ? cutSentence(sentence, REFERENCE_ARGUMENT_PROSE * 2) : sentence);
1346
1518
  used += sentence.length;
1347
1519
  }
1348
1520
  else {
@@ -1392,7 +1564,7 @@ function referenceObject(schema) {
1392
1564
  }
1393
1565
  /** One line per argument, nested fields indented beneath their object.
1394
1566
  * Each top-level argument is spelled out as deep as fits its budget. */
1395
- function referenceArguments(schema, lines) {
1567
+ function referenceArguments(schema, lines, context) {
1396
1568
  const properties = (schema.properties ?? {});
1397
1569
  for (const name of Object.keys(properties)) {
1398
1570
  const only = { ...schema, properties: { [name]: properties[name] } };
@@ -1401,14 +1573,14 @@ function referenceArguments(schema, lines) {
1401
1573
  // below the cut; then one level with names only.
1402
1574
  for (const [depth, names] of [[REFERENCE_DEPTH, true], [2, true], [2, false], [1, true]]) {
1403
1575
  block = [];
1404
- referenceArgumentLines(only, 0, depth, names, " ", block);
1576
+ referenceArgumentLines(only, 0, depth, names, " ", block, context);
1405
1577
  if (block.join("\n").length <= REFERENCE_ARGUMENT_BUDGET)
1406
1578
  break;
1407
1579
  }
1408
1580
  lines.push(...block);
1409
1581
  }
1410
1582
  }
1411
- function referenceArgumentLines(schema, depth, maxDepth, names, indent, lines) {
1583
+ function referenceArgumentLines(schema, depth, maxDepth, names, indent, lines, context) {
1412
1584
  const properties = (schema.properties ?? {});
1413
1585
  const required = new Set(Array.isArray(schema.required) ? schema.required : []);
1414
1586
  for (const [name, child] of Object.entries(properties)) {
@@ -1419,18 +1591,29 @@ function referenceArgumentLines(schema, depth, maxDepth, names, indent, lines) {
1419
1591
  ...(child.default !== undefined && !/\bdefault\b/i.test(description) ? ["default " + JSON.stringify(child.default)] : []),
1420
1592
  ...(child.deprecated === true && !/deprecated/i.test(description) ? ["deprecated"] : []),
1421
1593
  ];
1422
- lines.push(indent + name + " (" + referenceType(child) + (required.has(name) ? ", required" : "") + (extras.length ? ", " + extras.join(", ") : "") + ")" + (description ? ": " + description : ""));
1423
1594
  const nested = referenceObject(child);
1595
+ // An object argument reads as its named type (IssueFilter), which
1596
+ // read_docs can look up; everything else keeps its inline type.
1597
+ const expression = context.path.length === 0
1598
+ ? context.types?.args[context.tool]?.[name]
1599
+ : context.typeName ? context.types?.types[context.typeName]?.fields?.[name]?.type : undefined;
1600
+ const named = nested ? namedTypesIn(context.types, expression) : [];
1601
+ const type = named.length > 0 ? expression : referenceType(child);
1602
+ lines.push(indent + name + " (" + type + (required.has(name) ? ", required" : "") + (extras.length ? ", " + extras.join(", ") : "") + ")" + (description ? ": " + description : ""));
1424
1603
  if (!nested)
1425
1604
  continue;
1605
+ const path = [...context.path, name];
1606
+ const objects = named.filter((typeName) => context.types.types[typeName].fields);
1607
+ const inner = { ...context, path, typeName: objects.length === 1 ? objects[0] : undefined };
1426
1608
  if (depth + 1 < maxDepth) {
1427
- referenceArgumentLines(nested, depth + 1, maxDepth, names, indent + " ", lines);
1609
+ referenceArgumentLines(nested, depth + 1, maxDepth, names, indent + " ", lines, inner);
1428
1610
  continue;
1429
1611
  }
1430
1612
  if (!names)
1431
1613
  continue;
1432
1614
  const keys = Object.keys(nested.properties);
1433
- lines.push(indent + " fields: " + keys.slice(0, 40).join(", ") + (keys.length > 40 ? ", … " + (keys.length - 40) + " more" : "") + " (types with schema: true)");
1615
+ // Field types (and any cut names) are one path lookup away.
1616
+ lines.push(indent + " fields: " + keys.slice(0, 40).join(", ") + (keys.length > 40 ? ", … " + (keys.length - 40) + " more: read_docs " + JSON.stringify({ page: context.tool, path: path.join(".") }) + " lists all" : ""));
1434
1617
  }
1435
1618
  }
1436
1619
  /** A result's shape in one line: keys and types, one level into objects. */
@@ -1481,7 +1664,7 @@ export function referenceText(op, options = {}) {
1481
1664
  const input = toolInputSchema(op);
1482
1665
  if (Object.keys((input.properties ?? {})).length > 0) {
1483
1666
  lines.push("", "Arguments:");
1484
- referenceArguments(input, lines);
1667
+ referenceArguments(input, lines, { tool: op.tool, types: options.types, path: [] });
1485
1668
  }
1486
1669
  lines.push("", "Example arguments: " + JSON.stringify(example));
1487
1670
  if (op.outputSchema) {
@@ -1493,6 +1676,15 @@ export function referenceText(op, options = {}) {
1493
1676
  lines.push("", "Output schema: " + JSON.stringify(op.outputSchema));
1494
1677
  }
1495
1678
  else {
1679
+ // Say how to drill into an object argument: the page above stops at a
1680
+ // depth and a size, and the schema is the whole graph at once.
1681
+ const nested = Object.entries((input.properties ?? {}))
1682
+ .find(([, child]) => child && typeof child === "object" && referenceObject(child))?.[0];
1683
+ if (nested) {
1684
+ const named = namedTypesIn(options.types, options.types?.args[op.tool]?.[nested])[0];
1685
+ lines.push("", "Nested arguments: read_docs " + JSON.stringify({ page: op.tool, path: nested }) + " gives an argument's type and all its fields; extend the path (\"" + nested + ".<field>\") to go deeper."
1686
+ + (named ? " Named types read the same way: read_docs " + JSON.stringify({ page: named }) + "." : ""));
1687
+ }
1496
1688
  lines.push("", "Full input and output JSON Schemas: read_docs " + JSON.stringify({ page: op.tool, schema: true }) + ".");
1497
1689
  }
1498
1690
  return lines.join("\n");
@@ -1561,22 +1753,51 @@ function searchLabel(op) {
1561
1753
  export const READ_DOCS_LIMIT = 20_000;
1562
1754
  /** One part of a long page, ending with how to read the next part. */
1563
1755
  function docsPart(page, text, offset, schema = false) {
1756
+ return docsPartFor({ page, ...(schema ? { schema: true } : {}) }, text, offset);
1757
+ }
1758
+ /** docsPart for any read_docs arguments (a page, a path, schema). */
1759
+ function docsPartFor(request, text, offset) {
1564
1760
  if (offset <= 0 && text.length <= READ_DOCS_LIMIT)
1565
1761
  return text;
1566
1762
  const start = Math.min(Math.max(0, offset), text.length);
1567
1763
  const end = Math.min(text.length, start + READ_DOCS_LIMIT);
1568
1764
  const more = end < text.length
1569
- ? "\n\n[Characters " + start + "-" + end + " of " + text.length + ". Continue with read_docs " + JSON.stringify({ page, ...(schema ? { schema: true } : {}), offset: end }) + ".]"
1765
+ ? "\n\n[Characters " + start + "-" + end + " of " + text.length + ". Continue with read_docs " + JSON.stringify({ ...request, offset: end }) + ".]"
1570
1766
  : "\n\n[Characters " + start + "-" + end + " of " + text.length + "; end of page.]";
1571
1767
  return text.slice(start, end) + more;
1572
1768
  }
1769
+ /** How read_docs names a type lookup, for the pages that mention types. */
1770
+ function readDocsTypeHint(name) {
1771
+ return "read_docs " + JSON.stringify({ page: name });
1772
+ }
1773
+ /** One argument path within an operation or a named type. */
1774
+ function docsPathOutcome(source, root, page, path, offset) {
1775
+ const found = argumentPathText(source.inputTypes, root, path, readDocsTypeHint);
1776
+ if (found.ok)
1777
+ return { text: docsPartFor({ page, path }, found.text, offset), isError: false };
1778
+ const structured = {
1779
+ error: "NotFoundError", code: "NOT_FOUND", message: found.message,
1780
+ ...(found.available.length > 0 ? { available: found.available } : {}),
1781
+ next_steps: [found.available.length > 0 ? "Pass one of the available fields as the next path segment." : "read_docs " + JSON.stringify({ page }) + " lists the arguments."],
1782
+ };
1783
+ return { text: JSON.stringify(structured), isError: true, structured };
1784
+ }
1573
1785
  export async function docsRead(source, page, offset = 0, options = {}) {
1574
1786
  const opMatch = findOperation(source.ops, page);
1787
+ const typeMatch = opMatch ? undefined : findInputType(source.inputTypes, page);
1788
+ if (options.path !== undefined && options.path.trim() !== "") {
1789
+ if (opMatch)
1790
+ return docsPathOutcome(source, opMatch, page, options.path, offset);
1791
+ if (typeMatch)
1792
+ return docsPathOutcome(source, { type: typeMatch }, page, options.path, offset);
1793
+ }
1575
1794
  if (opMatch)
1576
- return { text: docsPart(page, referenceText(opMatch, options), offset, options.schema === true), isError: false };
1795
+ return { text: docsPart(page, referenceText(opMatch, { schema: options.schema, types: source.inputTypes }), offset, options.schema === true), isError: false };
1577
1796
  const omittedMatch = findOperation(source.omittedOps ?? [], page);
1578
1797
  if (omittedMatch)
1579
1798
  return omittedPlanLimit(source, [omittedMatch], omittedMatch.tool);
1799
+ if (typeMatch)
1800
+ return { text: docsPartFor({ page }, inputTypeText(source.inputTypes, typeMatch, readDocsTypeHint), offset), isError: false };
1580
1801
  let target = page;
1581
1802
  if (!/^https?:\/\//.test(target)) {
1582
1803
  const index = await fetchDocs(source, "llms.txt");
@@ -1611,7 +1832,8 @@ export async function callSharedTool(name, args, source, runOperation) {
1611
1832
  if (hidden)
1612
1833
  return hiddenOutcome(hidden);
1613
1834
  }
1614
- return typeof args.page === "string" ? docsRead(source, args.page, offset, { schema }) : argumentsError({ tool: name }, [{ code: "MISSING_ARGUMENT", argument: "page", message: "read_docs requires a page string." }]);
1835
+ const path = typeof args.path === "string" ? args.path : undefined;
1836
+ return typeof args.page === "string" ? docsRead(source, args.page, offset, { schema, path }) : argumentsError({ tool: name }, [{ code: "MISSING_ARGUMENT", argument: "page", message: "read_docs requires a page string." }]);
1615
1837
  }
1616
1838
  if (name === "execute") {
1617
1839
  if (typeof args.operation !== "string")
package/dist/mcp.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":";AAuBA,OAAO,EAAoC,KAAK,aAAa,EAAmB,MAAM,YAAY,CAAC;AAenG,OAAO,EAA8C,KAAK,6BAA6B,EAAE,KAAK,kCAAkC,EAAE,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACpL,YAAY,EAAE,6BAA6B,EAAE,kCAAkC,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC9H,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAma3D,MAAM,WAAW,cAAc;IAC7B,aAAa,CAAC,EAAE,6BAA6B,CAAC;IAC9C;kFAC8E;IAC9E,aAAa,CAAC,EAAE,kCAAkC,CAAC;IACnD;;;;iFAI6E;IAC7E,cAAc,CAAC,SAAS,EAAE,YAAY,GAAG,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CACjF;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAMjG;AAED;+EAC+E;AAC/E,wBAAsB,UAAU,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAErE"}
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":";AAuBA,OAAO,EAAoC,KAAK,aAAa,EAAmB,MAAM,YAAY,CAAC;AAenG,OAAO,EAA8C,KAAK,6BAA6B,EAAE,KAAK,kCAAkC,EAAE,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACpL,YAAY,EAAE,6BAA6B,EAAE,kCAAkC,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC9H,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAoa3D,MAAM,WAAW,cAAc;IAC7B,aAAa,CAAC,EAAE,6BAA6B,CAAC;IAC9C;kFAC8E;IAC9E,aAAa,CAAC,EAAE,kCAAkC,CAAC;IACnD;;;;iFAI6E;IAC7E,cAAc,CAAC,SAAS,EAAE,YAAY,GAAG,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CACjF;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAMjG;AAED;+EAC+E;AAC/E,wBAAsB,UAAU,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAErE"}
package/dist/mcp.js CHANGED
@@ -22,7 +22,7 @@ import { basename, join } from "node:path";
22
22
  import { fileURLToPath } from "node:url";
23
23
  import { TypeshipClient, formatDebugEvent } from "./index.js";
24
24
  import { asApiResult, mediaTypeForPath } from "./core/http.js";
25
- import { GLOBALS, OMITTED_OPS, OPS, buildArgs } from "./ops.js";
25
+ import { GLOBALS, INPUT_TYPES, OMITTED_OPS, OPS, buildArgs } from "./ops.js";
26
26
  import { DEFAULT_MAX_RESULT_CHARS, SUPPORTED_PROTOCOL_VERSIONS, McpAccountLinkRequired, argumentsError, asJsonRpc, binaryOutcome, callSharedTool, checkRequestHeaders, createStdioRpcHandler, dataOutcome, errorOutcome, handleRpc, hiddenOperations, isRpcOutcome, pageOutcome, parseIncludeList, prepareCall, resolveReferences, serverInstructions, requiredScopes, takeCancelled, textError, toolDefinitions, unmatchedIncludes, unsupportedAuthOutcome, visibleOps, } from "./mcp-protocol.js";
27
27
  import { fetchDocsText } from "./docs.js";
28
28
  import { assertCredentialDestination, assertStoredIdentity } from "./oauth-session.js";
@@ -34,7 +34,7 @@ export { McpAccountLinkRequired } from "./mcp-protocol.js";
34
34
  const BIN = "typeship";
35
35
  const PKG_NAME = "@typeship-ax/mcp";
36
36
  const SERVER_NAME = "typeship-mcp";
37
- const SERVER_VERSION = "0.22.0";
37
+ const SERVER_VERSION = "0.23.0";
38
38
  /** The MCP client's announced name (clientInfo in request _meta), for the User-Agent. */
39
39
  let MCP_CLIENT_NAME = null;
40
40
  function noteClientInfo(message) {
@@ -272,7 +272,7 @@ async function callOperationRaw(op, rawArgs, remote, client) {
272
272
  hadCredential: !!remote || CLIENT_CREDENTIALS.get(client) === true,
273
273
  requiredScopes: requiredScopes(op.security),
274
274
  };
275
- const shape = { fields, optionalFields, maxChars, pagination: op.pagination, args, safety: op.safety };
275
+ const shape = { fields, optionalFields, maxChars, pagination: op.pagination, args, safety: op.safety, outputSchema: op.outputSchema };
276
276
  try {
277
277
  const target = client[op.resource];
278
278
  let result = await asApiResult(target[op.method](...callArgs));
@@ -398,6 +398,7 @@ function saveBinary(bytes, _mediaType, suggestedName) {
398
398
  const docsSource = {
399
399
  ops: MCP_OPS,
400
400
  omittedOps: OMITTED_OPS,
401
+ inputTypes: INPUT_TYPES,
401
402
  hiddenOps: hiddenOperations(OPS, { readOnly: READ_ONLY, include: INCLUDE, uploads: LOCAL_PROCESS }, { readOnly: READ_ONLY_SWITCH, include: TOOLS_SWITCH }),
402
403
  generatedOperationCount: OPS.length,
403
404
  docsUrl: () => readJson("config.json")?.docsUrl ?? DOCS_URL_DEFAULT,
package/dist/ops.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { InputTypes } from "./type-docs.js";
1
2
  export interface ParamSpec {
2
3
  /** wire name (JSON key, query name, or path placeholder) */
3
4
  name: string;
@@ -166,6 +167,10 @@ export type OmittedOpSpec = Pick<OpSpec, "resource" | "method" | "command" | "co
166
167
  };
167
168
  };
168
169
  export declare const OMITTED_OPS: OmittedOpSpec[];
170
+ /** Named input types the operations' arguments reach, each described once
171
+ * with its fields typed by name: what docs lookups by type or argument
172
+ * path read. */
173
+ export declare const INPUT_TYPES: InputTypes;
169
174
  /** Parameters settable once on the client (globals option): CLI/MCP fill
170
175
  * them from flags or environment; the SDK falls back per request. */
171
176
  export declare const GLOBALS: {