@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.
- package/AGENTS.md +1 -1
- package/README.md +1 -1
- package/api.md +1 -1
- package/dist/arguments.d.ts +9 -2
- package/dist/arguments.d.ts.map +1 -1
- package/dist/arguments.js +17 -6
- package/dist/fields.d.ts +8 -1
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +91 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/mcp-protocol.d.ts +21 -0
- package/dist/mcp-protocol.d.ts.map +1 -1
- package/dist/mcp-protocol.js +280 -58
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +4 -3
- package/dist/ops.d.ts +5 -0
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js +66 -6
- package/dist/resources/deliveries.d.ts +1 -1
- package/dist/resources/deliveries.d.ts.map +1 -1
- package/dist/resources/deliveries.js +1 -1
- package/dist/resources/drafts.d.ts +1 -1
- package/dist/resources/drafts.d.ts.map +1 -1
- package/dist/resources/drafts.js +1 -1
- package/dist/resources/generations.d.ts +2 -2
- package/dist/resources/generations.d.ts.map +1 -1
- package/dist/resources/generations.js +2 -2
- package/dist/resources/releases.d.ts +1 -1
- package/dist/resources/releases.d.ts.map +1 -1
- package/dist/resources/releases.js +1 -1
- package/dist/resources/spec-revisions.d.ts +1 -1
- package/dist/resources/spec-revisions.d.ts.map +1 -1
- package/dist/resources/spec-revisions.js +1 -1
- package/dist/resources/targets.d.ts +1 -1
- package/dist/resources/targets.d.ts.map +1 -1
- package/dist/resources/targets.js +1 -1
- package/dist/type-docs.d.ts +61 -0
- package/dist/type-docs.d.ts.map +1 -0
- package/dist/type-docs.js +174 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/arguments.ts +19 -7
- package/src/fields.ts +81 -5
- package/src/index.ts +3 -3
- package/src/mcp-protocol.ts +279 -58
- package/src/mcp.ts +4 -3
- package/src/ops.ts +69 -6
- package/src/resources/deliveries.ts +2 -2
- package/src/resources/drafts.ts +2 -2
- package/src/resources/generations.ts +4 -4
- package/src/resources/releases.ts +2 -2
- package/src/resources/spec-revisions.ts +2 -2
- package/src/resources/targets.ts +2 -2
- package/src/type-docs.ts +205 -0
package/dist/mcp-protocol.js
CHANGED
|
@@ -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
|
-
|
|
565
|
-
|
|
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
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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
|
-
|
|
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,
|
|
784
|
-
* of its subscriberIds): the same
|
|
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 {}.
|
|
911
|
-
*
|
|
912
|
-
|
|
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
|
|
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
|
-
...(
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
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
|
|
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
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
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 ===
|
|
1193
|
-
return
|
|
1319
|
+
if (status === 401)
|
|
1320
|
+
return unauthenticated();
|
|
1194
1321
|
if (status === 403)
|
|
1195
|
-
return
|
|
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
|
-
/**
|
|
1316
|
-
|
|
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
|
|
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(/ /g, " ").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, "\"").replace(/'/g, "'").replace(/&/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
|
|
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
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
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;
|
|
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.
|
|
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: {
|