@corenel/mcp 0.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/LICENSE +93 -0
- package/dist/asAgentTools.d.ts +12 -0
- package/dist/asAgentTools.d.ts.map +1 -0
- package/dist/asAgentTools.js +71 -0
- package/dist/asAgentTools.js.map +1 -0
- package/dist/auth.d.ts +25 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +97 -0
- package/dist/auth.js.map +1 -0
- package/dist/cache.d.ts +17 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +81 -0
- package/dist/cache.js.map +1 -0
- package/dist/config.d.ts +71 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +239 -0
- package/dist/config.js.map +1 -0
- package/dist/gatedAgentTools.d.ts +12 -0
- package/dist/gatedAgentTools.d.ts.map +1 -0
- package/dist/gatedAgentTools.js +36 -0
- package/dist/gatedAgentTools.js.map +1 -0
- package/dist/httpClient.d.ts +47 -0
- package/dist/httpClient.d.ts.map +1 -0
- package/dist/httpClient.js +196 -0
- package/dist/httpClient.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth/discovery.d.ts +55 -0
- package/dist/oauth/discovery.d.ts.map +1 -0
- package/dist/oauth/discovery.js +211 -0
- package/dist/oauth/discovery.js.map +1 -0
- package/dist/oauth/login.d.ts +41 -0
- package/dist/oauth/login.d.ts.map +1 -0
- package/dist/oauth/login.js +206 -0
- package/dist/oauth/login.js.map +1 -0
- package/dist/oauth/loopback.d.ts +58 -0
- package/dist/oauth/loopback.d.ts.map +1 -0
- package/dist/oauth/loopback.js +165 -0
- package/dist/oauth/loopback.js.map +1 -0
- package/dist/oauth/pkce.d.ts +45 -0
- package/dist/oauth/pkce.d.ts.map +1 -0
- package/dist/oauth/pkce.js +67 -0
- package/dist/oauth/pkce.js.map +1 -0
- package/dist/oauth/register.d.ts +34 -0
- package/dist/oauth/register.d.ts.map +1 -0
- package/dist/oauth/register.js +57 -0
- package/dist/oauth/register.js.map +1 -0
- package/dist/oauth/tokens.d.ts +62 -0
- package/dist/oauth/tokens.d.ts.map +1 -0
- package/dist/oauth/tokens.js +118 -0
- package/dist/oauth/tokens.js.map +1 -0
- package/dist/policy.d.ts +10 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +71 -0
- package/dist/policy.js.map +1 -0
- package/dist/registry.d.ts +124 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +265 -0
- package/dist/registry.js.map +1 -0
- package/dist/timedFetch.d.ts +10 -0
- package/dist/timedFetch.d.ts.map +1 -0
- package/dist/timedFetch.js +39 -0
- package/dist/timedFetch.js.map +1 -0
- package/dist/toolNames.d.ts +2 -0
- package/dist/toolNames.d.ts.map +1 -0
- package/dist/toolNames.js +14 -0
- package/dist/toolNames.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Elastic License 2.0
|
|
2
|
+
|
|
3
|
+
URL: https://www.elastic.co/licensing/elastic-license
|
|
4
|
+
|
|
5
|
+
## Acceptance
|
|
6
|
+
|
|
7
|
+
By using the software, you agree to all of the terms and conditions below.
|
|
8
|
+
|
|
9
|
+
## Copyright License
|
|
10
|
+
|
|
11
|
+
The licensor grants you a non-exclusive, royalty-free, worldwide,
|
|
12
|
+
non-sublicensable, non-transferable license to use, copy, distribute, make
|
|
13
|
+
available, and prepare derivative works of the software, in each case subject to
|
|
14
|
+
the limitations and conditions below.
|
|
15
|
+
|
|
16
|
+
## Limitations
|
|
17
|
+
|
|
18
|
+
You may not provide the software to third parties as a hosted or managed
|
|
19
|
+
service, where the service provides users with access to any substantial set of
|
|
20
|
+
the features or functionality of the software.
|
|
21
|
+
|
|
22
|
+
You may not move, change, disable, or circumvent the license key functionality
|
|
23
|
+
in the software, and you may not remove or obscure any functionality in the
|
|
24
|
+
software that is protected by the license key.
|
|
25
|
+
|
|
26
|
+
You may not alter, remove, or obscure any licensing, copyright, or other notices
|
|
27
|
+
of the licensor in the software. Any use of the licensor's trademarks is subject
|
|
28
|
+
to applicable law.
|
|
29
|
+
|
|
30
|
+
## Patents
|
|
31
|
+
|
|
32
|
+
The licensor grants you a license, under any patent claims the licensor can
|
|
33
|
+
license, or becomes able to license, to make, have made, use, sell, offer for
|
|
34
|
+
sale, import and have imported the software, in each case subject to the
|
|
35
|
+
limitations and conditions in this license. This license does not cover any
|
|
36
|
+
patent claims that you cause to be infringed by modifications or additions to
|
|
37
|
+
the software. If you or your company make any written claim that the software
|
|
38
|
+
infringes or contributes to infringement of any patent, your patent license for
|
|
39
|
+
the software granted under these terms ends immediately. If your company makes
|
|
40
|
+
such a claim, your patent license ends immediately for work on behalf of your
|
|
41
|
+
company.
|
|
42
|
+
|
|
43
|
+
## Notices
|
|
44
|
+
|
|
45
|
+
You must ensure that anyone who gets a copy of any part of the software from you
|
|
46
|
+
also gets a copy of these terms.
|
|
47
|
+
|
|
48
|
+
If you modify the software, you must include in any modified copies of the
|
|
49
|
+
software prominent notices stating that you have modified the software.
|
|
50
|
+
|
|
51
|
+
## No Other Rights
|
|
52
|
+
|
|
53
|
+
These terms do not imply any licenses other than those expressly granted in
|
|
54
|
+
these terms.
|
|
55
|
+
|
|
56
|
+
## Termination
|
|
57
|
+
|
|
58
|
+
If you use the software in violation of these terms, such use is not licensed,
|
|
59
|
+
and your licenses will automatically terminate. If the licensor provides you
|
|
60
|
+
with a notice of your violation, and you cease all violation of this license no
|
|
61
|
+
later than 30 days after you receive that notice, your licenses will be
|
|
62
|
+
reinstated retroactively. However, if you violate these terms after such
|
|
63
|
+
reinstatement, any additional violation of these terms will cause your licenses
|
|
64
|
+
to terminate automatically and permanently.
|
|
65
|
+
|
|
66
|
+
## No Liability
|
|
67
|
+
|
|
68
|
+
*As far as the law allows, the software comes as is, without any warranty or
|
|
69
|
+
condition, and the licensor will not be liable to you for any damages arising out
|
|
70
|
+
of these terms or the use or nature of the software, under any kind of legal
|
|
71
|
+
claim.*
|
|
72
|
+
|
|
73
|
+
## Definitions
|
|
74
|
+
|
|
75
|
+
The **licensor** is the entity offering these terms, and the **software** is the
|
|
76
|
+
software the licensor makes available under these terms, including any portion of
|
|
77
|
+
it.
|
|
78
|
+
|
|
79
|
+
**you** refers to the individual or entity agreeing to these terms.
|
|
80
|
+
|
|
81
|
+
**your company** is any legal entity, sole proprietorship, or other kind of
|
|
82
|
+
organization that you work for, plus all organizations that have control over, are
|
|
83
|
+
under the control of, or are under common control with that organization.
|
|
84
|
+
**control** means ownership of substantially all the assets of an entity, or the
|
|
85
|
+
power to direct its management and policies by vote, contract, or otherwise.
|
|
86
|
+
Control can be direct or indirect.
|
|
87
|
+
|
|
88
|
+
**your licenses** are all the licenses granted to you for the software under these
|
|
89
|
+
terms.
|
|
90
|
+
|
|
91
|
+
**use** means anything you do with the software requiring one of your licenses.
|
|
92
|
+
|
|
93
|
+
**trademark** means trademarks, service marks, and similar rights.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AgentTool } from '@corenel/protocol';
|
|
2
|
+
import type { McpRegistry } from './registry';
|
|
3
|
+
/** Build one `AgentTool` per registry entry, in the registry's order.
|
|
4
|
+
*
|
|
5
|
+
* A server/tool pair whose name can't be safely packed into a wire name
|
|
6
|
+
* (see toolNames.ts's module comment for the exact rule) contributes NO
|
|
7
|
+
* tool -- silently dropping it would mean a user who added a server never
|
|
8
|
+
* finds out one of its tools is simply missing, so the refusal is logged to
|
|
9
|
+
* stderr, matching this package's existing convention for loud, user-facing
|
|
10
|
+
* CLI failures (cli.ts's `console.error` calls). */
|
|
11
|
+
export declare function asAgentTools(registry: McpRegistry): AgentTool[];
|
|
12
|
+
//# sourceMappingURL=asAgentTools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asAgentTools.d.ts","sourceRoot":"","sources":["../src/asAgentTools.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAoC9C;;;;;;;oDAOoD;AACpD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,WAAW,GAAG,SAAS,EAAE,CAa/D"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { canEncodeMcpWireName, mcpToolAddress, mcpWireName } from './toolNames.js';
|
|
2
|
+
function isPlainObject(v) {
|
|
3
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
4
|
+
}
|
|
5
|
+
/** `McpTool.inputSchema` is `unknown` -- some servers send no schema at all,
|
|
6
|
+
* or something that isn't a JSON Schema object. Never pass that through as
|
|
7
|
+
* `undefined`: an `AgentTool` with no `parameters` is one a model can call
|
|
8
|
+
* with any arguments at all, which is worse than an (accurate) empty one. */
|
|
9
|
+
function toParameters(inputSchema) {
|
|
10
|
+
if (isPlainObject(inputSchema))
|
|
11
|
+
return inputSchema;
|
|
12
|
+
return { type: 'object', properties: {} };
|
|
13
|
+
}
|
|
14
|
+
/** MCP's `tools/call` result content is `unknown` by the time it reaches
|
|
15
|
+
* here (httpClient.ts passes the JSON-RPC `result.content` through as-is).
|
|
16
|
+
* The common shape per the MCP spec is an array of content blocks
|
|
17
|
+
* (`{ type: 'text', text }` and friends); join the text blocks the way the
|
|
18
|
+
* model expects a tool result to read, and fall back to JSON for anything
|
|
19
|
+
* that isn't that shape rather than losing the payload. */
|
|
20
|
+
function formatContent(content) {
|
|
21
|
+
if (typeof content === 'string')
|
|
22
|
+
return content;
|
|
23
|
+
if (Array.isArray(content)) {
|
|
24
|
+
const parts = content.map((block) => {
|
|
25
|
+
if (isPlainObject(block) && typeof block.text === 'string')
|
|
26
|
+
return block.text;
|
|
27
|
+
return JSON.stringify(block);
|
|
28
|
+
});
|
|
29
|
+
return parts.join('\n');
|
|
30
|
+
}
|
|
31
|
+
if (content == null)
|
|
32
|
+
return '';
|
|
33
|
+
return JSON.stringify(content);
|
|
34
|
+
}
|
|
35
|
+
/** Build one `AgentTool` per registry entry, in the registry's order.
|
|
36
|
+
*
|
|
37
|
+
* A server/tool pair whose name can't be safely packed into a wire name
|
|
38
|
+
* (see toolNames.ts's module comment for the exact rule) contributes NO
|
|
39
|
+
* tool -- silently dropping it would mean a user who added a server never
|
|
40
|
+
* finds out one of its tools is simply missing, so the refusal is logged to
|
|
41
|
+
* stderr, matching this package's existing convention for loud, user-facing
|
|
42
|
+
* CLI failures (cli.ts's `console.error` calls). */
|
|
43
|
+
export function asAgentTools(registry) {
|
|
44
|
+
const out = [];
|
|
45
|
+
for (const { server, tool } of registry.tools()) {
|
|
46
|
+
if (!canEncodeMcpWireName(server, tool.name)) {
|
|
47
|
+
console.error(`MCP tool refused: server "${server}" tool "${tool.name}" cannot be encoded as a wire name ` +
|
|
48
|
+
'(unsupported characters or too long) -- this tool is not available to the model');
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
out.push(toAgentTool(registry, server, tool));
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
function toAgentTool(registry, server, tool) {
|
|
56
|
+
const name = mcpWireName(server, tool.name);
|
|
57
|
+
const address = mcpToolAddress(server, tool.name);
|
|
58
|
+
return {
|
|
59
|
+
name,
|
|
60
|
+
namespace: 'mcp',
|
|
61
|
+
description: tool.description?.trim() || `MCP tool "${tool.name}" on server "${server}" (${address}).`,
|
|
62
|
+
parameters: toParameters(tool.inputSchema),
|
|
63
|
+
mutates: true,
|
|
64
|
+
async run(args) {
|
|
65
|
+
const result = await registry.call(server, tool.name, args);
|
|
66
|
+
const text = formatContent(result.content);
|
|
67
|
+
return result.isError ? `Error: ${text}` : text;
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=asAgentTools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asAgentTools.js","sourceRoot":"","sources":["../src/asAgentTools.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { McpServer } from './types';
|
|
2
|
+
export interface AuthProvider {
|
|
3
|
+
/** Headers to send with a request. Throws when the server has no usable
|
|
4
|
+
* credential stored (never logged in, or an OAuth refresh failed) -- there
|
|
5
|
+
* is nothing safe to send, and the caller should surface that as "run
|
|
6
|
+
* `corenel mcp login <name>`" rather than send an empty/stale header. */
|
|
7
|
+
headers(): Promise<{
|
|
8
|
+
name: string;
|
|
9
|
+
value: string;
|
|
10
|
+
}[]>;
|
|
11
|
+
/** Call after the SERVER returns 401. Returns true when a credential was
|
|
12
|
+
* refreshed and the call is worth retrying; false when there was nothing
|
|
13
|
+
* to refresh (headers credential, no refresh token, or the refresh
|
|
14
|
+
* itself failed) -- retrying in that case is pure latency. */
|
|
15
|
+
onUnauthorized(): Promise<boolean>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Build the `AuthProvider` for one configured MCP server.
|
|
19
|
+
*
|
|
20
|
+
* `fetchImpl` defaults to the global `fetch` and exists so a test can inject
|
|
21
|
+
* a stub without a real socket -- production callers (Task 6) pass none and
|
|
22
|
+
* get the real network.
|
|
23
|
+
*/
|
|
24
|
+
export declare function authProviderFor(stateDir: string, server: McpServer, fetchImpl?: typeof fetch, accountToken?: () => Promise<string | null>): AuthProvider;
|
|
25
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,SAAS,EAAsB,MAAM,SAAS,CAAC;AAI7D,MAAM,WAAW,YAAY;IAC3B;;;8EAG0E;IAC1E,OAAO,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IACtD;;;mEAG+D;IAC/D,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CACpC;AASD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,SAAS,EACjB,SAAS,GAAE,OAAO,KAAa,EAW/B,YAAY,GAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAoB,GAC5D,YAAY,CAmDd"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/* THE SEAM: how an MCP HTTP CLIENT (Task 6) authenticates a request without
|
|
2
|
+
* ever learning whether the credential behind it is a static header or a
|
|
3
|
+
* live OAuth token.
|
|
4
|
+
*
|
|
5
|
+
* `AuthProvider` is the whole of that contract:
|
|
6
|
+
*
|
|
7
|
+
* - `headers()` returns the request headers to send. For a `headers`
|
|
8
|
+
* credential that is just the stored list. For an `oauth` credential it
|
|
9
|
+
* is `Authorization: Bearer <accessToken>` -- refreshing FIRST if the
|
|
10
|
+
* stored token is expired (or expiring within the skew window;
|
|
11
|
+
* `oauth/tokens.ts`'s `isExpired`), so the caller never sends a token it
|
|
12
|
+
* already knows is stale.
|
|
13
|
+
* - `onUnauthorized()` is what a caller runs after the SERVER, not this
|
|
14
|
+
* module, says 401 -- the token looked fresh by the clock but the server
|
|
15
|
+
* disagreed (clock skew beyond the margin, a revoked token, whatever).
|
|
16
|
+
* It returns true when it refreshed a credential worth retrying with,
|
|
17
|
+
* false when there was nothing to do. A `headers` credential has no
|
|
18
|
+
* refresh concept at all, so it returns false immediately -- retrying a
|
|
19
|
+
* static header that was just rejected is pure latency, never a fix.
|
|
20
|
+
*
|
|
21
|
+
* This module owns the refresh (`oauth/tokens.ts`) and expiry decision, and
|
|
22
|
+
* persists a refreshed credential back through `writeMcpCredential` before
|
|
23
|
+
* handing out its headers -- Task 6's client must never read or write
|
|
24
|
+
* auth.json itself, or the two would drift the way daemon.json/auth.json
|
|
25
|
+
* already drift when two writers touch one file without a merge (see
|
|
26
|
+
* mcp/config.ts's module comment).
|
|
27
|
+
*/
|
|
28
|
+
import { readMcpCredential, writeMcpCredential } from './config.js';
|
|
29
|
+
import { discoverAuthMetadata } from './oauth/discovery.js';
|
|
30
|
+
import { refresh as refreshOAuthToken, isExpired } from './oauth/tokens.js';
|
|
31
|
+
/**
|
|
32
|
+
* Build the `AuthProvider` for one configured MCP server.
|
|
33
|
+
*
|
|
34
|
+
* `fetchImpl` defaults to the global `fetch` and exists so a test can inject
|
|
35
|
+
* a stub without a real socket -- production callers (Task 6) pass none and
|
|
36
|
+
* get the real network.
|
|
37
|
+
*/
|
|
38
|
+
export function authProviderFor(stateDir, server, fetchImpl = fetch,
|
|
39
|
+
/* The daemon's OWN account token, read LIVE rather than stored. Only used for
|
|
40
|
+
* a `hosted` server, where the relay needs to know which account is calling.
|
|
41
|
+
*
|
|
42
|
+
* Live and not stored because that token is a short-lived OAuth access token
|
|
43
|
+
* with a refresh behind it: a copy pasted into a static header is frozen at
|
|
44
|
+
* the moment it was pasted and starts failing within the hour -- with a 401
|
|
45
|
+
* that reads as an MCP problem rather than an expiry. Null means signed out,
|
|
46
|
+
* which is deliberately sent as NO header at all: the relay's own "not
|
|
47
|
+
* authenticated" is a better answer than the MCP server saying something
|
|
48
|
+
* about Microsoft. */
|
|
49
|
+
accountToken = async () => null) {
|
|
50
|
+
async function doRefresh(cred) {
|
|
51
|
+
if (!cred.refreshToken) {
|
|
52
|
+
throw new Error(`"${server.name}" has no refresh token on file -- run: corenel mcp login ${server.name}`);
|
|
53
|
+
}
|
|
54
|
+
const meta = await discoverAuthMetadata(cred.authServer, fetchImpl);
|
|
55
|
+
const refreshed = await refreshOAuthToken(meta, cred, fetchImpl);
|
|
56
|
+
await writeMcpCredential(stateDir, server.name, refreshed);
|
|
57
|
+
return refreshed;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
async headers() {
|
|
61
|
+
const cred = await readMcpCredential(stateDir, server.name);
|
|
62
|
+
/* A HOSTED server may legitimately have no credential yet: the relay is
|
|
63
|
+
* still reachable with the account token alone, and the upstream 401 is
|
|
64
|
+
* what carries the WWW-Authenticate that starts the OAuth flow. A direct
|
|
65
|
+
* server with no credential has nowhere to go, so it still refuses. */
|
|
66
|
+
if (!cred && !server.hosted) {
|
|
67
|
+
throw new Error(`No credential stored for MCP server "${server.name}" -- run: corenel mcp login ${server.name} (or set-header, for a static token).`);
|
|
68
|
+
}
|
|
69
|
+
const mcpHeaders = !cred ? []
|
|
70
|
+
: cred.kind === 'headers' ? cred.headers
|
|
71
|
+
: [{ name: 'Authorization', value: `Bearer ${(isExpired(cred, Date.now()) ? await doRefresh(cred) : cred).accessToken}` }];
|
|
72
|
+
if (!server.hosted)
|
|
73
|
+
return mcpHeaders;
|
|
74
|
+
/* Only the header that authenticates to the MCP SERVER moves aside --
|
|
75
|
+
* `Authorization`. A server wanting `X-Api-Key` is authenticating with
|
|
76
|
+
* that, and moving it would break it for no reason. */
|
|
77
|
+
const moved = mcpHeaders.map((h) => (h.name.toLowerCase() === 'authorization' ? { name: 'X-MCP-Authorization', value: h.value } : h));
|
|
78
|
+
const account = await accountToken();
|
|
79
|
+
return account ? [...moved, { name: 'Authorization', value: `Bearer ${account}` }] : moved;
|
|
80
|
+
},
|
|
81
|
+
async onUnauthorized() {
|
|
82
|
+
const cred = await readMcpCredential(stateDir, server.name);
|
|
83
|
+
// Nothing to refresh: no credential at all, or a static header --
|
|
84
|
+
// retrying a header the server just rejected fixes nothing.
|
|
85
|
+
if (!cred || cred.kind !== 'oauth')
|
|
86
|
+
return false;
|
|
87
|
+
try {
|
|
88
|
+
await doRefresh(cred);
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { McpTool } from './httpClient';
|
|
2
|
+
import type { McpServer, McpCredential } from './types';
|
|
3
|
+
export interface CachedServer {
|
|
4
|
+
url: string;
|
|
5
|
+
fingerprint: string;
|
|
6
|
+
/** Epoch ms, shown as cache age in /mcp. */
|
|
7
|
+
fetchedAt: number;
|
|
8
|
+
tools: McpTool[];
|
|
9
|
+
}
|
|
10
|
+
export declare function toolCachePath(stateDir: string): string;
|
|
11
|
+
/** Identifies WHICH credential was in force without revealing it. Re-authenticating
|
|
12
|
+
* changes this, which is what drops the stale tool list. */
|
|
13
|
+
export declare function credentialFingerprint(cred: McpCredential | null): string;
|
|
14
|
+
export declare function readToolCache(stateDir: string): Promise<Record<string, CachedServer>>;
|
|
15
|
+
export declare function writeToolCache(stateDir: string, cache: Record<string, CachedServer>): Promise<void>;
|
|
16
|
+
export declare function cacheEntryFor(cache: Record<string, CachedServer>, server: McpServer, cred: McpCredential | null): CachedServer | null;
|
|
17
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,EAAE,CAAC;CAClB;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;6DAC6D;AAC7D,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,GAAG,MAAM,CAMxE;AAgBD,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAe3F;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAQzG;AAED,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EACnC,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,aAAa,GAAG,IAAI,GACzB,YAAY,GAAG,IAAI,CAMrB"}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* THE CACHE THAT LETS A TERMINAL START COLD.
|
|
2
|
+
*
|
|
3
|
+
* McpRegistry.connectAll opens a socket per server with a 30s deadline each.
|
|
4
|
+
* A daemon pays that once; a TUI launched fifty times a day cannot pay it at
|
|
5
|
+
* all. So the CLI reads tool lists from here at startup and opens a
|
|
6
|
+
* connection only when something actually reaches for a tool.
|
|
7
|
+
*
|
|
8
|
+
* The fingerprint is a HASH, never the credential. This file sits in the same
|
|
9
|
+
* dot-dir as auth.json but carries none of its secrecy guarantees -- it exists
|
|
10
|
+
* to be read quickly at startup, and a token copied into it would be a
|
|
11
|
+
* credential in a file nobody thinks of as one.
|
|
12
|
+
*/
|
|
13
|
+
import { createHash } from 'node:crypto';
|
|
14
|
+
import { readFile, writeFile, mkdir, rename } from 'node:fs/promises';
|
|
15
|
+
import { dirname, join } from 'node:path';
|
|
16
|
+
export function toolCachePath(stateDir) {
|
|
17
|
+
return join(stateDir, 'mcp-tools.json');
|
|
18
|
+
}
|
|
19
|
+
/** Identifies WHICH credential was in force without revealing it. Re-authenticating
|
|
20
|
+
* changes this, which is what drops the stale tool list. */
|
|
21
|
+
export function credentialFingerprint(cred) {
|
|
22
|
+
if (!cred)
|
|
23
|
+
return 'none';
|
|
24
|
+
const material = cred.kind === 'headers'
|
|
25
|
+
? `headers:${cred.headers.map((h) => `${h.name}=${h.value}`).sort().join('\u0000')}`
|
|
26
|
+
: `oauth:${cred.accessToken}\u0000${cred.clientId}\u0000${cred.authServer}`;
|
|
27
|
+
return createHash('sha256').update(material).digest('hex').slice(0, 16);
|
|
28
|
+
}
|
|
29
|
+
/** A cache file is untrusted input: it can be hand-edited, half-written by a
|
|
30
|
+
* crash, or left behind by an older version. An entry that does not match the
|
|
31
|
+
* shape is DROPPED rather than repaired -- the cost of dropping one is a single
|
|
32
|
+
* network connection, and the cost of trusting it is malformed tool definitions
|
|
33
|
+
* reaching the model. */
|
|
34
|
+
function isCachedServer(v) {
|
|
35
|
+
if (!v || typeof v !== 'object' || Array.isArray(v))
|
|
36
|
+
return false;
|
|
37
|
+
const e = v;
|
|
38
|
+
return typeof e.url === 'string'
|
|
39
|
+
&& typeof e.fingerprint === 'string'
|
|
40
|
+
&& typeof e.fetchedAt === 'number'
|
|
41
|
+
&& Array.isArray(e.tools);
|
|
42
|
+
}
|
|
43
|
+
export async function readToolCache(stateDir) {
|
|
44
|
+
try {
|
|
45
|
+
const raw = await readFile(toolCachePath(stateDir), 'utf8');
|
|
46
|
+
const parsed = JSON.parse(raw);
|
|
47
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
48
|
+
return {};
|
|
49
|
+
const out = {};
|
|
50
|
+
for (const [name, entry] of Object.entries(parsed)) {
|
|
51
|
+
if (isCachedServer(entry))
|
|
52
|
+
out[name] = entry;
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// A missing, unreadable or corrupt cache is not an error: it means the next
|
|
58
|
+
// tool reach opens a connection. Never let it stop startup.
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export async function writeToolCache(stateDir, cache) {
|
|
63
|
+
const path = toolCachePath(stateDir);
|
|
64
|
+
await mkdir(dirname(path), { recursive: true });
|
|
65
|
+
// Temp-then-rename: a crash or a second terminal writing concurrently must not
|
|
66
|
+
// leave a half-written cache behind. rename() is atomic on Windows and POSIX.
|
|
67
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
68
|
+
await writeFile(tmp, `${JSON.stringify(cache, null, 2)}\n`, 'utf8');
|
|
69
|
+
await rename(tmp, path);
|
|
70
|
+
}
|
|
71
|
+
export function cacheEntryFor(cache, server, cred) {
|
|
72
|
+
const hit = cache[server.name];
|
|
73
|
+
if (!hit)
|
|
74
|
+
return null;
|
|
75
|
+
if (hit.url !== server.url)
|
|
76
|
+
return null;
|
|
77
|
+
if (hit.fingerprint !== credentialFingerprint(cred))
|
|
78
|
+
return null;
|
|
79
|
+
return hit;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { McpServer, McpCredential } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The configured MCP servers, or `[]` on a machine that has none.
|
|
4
|
+
*
|
|
5
|
+
* `readDaemonConfig` does not deep-validate a known key -- a hand-edited
|
|
6
|
+
* daemon.json can carry anything under `mcpServers` -- so this filters to
|
|
7
|
+
* entries that are actually usable, dropping the rest rather than serving a
|
|
8
|
+
* half-populated server (missing url, wrong type, not an object at all).
|
|
9
|
+
*/
|
|
10
|
+
export declare function readMcpServers(stateDir: string): Promise<McpServer[]>;
|
|
11
|
+
/** Write the server list through daemon.json, preserving every other key --
|
|
12
|
+
* including keys this process does not know about, via the `unknown` bucket
|
|
13
|
+
* `readDaemonConfig` already separates out. */
|
|
14
|
+
export declare function writeMcpServers(stateDir: string, servers: McpServer[]): Promise<void>;
|
|
15
|
+
/** The stored credential for `server`, or null when it has none. A malformed
|
|
16
|
+
* `mcp` section, a malformed entry within it, or an entry that is not a
|
|
17
|
+
* usable credential of either kind all read as "no credential" rather than
|
|
18
|
+
* throwing -- matching how a corrupt auth.json is already treated elsewhere
|
|
19
|
+
* in this package. */
|
|
20
|
+
export declare function readMcpCredential(stateDir: string, server: string): Promise<McpCredential | null>;
|
|
21
|
+
/**
|
|
22
|
+
* Store `server`'s credential, merging into auth.json rather than replacing
|
|
23
|
+
* it -- see the module comment. A secret written here never touches
|
|
24
|
+
* daemon.json: this function is the only writer of the `mcp` section, and it
|
|
25
|
+
* writes exclusively to auth.json.
|
|
26
|
+
*/
|
|
27
|
+
/** A dynamic client registration, kept so we do not mint a new one per sign-in. */
|
|
28
|
+
export interface McpRegisteredClient {
|
|
29
|
+
clientId: string;
|
|
30
|
+
clientSecret?: string;
|
|
31
|
+
/** The authorization server it was registered WITH. A server that changes its
|
|
32
|
+
* authorization server invalidates the registration, and reusing one across
|
|
33
|
+
* that change sends a client_id the new server has never issued. */
|
|
34
|
+
authServer: string;
|
|
35
|
+
/** A FIXED loopback port for the redirect, when the authorization server
|
|
36
|
+
* will not honour RFC 8252 s7.3 (which requires accepting ANY port on a
|
|
37
|
+
* loopback redirect URI).
|
|
38
|
+
*
|
|
39
|
+
* Slack matches redirect URLs exactly, so the single URL an operator
|
|
40
|
+
* registers in their own app settings has to name the port the daemon will
|
|
41
|
+
* actually listen on -- an ephemeral one could never match it. Absent
|
|
42
|
+
* everywhere else, where a random port is both free of collisions and one
|
|
43
|
+
* less thing for an operator to keep in sync. */
|
|
44
|
+
redirectPort?: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The OAuth client previously registered for `server`, or null.
|
|
48
|
+
*
|
|
49
|
+
* WHY THIS IS STORED AT ALL. Every sign-in used to perform a fresh dynamic
|
|
50
|
+
* client registration, so each click on Sign in minted another OAuth client at
|
|
51
|
+
* the vendor. Servers rate-limit that -- Figma answers 403 after a few -- and
|
|
52
|
+
* the refusal surfaces as a sign-in failure on a service that works, with
|
|
53
|
+
* nothing to suggest the cause is our own retry.
|
|
54
|
+
*
|
|
55
|
+
* Reuse is safe across the random loopback PORT this picks per attempt: RFC
|
|
56
|
+
* 8252 section 7.3 requires an authorization server to accept any port for a
|
|
57
|
+
* loopback redirect URI. `authServer` is stored so a registration is dropped
|
|
58
|
+
* if the server later points somewhere else, which is the one case where a
|
|
59
|
+
* cached client_id would be presented to something that never issued it.
|
|
60
|
+
*/
|
|
61
|
+
export declare function readMcpClient(stateDir: string, server: string): Promise<McpRegisteredClient | null>;
|
|
62
|
+
/** Store `server`'s registered client, merging into auth.json exactly as
|
|
63
|
+
* writeMcpCredential does -- same file, same 0600, same preservation of every
|
|
64
|
+
* other key including ones this build knows nothing about. */
|
|
65
|
+
export declare function writeMcpClient(stateDir: string, server: string, client: McpRegisteredClient): Promise<void>;
|
|
66
|
+
/** Forget `server`'s registered client, so the next sign-in registers afresh.
|
|
67
|
+
* Used when a server rejects the stored client_id -- the registration is gone
|
|
68
|
+
* on their side and keeping it only reproduces the failure. */
|
|
69
|
+
export declare function clearMcpClient(stateDir: string, server: string): Promise<void>;
|
|
70
|
+
export declare function writeMcpCredential(stateDir: string, server: string, cred: McpCredential): Promise<void>;
|
|
71
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAUxD;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAyB3E;AAED;;gDAEgD;AAChD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAG3F;AAmDD;;;;uBAIuB;AACvB,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAMvG;AAED;;;;;GAKG;AACH,mFAAmF;AACnF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;yEAEqE;IACrE,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;sDAQkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAaD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAMzG;AAED;;+DAE+D;AAC/D,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBjH;AAED;;gEAEgE;AAChE,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYpF;AAED,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAa7G"}
|