@typeship-ax/mcp 0.8.0 → 0.10.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 +31 -0
- package/README.md +66 -9
- package/api.json +3153 -761
- package/api.md +9796 -382
- package/dist/api-identity.d.ts +40 -0
- package/dist/api-identity.d.ts.map +1 -0
- package/dist/api-identity.js +128 -0
- package/dist/auth-profiles.d.ts +30 -0
- package/dist/auth-profiles.d.ts.map +1 -0
- package/dist/auth-profiles.js +138 -0
- package/dist/core/http.d.ts +17 -2
- package/dist/core/http.d.ts.map +1 -1
- package/dist/core/http.js +78 -17
- package/dist/credential-storage.d.ts +24 -0
- package/dist/credential-storage.d.ts.map +1 -0
- package/dist/credential-storage.js +207 -0
- package/dist/docs.d.ts +25 -0
- package/dist/docs.d.ts.map +1 -1
- package/dist/docs.js +144 -0
- package/dist/errors.d.ts +18 -10
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +24 -14
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -4
- package/dist/mcp-authorization.d.ts +52 -0
- package/dist/mcp-authorization.d.ts.map +1 -0
- package/dist/mcp-authorization.js +232 -0
- package/dist/mcp-protocol.d.ts +51 -2
- package/dist/mcp-protocol.d.ts.map +1 -1
- package/dist/mcp-protocol.js +249 -37
- package/dist/mcp.d.ts +21 -3
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +185 -68
- package/dist/named-credentials.d.ts +21 -0
- package/dist/named-credentials.d.ts.map +1 -0
- package/dist/named-credentials.js +86 -0
- package/dist/oauth-request.d.ts +21 -0
- package/dist/oauth-request.d.ts.map +1 -0
- package/dist/oauth-request.js +119 -0
- package/dist/oauth-session.d.ts +106 -0
- package/dist/oauth-session.d.ts.map +1 -0
- package/dist/oauth-session.js +244 -0
- package/dist/ops.d.ts +14 -1
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js +34 -30
- package/dist/resources/account.d.ts +2 -2
- package/dist/resources/account.d.ts.map +1 -1
- package/dist/resources/account.js +1 -0
- package/dist/resources/api-keys.d.ts +3 -3
- package/dist/resources/api-keys.d.ts.map +1 -1
- package/dist/resources/api-keys.js +2 -0
- package/dist/resources/definition-revisions.d.ts +5 -5
- package/dist/resources/definition-revisions.d.ts.map +1 -1
- package/dist/resources/definition-revisions.js +4 -0
- package/dist/resources/definitions.d.ts +15 -4
- package/dist/resources/definitions.d.ts.map +1 -1
- package/dist/resources/definitions.js +11 -2
- package/dist/resources/generate.d.ts +14 -3
- package/dist/resources/generate.d.ts.map +1 -1
- package/dist/resources/generate.js +10 -2
- package/dist/resources/generations.d.ts +3 -3
- package/dist/resources/generations.d.ts.map +1 -1
- package/dist/resources/generations.js +2 -0
- package/dist/resources/projects.d.ts +56 -20
- package/dist/resources/projects.d.ts.map +1 -1
- package/dist/resources/projects.js +40 -4
- package/dist/resources/targets.d.ts +84 -10
- package/dist/resources/targets.d.ts.map +1 -1
- package/dist/resources/targets.js +127 -2
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +55 -26
- package/dist/types.d.ts +602 -123
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +24 -0
- package/dist/worker.js +4 -4
- package/package.json +11 -1
- package/server.json +42 -0
- package/src/api-identity.ts +98 -0
- package/src/auth-profiles.ts +114 -0
- package/src/core/http.ts +88 -19
- package/src/credential-storage.ts +183 -0
- package/src/docs.ts +138 -0
- package/src/errors.ts +26 -15
- package/src/index.ts +29 -4
- package/src/mcp-authorization.ts +211 -0
- package/src/mcp-protocol.ts +287 -38
- package/src/mcp.ts +186 -72
- package/src/named-credentials.ts +74 -0
- package/src/oauth-request.ts +90 -0
- package/src/oauth-session.ts +258 -0
- package/src/ops.ts +48 -31
- package/src/resources/account.ts +3 -0
- package/src/resources/api-keys.ts +5 -0
- package/src/resources/definition-revisions.ts +9 -0
- package/src/resources/definitions.ts +25 -0
- package/src/resources/generate.ts +23 -0
- package/src/resources/generations.ts +5 -0
- package/src/resources/projects.ts +95 -7
- package/src/resources/targets.ts +241 -0
- package/src/schemas.ts +55 -26
- package/src/types.ts +640 -123
- package/src/worker.ts +4 -4
package/dist/mcp-protocol.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* Spec: https://modelcontextprotocol.io/specification/2026-07-28
|
|
16
16
|
*/
|
|
17
17
|
import { dateKindOf, relativeDate } from "./dates.js";
|
|
18
|
-
import { resolveDocsContentUrl } from "./docs.js";
|
|
18
|
+
import { docsReadTarget, resolveDocsContentUrl, searchConnectedGuides } from "./docs.js";
|
|
19
19
|
export const MCP_PROTOCOL_VERSION = "2026-07-28";
|
|
20
20
|
/** Revisions served. Legacy (initialize-handshake) revisions are not; an
|
|
21
21
|
* initialize request gets an error naming this list, as the spec asks of
|
|
@@ -29,6 +29,30 @@ export const META_SERVER_INFO = "io.modelcontextprotocol/serverInfo";
|
|
|
29
29
|
* ask for less. Roughly 16k tokens: under every major client's own cap, so
|
|
30
30
|
* the agent sees this explanation instead of a mid-JSON chop. */
|
|
31
31
|
export const DEFAULT_MAX_RESULT_CHARS = 64_000;
|
|
32
|
+
/** Throw only from an application-owned credential resolver, before calling
|
|
33
|
+
* the API. The URL must show a sign-in/linking page, never a pre-authenticated
|
|
34
|
+
* resource, token or personal information. The page must verify the same user
|
|
35
|
+
* before linking. The runtime rechecks credentials on every subsequent call. */
|
|
36
|
+
export class McpAccountLinkRequired extends Error {
|
|
37
|
+
url;
|
|
38
|
+
constructor(url) {
|
|
39
|
+
super("Connect your API account in the browser to continue.");
|
|
40
|
+
this.name = "McpAccountLinkRequired";
|
|
41
|
+
try {
|
|
42
|
+
const parsed = new URL(url);
|
|
43
|
+
if (url.length > 2048 || url !== url.trim() || /[\u0000-\u0020\u007F"\\]/.test(url) || parsed.username || parsed.password || parsed.hash ||
|
|
44
|
+
!(parsed.protocol === "https:" || parsed.protocol === "http:" && ["127.0.0.1", "[::1]", "localhost"].includes(parsed.hostname)) ||
|
|
45
|
+
[...parsed.searchParams.keys()].some(key => /^(access_token|refresh_token|client_secret|api_key|password|authorization)$/i.test(key)))
|
|
46
|
+
throw new Error();
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
throw new Error("Provide a public HTTPS account-linking page without credentials or a fragment (HTTP loopback is allowed for development).");
|
|
50
|
+
}
|
|
51
|
+
this.url = url;
|
|
52
|
+
Object.freeze(this);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const API_LINK_INPUT = "typeship_api_account";
|
|
32
56
|
/** Does the operation take a file (multipart form or raw binary body)? */
|
|
33
57
|
export function isUploadOp(op) {
|
|
34
58
|
return op.bodyKind === "multipart" || op.bodyKind === "binary";
|
|
@@ -180,11 +204,43 @@ export async function handleRpc(server, incoming) {
|
|
|
180
204
|
if (args === null || typeof args !== "object" || Array.isArray(args)) {
|
|
181
205
|
return rpcError(id, -32602, "tools/call arguments must be an object", 400);
|
|
182
206
|
}
|
|
207
|
+
const responses = request.params?.inputResponses;
|
|
208
|
+
if (responses !== undefined && (responses === null || typeof responses !== "object" || Array.isArray(responses)))
|
|
209
|
+
return rpcError(id, -32602, "inputResponses must be an object", 400);
|
|
210
|
+
const response = responses && Object.hasOwn(responses, API_LINK_INPUT) ? responses[API_LINK_INPUT] : undefined;
|
|
211
|
+
if (response !== undefined) {
|
|
212
|
+
if (!response || typeof response !== "object" || Array.isArray(response) || !["accept", "decline", "cancel"].includes(response.action))
|
|
213
|
+
return rpcError(id, -32602, "Invalid API account-link response", 400);
|
|
214
|
+
if (response.action !== "accept") {
|
|
215
|
+
const cancelled = textError("API account linking was cancelled. No API request was made.", "ACCOUNT_LINK_CANCELLED");
|
|
216
|
+
return complete({ content: [{ type: "text", text: cancelled.text }], isError: true });
|
|
217
|
+
}
|
|
218
|
+
// A client acknowledgment is not authorization. Only a fresh lookup
|
|
219
|
+
// of the server's linked credentials can let the operation proceed.
|
|
220
|
+
}
|
|
183
221
|
const denied = server.beforeToolCall ? await server.beforeToolCall(name) : null;
|
|
184
222
|
if (denied)
|
|
185
223
|
return denied;
|
|
186
224
|
const started = Date.now();
|
|
187
|
-
|
|
225
|
+
let outcome;
|
|
226
|
+
try {
|
|
227
|
+
outcome = await server.callTool(name, args);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
if (!(error instanceof McpAccountLinkRequired))
|
|
231
|
+
throw error;
|
|
232
|
+
const caps = (request.params?._meta)[META_CLIENT_CAPS];
|
|
233
|
+
const urlMode = caps.elicitation?.url;
|
|
234
|
+
if (urlMode === null || typeof urlMode !== "object" || Array.isArray(urlMode)) {
|
|
235
|
+
const unsupported = textError("Connect your API account using an MCP client that supports browser account-linking prompts (URL-mode elicitation), then retry.", "ACCOUNT_LINK_REQUIRED");
|
|
236
|
+
return complete({ content: [{ type: "text", text: unsupported.text }], isError: true });
|
|
237
|
+
}
|
|
238
|
+
return { status: 200, message: { jsonrpc: "2.0", id, result: {
|
|
239
|
+
resultType: "input_required",
|
|
240
|
+
inputRequests: { [API_LINK_INPUT]: { method: "elicitation/create", params: { mode: "url", url: error.url, message: "Connect your API account in the browser to continue." } } },
|
|
241
|
+
_meta: { [META_SERVER_INFO]: server.serverInfo },
|
|
242
|
+
} } };
|
|
243
|
+
}
|
|
188
244
|
if (outcome === undefined)
|
|
189
245
|
return rpcError(id, -32602, server.unknownToolMessage?.(name) ?? "Unknown tool: " + name, 400);
|
|
190
246
|
if (server.afterToolCall)
|
|
@@ -373,7 +429,7 @@ function exampleMatchingPattern(pattern, minLength, maxLength) {
|
|
|
373
429
|
value = value.slice(0, maxLength);
|
|
374
430
|
return value;
|
|
375
431
|
};
|
|
376
|
-
const candidates = [prefix + "123", prefix + "example", prefix, "example", "value"];
|
|
432
|
+
const candidates = [prefix + "123", prefix + "example", prefix, "resource.method", "example.value", "example_123", "example", "value"];
|
|
377
433
|
for (const candidate of candidates) {
|
|
378
434
|
const value = fit(candidate);
|
|
379
435
|
regex.lastIndex = 0;
|
|
@@ -517,6 +573,11 @@ export function serverInstructions(input) {
|
|
|
517
573
|
parts.push("Plan-limited generation: generated " + generated + " of " + (generated + input.omittedOps.length) + " operations. Omitted operations: " + input.omittedOps.map((op) => op.tool + " (" + op.httpMethod + " " + op.path + ")").join(", ") + ". Calling or searching for one returns PLAN_LIMIT with upgrade next_steps.");
|
|
518
574
|
}
|
|
519
575
|
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).");
|
|
576
|
+
if (input.referenceResolution)
|
|
577
|
+
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.");
|
|
578
|
+
if (input.identityTool && input.ops.some((op) => op.params.some((param) => param.type === "string" && param.resolve !== false && userShapedReference(param.name)))) {
|
|
579
|
+
parts.push("User-shaped reference arguments also accept \"me\", resolved through " + input.identityTool + ".");
|
|
580
|
+
}
|
|
520
581
|
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.");
|
|
521
582
|
parts.push("Errors carry error, code, message, status, the API's body and next_steps.");
|
|
522
583
|
if (input.authHint)
|
|
@@ -750,6 +811,175 @@ export function prepareCall(op, rawArgs, options = {}) {
|
|
|
750
811
|
return { ok: false, outcome: argumentsError(op, issues) };
|
|
751
812
|
return { ok: true, call: { args, fields, maxChars: options.maxChars ?? DEFAULT_MAX_RESULT_CHARS } };
|
|
752
813
|
}
|
|
814
|
+
function referenceError(code, message, argument, nextSteps, candidates) {
|
|
815
|
+
const structured = {
|
|
816
|
+
error: code === "REFERENCE_NOT_FOUND" ? "ReferenceNotFound" : code === "REFERENCE_AMBIGUOUS" ? "AmbiguousReference" : code === "REFERENCE_SCAN_LIMIT" ? "ReferenceScanLimit" : "ReferenceResolutionUnavailable",
|
|
817
|
+
code,
|
|
818
|
+
message,
|
|
819
|
+
argument,
|
|
820
|
+
...(candidates ? { candidates } : {}),
|
|
821
|
+
next_steps: nextSteps,
|
|
822
|
+
};
|
|
823
|
+
return { text: JSON.stringify(structured), isError: true, structured };
|
|
824
|
+
}
|
|
825
|
+
function outcomeValue(outcome) {
|
|
826
|
+
if (outcome.structured !== undefined)
|
|
827
|
+
return outcome.structured;
|
|
828
|
+
try {
|
|
829
|
+
return JSON.parse(outcome.text);
|
|
830
|
+
}
|
|
831
|
+
catch {
|
|
832
|
+
return undefined;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
function referencePage(value) {
|
|
836
|
+
if (Array.isArray(value)) {
|
|
837
|
+
return { items: value.filter((item) => Boolean(item) && typeof item === "object" && !Array.isArray(item)), nextPage: null };
|
|
838
|
+
}
|
|
839
|
+
if (!value || typeof value !== "object")
|
|
840
|
+
return null;
|
|
841
|
+
const record = value;
|
|
842
|
+
const direct = Array.isArray(record.items) ? record.items : undefined;
|
|
843
|
+
const arrays = direct ? [direct] : Object.entries(record)
|
|
844
|
+
.filter(([name, entry]) => name !== "request_id" && name !== "requestId" && Array.isArray(entry))
|
|
845
|
+
.map(([, entry]) => entry);
|
|
846
|
+
if (arrays.length !== 1)
|
|
847
|
+
return null;
|
|
848
|
+
return {
|
|
849
|
+
items: arrays[0].filter((item) => Boolean(item) && typeof item === "object" && !Array.isArray(item)),
|
|
850
|
+
nextPage: record.nextPage && typeof record.nextPage === "object" && !Array.isArray(record.nextPage)
|
|
851
|
+
? record.nextPage
|
|
852
|
+
: null,
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
function userShapedReference(name) {
|
|
856
|
+
const normalized = name.toLowerCase().replace(/[^a-z0-9]/g, "").replace(/id$/, "");
|
|
857
|
+
return ["user", "assignee", "owner", "member", "actor", "creator", "account", "profile"].includes(normalized);
|
|
858
|
+
}
|
|
859
|
+
function referenceMatchLabel(fields) {
|
|
860
|
+
return fields.length === 1 ? fields[0] : fields.slice(0, -1).join(", ") + " or " + fields.at(-1);
|
|
861
|
+
}
|
|
862
|
+
function looksLikeIdentifier(value, resolver) {
|
|
863
|
+
if (/\s/.test(value))
|
|
864
|
+
return false;
|
|
865
|
+
if (resolver.idPattern !== undefined) {
|
|
866
|
+
try {
|
|
867
|
+
return new RegExp(resolver.idPattern).test(value);
|
|
868
|
+
}
|
|
869
|
+
catch {
|
|
870
|
+
return false;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
return /^\d+$/.test(value) ||
|
|
874
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value) ||
|
|
875
|
+
/^[a-z][a-z0-9]*_[a-z0-9][a-z0-9_-]*$/i.test(value) ||
|
|
876
|
+
/^[A-Z][A-Z0-9]{1,9}-\d+$/.test(value) ||
|
|
877
|
+
/^[A-Za-z0-9_-]{20,}$/.test(value);
|
|
878
|
+
}
|
|
879
|
+
function putReferenceCache(cache, key, value) {
|
|
880
|
+
cache.set(key, value);
|
|
881
|
+
while (cache.size > 256)
|
|
882
|
+
cache.delete(cache.keys().next().value);
|
|
883
|
+
}
|
|
884
|
+
function candidateRecord(item, resolver) {
|
|
885
|
+
return Object.fromEntries([resolver.id, ...resolver.match]
|
|
886
|
+
.filter((name, index, all) => all.indexOf(name) === index && item[name] !== undefined)
|
|
887
|
+
.map((name) => [name, item[name]]));
|
|
888
|
+
}
|
|
889
|
+
/** Resolve every eligible reference after validation/coercion and before the
|
|
890
|
+
* requested API call. Matching is exact (case-insensitive for strings),
|
|
891
|
+
* never fuzzy. Zero matches fail before the requested API call. */
|
|
892
|
+
export async function resolveReferences(op, preparedArgs, options) {
|
|
893
|
+
const args = { ...preparedArgs };
|
|
894
|
+
const maxPages = Math.max(1, Math.floor(options.maxPages ?? 5));
|
|
895
|
+
for (const param of op.params) {
|
|
896
|
+
const raw = args[param.name];
|
|
897
|
+
if (typeof raw !== "string" || param.resolve === false)
|
|
898
|
+
continue;
|
|
899
|
+
const value = raw.trim();
|
|
900
|
+
if (value.toLowerCase() === "me" && userShapedReference(param.name) && options.identityTool) {
|
|
901
|
+
const identity = findOperation(options.ops, options.identityTool);
|
|
902
|
+
if (identity) {
|
|
903
|
+
const cacheKey = "me:" + identity.tool;
|
|
904
|
+
const cached = options.cache.get(cacheKey);
|
|
905
|
+
if (cached !== undefined) {
|
|
906
|
+
args[param.name] = cached;
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
const outcome = await options.runOperation(identity, {});
|
|
910
|
+
if (outcome.isError)
|
|
911
|
+
return { ok: false, outcome };
|
|
912
|
+
const body = outcomeValue(outcome);
|
|
913
|
+
const id = body && typeof body === "object" && !Array.isArray(body) ? body.id : undefined;
|
|
914
|
+
if (typeof id !== "string" && typeof id !== "number") {
|
|
915
|
+
return { ok: false, outcome: referenceError("NOT_AVAILABLE", `${identity.tool} did not return a top-level id, so "me" cannot be resolved for ${param.name}.`, param.name, ["Pass the caller's exact ID instead."]) };
|
|
916
|
+
}
|
|
917
|
+
putReferenceCache(options.cache, cacheKey, id);
|
|
918
|
+
args[param.name] = id;
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
const resolver = param.resolve && typeof param.resolve === "object" ? param.resolve : undefined;
|
|
923
|
+
if (!resolver || looksLikeIdentifier(value, resolver))
|
|
924
|
+
continue;
|
|
925
|
+
const source = findOperation(options.ops, resolver.via);
|
|
926
|
+
if (!source)
|
|
927
|
+
continue;
|
|
928
|
+
const cacheKey = source.tool + ":" + resolver.id + ":" + resolver.match.join(",") + ":" + value.toLowerCase();
|
|
929
|
+
const cached = options.cache.get(cacheKey);
|
|
930
|
+
if (cached !== undefined) {
|
|
931
|
+
args[param.name] = cached;
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
const matches = new Map();
|
|
935
|
+
let pageArgs = {};
|
|
936
|
+
for (const sourceParam of source.params) {
|
|
937
|
+
if (args[sourceParam.name] !== undefined && sourceParam.name !== param.name)
|
|
938
|
+
pageArgs[sourceParam.name] = args[sourceParam.name];
|
|
939
|
+
}
|
|
940
|
+
if (resolver.filterParam)
|
|
941
|
+
pageArgs[resolver.filterParam] = value;
|
|
942
|
+
if (!hasOwnFieldsParam(source))
|
|
943
|
+
pageArgs.fields = [resolver.id, ...resolver.match];
|
|
944
|
+
let exhausted = false;
|
|
945
|
+
for (let pageNumber = 1; pageNumber <= maxPages; pageNumber++) {
|
|
946
|
+
const outcome = await options.runOperation(source, pageArgs);
|
|
947
|
+
if (outcome.isError)
|
|
948
|
+
return { ok: false, outcome };
|
|
949
|
+
const page = referencePage(outcomeValue(outcome));
|
|
950
|
+
if (!page) {
|
|
951
|
+
return { ok: false, outcome: referenceError("NOT_AVAILABLE", `${source.tool} did not return one recognizable item array, so ${param.name} cannot be resolved by name.`, param.name, ["Pass the exact ID instead.", `Check the resolver hint for ${source.tool}.`]) };
|
|
952
|
+
}
|
|
953
|
+
for (const item of page.items) {
|
|
954
|
+
const id = item[resolver.id];
|
|
955
|
+
if (typeof id !== "string" && typeof id !== "number")
|
|
956
|
+
continue;
|
|
957
|
+
const hit = resolver.match.some((field) => typeof item[field] === "string" && item[field].toLowerCase() === value.toLowerCase());
|
|
958
|
+
if (hit)
|
|
959
|
+
matches.set(typeof id + ":" + String(id), { id, item });
|
|
960
|
+
}
|
|
961
|
+
if (matches.size > 1) {
|
|
962
|
+
const candidates = [...matches.values()].map(({ item }) => candidateRecord(item, resolver));
|
|
963
|
+
return { ok: false, outcome: referenceError("REFERENCE_AMBIGUOUS", `${JSON.stringify(raw)} matches multiple candidates for ${param.name}; nothing was sent to ${op.tool}.`, param.name, ["Choose one candidate ID and call again."], candidates) };
|
|
964
|
+
}
|
|
965
|
+
if (!page.nextPage) {
|
|
966
|
+
exhausted = true;
|
|
967
|
+
break;
|
|
968
|
+
}
|
|
969
|
+
pageArgs = { ...pageArgs, ...page.nextPage };
|
|
970
|
+
}
|
|
971
|
+
if (!exhausted) {
|
|
972
|
+
return { ok: false, outcome: referenceError("REFERENCE_SCAN_LIMIT", `${source.tool} still had more results after ${maxPages} pages, so ${JSON.stringify(raw)} could not be resolved unambiguously.`, param.name, ["Pass the exact ID instead.", `Narrow ${source.tool} with its filters, or declare a more selective resolver.`]) };
|
|
973
|
+
}
|
|
974
|
+
const match = [...matches.values()][0];
|
|
975
|
+
if (!match) {
|
|
976
|
+
return { ok: false, outcome: referenceError("REFERENCE_NOT_FOUND", `${JSON.stringify(raw)} did not exactly match any ${referenceMatchLabel(resolver.match)} from ${source.tool}; nothing was sent to ${op.tool}.`, param.name, [`Call ${source.tool} to choose an exact ${referenceMatchLabel(resolver.match)} or ID, then call again.`]) };
|
|
977
|
+
}
|
|
978
|
+
putReferenceCache(options.cache, cacheKey, match.id);
|
|
979
|
+
args[param.name] = match.id;
|
|
980
|
+
}
|
|
981
|
+
return { ok: true, args };
|
|
982
|
+
}
|
|
753
983
|
/** The isError result for bad arguments: one stable code, one issue per
|
|
754
984
|
* argument, and the way out. */
|
|
755
985
|
export function argumentsError(op, issues) {
|
|
@@ -1168,8 +1398,6 @@ export function searchScore(op, query) {
|
|
|
1168
1398
|
return score;
|
|
1169
1399
|
}
|
|
1170
1400
|
export async function docsSearch(source, query, page = 1) {
|
|
1171
|
-
const term = query.trim().toLowerCase();
|
|
1172
|
-
const terms = searchTerms(query);
|
|
1173
1401
|
const sections = [];
|
|
1174
1402
|
const ranked = source.ops
|
|
1175
1403
|
.map((op) => ({ op, score: searchScore(op, query) }))
|
|
@@ -1193,34 +1421,20 @@ export async function docsSearch(source, query, page = 1) {
|
|
|
1193
1421
|
else if (ranked.length > 0) {
|
|
1194
1422
|
sections.push("No reference matches on page " + (pageIndex + 1) + "; there are " + Math.ceil(ranked.length / SEARCH_PAGE_SIZE) + " pages.");
|
|
1195
1423
|
}
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
for (const line of prose.split("\n")) {
|
|
1202
|
-
if (/^#{1,3} /.test(line))
|
|
1203
|
-
heading = line.replace(/^#+ /, "").trim();
|
|
1204
|
-
else {
|
|
1205
|
-
const lowerHeading = heading.toLowerCase();
|
|
1206
|
-
const lowerLine = line.toLowerCase();
|
|
1207
|
-
const matched = terms.filter((word) => lowerHeading.includes(word) || lowerLine.includes(word));
|
|
1208
|
-
if (matched.length > 0) {
|
|
1209
|
-
const allTerms = matched.length === terms.length;
|
|
1210
|
-
proseMatches.push({
|
|
1211
|
-
heading,
|
|
1212
|
-
excerpt: line.trim().slice(0, 160),
|
|
1213
|
-
score: matched.length * 10 + (allTerms ? 50 : 0) + (lowerHeading.includes(term) || lowerLine.includes(term) ? 25 : 0),
|
|
1214
|
-
});
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
proseMatches.sort((a, b) => b.score - a.score || a.heading.localeCompare(b.heading) || a.excerpt.localeCompare(b.excerpt));
|
|
1219
|
-
proseMatchCount = proseMatches.length;
|
|
1220
|
-
if (proseMatches.length > 0) {
|
|
1221
|
-
sections.push("Guide matches (best first):\n" + proseMatches.slice(0, 15).map((match) => "- [" + match.heading + "] " + match.excerpt).join("\n"));
|
|
1222
|
-
}
|
|
1424
|
+
const { guides, status } = await searchConnectedGuides(source.docsUrl(), source.docsIndexUrl?.() ?? null, (path) => fetchDocs(source, path), query);
|
|
1425
|
+
const guideSlice = guides.slice(pageIndex * SEARCH_PAGE_SIZE, (pageIndex + 1) * SEARCH_PAGE_SIZE);
|
|
1426
|
+
const proseMatchCount = guides.length;
|
|
1427
|
+
if (guideSlice.length > 0) {
|
|
1428
|
+
sections.push("Guide matches (best first, " + guides.length + " pages):\n" + guideSlice.map((match) => "- [" + match.title + (match.section ? " / " + match.section : "") + "](" + match.url + "): " + match.excerpt + "\n read_docs " + JSON.stringify({ page: match.url })).join("\n"));
|
|
1223
1429
|
}
|
|
1430
|
+
if (status === "unavailable")
|
|
1431
|
+
sections.push("The docs site is unavailable; the API reference was still searched.");
|
|
1432
|
+
const structured = {
|
|
1433
|
+
schema_version: "1", query, page: pageIndex + 1,
|
|
1434
|
+
reference: slice.map(({ op }) => ({ tool: op.tool, method: op.httpMethod, path: op.path, ...(op.summary ? { summary: op.summary } : {}), read_tool: { name: "read_docs", arguments: { page: op.tool } } })),
|
|
1435
|
+
guides: guideSlice.map((match) => ({ ...match, read_tool: { name: "read_docs", arguments: { page: match.url } } })),
|
|
1436
|
+
totals: { reference: ranked.length, guides: guides.length }, guides_status: status,
|
|
1437
|
+
};
|
|
1224
1438
|
if (omittedRanked.length > 0 && ranked.length === 0 && proseMatchCount === 0) {
|
|
1225
1439
|
return omittedPlanLimit(omittedRanked.map((result) => result.op));
|
|
1226
1440
|
}
|
|
@@ -1232,9 +1446,10 @@ export async function docsSearch(source, query, page = 1) {
|
|
|
1232
1446
|
return {
|
|
1233
1447
|
text: (coverage ? coverage + "\n\n" : "") + "No matches for: " + query + (source.docsUrl() === null && (source.docsIndexUrl?.() ?? null) === null ? " (a docs URL was not provided at generate time; only the API reference was searched)" : ""),
|
|
1234
1448
|
isError: false,
|
|
1449
|
+
structured,
|
|
1235
1450
|
};
|
|
1236
1451
|
}
|
|
1237
|
-
return { text: [...(coverage ? [coverage] : []), ...sections].join("\n\n"), isError: false };
|
|
1452
|
+
return { text: [...(coverage ? [coverage] : []), ...sections].join("\n\n"), isError: false, structured };
|
|
1238
1453
|
}
|
|
1239
1454
|
export async function docsRead(source, page) {
|
|
1240
1455
|
const opMatch = findOperation(source.ops, page);
|
|
@@ -1247,10 +1462,7 @@ export async function docsRead(source, page) {
|
|
|
1247
1462
|
let target = page;
|
|
1248
1463
|
if (!/^https?:\/\//.test(target)) {
|
|
1249
1464
|
const index = await fetchDocs(source, "llms.txt");
|
|
1250
|
-
|
|
1251
|
-
const hit = linked.find((u) => u.toLowerCase().includes(target.toLowerCase()));
|
|
1252
|
-
if (hit !== undefined)
|
|
1253
|
-
target = hit;
|
|
1465
|
+
target = docsReadTarget(index, source.docsUrl(), source.docsIndexUrl?.() ?? null, target);
|
|
1254
1466
|
}
|
|
1255
1467
|
const text = await fetchDocs(source, target);
|
|
1256
1468
|
if (text === null) {
|
package/dist/mcp.d.ts
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export
|
|
2
|
+
import { type ClientOptions } from "./index.js";
|
|
3
|
+
import { type McpAuthorizationConfiguration, type McpTokenIntrospectionConfiguration, type McpPrincipal } from "./mcp-authorization.js";
|
|
4
|
+
export type { McpAuthorizationConfiguration, McpTokenIntrospectionConfiguration, McpPrincipal } from "./mcp-authorization.js";
|
|
5
|
+
export { McpAccountLinkRequired } from "./mcp-protocol.js";
|
|
6
|
+
export interface McpHttpOptions {
|
|
7
|
+
authorization?: McpAuthorizationConfiguration;
|
|
8
|
+
/** Optional server-only introspection. When set, checks every token with the
|
|
9
|
+
* provider instead of local JWT verification. Never put secrets in config. */
|
|
10
|
+
introspection?: McpTokenIntrospectionConfiguration;
|
|
11
|
+
/** Application-owned lookup or token exchange for this verified identity.
|
|
12
|
+
* Keep it outside the generated directory so regeneration preserves it.
|
|
13
|
+
* Throw McpAccountLinkRequired with a public linking page when the user
|
|
14
|
+
* needs to connect an account. Always verify the link server-side on retry.
|
|
15
|
+
* The MCP access token and request headers are deliberately not provided. */
|
|
16
|
+
credentialsFor(principal: McpPrincipal): ClientOptions | Promise<ClientOptions>;
|
|
17
|
+
}
|
|
18
|
+
/** Create once at startup; metadata/key caching is isolated to this server. */
|
|
19
|
+
export declare function createMcpHandler(options: McpHttpOptions): (request: Request) => Promise<Response>;
|
|
20
|
+
/** A generated entry point cannot infer the host application's user-to-API
|
|
21
|
+
* credential mapping. It remains closed until the owner installs a handler. */
|
|
22
|
+
export declare function handleHttp(_request: Request): Promise<Response>;
|
|
5
23
|
//# sourceMappingURL=mcp.d.ts.map
|
package/dist/mcp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":";AAuBA,OAAO,EAAoC,KAAK,aAAa,EAAmB,MAAM,YAAY,CAAC;AAcnG,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;AA4U3D,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"}
|