@ai-ecoverse/slicc-agent 3.52.1 → 4.1.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/README.md +17 -13
- package/dist/client.js +3 -1
- package/dist/client.js.map +2 -2
- package/dist/control.js +4 -1
- package/dist/control.js.map +2 -2
- package/dist/freezer/index.js +27 -68
- package/dist/freezer/index.js.map +2 -2
- package/dist/host.js +2 -0
- package/dist/host.js.map +2 -2
- package/dist/index.js +0 -3
- package/dist/index.js.map +2 -2
- package/dist/kernel/agent-users.js +3 -0
- package/dist/kernel/agent-users.js.map +2 -2
- package/dist/main.js +111 -26
- package/dist/main.js.map +2 -2
- package/dist/mcp/accounts.js +18 -14
- package/dist/mcp/accounts.js.map +2 -2
- package/dist/mcp/connection.js +0 -2
- package/dist/mcp/connection.js.map +2 -2
- package/dist/mcp/credentials.js +6 -15
- package/dist/mcp/credentials.js.map +2 -2
- package/dist/mcp/index.js +0 -1
- package/dist/mcp/index.js.map +2 -2
- package/dist/mcp/oauth.js +15 -20
- package/dist/mcp/oauth.js.map +2 -2
- package/dist/mcp/runtime.js +36 -14
- package/dist/mcp/runtime.js.map +2 -2
- package/dist/mcp/tools.js +4 -6
- package/dist/mcp/tools.js.map +2 -2
- package/dist/models/index.js +7 -4
- package/dist/models/index.js.map +2 -2
- package/dist/monitor/index.js +5 -10
- package/dist/monitor/index.js.map +2 -2
- package/dist/monitor/redact.js +4 -5
- package/dist/monitor/redact.js.map +2 -2
- package/dist/secrets/credentials.js +32 -0
- package/dist/secrets/credentials.js.map +7 -0
- package/dist/secrets/endpoints.js +111 -0
- package/dist/secrets/endpoints.js.map +7 -0
- package/dist/secrets/extension.js +6 -3
- package/dist/secrets/extension.js.map +2 -2
- package/dist/secrets/fetch.js +160 -0
- package/dist/secrets/fetch.js.map +7 -0
- package/dist/secrets/records.js +35 -0
- package/dist/secrets/records.js.map +7 -0
- package/dist/secrets/store.js +50 -0
- package/dist/secrets/store.js.map +7 -0
- package/dist/secrets/vault.js +58 -13
- package/dist/secrets/vault.js.map +2 -2
- package/dist/services.js.map +2 -2
- package/dist/settings.js +85 -23
- package/dist/settings.js.map +2 -2
- package/dist/skills/index.js +20 -3
- package/dist/skills/index.js.map +3 -3
- package/dist/spectrum/agent-port.js +29 -1
- package/dist/spectrum/agent-port.js.map +2 -2
- package/dist/spectrum/settings-port.js +24 -6
- package/dist/spectrum/settings-port.js.map +2 -2
- package/dist/titles/extension.js +110 -0
- package/dist/titles/extension.js.map +7 -0
- package/dist/{freezer/title.js → titles/generate.js} +21 -2
- package/dist/titles/generate.js.map +7 -0
- package/dist/types/client.d.ts +2 -1
- package/dist/types/control.d.ts +1 -1
- package/dist/types/freezer/index.d.ts +2 -5
- package/dist/types/host.d.ts +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/main.d.ts +7 -2
- package/dist/types/mcp/accounts.d.ts +10 -4
- package/dist/types/mcp/connection.d.ts +0 -2
- package/dist/types/mcp/credentials.d.ts +6 -4
- package/dist/types/mcp/oauth.d.ts +3 -2
- package/dist/types/mcp/runtime.d.ts +18 -4
- package/dist/types/models/index.d.ts +2 -0
- package/dist/types/monitor/index.d.ts +0 -1
- package/dist/types/monitor/redact.d.ts +2 -2
- package/dist/types/secrets/credentials.d.ts +5 -0
- package/dist/types/secrets/endpoints.d.ts +27 -0
- package/dist/types/secrets/fetch.d.ts +13 -0
- package/dist/types/secrets/records.d.ts +13 -0
- package/dist/types/secrets/store.d.ts +7 -0
- package/dist/types/secrets/vault.d.ts +31 -1
- package/dist/types/services.d.ts +13 -1
- package/dist/types/settings.d.ts +17 -2
- package/dist/types/skills/index.d.ts +4 -1
- package/dist/types/spectrum/agent-port.d.ts +1 -1
- package/dist/types/spectrum/settings-port.d.ts +6 -4
- package/dist/types/titles/extension.d.ts +19 -0
- package/dist/types/{freezer/title.d.ts → titles/generate.d.ts} +4 -0
- package/package.json +6 -4
- package/dist/credentials.js +0 -100
- package/dist/credentials.js.map +0 -7
- package/dist/freezer/title.js.map +0 -7
- package/dist/types/credentials.d.ts +0 -12
package/dist/mcp/credentials.js
CHANGED
|
@@ -13,33 +13,24 @@ function secretNames(config) {
|
|
|
13
13
|
for (const name of placeholders(value)) names.add(name);
|
|
14
14
|
return [...names];
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
function resolveHeaders(server, config, records) {
|
|
17
17
|
const names = secretNames(config);
|
|
18
|
-
if (!names.length) return { ok: true, headers: { ...config.headers }
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const key = stored?.type === "api_key" ? stored.key : void 0;
|
|
22
|
-
const value = (name) => env[name] ?? (names.length === 1 ? key : void 0);
|
|
23
|
-
const missing = names.filter((name) => !value(name));
|
|
18
|
+
if (!names.length) return { ok: true, headers: { ...config.headers } };
|
|
19
|
+
const placeholder = (name) => (records.mcp(server, name) ?? (names.length === 1 ? records.mcp(server) : void 0))?.placeholder;
|
|
20
|
+
const missing = names.filter((name) => !placeholder(name));
|
|
24
21
|
if (missing.length) return { ok: false, missing };
|
|
25
22
|
const headers = {};
|
|
26
23
|
for (const [header, text] of Object.entries(config.headers ?? {}))
|
|
27
24
|
headers[header] = text.replace(
|
|
28
25
|
/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g,
|
|
29
|
-
(_, name) => String(
|
|
26
|
+
(_, name) => String(placeholder(name))
|
|
30
27
|
);
|
|
31
|
-
return { ok: true, headers
|
|
32
|
-
}
|
|
33
|
-
function scrub(text, secrets) {
|
|
34
|
-
let out = text;
|
|
35
|
-
for (const secret of secrets) if (secret.length >= 4) out = out.split(secret).join("[redacted]");
|
|
36
|
-
return out;
|
|
28
|
+
return { ok: true, headers };
|
|
37
29
|
}
|
|
38
30
|
export {
|
|
39
31
|
MCP_CREDENTIAL_PREFIX,
|
|
40
32
|
credentialKey,
|
|
41
33
|
resolveHeaders,
|
|
42
|
-
scrub,
|
|
43
34
|
secretNames
|
|
44
35
|
};
|
|
45
36
|
//# sourceMappingURL=credentials.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/mcp/credentials.ts"],
|
|
4
|
-
"sourcesContent": ["import
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { type McpServerConfig, placeholders } from './config.ts';\n\nexport const MCP_CREDENTIAL_PREFIX = 'mcp:';\n\nexport interface McpRecords {\n mcp(server: string, name?: string): { placeholder: string } | undefined;\n}\n\nasync function digest(text: string): Promise<string> {\n const bytes = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(text));\n return [...new Uint8Array(bytes).slice(0, 12)]\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('');\n}\n\nexport async function credentialKey(server: string, url: string): Promise<string> {\n return `${MCP_CREDENTIAL_PREFIX}${server}|${await digest(new URL(url).href)}`;\n}\n\nexport function secretNames(config: McpServerConfig): string[] {\n const names = new Set<string>();\n for (const value of Object.values(config.headers ?? {}))\n for (const name of placeholders(value)) names.add(name);\n return [...names];\n}\n\nexport type Resolved =\n | { ok: true; headers: Record<string, string> }\n | { ok: false; missing: string[] };\n\nexport function resolveHeaders(\n server: string,\n config: McpServerConfig,\n records: McpRecords\n): Resolved {\n const names = secretNames(config);\n if (!names.length) return { ok: true, headers: { ...config.headers } };\n const placeholder = (name: string) =>\n (records.mcp(server, name) ?? (names.length === 1 ? records.mcp(server) : undefined))\n ?.placeholder;\n const missing = names.filter((name) => !placeholder(name));\n if (missing.length) return { ok: false, missing };\n const headers: Record<string, string> = {};\n for (const [header, text] of Object.entries(config.headers ?? {}))\n headers[header] = text.replace(/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g, (_, name: string) =>\n String(placeholder(name))\n );\n return { ok: true, headers };\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAA+B,oBAAoB;AAE5C,MAAM,wBAAwB;AAMrC,eAAe,OAAO,MAA+B;AACnD,QAAM,QAAQ,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI,YAAY,EAAE,OAAO,IAAI,CAAC;AAClF,SAAO,CAAC,GAAG,IAAI,WAAW,KAAK,EAAE,MAAM,GAAG,EAAE,CAAC,EAC1C,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAChD,KAAK,EAAE;AACZ;AAEA,eAAsB,cAAc,QAAgB,KAA8B;AAChF,SAAO,GAAG,qBAAqB,GAAG,MAAM,IAAI,MAAM,OAAO,IAAI,IAAI,GAAG,EAAE,IAAI,CAAC;AAC7E;AAEO,SAAS,YAAY,QAAmC;AAC7D,QAAM,QAAQ,oBAAI,IAAY;AAC9B,aAAW,SAAS,OAAO,OAAO,OAAO,WAAW,CAAC,CAAC;AACpD,eAAW,QAAQ,aAAa,KAAK,EAAG,OAAM,IAAI,IAAI;AACxD,SAAO,CAAC,GAAG,KAAK;AAClB;AAMO,SAAS,eACd,QACA,QACA,SACU;AACV,QAAM,QAAQ,YAAY,MAAM;AAChC,MAAI,CAAC,MAAM,OAAQ,QAAO,EAAE,IAAI,MAAM,SAAS,EAAE,GAAG,OAAO,QAAQ,EAAE;AACrE,QAAM,cAAc,CAAC,UAClB,QAAQ,IAAI,QAAQ,IAAI,MAAM,MAAM,WAAW,IAAI,QAAQ,IAAI,MAAM,IAAI,UACtE;AACN,QAAM,UAAU,MAAM,OAAO,CAAC,SAAS,CAAC,YAAY,IAAI,CAAC;AACzD,MAAI,QAAQ,OAAQ,QAAO,EAAE,IAAI,OAAO,QAAQ;AAChD,QAAM,UAAkC,CAAC;AACzC,aAAW,CAAC,QAAQ,IAAI,KAAK,OAAO,QAAQ,OAAO,WAAW,CAAC,CAAC;AAC9D,YAAQ,MAAM,IAAI,KAAK;AAAA,MAAQ;AAAA,MAAmC,CAAC,GAAG,SACpE,OAAO,YAAY,IAAI,CAAC;AAAA,IAC1B;AACF,SAAO,EAAE,IAAI,MAAM,QAAQ;AAC7B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/mcp/index.js
CHANGED
package/dist/mcp/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/mcp/index.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type {\n Agent,\n Registry,\n ToolExecutionApi,\n ToolRegistration,\n} from '@earendil-works/pi-durable';\nimport type { CodemodeExtra } from './allow.ts';\nimport type { McpCatalog } from './catalog.ts';\nimport { parseMcpConfig } from './config.ts';\nimport { configPath, shown } from './paths.ts';\nimport type { McpAttach, McpRuntime } from './runtime.ts';\n\nexport { allows, type CodemodeExtra, McpAllowDoc, mcpEntries, serverAllowed } from './allow.ts';\nexport type { McpAccounts, McpAttach, McpRuntime, ServerStatus } from './runtime.ts';\n\nexport type McpLoader = () => Promise<typeof import('./runtime.ts')>;\n\nexport const loadRuntime: McpLoader = () => import('./runtime.ts');\n\nexport interface McpSetup {\n catalog(): McpCatalog | undefined;\n direct(names: readonly string[]): ToolRegistration[];\n codemode(api: ToolExecutionApi, agent: Agent, context: Context): Promise<CodemodeExtra>;\n attach(options: McpAttach, context: Context): Promise<McpRuntime>;\n}\n\nconst idle: McpRuntime = {\n accounts: async () => [],\n signIn: async (id) => {\n throw new Error(`${id} is not an MCP server that signs in`);\n },\n connect: async (id) => {\n throw new Error(`${id} is not an MCP server that takes a token`);\n },\n disconnect: async () => undefined,\n onChange: () => () => undefined,\n status: () => [],\n
|
|
5
|
-
"mappings": "AASA,SAAS,sBAAsB;AAC/B,SAAS,YAAY,aAAa;AAGlC,SAAS,QAA4B,aAAa,YAAY,qBAAqB;AAK5E,MAAM,cAAyB,MAAM,OAAO,cAAc;AASjE,MAAM,OAAmB;AAAA,EACvB,UAAU,YAAY,CAAC;AAAA,EACvB,QAAQ,OAAO,OAAO;AACpB,UAAM,IAAI,MAAM,GAAG,EAAE,qCAAqC;AAAA,EAC5D;AAAA,EACA,SAAS,OAAO,OAAO;AACrB,UAAM,IAAI,MAAM,GAAG,EAAE,0CAA0C;AAAA,EACjE;AAAA,EACA,YAAY,YAAY;AAAA,EACxB,UAAU,MAAM,MAAM;AAAA,EACtB,QAAQ,MAAM,CAAC;AAAA,EACf,
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type {\n Agent,\n Registry,\n ToolExecutionApi,\n ToolRegistration,\n} from '@earendil-works/pi-durable';\nimport type { CodemodeExtra } from './allow.ts';\nimport type { McpCatalog } from './catalog.ts';\nimport { parseMcpConfig } from './config.ts';\nimport { configPath, shown } from './paths.ts';\nimport type { McpAttach, McpRuntime } from './runtime.ts';\n\nexport { allows, type CodemodeExtra, McpAllowDoc, mcpEntries, serverAllowed } from './allow.ts';\nexport type { McpAccounts, McpAttach, McpRuntime, ServerStatus } from './runtime.ts';\n\nexport type McpLoader = () => Promise<typeof import('./runtime.ts')>;\n\nexport const loadRuntime: McpLoader = () => import('./runtime.ts');\n\nexport interface McpSetup {\n catalog(): McpCatalog | undefined;\n direct(names: readonly string[]): ToolRegistration[];\n codemode(api: ToolExecutionApi, agent: Agent, context: Context): Promise<CodemodeExtra>;\n attach(options: McpAttach, context: Context): Promise<McpRuntime>;\n}\n\nconst idle: McpRuntime = {\n accounts: async () => [],\n signIn: async (id) => {\n throw new Error(`${id} is not an MCP server that signs in`);\n },\n connect: async (id) => {\n throw new Error(`${id} is not an MCP server that takes a token`);\n },\n disconnect: async () => undefined,\n onChange: () => () => undefined,\n status: () => [],\n reload: async () => undefined,\n close: async () => undefined,\n};\n\nasync function unavailable(options: McpAttach, error: unknown, context: Context) {\n const file = configPath(options.home, options.agentDir);\n const named = shown(file, options.home);\n const read = await options.env.readTextFile(file, context);\n if (!parseMcpConfig(read.ok ? read.value : undefined).servers.length) return;\n await options.licks\n .deliver(\n {\n channel: 'fswatch',\n source: `mcp:${named}`,\n title: \"MCP servers can't start in this SLICC\",\n text: error instanceof Error ? error.message : String(error),\n body: `The MCP client didn't load, so the servers in ${named} stay off until SLICC is updated.`,\n target: 'cone',\n severity: 'warn',\n eventId: 'mcp\\nunavailable',\n },\n context\n )\n .catch(() => undefined);\n}\n\nexport function setupMcp(registry: Registry, load: McpLoader = loadRuntime): McpSetup {\n let catalog: McpCatalog | undefined;\n return {\n catalog: () => catalog,\n direct: (names) => catalog?.direct(names) ?? [],\n codemode: async (api, agent, context) =>\n catalog ? catalog.codemode(api, agent, context) : { tools: [], globals: [] },\n async attach(options, context) {\n let started: Awaited<ReturnType<Awaited<ReturnType<McpLoader>>['startMcp']>>;\n try {\n started = await (await load()).startMcp(registry, options, context);\n } catch (error) {\n await unavailable(options, error, context);\n return idle;\n }\n catalog = started.catalog;\n return started.runtime;\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "AASA,SAAS,sBAAsB;AAC/B,SAAS,YAAY,aAAa;AAGlC,SAAS,QAA4B,aAAa,YAAY,qBAAqB;AAK5E,MAAM,cAAyB,MAAM,OAAO,cAAc;AASjE,MAAM,OAAmB;AAAA,EACvB,UAAU,YAAY,CAAC;AAAA,EACvB,QAAQ,OAAO,OAAO;AACpB,UAAM,IAAI,MAAM,GAAG,EAAE,qCAAqC;AAAA,EAC5D;AAAA,EACA,SAAS,OAAO,OAAO;AACrB,UAAM,IAAI,MAAM,GAAG,EAAE,0CAA0C;AAAA,EACjE;AAAA,EACA,YAAY,YAAY;AAAA,EACxB,UAAU,MAAM,MAAM;AAAA,EACtB,QAAQ,MAAM,CAAC;AAAA,EACf,QAAQ,YAAY;AAAA,EACpB,OAAO,YAAY;AACrB;AAEA,eAAe,YAAY,SAAoB,OAAgB,SAAkB;AAC/E,QAAM,OAAO,WAAW,QAAQ,MAAM,QAAQ,QAAQ;AACtD,QAAM,QAAQ,MAAM,MAAM,QAAQ,IAAI;AACtC,QAAM,OAAO,MAAM,QAAQ,IAAI,aAAa,MAAM,OAAO;AACzD,MAAI,CAAC,eAAe,KAAK,KAAK,KAAK,QAAQ,MAAS,EAAE,QAAQ,OAAQ;AACtE,QAAM,QAAQ,MACX;AAAA,IACC;AAAA,MACE,SAAS;AAAA,MACT,QAAQ,OAAO,KAAK;AAAA,MACpB,OAAO;AAAA,MACP,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC3D,MAAM,iDAAiD,KAAK;AAAA,MAC5D,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,SAAS;AAAA,IACX;AAAA,IACA;AAAA,EACF,EACC,MAAM,MAAM,MAAS;AAC1B;AAEO,SAAS,SAAS,UAAoB,OAAkB,aAAuB;AACpF,MAAI;AACJ,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf,QAAQ,CAAC,UAAU,SAAS,OAAO,KAAK,KAAK,CAAC;AAAA,IAC9C,UAAU,OAAO,KAAK,OAAO,YAC3B,UAAU,QAAQ,SAAS,KAAK,OAAO,OAAO,IAAI,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,EAAE;AAAA,IAC7E,MAAM,OAAO,SAAS,SAAS;AAC7B,UAAI;AACJ,UAAI;AACF,kBAAU,OAAO,MAAM,KAAK,GAAG,SAAS,UAAU,SAAS,OAAO;AAAA,MACpE,SAAS,OAAO;AACd,cAAM,YAAY,SAAS,OAAO,OAAO;AACzC,eAAO;AAAA,MACT;AACA,gBAAU,QAAQ;AAClB,aAAO,QAAQ;AAAA,IACjB;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/mcp/oauth.js
CHANGED
|
@@ -32,7 +32,8 @@ function authProvider(provider, server) {
|
|
|
32
32
|
await authorizeMcp(provider, {
|
|
33
33
|
serverUrl: context.serverUrl,
|
|
34
34
|
...challenge.resourceMetadataUrl ? { resourceMetadataUrl: challenge.resourceMetadataUrl } : {},
|
|
35
|
-
|
|
35
|
+
skipRefresh: true,
|
|
36
|
+
...step ? { scope: stepUpScope(granted?.scope, challenge.scope) } : challenge.scope ? { scope: challenge.scope } : {},
|
|
36
37
|
...configured ? { authorizationServerMetadataUrl: new URL(configured) } : {},
|
|
37
38
|
fetch: context.fetch
|
|
38
39
|
});
|
|
@@ -69,12 +70,12 @@ function randomState() {
|
|
|
69
70
|
class StoredOAuth {
|
|
70
71
|
redirectUrl;
|
|
71
72
|
clientMetadata;
|
|
72
|
-
#
|
|
73
|
+
#store;
|
|
73
74
|
#server;
|
|
74
75
|
#writes = Promise.resolve();
|
|
75
|
-
constructor(server,
|
|
76
|
+
constructor(server, store, redirectUrl) {
|
|
76
77
|
this.#server = server;
|
|
77
|
-
this.#
|
|
78
|
+
this.#store = store;
|
|
78
79
|
this.redirectUrl = redirectUrl;
|
|
79
80
|
this.clientMetadata = {
|
|
80
81
|
client_name: server.config.oauth?.clientName ?? CLIENT_NAME,
|
|
@@ -93,26 +94,17 @@ class StoredOAuth {
|
|
|
93
94
|
}
|
|
94
95
|
async load() {
|
|
95
96
|
await this.#writes;
|
|
96
|
-
const
|
|
97
|
-
const state = stored?.type === "oauth" ? stored.state : void 0;
|
|
97
|
+
const state = await this.#store.get(await this.#key());
|
|
98
98
|
return state?.serverUrl === this.serverUrl ? state : { serverUrl: this.serverUrl };
|
|
99
99
|
}
|
|
100
100
|
async update(change) {
|
|
101
101
|
const write = this.#writes.then(async () => {
|
|
102
102
|
const key = await this.#key();
|
|
103
|
-
await this.#
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
type: "oauth",
|
|
110
|
-
access: next.tokens?.access_token ?? "",
|
|
111
|
-
refresh: next.tokens?.refresh_token ?? "",
|
|
112
|
-
expires: next.tokensExpireAt ?? 0,
|
|
113
|
-
state: next
|
|
114
|
-
};
|
|
115
|
-
});
|
|
103
|
+
const before = await this.#store.get(key);
|
|
104
|
+
await this.#store.set(
|
|
105
|
+
key,
|
|
106
|
+
change(before?.serverUrl === this.serverUrl ? before : { serverUrl: this.serverUrl })
|
|
107
|
+
);
|
|
116
108
|
});
|
|
117
109
|
this.#writes = write.catch(() => void 0);
|
|
118
110
|
await write;
|
|
@@ -176,7 +168,7 @@ class StoredOAuth {
|
|
|
176
168
|
}
|
|
177
169
|
async forget() {
|
|
178
170
|
await this.#writes;
|
|
179
|
-
await this.#
|
|
171
|
+
await this.#store.set(await this.#key(), void 0);
|
|
180
172
|
}
|
|
181
173
|
}
|
|
182
174
|
async function beginSignIn(provider, server, options) {
|
|
@@ -203,6 +195,9 @@ async function beginSignIn(provider, server, options) {
|
|
|
203
195
|
});
|
|
204
196
|
await provider.saveClientInformation(client);
|
|
205
197
|
}
|
|
198
|
+
await options.register(
|
|
199
|
+
new URL(metadata?.token_endpoint ?? new URL("/token", info.authorizationServerUrl)).href
|
|
200
|
+
);
|
|
206
201
|
const { verifier, challenge } = await pkce();
|
|
207
202
|
const state = randomState();
|
|
208
203
|
await provider.update((current) => ({ ...current, codeVerifier: verifier, oauthState: state }));
|
package/dist/mcp/oauth.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/mcp/oauth.ts"],
|
|
4
|
-
"sourcesContent": ["import type { CredentialStore } from '@earendil-works/pi-ai';\nimport type { AuthProvider, McpFetch, UnauthorizedContext } from '@earendil-works/pi-mcp';\nimport {\n authorizeMcp,\n discoverOAuthServerInfo,\n McpOAuthAuthorizationRequiredError,\n type McpOAuthState,\n type OAuthChallenge,\n parseWwwAuthenticate,\n registerClient,\n selectResource,\n stepUpScope,\n} from '@earendil-works/pi-mcp/oauth';\nimport type { McpSignIn } from '../services.ts';\nimport type { McpServerConfig } from './config.ts';\nimport { credentialKey } from './credentials.ts';\n\ntype Provider = Parameters<typeof authorizeMcp>[0];\ntype Tokens = Awaited<ReturnType<Provider['tokens']>>;\ntype Client = Awaited<ReturnType<Provider['clientInformation']>>;\ntype Discovery = NonNullable<McpOAuthState['discovery']>;\n\nexport const CLIENT_NAME = 'SLICC';\n\nexport function sentence(text: string): string {\n const trimmed = text.trim();\n const lowered = /^[A-Z][a-z]/.test(trimmed)\n ? `${trimmed[0]?.toLowerCase()}${trimmed.slice(1)}`\n : trimmed;\n return /[.!?]$/.test(lowered) ? lowered : `${lowered}.`;\n}\n\nconst LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]']);\n\nexport function secureUrl(url: URL): boolean {\n return url.protocol === 'https:' || (url.protocol === 'http:' && LOOPBACK.has(url.hostname));\n}\n\nexport function signInRequired(cause?: unknown): McpOAuthAuthorizationRequiredError {\n const error = new McpOAuthAuthorizationRequiredError();\n if (cause !== undefined) error.cause = cause;\n return error;\n}\n\nexport function authProvider(provider: StoredOAuth, server: OAuthServer): AuthProvider {\n const configured = server.config.oauth?.authServerMetadataUrl;\n let inFlight: Promise<void> | undefined;\n const authorize = async (\n context: UnauthorizedContext,\n step: boolean,\n challenge: OAuthChallenge\n ) => {\n const granted = step ? await provider.tokens() : undefined;\n try {\n await authorizeMcp(provider, {\n serverUrl: context.serverUrl,\n ...(challenge.resourceMetadataUrl\n ? { resourceMetadataUrl: challenge.resourceMetadataUrl }\n : {}),\n ...(step\n ? { scope: stepUpScope(granted?.scope, challenge.scope), skipRefresh: true }\n : challenge.scope\n ? { scope: challenge.scope }\n : {}),\n ...(configured ? { authorizationServerMetadataUrl: new URL(configured) } : {}),\n fetch: context.fetch,\n });\n } catch (error) {\n throw error instanceof McpOAuthAuthorizationRequiredError ? error : signInRequired(error);\n }\n };\n return {\n token: async () => (await provider.tokens())?.access_token,\n async onUnauthorized(context) {\n const challenge = parseWwwAuthenticate(context.response.headers.get('www-authenticate'));\n const step = challenge.error === 'insufficient_scope';\n if (!step && !inFlight && context.token !== undefined) {\n const current = (await provider.tokens())?.access_token;\n if (current !== undefined && current !== context.token) return;\n }\n inFlight ??= authorize(context, step, challenge).finally(() => {\n inFlight = undefined;\n });\n await inFlight;\n },\n };\n}\n\nconst bytesToBase64Url = (bytes: Uint8Array) =>\n btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n\nconst random = (length: number) => crypto.getRandomValues(new Uint8Array(length));\n\nexport async function pkce(): Promise<{ verifier: string; challenge: string }> {\n const verifier = bytesToBase64Url(random(32));\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));\n return { verifier, challenge: bytesToBase64Url(new Uint8Array(digest)) };\n}\n\nexport function randomState(): string {\n return [...random(32)].map((byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nexport interface OAuthServer {\n name: string;\n config: McpServerConfig;\n}\n\nexport class StoredOAuth implements Provider {\n readonly redirectUrl: string;\n readonly clientMetadata: Provider['clientMetadata'];\n readonly #credentials: CredentialStore;\n readonly #server: OAuthServer;\n #writes: Promise<unknown> = Promise.resolve();\n\n constructor(server: OAuthServer, credentials: CredentialStore, redirectUrl: string) {\n this.#server = server;\n this.#credentials = credentials;\n this.redirectUrl = redirectUrl;\n this.clientMetadata = {\n client_name: server.config.oauth?.clientName ?? CLIENT_NAME,\n redirect_uris: [redirectUrl],\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n token_endpoint_auth_method: 'none',\n ...(server.config.oauth?.scope ? { scope: server.config.oauth.scope } : {}),\n };\n }\n\n get serverUrl(): string {\n return String(new URL(this.#server.config.url));\n }\n\n async #key(): Promise<string> {\n return credentialKey(this.#server.name, this.#server.config.url);\n }\n\n async load(): Promise<McpOAuthState> {\n await this.#writes;\n const stored = await this.#credentials.read(await this.#key());\n const state =\n stored?.type === 'oauth' ? (stored.state as McpOAuthState | undefined) : undefined;\n return state?.serverUrl === this.serverUrl ? state : { serverUrl: this.serverUrl };\n }\n\n async update(change: (state: McpOAuthState) => McpOAuthState): Promise<void> {\n const write = this.#writes.then(async () => {\n const key = await this.#key();\n await this.#credentials.modify(key, async (current) => {\n const before =\n current?.type === 'oauth' ? (current.state as McpOAuthState | undefined) : undefined;\n const next = change(\n before?.serverUrl === this.serverUrl ? before : { serverUrl: this.serverUrl }\n );\n return {\n type: 'oauth',\n access: next.tokens?.access_token ?? '',\n refresh: next.tokens?.refresh_token ?? '',\n expires: next.tokensExpireAt ?? 0,\n state: next,\n };\n });\n });\n this.#writes = write.catch(() => undefined);\n await write;\n }\n\n async signedIn(): Promise<boolean> {\n return (await this.load()).tokens !== undefined;\n }\n\n state(): string {\n throw new McpOAuthAuthorizationRequiredError();\n }\n\n async clientInformation(): Promise<Client> {\n const configured = this.#server.config.oauth?.clientId;\n return configured ? { client_id: configured } : (await this.load()).clientInformation;\n }\n\n async saveClientInformation(information: NonNullable<Client>): Promise<void> {\n if (this.#server.config.oauth?.clientId) return;\n await this.update((state) => ({ ...state, clientInformation: information }));\n }\n\n async tokens(): Promise<Tokens> {\n return (await this.load()).tokens;\n }\n\n async saveTokens(tokens: NonNullable<Tokens>): Promise<void> {\n const expires =\n tokens.expires_in === undefined ? undefined : Date.now() + tokens.expires_in * 1000;\n await this.update((state) => {\n const next: McpOAuthState = { ...state, tokens };\n if (expires === undefined) delete next.tokensExpireAt;\n else next.tokensExpireAt = expires;\n return next;\n });\n }\n\n redirectToAuthorization(): void {\n throw new McpOAuthAuthorizationRequiredError();\n }\n\n async saveCodeVerifier(verifier: string): Promise<void> {\n await this.update((state) => ({ ...state, codeVerifier: verifier }));\n }\n\n async codeVerifier(): Promise<string> {\n const verifier = (await this.load()).codeVerifier;\n if (!verifier) throw new Error('No OAuth PKCE code verifier is stored');\n return verifier;\n }\n\n async invalidateCredentials(\n kind: 'all' | 'client' | 'tokens' | 'verifier' | 'discovery'\n ): Promise<void> {\n await this.update((state) => {\n const next = { ...state };\n if (kind === 'all' || kind === 'client') delete next.clientInformation;\n if (kind === 'all' || kind === 'tokens') {\n delete next.tokens;\n delete next.tokensExpireAt;\n }\n if (kind === 'all' || kind === 'verifier') delete next.codeVerifier;\n if (kind === 'all' || kind === 'discovery') delete next.discovery;\n if (kind === 'all') delete next.oauthState;\n return next;\n });\n }\n\n async saveDiscoveryState(discovery: Discovery): Promise<void> {\n await this.update((state) => ({ ...state, discovery }));\n }\n\n async discoveryState(): Promise<Discovery | undefined> {\n return (await this.load()).discovery;\n }\n\n async forget(): Promise<void> {\n await this.#writes;\n await this.#credentials.delete(await this.#key());\n }\n}\n\nexport type { McpSignIn } from '../services.ts';\n\nexport async function beginSignIn(\n provider: StoredOAuth,\n server: OAuthServer,\n options: { fetch: McpFetch; resourceMetadataUrl?: URL }\n): Promise<McpSignIn> {\n const configured = server.config.oauth?.authServerMetadataUrl;\n const info = await discoverOAuthServerInfo(server.config.url, {\n fetch: options.fetch,\n ...(options.resourceMetadataUrl ? { resourceMetadataUrl: options.resourceMetadataUrl } : {}),\n ...(configured ? { authorizationServerMetadataUrl: new URL(configured) } : {}),\n });\n if (!configured) await provider.saveDiscoveryState(info);\n const metadata = info.authorizationServerMetadata;\n if (metadata && !metadata.response_types_supported.includes('code'))\n throw new Error(\"The authorization server doesn't support authorization codes.\");\n if (\n metadata?.code_challenge_methods_supported &&\n !metadata.code_challenge_methods_supported.includes('S256')\n )\n throw new Error(\"The authorization server doesn't support PKCE S256.\");\n const scope =\n server.config.oauth?.scope || info.resourceMetadata?.scopes_supported?.join(' ') || undefined;\n let client = await provider.clientInformation();\n if (!client) {\n client = await registerClient(info.authorizationServerUrl, {\n ...(metadata ? { metadata } : {}),\n clientMetadata: provider.clientMetadata,\n ...(scope ? { scope } : {}),\n fetch: options.fetch,\n });\n await provider.saveClientInformation(client);\n }\n const { verifier, challenge } = await pkce();\n const state = randomState();\n await provider.update((current) => ({ ...current, codeVerifier: verifier, oauthState: state }));\n const url = new URL(\n metadata?.authorization_endpoint ?? new URL('/authorize', info.authorizationServerUrl)\n );\n if (!secureUrl(url)) throw new Error(\"The authorization server's sign-in address isn't https.\");\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', client.client_id);\n url.searchParams.set('code_challenge', challenge);\n url.searchParams.set('code_challenge_method', 'S256');\n url.searchParams.set('redirect_uri', provider.redirectUrl);\n url.searchParams.set('state', state);\n if (scope) url.searchParams.set('scope', scope);\n if (scope?.split(/\\s+/).includes('offline_access')) url.searchParams.set('prompt', 'consent');\n const resource = selectResource(server.config.url, info.resourceMetadata);\n if (resource) url.searchParams.set('resource', resource);\n return { url: url.href, state };\n}\n\nexport interface Callback {\n code?: string;\n state?: string;\n iss?: string;\n error?: string;\n error_description?: string;\n}\n\nexport async function finishSignIn(\n provider: StoredOAuth,\n server: OAuthServer,\n answer: string,\n fetch: McpFetch\n): Promise<void> {\n let callback: Callback;\n try {\n callback = JSON.parse(answer) as Callback;\n } catch {\n throw new Error(\"The sign-in answer isn't a callback.\");\n }\n const expected = (await provider.load()).oauthState;\n if (!expected || callback.state !== expected)\n throw new Error(\"The sign-in answer doesn't match this sign-in. Try again.\");\n if (callback.error)\n throw new Error(\n `Sign-in failed: ${sentence(callback.error_description?.trim() || callback.error)}`\n );\n if (!callback.code) throw new Error('The sign-in answer has no authorization code.');\n const configured = server.config.oauth?.authServerMetadataUrl;\n await authorizeMcp(provider, {\n serverUrl: server.config.url,\n authorizationCode: callback.code,\n ...(callback.iss ? { iss: callback.iss } : {}),\n ...(server.config.oauth?.scope ? { scope: server.config.oauth.scope } : {}),\n ...(configured ? { authorizationServerMetadataUrl: new URL(configured) } : {}),\n fetch,\n });\n await provider.update((state) => {\n const next = { ...state };\n delete next.oauthState;\n delete next.codeVerifier;\n return next;\n });\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import type { AuthProvider, McpFetch, UnauthorizedContext } from '@earendil-works/pi-mcp';\nimport {\n authorizeMcp,\n discoverOAuthServerInfo,\n McpOAuthAuthorizationRequiredError,\n type McpOAuthState,\n type OAuthChallenge,\n parseWwwAuthenticate,\n registerClient,\n selectResource,\n stepUpScope,\n} from '@earendil-works/pi-mcp/oauth';\nimport type { PlainStore } from '../secrets/store.ts';\nimport type { McpSignIn } from '../services.ts';\nimport type { McpServerConfig } from './config.ts';\nimport { credentialKey } from './credentials.ts';\n\ntype Provider = Parameters<typeof authorizeMcp>[0];\ntype Tokens = Awaited<ReturnType<Provider['tokens']>>;\ntype Client = Awaited<ReturnType<Provider['clientInformation']>>;\ntype Discovery = NonNullable<McpOAuthState['discovery']>;\n\nexport const CLIENT_NAME = 'SLICC';\n\nexport function sentence(text: string): string {\n const trimmed = text.trim();\n const lowered = /^[A-Z][a-z]/.test(trimmed)\n ? `${trimmed[0]?.toLowerCase()}${trimmed.slice(1)}`\n : trimmed;\n return /[.!?]$/.test(lowered) ? lowered : `${lowered}.`;\n}\n\nconst LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]']);\n\nexport function secureUrl(url: URL): boolean {\n return url.protocol === 'https:' || (url.protocol === 'http:' && LOOPBACK.has(url.hostname));\n}\n\nexport function signInRequired(cause?: unknown): McpOAuthAuthorizationRequiredError {\n const error = new McpOAuthAuthorizationRequiredError();\n if (cause !== undefined) error.cause = cause;\n return error;\n}\n\nexport function authProvider(provider: StoredOAuth, server: OAuthServer): AuthProvider {\n const configured = server.config.oauth?.authServerMetadataUrl;\n let inFlight: Promise<void> | undefined;\n const authorize = async (\n context: UnauthorizedContext,\n step: boolean,\n challenge: OAuthChallenge\n ) => {\n const granted = step ? await provider.tokens() : undefined;\n try {\n await authorizeMcp(provider, {\n serverUrl: context.serverUrl,\n ...(challenge.resourceMetadataUrl\n ? { resourceMetadataUrl: challenge.resourceMetadataUrl }\n : {}),\n skipRefresh: true,\n ...(step\n ? { scope: stepUpScope(granted?.scope, challenge.scope) }\n : challenge.scope\n ? { scope: challenge.scope }\n : {}),\n ...(configured ? { authorizationServerMetadataUrl: new URL(configured) } : {}),\n fetch: context.fetch,\n });\n } catch (error) {\n throw error instanceof McpOAuthAuthorizationRequiredError ? error : signInRequired(error);\n }\n };\n return {\n token: async () => (await provider.tokens())?.access_token,\n async onUnauthorized(context) {\n const challenge = parseWwwAuthenticate(context.response.headers.get('www-authenticate'));\n const step = challenge.error === 'insufficient_scope';\n if (!step && !inFlight && context.token !== undefined) {\n const current = (await provider.tokens())?.access_token;\n if (current !== undefined && current !== context.token) return;\n }\n inFlight ??= authorize(context, step, challenge).finally(() => {\n inFlight = undefined;\n });\n await inFlight;\n },\n };\n}\n\nconst bytesToBase64Url = (bytes: Uint8Array) =>\n btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n\nconst random = (length: number) => crypto.getRandomValues(new Uint8Array(length));\n\nexport async function pkce(): Promise<{ verifier: string; challenge: string }> {\n const verifier = bytesToBase64Url(random(32));\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));\n return { verifier, challenge: bytesToBase64Url(new Uint8Array(digest)) };\n}\n\nexport function randomState(): string {\n return [...random(32)].map((byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nexport interface OAuthServer {\n name: string;\n config: McpServerConfig;\n}\n\nexport class StoredOAuth implements Provider {\n readonly redirectUrl: string;\n readonly clientMetadata: Provider['clientMetadata'];\n readonly #store: PlainStore;\n readonly #server: OAuthServer;\n #writes: Promise<unknown> = Promise.resolve();\n\n constructor(server: OAuthServer, store: PlainStore, redirectUrl: string) {\n this.#server = server;\n this.#store = store;\n this.redirectUrl = redirectUrl;\n this.clientMetadata = {\n client_name: server.config.oauth?.clientName ?? CLIENT_NAME,\n redirect_uris: [redirectUrl],\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n token_endpoint_auth_method: 'none',\n ...(server.config.oauth?.scope ? { scope: server.config.oauth.scope } : {}),\n };\n }\n\n get serverUrl(): string {\n return String(new URL(this.#server.config.url));\n }\n\n async #key(): Promise<string> {\n return credentialKey(this.#server.name, this.#server.config.url);\n }\n\n async load(): Promise<McpOAuthState> {\n await this.#writes;\n const state = (await this.#store.get(await this.#key())) as McpOAuthState | undefined;\n return state?.serverUrl === this.serverUrl ? state : { serverUrl: this.serverUrl };\n }\n\n async update(change: (state: McpOAuthState) => McpOAuthState): Promise<void> {\n const write = this.#writes.then(async () => {\n const key = await this.#key();\n const before = (await this.#store.get(key)) as McpOAuthState | undefined;\n await this.#store.set(\n key,\n change(before?.serverUrl === this.serverUrl ? before : { serverUrl: this.serverUrl })\n );\n });\n this.#writes = write.catch(() => undefined);\n await write;\n }\n\n async signedIn(): Promise<boolean> {\n return (await this.load()).tokens !== undefined;\n }\n\n state(): string {\n throw new McpOAuthAuthorizationRequiredError();\n }\n\n async clientInformation(): Promise<Client> {\n const configured = this.#server.config.oauth?.clientId;\n return configured ? { client_id: configured } : (await this.load()).clientInformation;\n }\n\n async saveClientInformation(information: NonNullable<Client>): Promise<void> {\n if (this.#server.config.oauth?.clientId) return;\n await this.update((state) => ({ ...state, clientInformation: information }));\n }\n\n async tokens(): Promise<Tokens> {\n return (await this.load()).tokens;\n }\n\n async saveTokens(tokens: NonNullable<Tokens>): Promise<void> {\n const expires =\n tokens.expires_in === undefined ? undefined : Date.now() + tokens.expires_in * 1000;\n await this.update((state) => {\n const next: McpOAuthState = { ...state, tokens };\n if (expires === undefined) delete next.tokensExpireAt;\n else next.tokensExpireAt = expires;\n return next;\n });\n }\n\n redirectToAuthorization(): void {\n throw new McpOAuthAuthorizationRequiredError();\n }\n\n async saveCodeVerifier(verifier: string): Promise<void> {\n await this.update((state) => ({ ...state, codeVerifier: verifier }));\n }\n\n async codeVerifier(): Promise<string> {\n const verifier = (await this.load()).codeVerifier;\n if (!verifier) throw new Error('No OAuth PKCE code verifier is stored');\n return verifier;\n }\n\n async invalidateCredentials(\n kind: 'all' | 'client' | 'tokens' | 'verifier' | 'discovery'\n ): Promise<void> {\n await this.update((state) => {\n const next = { ...state };\n if (kind === 'all' || kind === 'client') delete next.clientInformation;\n if (kind === 'all' || kind === 'tokens') {\n delete next.tokens;\n delete next.tokensExpireAt;\n }\n if (kind === 'all' || kind === 'verifier') delete next.codeVerifier;\n if (kind === 'all' || kind === 'discovery') delete next.discovery;\n if (kind === 'all') delete next.oauthState;\n return next;\n });\n }\n\n async saveDiscoveryState(discovery: Discovery): Promise<void> {\n await this.update((state) => ({ ...state, discovery }));\n }\n\n async discoveryState(): Promise<Discovery | undefined> {\n return (await this.load()).discovery;\n }\n\n async forget(): Promise<void> {\n await this.#writes;\n await this.#store.set(await this.#key(), undefined);\n }\n}\n\nexport type { McpSignIn } from '../services.ts';\n\nexport async function beginSignIn(\n provider: StoredOAuth,\n server: OAuthServer,\n options: { fetch: McpFetch; resourceMetadataUrl?: URL; register(tokenUrl: string): Promise<void> }\n): Promise<McpSignIn> {\n const configured = server.config.oauth?.authServerMetadataUrl;\n const info = await discoverOAuthServerInfo(server.config.url, {\n fetch: options.fetch,\n ...(options.resourceMetadataUrl ? { resourceMetadataUrl: options.resourceMetadataUrl } : {}),\n ...(configured ? { authorizationServerMetadataUrl: new URL(configured) } : {}),\n });\n if (!configured) await provider.saveDiscoveryState(info);\n const metadata = info.authorizationServerMetadata;\n if (metadata && !metadata.response_types_supported.includes('code'))\n throw new Error(\"The authorization server doesn't support authorization codes.\");\n if (\n metadata?.code_challenge_methods_supported &&\n !metadata.code_challenge_methods_supported.includes('S256')\n )\n throw new Error(\"The authorization server doesn't support PKCE S256.\");\n const scope =\n server.config.oauth?.scope || info.resourceMetadata?.scopes_supported?.join(' ') || undefined;\n let client = await provider.clientInformation();\n if (!client) {\n client = await registerClient(info.authorizationServerUrl, {\n ...(metadata ? { metadata } : {}),\n clientMetadata: provider.clientMetadata,\n ...(scope ? { scope } : {}),\n fetch: options.fetch,\n });\n await provider.saveClientInformation(client);\n }\n await options.register(\n new URL(metadata?.token_endpoint ?? new URL('/token', info.authorizationServerUrl)).href\n );\n const { verifier, challenge } = await pkce();\n const state = randomState();\n await provider.update((current) => ({ ...current, codeVerifier: verifier, oauthState: state }));\n const url = new URL(\n metadata?.authorization_endpoint ?? new URL('/authorize', info.authorizationServerUrl)\n );\n if (!secureUrl(url)) throw new Error(\"The authorization server's sign-in address isn't https.\");\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', client.client_id);\n url.searchParams.set('code_challenge', challenge);\n url.searchParams.set('code_challenge_method', 'S256');\n url.searchParams.set('redirect_uri', provider.redirectUrl);\n url.searchParams.set('state', state);\n if (scope) url.searchParams.set('scope', scope);\n if (scope?.split(/\\s+/).includes('offline_access')) url.searchParams.set('prompt', 'consent');\n const resource = selectResource(server.config.url, info.resourceMetadata);\n if (resource) url.searchParams.set('resource', resource);\n return { url: url.href, state };\n}\n\nexport interface Callback {\n code?: string;\n state?: string;\n iss?: string;\n error?: string;\n error_description?: string;\n}\n\nexport async function finishSignIn(\n provider: StoredOAuth,\n server: OAuthServer,\n answer: string,\n fetch: McpFetch\n): Promise<void> {\n let callback: Callback;\n try {\n callback = JSON.parse(answer) as Callback;\n } catch {\n throw new Error(\"The sign-in answer isn't a callback.\");\n }\n const expected = (await provider.load()).oauthState;\n if (!expected || callback.state !== expected)\n throw new Error(\"The sign-in answer doesn't match this sign-in. Try again.\");\n if (callback.error)\n throw new Error(\n `Sign-in failed: ${sentence(callback.error_description?.trim() || callback.error)}`\n );\n if (!callback.code) throw new Error('The sign-in answer has no authorization code.');\n const configured = server.config.oauth?.authServerMetadataUrl;\n await authorizeMcp(provider, {\n serverUrl: server.config.url,\n authorizationCode: callback.code,\n ...(callback.iss ? { iss: callback.iss } : {}),\n ...(server.config.oauth?.scope ? { scope: server.config.oauth.scope } : {}),\n ...(configured ? { authorizationServerMetadataUrl: new URL(configured) } : {}),\n fetch,\n });\n await provider.update((state) => {\n const next = { ...state };\n delete next.oauthState;\n delete next.codeVerifier;\n return next;\n });\n}\n"],
|
|
5
|
+
"mappings": "AACA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAIP,SAAS,qBAAqB;AAOvB,MAAM,cAAc;AAEpB,SAAS,SAAS,MAAsB;AAC7C,QAAM,UAAU,KAAK,KAAK;AAC1B,QAAM,UAAU,cAAc,KAAK,OAAO,IACtC,GAAG,QAAQ,CAAC,GAAG,YAAY,CAAC,GAAG,QAAQ,MAAM,CAAC,CAAC,KAC/C;AACJ,SAAO,SAAS,KAAK,OAAO,IAAI,UAAU,GAAG,OAAO;AACtD;AAEA,MAAM,WAAW,oBAAI,IAAI,CAAC,aAAa,aAAa,OAAO,CAAC;AAErD,SAAS,UAAU,KAAmB;AAC3C,SAAO,IAAI,aAAa,YAAa,IAAI,aAAa,WAAW,SAAS,IAAI,IAAI,QAAQ;AAC5F;AAEO,SAAS,eAAe,OAAqD;AAClF,QAAM,QAAQ,IAAI,mCAAmC;AACrD,MAAI,UAAU,OAAW,OAAM,QAAQ;AACvC,SAAO;AACT;AAEO,SAAS,aAAa,UAAuB,QAAmC;AACrF,QAAM,aAAa,OAAO,OAAO,OAAO;AACxC,MAAI;AACJ,QAAM,YAAY,OAChB,SACA,MACA,cACG;AACH,UAAM,UAAU,OAAO,MAAM,SAAS,OAAO,IAAI;AACjD,QAAI;AACF,YAAM,aAAa,UAAU;AAAA,QAC3B,WAAW,QAAQ;AAAA,QACnB,GAAI,UAAU,sBACV,EAAE,qBAAqB,UAAU,oBAAoB,IACrD,CAAC;AAAA,QACL,aAAa;AAAA,QACb,GAAI,OACA,EAAE,OAAO,YAAY,SAAS,OAAO,UAAU,KAAK,EAAE,IACtD,UAAU,QACR,EAAE,OAAO,UAAU,MAAM,IACzB,CAAC;AAAA,QACP,GAAI,aAAa,EAAE,gCAAgC,IAAI,IAAI,UAAU,EAAE,IAAI,CAAC;AAAA,QAC5E,OAAO,QAAQ;AAAA,MACjB,CAAC;AAAA,IACH,SAAS,OAAO;AACd,YAAM,iBAAiB,qCAAqC,QAAQ,eAAe,KAAK;AAAA,IAC1F;AAAA,EACF;AACA,SAAO;AAAA,IACL,OAAO,aAAa,MAAM,SAAS,OAAO,IAAI;AAAA,IAC9C,MAAM,eAAe,SAAS;AAC5B,YAAM,YAAY,qBAAqB,QAAQ,SAAS,QAAQ,IAAI,kBAAkB,CAAC;AACvF,YAAM,OAAO,UAAU,UAAU;AACjC,UAAI,CAAC,QAAQ,CAAC,YAAY,QAAQ,UAAU,QAAW;AACrD,cAAM,WAAW,MAAM,SAAS,OAAO,IAAI;AAC3C,YAAI,YAAY,UAAa,YAAY,QAAQ,MAAO;AAAA,MAC1D;AACA,mBAAa,UAAU,SAAS,MAAM,SAAS,EAAE,QAAQ,MAAM;AAC7D,mBAAW;AAAA,MACb,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAEA,MAAM,mBAAmB,CAAC,UACxB,KAAK,OAAO,aAAa,GAAG,KAAK,CAAC,EAC/B,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,EAAE;AAEtB,MAAM,SAAS,CAAC,WAAmB,OAAO,gBAAgB,IAAI,WAAW,MAAM,CAAC;AAEhF,eAAsB,OAAyD;AAC7E,QAAM,WAAW,iBAAiB,OAAO,EAAE,CAAC;AAC5C,QAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI,YAAY,EAAE,OAAO,QAAQ,CAAC;AACvF,SAAO,EAAE,UAAU,WAAW,iBAAiB,IAAI,WAAW,MAAM,CAAC,EAAE;AACzE;AAEO,SAAS,cAAsB;AACpC,SAAO,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAClF;AAOO,MAAM,YAAgC;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT,UAA4B,QAAQ,QAAQ;AAAA,EAE5C,YAAY,QAAqB,OAAmB,aAAqB;AACvE,SAAK,UAAU;AACf,SAAK,SAAS;AACd,SAAK,cAAc;AACnB,SAAK,iBAAiB;AAAA,MACpB,aAAa,OAAO,OAAO,OAAO,cAAc;AAAA,MAChD,eAAe,CAAC,WAAW;AAAA,MAC3B,aAAa,CAAC,sBAAsB,eAAe;AAAA,MACnD,gBAAgB,CAAC,MAAM;AAAA,MACvB,4BAA4B;AAAA,MAC5B,GAAI,OAAO,OAAO,OAAO,QAAQ,EAAE,OAAO,OAAO,OAAO,MAAM,MAAM,IAAI,CAAC;AAAA,IAC3E;AAAA,EACF;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,OAAO,IAAI,IAAI,KAAK,QAAQ,OAAO,GAAG,CAAC;AAAA,EAChD;AAAA,EAEA,MAAM,OAAwB;AAC5B,WAAO,cAAc,KAAK,QAAQ,MAAM,KAAK,QAAQ,OAAO,GAAG;AAAA,EACjE;AAAA,EAEA,MAAM,OAA+B;AACnC,UAAM,KAAK;AACX,UAAM,QAAS,MAAM,KAAK,OAAO,IAAI,MAAM,KAAK,KAAK,CAAC;AACtD,WAAO,OAAO,cAAc,KAAK,YAAY,QAAQ,EAAE,WAAW,KAAK,UAAU;AAAA,EACnF;AAAA,EAEA,MAAM,OAAO,QAAgE;AAC3E,UAAM,QAAQ,KAAK,QAAQ,KAAK,YAAY;AAC1C,YAAM,MAAM,MAAM,KAAK,KAAK;AAC5B,YAAM,SAAU,MAAM,KAAK,OAAO,IAAI,GAAG;AACzC,YAAM,KAAK,OAAO;AAAA,QAChB;AAAA,QACA,OAAO,QAAQ,cAAc,KAAK,YAAY,SAAS,EAAE,WAAW,KAAK,UAAU,CAAC;AAAA,MACtF;AAAA,IACF,CAAC;AACD,SAAK,UAAU,MAAM,MAAM,MAAM,MAAS;AAC1C,UAAM;AAAA,EACR;AAAA,EAEA,MAAM,WAA6B;AACjC,YAAQ,MAAM,KAAK,KAAK,GAAG,WAAW;AAAA,EACxC;AAAA,EAEA,QAAgB;AACd,UAAM,IAAI,mCAAmC;AAAA,EAC/C;AAAA,EAEA,MAAM,oBAAqC;AACzC,UAAM,aAAa,KAAK,QAAQ,OAAO,OAAO;AAC9C,WAAO,aAAa,EAAE,WAAW,WAAW,KAAK,MAAM,KAAK,KAAK,GAAG;AAAA,EACtE;AAAA,EAEA,MAAM,sBAAsB,aAAiD;AAC3E,QAAI,KAAK,QAAQ,OAAO,OAAO,SAAU;AACzC,UAAM,KAAK,OAAO,CAAC,WAAW,EAAE,GAAG,OAAO,mBAAmB,YAAY,EAAE;AAAA,EAC7E;AAAA,EAEA,MAAM,SAA0B;AAC9B,YAAQ,MAAM,KAAK,KAAK,GAAG;AAAA,EAC7B;AAAA,EAEA,MAAM,WAAW,QAA4C;AAC3D,UAAM,UACJ,OAAO,eAAe,SAAY,SAAY,KAAK,IAAI,IAAI,OAAO,aAAa;AACjF,UAAM,KAAK,OAAO,CAAC,UAAU;AAC3B,YAAM,OAAsB,EAAE,GAAG,OAAO,OAAO;AAC/C,UAAI,YAAY,OAAW,QAAO,KAAK;AAAA,UAClC,MAAK,iBAAiB;AAC3B,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,0BAAgC;AAC9B,UAAM,IAAI,mCAAmC;AAAA,EAC/C;AAAA,EAEA,MAAM,iBAAiB,UAAiC;AACtD,UAAM,KAAK,OAAO,CAAC,WAAW,EAAE,GAAG,OAAO,cAAc,SAAS,EAAE;AAAA,EACrE;AAAA,EAEA,MAAM,eAAgC;AACpC,UAAM,YAAY,MAAM,KAAK,KAAK,GAAG;AACrC,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,uCAAuC;AACtE,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,sBACJ,MACe;AACf,UAAM,KAAK,OAAO,CAAC,UAAU;AAC3B,YAAM,OAAO,EAAE,GAAG,MAAM;AACxB,UAAI,SAAS,SAAS,SAAS,SAAU,QAAO,KAAK;AACrD,UAAI,SAAS,SAAS,SAAS,UAAU;AACvC,eAAO,KAAK;AACZ,eAAO,KAAK;AAAA,MACd;AACA,UAAI,SAAS,SAAS,SAAS,WAAY,QAAO,KAAK;AACvD,UAAI,SAAS,SAAS,SAAS,YAAa,QAAO,KAAK;AACxD,UAAI,SAAS,MAAO,QAAO,KAAK;AAChC,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,mBAAmB,WAAqC;AAC5D,UAAM,KAAK,OAAO,CAAC,WAAW,EAAE,GAAG,OAAO,UAAU,EAAE;AAAA,EACxD;AAAA,EAEA,MAAM,iBAAiD;AACrD,YAAQ,MAAM,KAAK,KAAK,GAAG;AAAA,EAC7B;AAAA,EAEA,MAAM,SAAwB;AAC5B,UAAM,KAAK;AACX,UAAM,KAAK,OAAO,IAAI,MAAM,KAAK,KAAK,GAAG,MAAS;AAAA,EACpD;AACF;AAIA,eAAsB,YACpB,UACA,QACA,SACoB;AACpB,QAAM,aAAa,OAAO,OAAO,OAAO;AACxC,QAAM,OAAO,MAAM,wBAAwB,OAAO,OAAO,KAAK;AAAA,IAC5D,OAAO,QAAQ;AAAA,IACf,GAAI,QAAQ,sBAAsB,EAAE,qBAAqB,QAAQ,oBAAoB,IAAI,CAAC;AAAA,IAC1F,GAAI,aAAa,EAAE,gCAAgC,IAAI,IAAI,UAAU,EAAE,IAAI,CAAC;AAAA,EAC9E,CAAC;AACD,MAAI,CAAC,WAAY,OAAM,SAAS,mBAAmB,IAAI;AACvD,QAAM,WAAW,KAAK;AACtB,MAAI,YAAY,CAAC,SAAS,yBAAyB,SAAS,MAAM;AAChE,UAAM,IAAI,MAAM,+DAA+D;AACjF,MACE,UAAU,oCACV,CAAC,SAAS,iCAAiC,SAAS,MAAM;AAE1D,UAAM,IAAI,MAAM,qDAAqD;AACvE,QAAM,QACJ,OAAO,OAAO,OAAO,SAAS,KAAK,kBAAkB,kBAAkB,KAAK,GAAG,KAAK;AACtF,MAAI,SAAS,MAAM,SAAS,kBAAkB;AAC9C,MAAI,CAAC,QAAQ;AACX,aAAS,MAAM,eAAe,KAAK,wBAAwB;AAAA,MACzD,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,MAC/B,gBAAgB,SAAS;AAAA,MACzB,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,MACzB,OAAO,QAAQ;AAAA,IACjB,CAAC;AACD,UAAM,SAAS,sBAAsB,MAAM;AAAA,EAC7C;AACA,QAAM,QAAQ;AAAA,IACZ,IAAI,IAAI,UAAU,kBAAkB,IAAI,IAAI,UAAU,KAAK,sBAAsB,CAAC,EAAE;AAAA,EACtF;AACA,QAAM,EAAE,UAAU,UAAU,IAAI,MAAM,KAAK;AAC3C,QAAM,QAAQ,YAAY;AAC1B,QAAM,SAAS,OAAO,CAAC,aAAa,EAAE,GAAG,SAAS,cAAc,UAAU,YAAY,MAAM,EAAE;AAC9F,QAAM,MAAM,IAAI;AAAA,IACd,UAAU,0BAA0B,IAAI,IAAI,cAAc,KAAK,sBAAsB;AAAA,EACvF;AACA,MAAI,CAAC,UAAU,GAAG,EAAG,OAAM,IAAI,MAAM,yDAAyD;AAC9F,MAAI,aAAa,IAAI,iBAAiB,MAAM;AAC5C,MAAI,aAAa,IAAI,aAAa,OAAO,SAAS;AAClD,MAAI,aAAa,IAAI,kBAAkB,SAAS;AAChD,MAAI,aAAa,IAAI,yBAAyB,MAAM;AACpD,MAAI,aAAa,IAAI,gBAAgB,SAAS,WAAW;AACzD,MAAI,aAAa,IAAI,SAAS,KAAK;AACnC,MAAI,MAAO,KAAI,aAAa,IAAI,SAAS,KAAK;AAC9C,MAAI,OAAO,MAAM,KAAK,EAAE,SAAS,gBAAgB,EAAG,KAAI,aAAa,IAAI,UAAU,SAAS;AAC5F,QAAM,WAAW,eAAe,OAAO,OAAO,KAAK,KAAK,gBAAgB;AACxE,MAAI,SAAU,KAAI,aAAa,IAAI,YAAY,QAAQ;AACvD,SAAO,EAAE,KAAK,IAAI,MAAM,MAAM;AAChC;AAUA,eAAsB,aACpB,UACA,QACA,QACA,OACe;AACf,MAAI;AACJ,MAAI;AACF,eAAW,KAAK,MAAM,MAAM;AAAA,EAC9B,QAAQ;AACN,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACxD;AACA,QAAM,YAAY,MAAM,SAAS,KAAK,GAAG;AACzC,MAAI,CAAC,YAAY,SAAS,UAAU;AAClC,UAAM,IAAI,MAAM,2DAA2D;AAC7E,MAAI,SAAS;AACX,UAAM,IAAI;AAAA,MACR,mBAAmB,SAAS,SAAS,mBAAmB,KAAK,KAAK,SAAS,KAAK,CAAC;AAAA,IACnF;AACF,MAAI,CAAC,SAAS,KAAM,OAAM,IAAI,MAAM,+CAA+C;AACnF,QAAM,aAAa,OAAO,OAAO,OAAO;AACxC,QAAM,aAAa,UAAU;AAAA,IAC3B,WAAW,OAAO,OAAO;AAAA,IACzB,mBAAmB,SAAS;AAAA,IAC5B,GAAI,SAAS,MAAM,EAAE,KAAK,SAAS,IAAI,IAAI,CAAC;AAAA,IAC5C,GAAI,OAAO,OAAO,OAAO,QAAQ,EAAE,OAAO,OAAO,OAAO,MAAM,MAAM,IAAI,CAAC;AAAA,IACzE,GAAI,aAAa,EAAE,gCAAgC,IAAI,IAAI,UAAU,EAAE,IAAI,CAAC;AAAA,IAC5E;AAAA,EACF,CAAC;AACD,QAAM,SAAS,OAAO,CAAC,UAAU;AAC/B,UAAM,OAAO,EAAE,GAAG,MAAM;AACxB,WAAO,KAAK;AACZ,WAAO,KAAK;AACZ,WAAO;AAAA,EACT,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/mcp/runtime.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StreamableHttpTransport } from "@earendil-works/pi-mcp";
|
|
2
|
+
import { scrubText } from "../secrets/scrub.js";
|
|
2
3
|
import { mcpAccounts } from "./accounts.js";
|
|
3
4
|
import { McpCatalog } from "./catalog.js";
|
|
4
5
|
import {
|
|
@@ -7,7 +8,7 @@ import {
|
|
|
7
8
|
usesOAuth
|
|
8
9
|
} from "./config.js";
|
|
9
10
|
import { McpServerConnection } from "./connection.js";
|
|
10
|
-
import { resolveHeaders,
|
|
11
|
+
import { resolveHeaders, secretNames } from "./credentials.js";
|
|
11
12
|
import { authProvider as oauthAuth, StoredOAuth } from "./oauth.js";
|
|
12
13
|
import { configPath, logPath, shown } from "./paths.js";
|
|
13
14
|
import { patchSse } from "./sse.js";
|
|
@@ -54,7 +55,7 @@ ${connection.missing.join(",")}`
|
|
|
54
55
|
auth`
|
|
55
56
|
};
|
|
56
57
|
if (connection.state !== "failed") return void 0;
|
|
57
|
-
const error =
|
|
58
|
+
const error = connection.error ?? "unknown error";
|
|
58
59
|
const blocked = cors && CORS_ERROR.test(error);
|
|
59
60
|
return {
|
|
60
61
|
...base,
|
|
@@ -79,9 +80,9 @@ connected
|
|
|
79
80
|
${connection.entry.config.url}`
|
|
80
81
|
};
|
|
81
82
|
}
|
|
82
|
-
function logLine(server, params,
|
|
83
|
+
function logLine(server, params, at) {
|
|
83
84
|
const { level, logger, data } = params ?? {};
|
|
84
|
-
const text =
|
|
85
|
+
const text = typeof data === "string" ? data : JSON.stringify(data) ?? "";
|
|
85
86
|
return `${at.toISOString()} [${server}] ${level ?? "info"}${logger ? ` ${logger}` : ""}: ${text}
|
|
86
87
|
`;
|
|
87
88
|
}
|
|
@@ -91,8 +92,9 @@ function serverLog(options, context) {
|
|
|
91
92
|
const { env } = options;
|
|
92
93
|
return {
|
|
93
94
|
write(connection, params) {
|
|
94
|
-
const
|
|
95
|
+
const text = logLine(connection.name, params, /* @__PURE__ */ new Date());
|
|
95
96
|
logging = logging.then(async () => {
|
|
97
|
+
const line = await scrubText(text);
|
|
96
98
|
const info = await env.fileInfo(path, context);
|
|
97
99
|
if (info.ok && info.value.size > MCP_LOG_MAX_BYTES)
|
|
98
100
|
await env.renameFile(path, `${path}.1`, context);
|
|
@@ -118,22 +120,30 @@ async function attachMcp(catalog, options, context) {
|
|
|
118
120
|
let initial = /* @__PURE__ */ new Set();
|
|
119
121
|
let first = true;
|
|
120
122
|
const log = serverLog(options, context);
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
+
const { vault } = options;
|
|
124
|
+
const base = (input, init) => options.fetch(input, init);
|
|
125
|
+
const fetcher = (server) => vault.fetch(server, base);
|
|
126
|
+
const oauth = (entry) => new StoredOAuth(entry, options.store, options.redirect);
|
|
123
127
|
const transport = async (entry) => {
|
|
124
|
-
const resolved =
|
|
128
|
+
const resolved = resolveHeaders(entry.name, entry.config, vault.records);
|
|
125
129
|
if (!resolved.ok) return { missing: resolved.missing };
|
|
126
130
|
const authProvider = usesOAuth(entry.config) ? oauthAuth(oauth(entry), entry) : void 0;
|
|
127
131
|
return {
|
|
128
132
|
transport: new StreamableHttpTransport({
|
|
129
133
|
url: entry.config.url,
|
|
130
134
|
headers: resolved.headers,
|
|
131
|
-
fetch: fetcher,
|
|
135
|
+
fetch: fetcher(entry.name),
|
|
132
136
|
...authProvider ? { authProvider } : {}
|
|
133
|
-
})
|
|
134
|
-
secrets: resolved.secrets
|
|
137
|
+
})
|
|
135
138
|
};
|
|
136
139
|
};
|
|
140
|
+
const register = (entries) => vault.servers(
|
|
141
|
+
[...entries].map((entry) => ({
|
|
142
|
+
server: entry.name,
|
|
143
|
+
url: entry.config.url,
|
|
144
|
+
names: secretNames(entry.config)
|
|
145
|
+
}))
|
|
146
|
+
).catch(() => void 0);
|
|
137
147
|
const open = (entry) => {
|
|
138
148
|
const connection = new McpServerConnection({
|
|
139
149
|
entry,
|
|
@@ -170,6 +180,7 @@ async function attachMcp(catalog, options, context) {
|
|
|
170
180
|
}
|
|
171
181
|
if (first) initial = new Set(wanted.keys());
|
|
172
182
|
first = false;
|
|
183
|
+
void register(wanted.values());
|
|
173
184
|
for (const [name, entry] of wanted) if (!catalog.connections.has(name)) open(entry);
|
|
174
185
|
catalog.rebuild();
|
|
175
186
|
notify();
|
|
@@ -192,10 +203,21 @@ async function attachMcp(catalog, options, context) {
|
|
|
192
203
|
void connection.close();
|
|
193
204
|
catalog.rebuild();
|
|
194
205
|
};
|
|
206
|
+
const stopRecords = vault.records.state.subscribe(() => {
|
|
207
|
+
for (const connection of [...catalog.connections.values()])
|
|
208
|
+
if (connection.state === "needs-secret") restart(connection);
|
|
209
|
+
notify();
|
|
210
|
+
});
|
|
195
211
|
return {
|
|
196
212
|
...mcpAccounts(
|
|
197
213
|
catalog,
|
|
198
|
-
{
|
|
214
|
+
{
|
|
215
|
+
records: vault.records,
|
|
216
|
+
oauth,
|
|
217
|
+
fetch: fetcher,
|
|
218
|
+
grant: (server) => vault.grant(server, fetcher(server)),
|
|
219
|
+
register: (server, url) => vault.token(server, url)
|
|
220
|
+
},
|
|
199
221
|
restart,
|
|
200
222
|
listeners
|
|
201
223
|
),
|
|
@@ -203,12 +225,12 @@ async function attachMcp(catalog, options, context) {
|
|
|
203
225
|
name: connection.name,
|
|
204
226
|
state: connection.state,
|
|
205
227
|
tools: connection.tools.length,
|
|
206
|
-
...connection.error ? { error:
|
|
228
|
+
...connection.error ? { error: connection.error } : {}
|
|
207
229
|
})),
|
|
208
|
-
secrets: () => [...catalog.connections.values()].flatMap((connection) => connection.secrets),
|
|
209
230
|
reload,
|
|
210
231
|
async close(using) {
|
|
211
232
|
clearTimeout(timer);
|
|
233
|
+
stopRecords();
|
|
212
234
|
await watcher?.close(using);
|
|
213
235
|
const all = [...catalog.connections.values()];
|
|
214
236
|
catalog.connections.clear();
|
package/dist/mcp/runtime.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/mcp/runtime.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { CredentialStore } from '@earendil-works/pi-ai';\nimport type { Registry } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport { StreamableHttpTransport } from '@earendil-works/pi-mcp';\nimport type { LickEvent, Licks } from '../licks/licks.ts';\nimport { type McpAccounts, mcpAccounts } from './accounts.ts';\nimport { McpCatalog } from './catalog.ts';\nimport {\n type McpConfigProblem,\n type McpServerEntry,\n parseMcpConfig,\n sameConfig,\n usesOAuth,\n} from './config.ts';\nimport { McpServerConnection } from './connection.ts';\nimport { resolveHeaders, scrub, secretNames } from './credentials.ts';\nimport { authProvider as oauthAuth, StoredOAuth } from './oauth.ts';\nimport { configPath, logPath, shown } from './paths.ts';\nimport { patchSse } from './sse.ts';\n\nexport const MCP_LOG_MAX_BYTES = 5 * 1024 * 1024;\n\nexport { configPath, logPath, shown } from './paths.ts';\n\nexport interface McpAttach {\n licks: Licks;\n env: ExecutionEnv;\n home: string;\n agentDir?: string;\n credentials: CredentialStore;\n fetch: typeof fetch;\n version: string;\n reloadMs: number;\n codemode: boolean;\n cors: boolean;\n redirect: string;\n}\n\nexport interface ServerStatus {\n name: string;\n state: string;\n tools: number;\n error?: string;\n}\n\nexport type { McpAccounts } from './accounts.ts';\n\nexport interface McpRuntime extends McpAccounts {\n status(): ServerStatus[];\n secrets(): string[];\n reload(context: Context): Promise<void>;\n close(context: Context): Promise<void>;\n}\n\nexport function problemLick(problem: McpConfigProblem, home: string, dir?: string): LickEvent {\n const file = shown(configPath(home, dir), home);\n const where = problem.server ? `server \"${problem.server}\"` : file;\n const field = problem.field ? `, field ${problem.field}` : '';\n return {\n channel: 'fswatch',\n source: `mcp:${file}`,\n title: `MCP config problem: ${where}${field}`,\n text: problem.message,\n body: `${file}: the entry is skipped until it is fixed. The format is in the slicc-agent README.`,\n target: 'cone',\n severity: 'warn',\n eventId: `${problem.server ?? ''}\\n${problem.field ?? ''}\\n${problem.message}`,\n };\n}\n\nconst CORS_ERROR = /failed to fetch|networkerror|load failed|cors/i;\n\nexport function stateLick(connection: McpServerConnection, cors: boolean): LickEvent | undefined {\n const name = connection.name;\n const base = { channel: 'fswatch' as const, source: `mcp:${name}`, target: 'cone' as const };\n if (connection.state === 'needs-secret')\n return {\n ...base,\n title: `MCP server \"${name}\" needs a token`,\n text: `Its headers use ${connection.missing.map((item) => `\\${${item}}`).join(', ')}. Add the value in Settings \u203A MCP servers.`,\n severity: 'warn',\n eventId: `${name}\\nsecret\\n${connection.missing.join(',')}`,\n };\n if (connection.state === 'needs-auth')\n return {\n ...base,\n title: secretNames(connection.entry.config).length\n ? `MCP server \"${name}\" rejected its token`\n : `MCP server \"${name}\" needs sign-in`,\n text: connection.signInMessage(),\n severity: 'warn',\n eventId: `${name}\\nauth`,\n };\n if (connection.state !== 'failed') return undefined;\n const error = scrub(connection.error ?? 'unknown error', connection.secrets);\n const blocked = cors && CORS_ERROR.test(error);\n return {\n ...base,\n title: `MCP server \"${name}\" can't connect`,\n text: blocked\n ? \"Seven reaches it with the page's own fetch, and the server sends no CORS headers. Link the `slicc` CLI or connect the SLICC extension; either fetches without CORS.\"\n : error,\n severity: 'warn',\n eventId: `${name}\\nfailed\\n${blocked ? 'cors' : error}`,\n };\n}\n\nexport function connectedLick(connection: McpServerConnection): LickEvent {\n const count = connection.tools.length;\n return {\n channel: 'fswatch',\n source: `mcp:${connection.name}`,\n title: `MCP server \"${connection.name}\" connected`,\n text: `${connection.entry.config.url} offers ${count} tool${count === 1 ? '' : 's'}.`,\n target: 'cone',\n eventId: `${connection.name}\\nconnected\\n${connection.entry.config.url}`,\n };\n}\n\nexport function logLine(\n server: string,\n params: unknown,\n secrets: readonly string[],\n at: Date\n): string {\n const { level, logger, data } = (params ?? {}) as {\n level?: string;\n logger?: string;\n data?: unknown;\n };\n const text = scrub(typeof data === 'string' ? data : (JSON.stringify(data) ?? ''), secrets);\n return `${at.toISOString()} [${server}] ${level ?? 'info'}${logger ? ` ${logger}` : ''}: ${text}\\n`;\n}\n\nfunction serverLog(options: McpAttach, context: Context) {\n let logging = Promise.resolve();\n const path = logPath(options.home, options.agentDir);\n const { env } = options;\n return {\n write(connection: McpServerConnection, params: unknown) {\n const line = logLine(connection.name, params, connection.secrets, new Date());\n logging = logging.then(async () => {\n const info = await env.fileInfo(path, context);\n if (info.ok && info.value.size > MCP_LOG_MAX_BYTES)\n await env.renameFile(path, `${path}.1`, context);\n await env.appendFile(path, line, context);\n });\n },\n settled: () => logging,\n };\n}\n\nexport async function attachMcp(\n catalog: McpCatalog,\n options: McpAttach,\n context: Context\n): Promise<McpRuntime> {\n const { env, home } = options;\n catalog.codemodeOn = options.codemode;\n const reported = new Set<string>();\n const report = async (lick: LickEvent | undefined, using: Context) => {\n if (!lick?.eventId || reported.has(lick.eventId)) return;\n reported.add(lick.eventId);\n await options.licks.deliver(lick, using).catch(() => undefined);\n };\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const listener of listeners) listener();\n };\n let initial: Set<string> = new Set();\n let first = true;\n const log = serverLog(options, context);\n const fetcher = (input: string | URL, init?: RequestInit) => options.fetch(input, init);\n const oauth = (entry: McpServerEntry) =>\n new StoredOAuth(entry, options.credentials, options.redirect);\n const transport = async (entry: McpServerEntry) => {\n const resolved = await resolveHeaders(entry.name, entry.config, options.credentials);\n if (!resolved.ok) return { missing: resolved.missing };\n const authProvider = usesOAuth(entry.config) ? oauthAuth(oauth(entry), entry) : undefined;\n return {\n transport: new StreamableHttpTransport({\n url: entry.config.url,\n headers: resolved.headers,\n fetch: fetcher,\n ...(authProvider ? { authProvider } : {}),\n }),\n secrets: resolved.secrets,\n };\n };\n const open = (entry: McpServerEntry) => {\n const connection = new McpServerConnection({\n entry,\n version: options.version,\n transport,\n onTools: () => catalog.rebuild(),\n onLog: log.write,\n onChange: (changed) => {\n if (catalog.connections.get(changed.name) !== changed) return;\n if (changed.state === 'connected' && !initial.has(changed.name))\n void report(connectedLick(changed), context);\n void report(stateLick(changed, options.cors), context);\n if (changed.state !== 'connected') catalog.rebuild();\n notify();\n },\n });\n catalog.connections.set(entry.name, connection);\n void connection.getClient().catch(() => undefined);\n };\n const reload = async (using: Context) => {\n const read = await env.readTextFile(configPath(home, options.agentDir), using);\n const parsed = parseMcpConfig(read.ok ? read.value : undefined);\n for (const problem of parsed.problems)\n await report(problemLick(problem, home, options.agentDir), using);\n const wanted = new Map(\n parsed.servers\n .filter((entry) => entry.config.enabled !== false)\n .map((entry) => [entry.name, entry])\n );\n const closing: Promise<void>[] = [];\n for (const [name, connection] of [...catalog.connections]) {\n const next = wanted.get(name);\n if (next && sameConfig(next.config, connection.entry.config)) continue;\n catalog.connections.delete(name);\n closing.push(connection.close());\n }\n if (first) initial = new Set(wanted.keys());\n first = false;\n for (const [name, entry] of wanted) if (!catalog.connections.has(name)) open(entry);\n catalog.rebuild();\n notify();\n await Promise.all(closing);\n };\n await reload(context);\n let timer: ReturnType<typeof setTimeout> | undefined;\n const schedule = () => {\n clearTimeout(timer);\n timer = setTimeout(() => void reload(context).catch(() => undefined), options.reloadMs);\n };\n const watched = await env.watch(\n [{ path: configPath(home, options.agentDir) }],\n schedule,\n context\n );\n const watcher: FileWatcher | undefined = watched.ok ? watched.value : undefined;\n const restart = (connection: McpServerConnection) => {\n open(connection.entry);\n void connection.close();\n catalog.rebuild();\n };\n return {\n ...mcpAccounts(\n catalog,\n { credentials: options.credentials, oauth, fetch: fetcher },\n restart,\n listeners\n ),\n status: () =>\n [...catalog.connections.values()].map((connection) => ({\n name: connection.name,\n state: connection.state,\n tools: connection.tools.length,\n ...(connection.error ? { error: scrub(connection.error, connection.secrets) } : {}),\n })),\n secrets: () => [...catalog.connections.values()].flatMap((connection) => connection.secrets),\n reload,\n async close(using) {\n clearTimeout(timer);\n await watcher?.close(using);\n const all = [...catalog.connections.values()];\n catalog.connections.clear();\n await Promise.all(all.map((connection) => connection.close()));\n catalog.rebuild();\n await log.settled();\n },\n };\n}\n\nexport async function startMcp(\n registry: Registry,\n options: McpAttach,\n context: Context\n): Promise<{ catalog: McpCatalog; runtime: McpRuntime }> {\n patchSse();\n const catalog = new McpCatalog(registry);\n return { catalog, runtime: await attachMcp(catalog, options, context) };\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@earendil-works/chord';\nimport type { Registry } from '@earendil-works/pi-durable';\nimport type { ExecutionEnv, FileWatcher } from '@earendil-works/pi-durable/env';\nimport { StreamableHttpTransport } from '@earendil-works/pi-mcp';\nimport type { LickEvent, Licks } from '../licks/licks.ts';\nimport type { McpServerSource } from '../secrets/endpoints.ts';\nimport { scrubText } from '../secrets/scrub.ts';\nimport type { PlainStore } from '../secrets/store.ts';\nimport { type McpAccounts, mcpAccounts } from './accounts.ts';\nimport { McpCatalog } from './catalog.ts';\nimport {\n type McpConfigProblem,\n type McpServerEntry,\n parseMcpConfig,\n sameConfig,\n usesOAuth,\n} from './config.ts';\nimport { McpServerConnection } from './connection.ts';\nimport { type McpRecords, resolveHeaders, secretNames } from './credentials.ts';\nimport { authProvider as oauthAuth, StoredOAuth } from './oauth.ts';\nimport { configPath, logPath, shown } from './paths.ts';\nimport { patchSse } from './sse.ts';\n\nexport const MCP_LOG_MAX_BYTES = 5 * 1024 * 1024;\n\nexport { configPath, logPath, shown } from './paths.ts';\n\nexport interface McpVault {\n records: McpRecords & {\n refresh(context: Context): Promise<void>;\n state: { subscribe(listener: () => void): () => void };\n };\n fetch(server: string, base: typeof fetch): typeof fetch;\n grant(server: string, base: typeof fetch): typeof fetch;\n servers(list: readonly McpServerSource[]): Promise<void>;\n token(server: string, url: string): Promise<void>;\n}\n\nexport interface McpAttach {\n licks: Licks;\n env: ExecutionEnv;\n home: string;\n agentDir?: string;\n store: PlainStore;\n vault: McpVault;\n fetch: typeof fetch;\n version: string;\n reloadMs: number;\n codemode: boolean;\n cors: boolean;\n redirect: string;\n}\n\nexport interface ServerStatus {\n name: string;\n state: string;\n tools: number;\n error?: string;\n}\n\nexport type { McpAccounts } from './accounts.ts';\n\nexport interface McpRuntime extends McpAccounts {\n status(): ServerStatus[];\n reload(context: Context): Promise<void>;\n close(context: Context): Promise<void>;\n}\n\nexport function problemLick(problem: McpConfigProblem, home: string, dir?: string): LickEvent {\n const file = shown(configPath(home, dir), home);\n const where = problem.server ? `server \"${problem.server}\"` : file;\n const field = problem.field ? `, field ${problem.field}` : '';\n return {\n channel: 'fswatch',\n source: `mcp:${file}`,\n title: `MCP config problem: ${where}${field}`,\n text: problem.message,\n body: `${file}: the entry is skipped until it is fixed. The format is in the slicc-agent README.`,\n target: 'cone',\n severity: 'warn',\n eventId: `${problem.server ?? ''}\\n${problem.field ?? ''}\\n${problem.message}`,\n };\n}\n\nconst CORS_ERROR = /failed to fetch|networkerror|load failed|cors/i;\n\nexport function stateLick(connection: McpServerConnection, cors: boolean): LickEvent | undefined {\n const name = connection.name;\n const base = { channel: 'fswatch' as const, source: `mcp:${name}`, target: 'cone' as const };\n if (connection.state === 'needs-secret')\n return {\n ...base,\n title: `MCP server \"${name}\" needs a token`,\n text: `Its headers use ${connection.missing.map((item) => `\\${${item}}`).join(', ')}. Add the value in Settings \u203A MCP servers.`,\n severity: 'warn',\n eventId: `${name}\\nsecret\\n${connection.missing.join(',')}`,\n };\n if (connection.state === 'needs-auth')\n return {\n ...base,\n title: secretNames(connection.entry.config).length\n ? `MCP server \"${name}\" rejected its token`\n : `MCP server \"${name}\" needs sign-in`,\n text: connection.signInMessage(),\n severity: 'warn',\n eventId: `${name}\\nauth`,\n };\n if (connection.state !== 'failed') return undefined;\n const error = connection.error ?? 'unknown error';\n const blocked = cors && CORS_ERROR.test(error);\n return {\n ...base,\n title: `MCP server \"${name}\" can't connect`,\n text: blocked\n ? \"Seven reaches it with the page's own fetch, and the server sends no CORS headers. Link the `slicc` CLI or connect the SLICC extension; either fetches without CORS.\"\n : error,\n severity: 'warn',\n eventId: `${name}\\nfailed\\n${blocked ? 'cors' : error}`,\n };\n}\n\nexport function connectedLick(connection: McpServerConnection): LickEvent {\n const count = connection.tools.length;\n return {\n channel: 'fswatch',\n source: `mcp:${connection.name}`,\n title: `MCP server \"${connection.name}\" connected`,\n text: `${connection.entry.config.url} offers ${count} tool${count === 1 ? '' : 's'}.`,\n target: 'cone',\n eventId: `${connection.name}\\nconnected\\n${connection.entry.config.url}`,\n };\n}\n\nexport function logLine(server: string, params: unknown, at: Date): string {\n const { level, logger, data } = (params ?? {}) as {\n level?: string;\n logger?: string;\n data?: unknown;\n };\n const text = typeof data === 'string' ? data : (JSON.stringify(data) ?? '');\n return `${at.toISOString()} [${server}] ${level ?? 'info'}${logger ? ` ${logger}` : ''}: ${text}\\n`;\n}\n\nfunction serverLog(options: McpAttach, context: Context) {\n let logging = Promise.resolve();\n const path = logPath(options.home, options.agentDir);\n const { env } = options;\n return {\n write(connection: McpServerConnection, params: unknown) {\n const text = logLine(connection.name, params, new Date());\n logging = logging.then(async () => {\n const line = await scrubText(text);\n const info = await env.fileInfo(path, context);\n if (info.ok && info.value.size > MCP_LOG_MAX_BYTES)\n await env.renameFile(path, `${path}.1`, context);\n await env.appendFile(path, line, context);\n });\n },\n settled: () => logging,\n };\n}\n\nexport async function attachMcp(\n catalog: McpCatalog,\n options: McpAttach,\n context: Context\n): Promise<McpRuntime> {\n const { env, home } = options;\n catalog.codemodeOn = options.codemode;\n const reported = new Set<string>();\n const report = async (lick: LickEvent | undefined, using: Context) => {\n if (!lick?.eventId || reported.has(lick.eventId)) return;\n reported.add(lick.eventId);\n await options.licks.deliver(lick, using).catch(() => undefined);\n };\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const listener of listeners) listener();\n };\n let initial: Set<string> = new Set();\n let first = true;\n const log = serverLog(options, context);\n const { vault } = options;\n const base = (input: string | URL | Request, init?: RequestInit) => options.fetch(input, init);\n const fetcher = (server: string) => vault.fetch(server, base);\n const oauth = (entry: McpServerEntry) => new StoredOAuth(entry, options.store, options.redirect);\n const transport = async (entry: McpServerEntry) => {\n const resolved = resolveHeaders(entry.name, entry.config, vault.records);\n if (!resolved.ok) return { missing: resolved.missing };\n const authProvider = usesOAuth(entry.config) ? oauthAuth(oauth(entry), entry) : undefined;\n return {\n transport: new StreamableHttpTransport({\n url: entry.config.url,\n headers: resolved.headers,\n fetch: fetcher(entry.name),\n ...(authProvider ? { authProvider } : {}),\n }),\n };\n };\n const register = (entries: Iterable<McpServerEntry>) =>\n vault\n .servers(\n [...entries].map((entry) => ({\n server: entry.name,\n url: entry.config.url,\n names: secretNames(entry.config),\n }))\n )\n .catch(() => undefined);\n const open = (entry: McpServerEntry) => {\n const connection = new McpServerConnection({\n entry,\n version: options.version,\n transport,\n onTools: () => catalog.rebuild(),\n onLog: log.write,\n onChange: (changed) => {\n if (catalog.connections.get(changed.name) !== changed) return;\n if (changed.state === 'connected' && !initial.has(changed.name))\n void report(connectedLick(changed), context);\n void report(stateLick(changed, options.cors), context);\n if (changed.state !== 'connected') catalog.rebuild();\n notify();\n },\n });\n catalog.connections.set(entry.name, connection);\n void connection.getClient().catch(() => undefined);\n };\n const reload = async (using: Context) => {\n const read = await env.readTextFile(configPath(home, options.agentDir), using);\n const parsed = parseMcpConfig(read.ok ? read.value : undefined);\n for (const problem of parsed.problems)\n await report(problemLick(problem, home, options.agentDir), using);\n const wanted = new Map(\n parsed.servers\n .filter((entry) => entry.config.enabled !== false)\n .map((entry) => [entry.name, entry])\n );\n const closing: Promise<void>[] = [];\n for (const [name, connection] of [...catalog.connections]) {\n const next = wanted.get(name);\n if (next && sameConfig(next.config, connection.entry.config)) continue;\n catalog.connections.delete(name);\n closing.push(connection.close());\n }\n if (first) initial = new Set(wanted.keys());\n first = false;\n void register(wanted.values());\n for (const [name, entry] of wanted) if (!catalog.connections.has(name)) open(entry);\n catalog.rebuild();\n notify();\n await Promise.all(closing);\n };\n await reload(context);\n let timer: ReturnType<typeof setTimeout> | undefined;\n const schedule = () => {\n clearTimeout(timer);\n timer = setTimeout(() => void reload(context).catch(() => undefined), options.reloadMs);\n };\n const watched = await env.watch(\n [{ path: configPath(home, options.agentDir) }],\n schedule,\n context\n );\n const watcher: FileWatcher | undefined = watched.ok ? watched.value : undefined;\n const restart = (connection: McpServerConnection) => {\n open(connection.entry);\n void connection.close();\n catalog.rebuild();\n };\n const stopRecords = vault.records.state.subscribe(() => {\n for (const connection of [...catalog.connections.values()])\n if (connection.state === 'needs-secret') restart(connection);\n notify();\n });\n return {\n ...mcpAccounts(\n catalog,\n {\n records: vault.records,\n oauth,\n fetch: fetcher,\n grant: (server) => vault.grant(server, fetcher(server)),\n register: (server, url) => vault.token(server, url),\n },\n restart,\n listeners\n ),\n status: () =>\n [...catalog.connections.values()].map((connection) => ({\n name: connection.name,\n state: connection.state,\n tools: connection.tools.length,\n ...(connection.error ? { error: connection.error } : {}),\n })),\n reload,\n async close(using) {\n clearTimeout(timer);\n stopRecords();\n await watcher?.close(using);\n const all = [...catalog.connections.values()];\n catalog.connections.clear();\n await Promise.all(all.map((connection) => connection.close()));\n catalog.rebuild();\n await log.settled();\n },\n };\n}\n\nexport async function startMcp(\n registry: Registry,\n options: McpAttach,\n context: Context\n): Promise<{ catalog: McpCatalog; runtime: McpRuntime }> {\n patchSse();\n const catalog = new McpCatalog(registry);\n return { catalog, runtime: await attachMcp(catalog, options, context) };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["configPath", "logPath", "shown"]
|
|
7
7
|
}
|
package/dist/mcp/tools.js
CHANGED
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
scriptResult,
|
|
16
16
|
spiller
|
|
17
17
|
} from "./content.js";
|
|
18
|
-
import { scrub } from "./credentials.js";
|
|
19
18
|
const LIST_MCP_RESOURCES = "list_mcp_resources";
|
|
20
19
|
const LIST_MCP_RESOURCE_TEMPLATES = "list_mcp_resource_templates";
|
|
21
20
|
function parametersOf(schema) {
|
|
@@ -56,9 +55,8 @@ function progressText(progress) {
|
|
|
56
55
|
const total = progress.total === void 0 ? "" : `/${progress.total}`;
|
|
57
56
|
return progress.message ?? `Progress ${progress.progress}${total}`;
|
|
58
57
|
}
|
|
59
|
-
function failure(
|
|
60
|
-
|
|
61
|
-
throw new Error(scrub(message, entry.connection.secrets));
|
|
58
|
+
function failure(error) {
|
|
59
|
+
throw new Error(error instanceof Error ? error.message : String(error));
|
|
62
60
|
}
|
|
63
61
|
function directTool(entry, readable) {
|
|
64
62
|
return defineTool({
|
|
@@ -75,7 +73,7 @@ function directTool(entry, readable) {
|
|
|
75
73
|
};
|
|
76
74
|
const signal = signalOf(context);
|
|
77
75
|
if (signal) options.signal = signal;
|
|
78
|
-
const result = await entry.connection.callTool(entry.tool.name, args, options).catch(
|
|
76
|
+
const result = await entry.connection.callTool(entry.tool.name, args, options).catch(failure);
|
|
79
77
|
const converted = await convertResult(
|
|
80
78
|
entry.server,
|
|
81
79
|
entry.tool.name,
|
|
@@ -109,7 +107,7 @@ function scriptTool(entry, signal) {
|
|
|
109
107
|
const result = await entry.connection.callTool(entry.tool.name, args ?? {}, {
|
|
110
108
|
signal: combined,
|
|
111
109
|
timeoutMs: entry.connection.timeoutMs
|
|
112
|
-
}).catch(
|
|
110
|
+
}).catch(failure);
|
|
113
111
|
return scriptResult(result);
|
|
114
112
|
}
|
|
115
113
|
};
|