@typeship-ax/mcp 0.8.0 → 0.9.1
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 +1617 -712
- package/api.md +8292 -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 +30 -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 +20 -9
- package/dist/resources/targets.d.ts.map +1 -1
- package/dist/resources/targets.js +14 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +38 -22
- package/dist/types.d.ts +385 -119
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -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 +44 -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 +32 -0
- package/src/schemas.ts +38 -22
- package/src/types.ts +404 -119
- package/src/worker.ts +4 -4
package/dist/mcp.js
CHANGED
|
@@ -5,28 +5,35 @@
|
|
|
5
5
|
// live in ./mcp-protocol.js, shared with typeship's hosted endpoint. Two
|
|
6
6
|
// transports, zero dependencies:
|
|
7
7
|
// node mcp.js stdio (newline-delimited JSON-RPC 2.0)
|
|
8
|
-
//
|
|
8
|
+
// createMcpHandler() configured Streamable HTTP handler
|
|
9
9
|
// Surface switches (flags or environment):
|
|
10
10
|
// --read-only / TYPESHIP_MCP_READ_ONLY=1 reads only; writes are not callable
|
|
11
11
|
// --tools a,b / TYPESHIP_MCP_TOOLS=a,b only these resources or tools
|
|
12
12
|
// TYPESHIP_MCP_MAX_RESULT_CHARS=<n> result size cap (default 64000)
|
|
13
|
-
// handleHttp()
|
|
14
|
-
//
|
|
15
|
-
//
|
|
13
|
+
// The bare handleHttp()/--http entry stays closed until a handler is configured.
|
|
14
|
+
// HTTP validates a dedicated MCP token and resolves API credentials through
|
|
15
|
+
// createMcpHandler's application-owned callback; stdio resolves auth from the
|
|
16
16
|
// environment, then from credentials/config saved by the CLI's `login`
|
|
17
17
|
// and `config` commands (same files, so one login covers both bins).
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
18
19
|
import { mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
19
20
|
import { homedir, tmpdir } from "node:os";
|
|
20
21
|
import { basename, join } from "node:path";
|
|
21
22
|
import { fileURLToPath } from "node:url";
|
|
22
23
|
import { TypeshipClient, formatDebugEvent } from "./index.js";
|
|
23
24
|
import { GLOBALS, OMITTED_OPS, OPS, buildArgs } from "./ops.js";
|
|
24
|
-
import { DEFAULT_MAX_RESULT_CHARS, SUPPORTED_PROTOCOL_VERSIONS, argumentsError, asJsonRpc, binaryOutcome, callSharedTool, checkRequestHeaders, dataOutcome, errorOutcome, handleRpc, isRpcOutcome, pageOutcome, parseIncludeList, prepareCall, serverInstructions, takeCancelled, textError, toolDefinitions, visibleOps, } from "./mcp-protocol.js";
|
|
25
|
+
import { DEFAULT_MAX_RESULT_CHARS, SUPPORTED_PROTOCOL_VERSIONS, McpAccountLinkRequired, argumentsError, asJsonRpc, binaryOutcome, callSharedTool, checkRequestHeaders, dataOutcome, errorOutcome, handleRpc, isRpcOutcome, pageOutcome, parseIncludeList, prepareCall, resolveReferences, serverInstructions, takeCancelled, textError, toolDefinitions, visibleOps, } from "./mcp-protocol.js";
|
|
25
26
|
import { fetchDocsText } from "./docs.js";
|
|
27
|
+
import { assertCredentialDestination, assertStoredIdentity } from "./oauth-session.js";
|
|
28
|
+
import { createCredentialStore } from "./credential-storage.js";
|
|
29
|
+
import { parseNamedCredentials, resolveNamedCredentials, namedCredentialAvailability } from "./named-credentials.js";
|
|
30
|
+
import { resolveProfile, profileFlag, readProfileConfig } from "./auth-profiles.js";
|
|
31
|
+
import { createMcpAuthorizer, McpAuthorizationError } from "./mcp-authorization.js";
|
|
32
|
+
export { McpAccountLinkRequired } from "./mcp-protocol.js";
|
|
26
33
|
const BIN = "typeship";
|
|
27
34
|
const PKG_NAME = "@typeship-ax/mcp";
|
|
28
35
|
const SERVER_NAME = "typeship-mcp";
|
|
29
|
-
const SERVER_VERSION = "0.
|
|
36
|
+
const SERVER_VERSION = "0.9.1";
|
|
30
37
|
/** The MCP client's announced name (clientInfo in request _meta), for the User-Agent. */
|
|
31
38
|
let MCP_CLIENT_NAME = null;
|
|
32
39
|
function noteClientInfo(message) {
|
|
@@ -34,10 +41,10 @@ function noteClientInfo(message) {
|
|
|
34
41
|
const name = meta?.["io.modelcontextprotocol/clientInfo"]?.name;
|
|
35
42
|
if (typeof name === "string" && name && name !== MCP_CLIENT_NAME) {
|
|
36
43
|
MCP_CLIENT_NAME = name.slice(0, 60);
|
|
37
|
-
clientInstance = undefined;
|
|
38
44
|
}
|
|
39
45
|
}
|
|
40
46
|
const DEFAULT_BASE_URL = "https://typeship.dev/api/v1";
|
|
47
|
+
const NAMED_SCHEMES = { "apiKey": { "kind": "bearer", "options": ["bearerToken"] } };
|
|
41
48
|
const AUTH_SCALARS = [{ "option": "bearerToken", "flag": "token", "env": "TYPESHIP_TOKEN" }];
|
|
42
49
|
const BASIC = null;
|
|
43
50
|
const ENVIRONMENTS = {};
|
|
@@ -46,8 +53,6 @@ const DOCS_INDEX_URL_DEFAULT = null;
|
|
|
46
53
|
/** "meta" collapses per-operation tools into search/read/execute so huge
|
|
47
54
|
* APIs don't flood agent context with hundreds of tools. */
|
|
48
55
|
const TOOL_MODE = "meta";
|
|
49
|
-
/** Authorization server for OAuth discovery (RFC 9728), from the spec. */
|
|
50
|
-
const OAUTH_ISSUER = null;
|
|
51
56
|
/** Project-supplied guidance appended to the server instructions. */
|
|
52
57
|
const CUSTOM_INSTRUCTIONS = null;
|
|
53
58
|
/** The tool that returns the caller (the CLI's whoami target), named in the instructions. */
|
|
@@ -60,15 +65,18 @@ const INCLUDE = parseIncludeList(ARGV.includes("--tools") ? ARGV[ARGV.indexOf("-
|
|
|
60
65
|
const MAX_RESULT_CHARS = Number(process.env["TYPESHIP_MCP_MAX_RESULT_CHARS"]) || DEFAULT_MAX_RESULT_CHARS;
|
|
61
66
|
/** One sentence on where credentials come from on each transport; goes
|
|
62
67
|
* into the instructions and into 401 results. */
|
|
63
|
-
const AUTH_HINT_STDIO = "Credentials come from the MCP server's environment (TYPESHIP_TOKEN) or from 'typeship login'";
|
|
64
|
-
const AUTH_HINT_HTTP = "
|
|
68
|
+
const AUTH_HINT_STDIO = "Credentials come from the MCP server's environment (TYPESHIP_CREDENTIALS, TYPESHIP_TOKEN) or from 'typeship login'";
|
|
69
|
+
const AUTH_HINT_HTTP = "Sign in to this MCP server. The server resolves your API credentials separately; its connection token is never forwarded to the API.";
|
|
65
70
|
/** The tool list is fixed at generation, so clients may cache it for an
|
|
66
71
|
* hour and shared caches may hold it (identical for every caller). */
|
|
67
72
|
const TOOLS_TTL_MS = 60 * 60 * 1000;
|
|
68
|
-
function
|
|
73
|
+
function configRoot() {
|
|
69
74
|
return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), BIN);
|
|
70
75
|
}
|
|
76
|
+
function configDir() { return resolveProfile(configRoot(), { flag: profileFlag(ARGV), environment: process.env["TYPESHIP_PROFILE"] }).directory; }
|
|
71
77
|
function readJson(file) {
|
|
78
|
+
if (!LOCAL_CREDENTIALS)
|
|
79
|
+
return null;
|
|
72
80
|
try {
|
|
73
81
|
return JSON.parse(readFileSync(join(configDir(), file), "utf8"));
|
|
74
82
|
}
|
|
@@ -76,9 +84,22 @@ function readJson(file) {
|
|
|
76
84
|
return null;
|
|
77
85
|
}
|
|
78
86
|
}
|
|
79
|
-
function makeClient() {
|
|
80
|
-
const
|
|
81
|
-
const
|
|
87
|
+
function makeClient(op) {
|
|
88
|
+
const profile = LOCAL_CREDENTIALS ? resolveProfile(configRoot(), { flag: profileFlag(ARGV), environment: process.env["TYPESHIP_PROFILE"] }) : null;
|
|
89
|
+
const store = createCredentialStore(profile?.directory ?? configRoot(), process.env["TYPESHIP_CREDENTIAL_STORE"], "TYPESHIP_CREDENTIAL_STORE");
|
|
90
|
+
if (BASIC && (process.env[BASIC.envUser] !== undefined) !== (process.env[BASIC.envPass] !== undefined))
|
|
91
|
+
throw new Error("Supply both Basic-auth environment variables together, or use a complete named Basic credential.");
|
|
92
|
+
const envNamed = LOCAL_CREDENTIALS && process.env["TYPESHIP_CREDENTIALS"] !== undefined ? parseNamedCredentials(process.env["TYPESHIP_CREDENTIALS"], NAMED_SCHEMES) : {};
|
|
93
|
+
const explicitOptions = new Set(AUTH_SCALARS.filter((a) => process.env[a.env] !== undefined).map((a) => a.option));
|
|
94
|
+
if (BASIC && process.env[BASIC.envUser] !== undefined && process.env[BASIC.envPass] !== undefined)
|
|
95
|
+
explicitOptions.add("basicAuth");
|
|
96
|
+
namedCredentialAvailability(NAMED_SCHEMES, explicitOptions, envNamed);
|
|
97
|
+
const identityPolicy = {};
|
|
98
|
+
const identityOp = OPS.find((candidate) => candidate.tool === IDENTITY_TOOL);
|
|
99
|
+
const identity = identityOp && Object.keys(identityPolicy).length ? { operation: identityOp.resource + "." + identityOp.method, fields: identityPolicy } : undefined;
|
|
100
|
+
const explicitCredentials = op.credentialOptions?.some((alternative) => alternative.length > 0 && alternative.every((option) => explicitOptions.has(option)));
|
|
101
|
+
const stored = op.auth !== "none" && LOCAL_CREDENTIALS && !explicitCredentials ? store.read() : null;
|
|
102
|
+
const config = profile ? readProfileConfig(profile.directory) : {};
|
|
82
103
|
const baseUrl = process.env["TYPESHIP_BASE_URL"]
|
|
83
104
|
?? config?.baseUrl
|
|
84
105
|
?? (config?.environment !== undefined ? ENVIRONMENTS[config.environment] : undefined)
|
|
@@ -88,6 +109,10 @@ function makeClient() {
|
|
|
88
109
|
// instead of a server that vanished mid-conversation.
|
|
89
110
|
throw new Error("No base URL configured: set TYPESHIP_BASE_URL in the MCP server's environment, or run '" + BIN + " config set base-url <url>'.");
|
|
90
111
|
}
|
|
112
|
+
if (stored) {
|
|
113
|
+
assertCredentialDestination(stored, { apiBaseUrl: baseUrl, environment: config.environment, profile: profile?.name });
|
|
114
|
+
assertStoredIdentity(stored, identity);
|
|
115
|
+
}
|
|
91
116
|
const options = { baseUrl };
|
|
92
117
|
for (const a of AUTH_SCALARS) {
|
|
93
118
|
const v = process.env[a.env] ?? stored?.scalars?.[a.option];
|
|
@@ -100,9 +125,16 @@ function makeClient() {
|
|
|
100
125
|
if (username !== undefined && password !== undefined)
|
|
101
126
|
options.basicAuth = { username, password };
|
|
102
127
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
128
|
+
const envOptions = {};
|
|
129
|
+
for (const a of AUTH_SCALARS)
|
|
130
|
+
if (process.env[a.env] !== undefined)
|
|
131
|
+
envOptions[a.option] = process.env[a.env];
|
|
132
|
+
if (BASIC && process.env[BASIC.envUser] !== undefined && process.env[BASIC.envPass] !== undefined)
|
|
133
|
+
envOptions.basicAuth = { username: process.env[BASIC.envUser], password: process.env[BASIC.envPass] };
|
|
134
|
+
options.credentials = resolveNamedCredentials(NAMED_SCHEMES, [
|
|
135
|
+
{ named: stored?.named, options: { ...stored?.scalars, ...(stored?.basic ? { basicAuth: stored.basic } : {}) } },
|
|
136
|
+
{ named: envNamed, options: envOptions },
|
|
137
|
+
]);
|
|
106
138
|
for (const g of GLOBALS) {
|
|
107
139
|
const value = process.env["TYPESHIP_" + g.envSuffix];
|
|
108
140
|
if (value !== undefined)
|
|
@@ -113,15 +145,25 @@ function makeClient() {
|
|
|
113
145
|
}
|
|
114
146
|
// The local MCP server identifies itself (surface + the client it serves, when announced).
|
|
115
147
|
options.defaultHeaders = { "User-Agent": PKG_NAME + "-mcp/" + SERVER_VERSION + " (typeship" + (MCP_CLIENT_NAME ? "; client=" + MCP_CLIENT_NAME : "") + ")" };
|
|
116
|
-
|
|
148
|
+
// Keep the SDK's machine-token cache while re-evaluating local configuration.
|
|
149
|
+
const key = createHash("sha256").update(JSON.stringify([options, config, profile?.name, stored?.oauth?.sessionId, null, process.env["TYPESHIP_DEBUG"]])).digest("hex");
|
|
150
|
+
if (clientInstance && clientKey === key)
|
|
151
|
+
return clientInstance;
|
|
152
|
+
const client = new TypeshipClient(options);
|
|
153
|
+
clientKey = key;
|
|
154
|
+
CLIENT_CREDENTIALS.set(client, Object.keys(options.credentials ?? {}).length > 0 || AUTH_SCALARS.some((a) => options[a.option] !== undefined) || options.basicAuth !== undefined || options.bearerToken !== undefined || options.clientCredentials !== undefined);
|
|
155
|
+
return (clientInstance = client);
|
|
117
156
|
}
|
|
118
157
|
let clientInstance;
|
|
119
|
-
|
|
120
|
-
|
|
158
|
+
let clientKey = "";
|
|
159
|
+
const CLIENT_CREDENTIALS = new WeakMap();
|
|
160
|
+
/** Resolve configuration and credentials for each tool call, including login/logout changes. */
|
|
161
|
+
function getClient(op) {
|
|
162
|
+
return makeClient(op);
|
|
121
163
|
}
|
|
122
164
|
/** Run one operation through the generated SDK: the same client, the same
|
|
123
165
|
* typed errors and pagination a hand-written caller would get. */
|
|
124
|
-
async function
|
|
166
|
+
async function callOperationRaw(op, rawArgs, remote, client) {
|
|
125
167
|
// Arguments are checked against the tool's schema first: unknown names,
|
|
126
168
|
// wrong types and missing requirements come back as one isError result,
|
|
127
169
|
// nothing reaches the API half-formed and nothing is dropped silently.
|
|
@@ -138,7 +180,7 @@ async function callOperation(op, rawArgs, authHeader) {
|
|
|
138
180
|
// argument errors, reported before anything is sent.
|
|
139
181
|
const fileIssues = [];
|
|
140
182
|
let rawBody = op.bodyStyle === "data" ? args.body : undefined;
|
|
141
|
-
if (LOCAL_PROCESS) {
|
|
183
|
+
if (LOCAL_PROCESS && !remote) {
|
|
142
184
|
for (const p of op.params) {
|
|
143
185
|
if (p.type !== "file" || typeof values[p.name] !== "string")
|
|
144
186
|
continue;
|
|
@@ -161,16 +203,14 @@ async function callOperation(op, rawArgs, authHeader) {
|
|
|
161
203
|
if (fileIssues.length > 0)
|
|
162
204
|
return argumentsError(op, fileIssues);
|
|
163
205
|
const callArgs = buildArgs(op, values, rawBody, typeof args.select === "string" ? args.select : undefined);
|
|
164
|
-
if (authHeader)
|
|
165
|
-
callArgs.push({ headers: { Authorization: authHeader } });
|
|
166
206
|
const errorContext = {
|
|
167
|
-
authHint:
|
|
207
|
+
authHint: remote ? AUTH_HINT_HTTP : AUTH_HINT_STDIO,
|
|
168
208
|
docsUrl: docsSource.docsUrl(),
|
|
169
|
-
hadCredential:
|
|
209
|
+
hadCredential: !!remote || CLIENT_CREDENTIALS.get(client) === true,
|
|
170
210
|
};
|
|
171
211
|
const shape = { fields, maxChars, pagination: op.pagination, args };
|
|
172
212
|
try {
|
|
173
|
-
const target =
|
|
213
|
+
const target = client[op.resource];
|
|
174
214
|
const result = await target[op.method](...callArgs);
|
|
175
215
|
if (!result.ok)
|
|
176
216
|
return errorOutcome(result.error, errorContext);
|
|
@@ -181,17 +221,62 @@ async function callOperation(op, rawArgs, authHeader) {
|
|
|
181
221
|
// A binary body (the SDK hands back a Blob): an image block, or a file
|
|
182
222
|
// on disk, never "{}".
|
|
183
223
|
if (result.data instanceof Blob)
|
|
184
|
-
return binaryOutcome(result.data, { tool: op.tool, ...(LOCAL_PROCESS ? { saveBinary } : {}) });
|
|
224
|
+
return binaryOutcome(result.data, { tool: op.tool, ...(LOCAL_PROCESS && !remote ? { saveBinary } : {}) });
|
|
185
225
|
return dataOutcome(result.data, shape);
|
|
186
226
|
}
|
|
187
227
|
catch (e) {
|
|
188
228
|
return textError(e.message ?? "Tool call failed");
|
|
189
229
|
}
|
|
190
230
|
}
|
|
231
|
+
/** Name-or-ID resolution is the one wrapper around the ordinary executor,
|
|
232
|
+
* so a direct operation tool and compact execute take exactly the same path. */
|
|
233
|
+
const REFERENCE_CACHES = new Map();
|
|
234
|
+
function referenceCacheFor(authHeader) {
|
|
235
|
+
const key = createHash("sha256").update(JSON.stringify([authHeader ?? "stdio", clientKey])).digest("hex");
|
|
236
|
+
let cache = REFERENCE_CACHES.get(key);
|
|
237
|
+
if (!cache) {
|
|
238
|
+
cache = new Map();
|
|
239
|
+
REFERENCE_CACHES.set(key, cache);
|
|
240
|
+
while (REFERENCE_CACHES.size > 16)
|
|
241
|
+
REFERENCE_CACHES.delete(REFERENCE_CACHES.keys().next().value);
|
|
242
|
+
}
|
|
243
|
+
return cache;
|
|
244
|
+
}
|
|
245
|
+
async function callOperation(op, rawArgs, remote) {
|
|
246
|
+
const prepared = prepareCall(op, rawArgs, { maxChars: MAX_RESULT_CHARS });
|
|
247
|
+
if (!prepared.ok)
|
|
248
|
+
return prepared.outcome;
|
|
249
|
+
// One client/session for the entire tool, including name-to-ID lookups.
|
|
250
|
+
// If login changes during a lookup, the token callback rejects the old session.
|
|
251
|
+
let client;
|
|
252
|
+
try {
|
|
253
|
+
client = remote ? await remote.client() : getClient(op);
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
if (remote && error instanceof McpAccountLinkRequired)
|
|
257
|
+
throw error;
|
|
258
|
+
return textError(remote ? "API access is unavailable for this MCP account. Reconnect your API account or contact the server owner." : error.message);
|
|
259
|
+
}
|
|
260
|
+
const resolved = await resolveReferences(op, prepared.call.args, {
|
|
261
|
+
ops: OPS,
|
|
262
|
+
identityTool: IDENTITY_TOOL,
|
|
263
|
+
cache: remote?.references ?? referenceCacheFor(),
|
|
264
|
+
runOperation: (source, args) => callOperationRaw(source, args, remote, client),
|
|
265
|
+
});
|
|
266
|
+
if (!resolved.ok)
|
|
267
|
+
return resolved.outcome;
|
|
268
|
+
const args = {
|
|
269
|
+
...resolved.args,
|
|
270
|
+
...(prepared.call.fields ? { fields: prepared.call.fields.map((path) => path.join(".")) } : {}),
|
|
271
|
+
};
|
|
272
|
+
return callOperationRaw(op, args, remote, client);
|
|
273
|
+
}
|
|
191
274
|
/** Running as a process on this machine (stdio, or --http launched here),
|
|
192
275
|
* as opposed to imported by a worker: the server can read and write local
|
|
193
276
|
* files, so uploads are tools and binaries are saved to disk. */
|
|
194
277
|
const LOCAL_PROCESS = invokedDirectly();
|
|
278
|
+
/** Saved human sessions are available only to local stdio, never HTTP/worker callers. */
|
|
279
|
+
const LOCAL_CREDENTIALS = LOCAL_PROCESS && !ARGV.includes("--http");
|
|
195
280
|
/** The callable operations: event streams are CLI-only, uploads are tools
|
|
196
281
|
* only on a local server (mcpExposed), writes are out under --read-only,
|
|
197
282
|
* and --tools narrows to a subset. A hidden operation is unknown to
|
|
@@ -219,32 +304,34 @@ const docsSource = {
|
|
|
219
304
|
docsIndexUrl: () => readJson("config.json")?.docsUrl ? null : DOCS_INDEX_URL_DEFAULT,
|
|
220
305
|
fetchText: fetchDocsText,
|
|
221
306
|
};
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
307
|
+
function serverFor(remote) {
|
|
308
|
+
const operations = remote ? visibleOps(MCP_OPS, { uploads: false }) : MCP_OPS;
|
|
309
|
+
const source = remote ? { ...docsSource, ops: operations } : docsSource;
|
|
225
310
|
const website = docsSource.docsUrl();
|
|
226
311
|
return {
|
|
227
312
|
serverInfo: { name: SERVER_NAME, title: "typeship", version: SERVER_VERSION, ...(website ? { websiteUrl: website } : {}) },
|
|
228
313
|
instructions: serverInstructions({
|
|
229
314
|
title: "typeship",
|
|
230
|
-
|
|
315
|
+
ops: operations,
|
|
316
|
+
toolCount: operations.length,
|
|
231
317
|
generatedOperationCount: OPS.length,
|
|
232
318
|
omittedOps: docsSource.omittedOps,
|
|
233
319
|
mode: TOOL_MODE,
|
|
234
320
|
readOnly: READ_ONLY,
|
|
235
321
|
hiddenWrites: HIDDEN_WRITES,
|
|
236
|
-
authHint:
|
|
322
|
+
authHint: remote ? AUTH_HINT_HTTP : AUTH_HINT_STDIO,
|
|
237
323
|
identityTool: MCP_OPS.some((o) => o.tool === IDENTITY_TOOL) ? IDENTITY_TOOL : null,
|
|
238
|
-
|
|
324
|
+
referenceResolution: MCP_OPS.some((o) => o.params.some((p) => p.resolve && typeof p.resolve === "object")),
|
|
325
|
+
uploads: !remote && HAS_UPLOADS,
|
|
239
326
|
custom: CUSTOM_INSTRUCTIONS,
|
|
240
327
|
}),
|
|
241
328
|
toolsTtlMs: TOOLS_TTL_MS,
|
|
242
|
-
listTools: () => toolDefinitions(
|
|
329
|
+
listTools: () => toolDefinitions(source.ops, TOOL_MODE, source.omittedOps),
|
|
243
330
|
unknownToolMessage: TOOL_MODE === "meta"
|
|
244
331
|
? (name) => "Unknown tool: " + name + ". This server uses compact mode; call search_docs to discover an operation, then call execute with operation: \"" + name + "\"."
|
|
245
332
|
: undefined,
|
|
246
333
|
callTool: async (name, args) => {
|
|
247
|
-
const shared = await callSharedTool(name, args,
|
|
334
|
+
const shared = await callSharedTool(name, args, source, (op, opArgs) => callOperation(op, opArgs, remote));
|
|
248
335
|
if (shared !== undefined)
|
|
249
336
|
return shared;
|
|
250
337
|
// tools/list is the callable contract. In meta mode every operation
|
|
@@ -252,8 +339,8 @@ function serverFor(authHeader) {
|
|
|
252
339
|
// confirmation gate; an unlisted operation name must stay unknown.
|
|
253
340
|
if (TOOL_MODE !== "operations")
|
|
254
341
|
return undefined;
|
|
255
|
-
const op =
|
|
256
|
-
return op ? callOperation(op, args,
|
|
342
|
+
const op = operations.find((o) => o.tool === name);
|
|
343
|
+
return op ? callOperation(op, args, remote) : undefined;
|
|
257
344
|
},
|
|
258
345
|
};
|
|
259
346
|
}
|
|
@@ -263,7 +350,7 @@ function serverFor(authHeader) {
|
|
|
263
350
|
* validating the header when present (DNS-rebinding defense), so browser
|
|
264
351
|
* callers must be same-host, localhost, or listed in
|
|
265
352
|
* TYPESHIP_MCP_ALLOWED_ORIGINS (comma-separated, "*" for any). */
|
|
266
|
-
function originAllowed(request) {
|
|
353
|
+
function originAllowed(request, resourceOrigin) {
|
|
267
354
|
const origin = request.headers.get("origin");
|
|
268
355
|
if (origin === null)
|
|
269
356
|
return true;
|
|
@@ -272,9 +359,9 @@ function originAllowed(request) {
|
|
|
272
359
|
return true;
|
|
273
360
|
try {
|
|
274
361
|
const o = new URL(origin);
|
|
275
|
-
if (o.
|
|
362
|
+
if (o.origin === resourceOrigin)
|
|
276
363
|
return true;
|
|
277
|
-
return ["localhost", "127.0.0.1", "[::1]"].includes(o.hostname);
|
|
364
|
+
return ["localhost", "127.0.0.1", "[::1]"].includes(new URL(resourceOrigin).hostname) && ["localhost", "127.0.0.1", "[::1]"].includes(o.hostname);
|
|
278
365
|
}
|
|
279
366
|
catch {
|
|
280
367
|
return false;
|
|
@@ -283,45 +370,75 @@ function originAllowed(request) {
|
|
|
283
370
|
function corsHeaders(request) {
|
|
284
371
|
return {
|
|
285
372
|
"Access-Control-Allow-Origin": request.headers.get("origin") ?? "*",
|
|
286
|
-
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
373
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
374
|
+
"Access-Control-Expose-Headers": "WWW-Authenticate",
|
|
287
375
|
"Access-Control-Allow-Headers": "Content-Type, Authorization, Accept, MCP-Protocol-Version, Mcp-Method, Mcp-Name",
|
|
288
376
|
Vary: "Origin",
|
|
289
377
|
};
|
|
290
378
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
379
|
+
/** Create once at startup; metadata/key caching is isolated to this server. */
|
|
380
|
+
export function createMcpHandler(options) {
|
|
381
|
+
const configured = {};
|
|
382
|
+
const apiBaseUrl = process.env["TYPESHIP_BASE_URL"] ?? DEFAULT_BASE_URL ?? undefined;
|
|
383
|
+
const authorizer = createMcpAuthorizer(options.authorization ?? { issuer: configured.mcpIssuer ?? "", resource: configured.mcpResource ?? "", ...(configured.mcpJwksUrl ? { jwksUrl: configured.mcpJwksUrl } : {}), scopes: configured.mcpScopes ?? [] }, options.introspection);
|
|
384
|
+
if (typeof options.credentialsFor !== "function")
|
|
385
|
+
throw new Error("Provide credentialsFor to resolve each MCP caller's upstream API credentials.");
|
|
386
|
+
return (request) => authorizedHttp(request, authorizer, options.credentialsFor, apiBaseUrl).catch(() => new Response(JSON.stringify({ error: "MCP request failed." }), { status: 500, headers: { "Content-Type": "application/json", "Cache-Control": "no-store" } }));
|
|
297
387
|
}
|
|
298
|
-
/**
|
|
299
|
-
*
|
|
300
|
-
export async function handleHttp(
|
|
388
|
+
/** A generated entry point cannot infer the host application's user-to-API
|
|
389
|
+
* credential mapping. It remains closed until the owner installs a handler. */
|
|
390
|
+
export async function handleHttp(_request) {
|
|
391
|
+
return new Response(JSON.stringify({ error: "Configure createMcpHandler with MCP authorization and credentialsFor before serving remote requests." }), { status: 503, headers: { "Content-Type": "application/json", "Cache-Control": "no-store" } });
|
|
392
|
+
}
|
|
393
|
+
async function authorizedHttp(request, authorizer, credentialsFor, apiBaseUrl) {
|
|
301
394
|
const url = new URL(request.url);
|
|
302
|
-
const
|
|
303
|
-
const
|
|
395
|
+
const resource = new URL(authorizer.configuration.resource);
|
|
396
|
+
const cors = originAllowed(request, resource.origin) ? corsHeaders(request) : {};
|
|
397
|
+
const json = (status, body, extra = {}) => new Response(JSON.stringify(body), { status, headers: { ...cors, "Content-Type": "application/json", "Cache-Control": "no-store", ...extra } });
|
|
304
398
|
if (request.method === "OPTIONS") {
|
|
305
|
-
return new Response(null, { status: originAllowed(request) ? 204 : 403, headers: cors });
|
|
399
|
+
return new Response(null, { status: originAllowed(request, resource.origin) ? 204 : 403, headers: cors });
|
|
306
400
|
}
|
|
307
|
-
if (!originAllowed(request)) {
|
|
401
|
+
if (!originAllowed(request, resource.origin)) {
|
|
308
402
|
return json(403, { jsonrpc: "2.0", error: { code: -32600, message: "Origin not allowed. Set TYPESHIP_MCP_ALLOWED_ORIGINS to permit browser callers." } });
|
|
309
403
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
404
|
+
const metadataPath = "/.well-known/oauth-protected-resource" + resource.pathname.replace(/\/$/, "");
|
|
405
|
+
if (url.pathname === metadataPath && request.method === "GET")
|
|
406
|
+
return json(200, authorizer.metadata());
|
|
407
|
+
if (url.pathname !== resource.pathname)
|
|
408
|
+
return json(404, { error: "Unknown MCP endpoint." });
|
|
313
409
|
if (request.method !== "POST") {
|
|
314
410
|
// GET streams, DELETE (session end) and Mcp-Session-Id belong to earlier
|
|
315
411
|
// revisions; the spec asks modern-only servers to answer 405.
|
|
316
412
|
return json(405, { error: "POST JSON-RPC messages to this endpoint (MCP " + SUPPORTED_PROTOCOL_VERSIONS[0] + ", stateless)." }, { Allow: "POST, OPTIONS" });
|
|
317
413
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
414
|
+
let principal;
|
|
415
|
+
try {
|
|
416
|
+
principal = await authorizer.authorize(request.headers.get("authorization"));
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
const failure = error instanceof McpAuthorizationError ? error : new McpAuthorizationError(503, "authorization_unavailable");
|
|
420
|
+
return json(failure.status, { error: failure.code, message: failure.message }, failure.status === 503 ? {} : { "WWW-Authenticate": authorizer.challenge(failure.code === "insufficient_scope" ? "insufficient_scope" : "invalid_token") });
|
|
324
421
|
}
|
|
422
|
+
let client;
|
|
423
|
+
const remote = { references: new Map(), client: () => client ??= (async () => {
|
|
424
|
+
const credentials = await credentialsFor(principal);
|
|
425
|
+
if (!credentials || typeof credentials !== "object")
|
|
426
|
+
throw new Error("API credentials unavailable.");
|
|
427
|
+
const baseUrl = apiBaseUrl;
|
|
428
|
+
if (typeof baseUrl !== "string")
|
|
429
|
+
throw new Error("Configure the upstream API URL.");
|
|
430
|
+
if (credentials.baseUrl !== undefined && credentials.baseUrl !== baseUrl)
|
|
431
|
+
throw new Error("API credentials target a different API URL.");
|
|
432
|
+
const token = request.headers.get("authorization").slice(7);
|
|
433
|
+
const transport = credentials.fetch ?? fetch;
|
|
434
|
+
return new TypeshipClient({ ...credentials, baseUrl, fetch: async (input, init) => {
|
|
435
|
+
const headers = new Headers(init?.headers);
|
|
436
|
+
const destination = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url);
|
|
437
|
+
if ([...headers.values()].some(value => value === token || value === "Bearer " + token) || [...destination.searchParams.values()].includes(token))
|
|
438
|
+
throw new Error("An MCP connection token cannot authorize an upstream API request.");
|
|
439
|
+
return transport(input, init);
|
|
440
|
+
} });
|
|
441
|
+
})() };
|
|
325
442
|
let incoming;
|
|
326
443
|
try {
|
|
327
444
|
incoming = await request.json();
|
|
@@ -342,10 +459,10 @@ export async function handleHttp(request) {
|
|
|
342
459
|
noteClientInfo(parsed);
|
|
343
460
|
let outcome;
|
|
344
461
|
try {
|
|
345
|
-
outcome = await handleRpc(serverFor(
|
|
462
|
+
outcome = await handleRpc(serverFor(remote), parsed);
|
|
346
463
|
}
|
|
347
464
|
catch (e) {
|
|
348
|
-
return json(500, { jsonrpc: "2.0", id: parsed.id ?? null, error: { code: -32603, message:
|
|
465
|
+
return json(500, { jsonrpc: "2.0", id: parsed.id ?? null, error: { code: -32603, message: "Internal error" } });
|
|
349
466
|
}
|
|
350
467
|
if (outcome.message === null) {
|
|
351
468
|
return new Response(null, { status: outcome.status, headers: cors });
|
|
@@ -413,7 +530,7 @@ async function startStdio() {
|
|
|
413
530
|
})
|
|
414
531
|
.catch((e) => {
|
|
415
532
|
if (id !== undefined) {
|
|
416
|
-
write({ jsonrpc: "2.0", id: id ?? null, error: { code: -32603, message:
|
|
533
|
+
write({ jsonrpc: "2.0", id: id ?? null, error: { code: -32603, message: "Internal error" } });
|
|
417
534
|
}
|
|
418
535
|
});
|
|
419
536
|
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Runtime credentials keyed by the API's exact security-scheme names. */
|
|
2
|
+
export type NamedCredential = string | {
|
|
3
|
+
username: string;
|
|
4
|
+
password: string;
|
|
5
|
+
};
|
|
6
|
+
export type NamedCredentials = Record<string, NamedCredential>;
|
|
7
|
+
export type CredentialSchemes = Record<string, {
|
|
8
|
+
kind: string;
|
|
9
|
+
options: string[];
|
|
10
|
+
}>;
|
|
11
|
+
export declare function parseNamedCredentials(value: unknown, schemes: CredentialSchemes): NamedCredentials;
|
|
12
|
+
/** Low-to-high source order. Within a source, exact names beat convenience options. */
|
|
13
|
+
export declare function resolveNamedCredentials(schemes: CredentialSchemes, layers: {
|
|
14
|
+
named?: NamedCredentials;
|
|
15
|
+
options?: Record<string, unknown>;
|
|
16
|
+
}[]): NamedCredentials;
|
|
17
|
+
/** Make convenience and named inputs comparable without expanding combinations. */
|
|
18
|
+
export declare function namedCredentialAvailability(schemes: CredentialSchemes, options: Set<string>, named: NamedCredentials): Set<string>;
|
|
19
|
+
/** Bound file/stdin reads before allocating a full credential document. */
|
|
20
|
+
export declare function readNamedCredentialsFile(input: string, schemes: CredentialSchemes): NamedCredentials;
|
|
21
|
+
//# sourceMappingURL=named-credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"named-credentials.d.ts","sourceRoot":"","sources":["../src/named-credentials.ts"],"names":[],"mappings":"AAEA,0EAA0E;AAC1E,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAC9E,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAC/D,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAAC;AAEpF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAqBlG;AAED,uFAAuF;AACvF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EAAE,GAAG,gBAAgB,CAa/J;AAED,mFAAmF;AACnF,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,gBAAgB,GAAG,GAAG,CAAC,MAAM,CAAC,CAKlI;AAED,2EAA2E;AAC3E,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAkBpG"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { openSync, readSync, closeSync } from "node:fs";
|
|
2
|
+
export function parseNamedCredentials(value, schemes) {
|
|
3
|
+
if (typeof value === "string") {
|
|
4
|
+
if (new TextEncoder().encode(value).byteLength > 1_048_576)
|
|
5
|
+
throw new Error("Named credentials exceed the 1 MiB limit.");
|
|
6
|
+
try {
|
|
7
|
+
value = JSON.parse(value);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
throw new Error("Named credentials must be a JSON object keyed by security scheme. Credential contents are not included in errors.");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
14
|
+
throw new Error("Named credentials must be a JSON object keyed by security scheme.");
|
|
15
|
+
const result = Object.create(null);
|
|
16
|
+
for (const [name, credential] of Object.entries(value)) {
|
|
17
|
+
if (!Object.hasOwn(schemes, name))
|
|
18
|
+
throw new Error("Named credentials contain an unknown or unsupported security scheme. Check the names listed by login --help.");
|
|
19
|
+
if (schemes[name].kind === "basic") {
|
|
20
|
+
if (!credential || typeof credential !== "object" || Array.isArray(credential) || Object.keys(credential).some((key) => key !== "username" && key !== "password") ||
|
|
21
|
+
typeof credential.username !== "string" || typeof credential.password !== "string" || !credential.username || !credential.password || credential.username.includes(":")) {
|
|
22
|
+
throw new Error("A named Basic credential requires a nonempty username and password; the username cannot contain a colon.");
|
|
23
|
+
}
|
|
24
|
+
result[name] = { username: credential.username, password: credential.password };
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
if (typeof credential !== "string" || !credential || /[\x00-\x1f\x7f]/.test(credential) || (schemes[name].kind === "bearer" && credential.includes(" ")))
|
|
28
|
+
throw new Error("A named token or API key must be a nonempty string without control characters; bearer tokens cannot contain spaces.");
|
|
29
|
+
result[name] = credential;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
/** Low-to-high source order. Within a source, exact names beat convenience options. */
|
|
35
|
+
export function resolveNamedCredentials(schemes, layers) {
|
|
36
|
+
const resolved = Object.create(null);
|
|
37
|
+
for (const layer of layers) {
|
|
38
|
+
for (const [name, scheme] of Object.entries(schemes)) {
|
|
39
|
+
for (const option of scheme.options) {
|
|
40
|
+
if (option === "clientCredentials")
|
|
41
|
+
continue;
|
|
42
|
+
const value = layer.options?.[option];
|
|
43
|
+
if (value !== undefined)
|
|
44
|
+
resolved[name] = value;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (layer.named)
|
|
48
|
+
Object.assign(resolved, parseNamedCredentials(layer.named, schemes));
|
|
49
|
+
}
|
|
50
|
+
return resolved;
|
|
51
|
+
}
|
|
52
|
+
/** Make convenience and named inputs comparable without expanding combinations. */
|
|
53
|
+
export function namedCredentialAvailability(schemes, options, named) {
|
|
54
|
+
for (const [name, scheme] of Object.entries(schemes)) {
|
|
55
|
+
if (Object.hasOwn(named, name) || scheme.options.some((option) => options.has(option)))
|
|
56
|
+
options.add("credentials." + name);
|
|
57
|
+
}
|
|
58
|
+
return options;
|
|
59
|
+
}
|
|
60
|
+
/** Bound file/stdin reads before allocating a full credential document. */
|
|
61
|
+
export function readNamedCredentialsFile(input, schemes) {
|
|
62
|
+
let fd;
|
|
63
|
+
const chunks = [];
|
|
64
|
+
let total = 0;
|
|
65
|
+
try {
|
|
66
|
+
fd = input === "-" ? 0 : openSync(input.slice(1), "r");
|
|
67
|
+
while (true) {
|
|
68
|
+
const buffer = Buffer.alloc(Math.min(32_768, 1_048_577 - total));
|
|
69
|
+
const size = readSync(fd, buffer, 0, buffer.length, null);
|
|
70
|
+
if (!size)
|
|
71
|
+
break;
|
|
72
|
+
total += size;
|
|
73
|
+
if (total > 1_048_576)
|
|
74
|
+
throw new Error("size");
|
|
75
|
+
chunks.push(buffer.subarray(0, size));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
throw new Error("Cannot read named credentials. Supply a readable JSON file or stdin input of at most 1 MiB.");
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
if (fd !== undefined && input !== "-")
|
|
83
|
+
closeSync(fd);
|
|
84
|
+
}
|
|
85
|
+
return parseNamedCredentials(Buffer.concat(chunks).toString("utf8"), schemes);
|
|
86
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare class OAuthResponseError extends Error {
|
|
2
|
+
readonly code: "request_failed" | "timed_out" | "cancelled" | "response_too_large" | "invalid_response";
|
|
3
|
+
constructor(code: OAuthResponseError["code"]);
|
|
4
|
+
}
|
|
5
|
+
export type DeviceOAuthError = "authorization_pending" | "slow_down" | "access_denied" | "expired_token";
|
|
6
|
+
interface AuthenticationResponse {
|
|
7
|
+
status: number;
|
|
8
|
+
data?: Record<string, unknown>;
|
|
9
|
+
error?: DeviceOAuthError;
|
|
10
|
+
}
|
|
11
|
+
/** Error bodies and transport causes may contain credentials and are discarded. */
|
|
12
|
+
export declare function oauthJsonRequest(url: string | URL, init?: RequestInit, timeoutMs?: number): Promise<AuthenticationResponse>;
|
|
13
|
+
/** Device polling needs four protocol errors. Return only their exact names,
|
|
14
|
+
* never descriptions, error URIs, arbitrary error codes or other body fields. */
|
|
15
|
+
export declare function oauthDeviceRequest(url: string | URL, init?: RequestInit, timeoutMs?: number): Promise<AuthenticationResponse>;
|
|
16
|
+
/** Revocation may return an empty success body. Do not wait for or parse it. */
|
|
17
|
+
export declare function oauthStatusRequest(url: string | URL, init?: RequestInit, timeoutMs?: number): Promise<{
|
|
18
|
+
status: number;
|
|
19
|
+
}>;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=oauth-request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-request.d.ts","sourceRoot":"","sources":["../src/oauth-request.ts"],"names":[],"mappings":"AACA,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAE,gBAAgB,GAAG,WAAW,GAAG,WAAW,GAAG,oBAAoB,GAAG,kBAAkB,CAAC;gBAC5F,IAAI,EAAE,kBAAkB,CAAC,MAAM,CAAC;CAY7C;AAED,MAAM,MAAM,gBAAgB,GAAG,uBAAuB,GAAG,WAAW,GAAG,eAAe,GAAG,eAAe,CAAC;AACzG,UAAU,sBAAsB;IAAG,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,gBAAgB,CAAA;CAAE;AAE7G,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,GAAE,WAAgB,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAE/H;AACD;iFACiF;AACjF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,GAAE,WAAgB,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAEjI;AACD,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,GAAE,WAAgB,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAE7H"}
|