@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.
Files changed (74) hide show
  1. package/LICENSE +93 -0
  2. package/dist/asAgentTools.d.ts +12 -0
  3. package/dist/asAgentTools.d.ts.map +1 -0
  4. package/dist/asAgentTools.js +71 -0
  5. package/dist/asAgentTools.js.map +1 -0
  6. package/dist/auth.d.ts +25 -0
  7. package/dist/auth.d.ts.map +1 -0
  8. package/dist/auth.js +97 -0
  9. package/dist/auth.js.map +1 -0
  10. package/dist/cache.d.ts +17 -0
  11. package/dist/cache.d.ts.map +1 -0
  12. package/dist/cache.js +81 -0
  13. package/dist/cache.js.map +1 -0
  14. package/dist/config.d.ts +71 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +239 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/gatedAgentTools.d.ts +12 -0
  19. package/dist/gatedAgentTools.d.ts.map +1 -0
  20. package/dist/gatedAgentTools.js +36 -0
  21. package/dist/gatedAgentTools.js.map +1 -0
  22. package/dist/httpClient.d.ts +47 -0
  23. package/dist/httpClient.d.ts.map +1 -0
  24. package/dist/httpClient.js +196 -0
  25. package/dist/httpClient.js.map +1 -0
  26. package/dist/index.d.ts +15 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +9 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/oauth/discovery.d.ts +55 -0
  31. package/dist/oauth/discovery.d.ts.map +1 -0
  32. package/dist/oauth/discovery.js +211 -0
  33. package/dist/oauth/discovery.js.map +1 -0
  34. package/dist/oauth/login.d.ts +41 -0
  35. package/dist/oauth/login.d.ts.map +1 -0
  36. package/dist/oauth/login.js +206 -0
  37. package/dist/oauth/login.js.map +1 -0
  38. package/dist/oauth/loopback.d.ts +58 -0
  39. package/dist/oauth/loopback.d.ts.map +1 -0
  40. package/dist/oauth/loopback.js +165 -0
  41. package/dist/oauth/loopback.js.map +1 -0
  42. package/dist/oauth/pkce.d.ts +45 -0
  43. package/dist/oauth/pkce.d.ts.map +1 -0
  44. package/dist/oauth/pkce.js +67 -0
  45. package/dist/oauth/pkce.js.map +1 -0
  46. package/dist/oauth/register.d.ts +34 -0
  47. package/dist/oauth/register.d.ts.map +1 -0
  48. package/dist/oauth/register.js +57 -0
  49. package/dist/oauth/register.js.map +1 -0
  50. package/dist/oauth/tokens.d.ts +62 -0
  51. package/dist/oauth/tokens.d.ts.map +1 -0
  52. package/dist/oauth/tokens.js +118 -0
  53. package/dist/oauth/tokens.js.map +1 -0
  54. package/dist/policy.d.ts +10 -0
  55. package/dist/policy.d.ts.map +1 -0
  56. package/dist/policy.js +71 -0
  57. package/dist/policy.js.map +1 -0
  58. package/dist/registry.d.ts +124 -0
  59. package/dist/registry.d.ts.map +1 -0
  60. package/dist/registry.js +265 -0
  61. package/dist/registry.js.map +1 -0
  62. package/dist/timedFetch.d.ts +10 -0
  63. package/dist/timedFetch.d.ts.map +1 -0
  64. package/dist/timedFetch.js +39 -0
  65. package/dist/timedFetch.js.map +1 -0
  66. package/dist/toolNames.d.ts +2 -0
  67. package/dist/toolNames.d.ts.map +1 -0
  68. package/dist/toolNames.js +14 -0
  69. package/dist/toolNames.js.map +1 -0
  70. package/dist/types.d.ts +62 -0
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/types.js +19 -0
  73. package/dist/types.js.map +1 -0
  74. package/package.json +44 -0
package/dist/config.js ADDED
@@ -0,0 +1,239 @@
1
+ /* WHERE AN MCP SERVER AND ITS CREDENTIAL LIVE, AND HOW THEY ARE KEPT APART.
2
+ *
3
+ * The server list is config: it goes through readDaemonConfig/writeDaemonConfig
4
+ * exactly like every other daemon.json key, so unknown keys survive a write and
5
+ * a missing/corrupt file still starts on defaults. The credential is not: it
6
+ * lives in <stateDir>/auth.json, under a top-level `mcp` object keyed by server
7
+ * name, alongside (and never disturbing) the user's own sign-in record that
8
+ * auth/store.ts already keeps there.
9
+ *
10
+ * TWO GUARDS worth calling out because a later change could quietly break
11
+ * either:
12
+ *
13
+ * 1. "Never partially applied" is enforced at the RECORD level, not the file
14
+ * level. readMcpServers drops any entry that is not an object with a
15
+ * non-empty string name AND url -- a malformed entry contributes nothing,
16
+ * not half of itself -- while entries around it still come through.
17
+ *
18
+ * 2. auth.json is READ-MODIFY-WRITE-merged on every credential write. This
19
+ * file may hold the user's own accessToken/refreshToken alongside `mcp`;
20
+ * replacing the whole file instead of merging into it would sign the user
21
+ * out (or drop an unrelated server's credential) as a side effect of an
22
+ * MCP write neither operation has any business touching. The other
23
+ * direction of that same merge matters too: auth/store.ts's
24
+ * `saveCredential` (every sign-in and token refresh) must preserve this
25
+ * module's `mcp` section rather than overwrite the whole file with just
26
+ * the user's own credential -- see the comment there.
27
+ *
28
+ * 3. readMcpCredential holds a stored entry to the SAME "never partially
29
+ * applied" standard as readMcpServers: `isUsableCredential` checks the
30
+ * tagged union's required fields for whichever `kind` is present, so a
31
+ * half-written credential reads as absent, not as a broken one handed to
32
+ * a caller.
33
+ */
34
+ import { readFile, writeFile, mkdir, chmod } from 'node:fs/promises';
35
+ import { credentialFilePath, credentialDir } from '@corenel/tools-node';
36
+ import { readDaemonConfig, writeDaemonConfig } from '@corenel/config';
37
+ /** True for a value that is a usable {name,url} server entry -- the shape
38
+ * `readMcpServers` requires of every element it keeps. */
39
+ function isUsableServer(v) {
40
+ if (typeof v !== 'object' || v === null)
41
+ return false;
42
+ const r = v;
43
+ return typeof r.name === 'string' && r.name.length > 0 && typeof r.url === 'string' && r.url.length > 0;
44
+ }
45
+ /**
46
+ * The configured MCP servers, or `[]` on a machine that has none.
47
+ *
48
+ * `readDaemonConfig` does not deep-validate a known key -- a hand-edited
49
+ * daemon.json can carry anything under `mcpServers` -- so this filters to
50
+ * entries that are actually usable, dropping the rest rather than serving a
51
+ * half-populated server (missing url, wrong type, not an object at all).
52
+ */
53
+ export async function readMcpServers(stateDir) {
54
+ const { config } = await readDaemonConfig(stateDir);
55
+ const raw = config.mcpServers;
56
+ if (!Array.isArray(raw))
57
+ return [];
58
+ /* The mapping is deliberate rather than a pass-through -- it is what keeps a
59
+ * hand-edited daemon.json from smuggling arbitrary keys into an McpServer --
60
+ * so EVERY field the daemon acts on has to be named here. `hosted` was not,
61
+ * and the consequence was silent: the relay auth path read `undefined` on
62
+ * every server while its own unit test, which builds the object by hand,
63
+ * stayed green.
64
+ *
65
+ * `=== true` and not a truthy check: this flag decides whether the Corenel
66
+ * account token is attached, so `"hosted": "no"` must read as off. Omitted
67
+ * when false so the round-trip matches what `addServer` writes. */
68
+ return raw.filter(isUsableServer).map((s) => {
69
+ const out = { name: s.name, url: s.url };
70
+ if (s.hosted === true)
71
+ out.hosted = true;
72
+ /* Named here for the same reason `hosted` is: this mapping is what stops a
73
+ * hand-edited daemon.json smuggling arbitrary keys in, so every field the
74
+ * daemon acts on has to appear. A dropped `authUrl` sends the login flow
75
+ * back to probing the relay route, which is the bug this field exists to
76
+ * fix. */
77
+ if (typeof s.authUrl === 'string' && s.authUrl.length > 0)
78
+ out.authUrl = s.authUrl;
79
+ return out;
80
+ });
81
+ }
82
+ /** Write the server list through daemon.json, preserving every other key --
83
+ * including keys this process does not know about, via the `unknown` bucket
84
+ * `readDaemonConfig` already separates out. */
85
+ export async function writeMcpServers(stateDir, servers) {
86
+ const { config, unknown } = await readDaemonConfig(stateDir);
87
+ await writeDaemonConfig(stateDir, { ...config, mcpServers: servers }, unknown);
88
+ }
89
+ /** Read auth.json as a bag of top-level keys, never throwing -- a missing or
90
+ * corrupt file reads as empty, exactly like readDaemonConfig treats
91
+ * daemon.json, so a credential lookup can never crash the daemon. */
92
+ async function readAuthJson(stateDir) {
93
+ let raw;
94
+ try {
95
+ raw = await readFile(credentialFilePath(stateDir), 'utf8');
96
+ }
97
+ catch {
98
+ return {};
99
+ }
100
+ try {
101
+ const parsed = JSON.parse(raw);
102
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
103
+ return {};
104
+ return parsed;
105
+ }
106
+ catch {
107
+ return {};
108
+ }
109
+ }
110
+ function isMcpAuthSection(v) {
111
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
112
+ }
113
+ /** True for a value that is a usable credential of either tagged-union kind --
114
+ * the same "never partially applied" posture `isUsableServer` holds servers
115
+ * to, applied here so a hand-edited or half-written auth.json (e.g.
116
+ * `{kind:'headers'}` with no `headers` array) reads as no credential rather
117
+ * than reaching a caller that will crash or silently send nothing. */
118
+ function isUsableCredential(v) {
119
+ if (typeof v !== 'object' || v === null)
120
+ return false;
121
+ const r = v;
122
+ if (r.kind === 'headers') {
123
+ return Array.isArray(r.headers) && r.headers.every((h) => typeof h === 'object' && h !== null
124
+ && typeof h.name === 'string'
125
+ && typeof h.value === 'string');
126
+ }
127
+ if (r.kind === 'oauth') {
128
+ return typeof r.accessToken === 'string' && r.accessToken.length > 0
129
+ && typeof r.clientId === 'string' && r.clientId.length > 0
130
+ && typeof r.authServer === 'string' && r.authServer.length > 0;
131
+ }
132
+ return false;
133
+ }
134
+ /** The stored credential for `server`, or null when it has none. A malformed
135
+ * `mcp` section, a malformed entry within it, or an entry that is not a
136
+ * usable credential of either kind all read as "no credential" rather than
137
+ * throwing -- matching how a corrupt auth.json is already treated elsewhere
138
+ * in this package. */
139
+ export async function readMcpCredential(stateDir, server) {
140
+ const doc = await readAuthJson(stateDir);
141
+ const section = doc.mcp;
142
+ if (!isMcpAuthSection(section))
143
+ return null;
144
+ const cred = section[server];
145
+ return isUsableCredential(cred) ? cred : null;
146
+ }
147
+ function isUsableClient(v) {
148
+ if (typeof v !== 'object' || v === null)
149
+ return false;
150
+ const r = v;
151
+ return typeof r.clientId === 'string' && r.clientId.length > 0
152
+ && typeof r.authServer === 'string' && r.authServer.length > 0
153
+ && (r.clientSecret === undefined || typeof r.clientSecret === 'string')
154
+ && (r.redirectPort === undefined
155
+ || (typeof r.redirectPort === 'number' && Number.isInteger(r.redirectPort)
156
+ && r.redirectPort > 0 && r.redirectPort < 65536));
157
+ }
158
+ /**
159
+ * The OAuth client previously registered for `server`, or null.
160
+ *
161
+ * WHY THIS IS STORED AT ALL. Every sign-in used to perform a fresh dynamic
162
+ * client registration, so each click on Sign in minted another OAuth client at
163
+ * the vendor. Servers rate-limit that -- Figma answers 403 after a few -- and
164
+ * the refusal surfaces as a sign-in failure on a service that works, with
165
+ * nothing to suggest the cause is our own retry.
166
+ *
167
+ * Reuse is safe across the random loopback PORT this picks per attempt: RFC
168
+ * 8252 section 7.3 requires an authorization server to accept any port for a
169
+ * loopback redirect URI. `authServer` is stored so a registration is dropped
170
+ * if the server later points somewhere else, which is the one case where a
171
+ * cached client_id would be presented to something that never issued it.
172
+ */
173
+ export async function readMcpClient(stateDir, server) {
174
+ const doc = await readAuthJson(stateDir);
175
+ const section = doc.mcpClients;
176
+ if (typeof section !== 'object' || section === null || Array.isArray(section))
177
+ return null;
178
+ const rec = section[server];
179
+ return isUsableClient(rec) ? rec : null;
180
+ }
181
+ /** Store `server`'s registered client, merging into auth.json exactly as
182
+ * writeMcpCredential does -- same file, same 0600, same preservation of every
183
+ * other key including ones this build knows nothing about. */
184
+ export async function writeMcpClient(stateDir, server, client) {
185
+ const doc = await readAuthJson(stateDir);
186
+ const prev = doc.mcpClients;
187
+ const section = (typeof prev === 'object' && prev !== null && !Array.isArray(prev))
188
+ ? { ...prev }
189
+ : {};
190
+ section[server] = client;
191
+ const next = { ...doc, mcpClients: section };
192
+ const path = credentialFilePath(stateDir);
193
+ await mkdir(credentialDir(stateDir), { recursive: true });
194
+ await writeFile(path, `${JSON.stringify(next, null, 2)}
195
+ `, { mode: 0o600 });
196
+ try {
197
+ await chmod(path, 0o600);
198
+ }
199
+ catch {
200
+ /* best effort on platforms without POSIX modes, matching auth/store.ts */
201
+ }
202
+ }
203
+ /** Forget `server`'s registered client, so the next sign-in registers afresh.
204
+ * Used when a server rejects the stored client_id -- the registration is gone
205
+ * on their side and keeping it only reproduces the failure. */
206
+ export async function clearMcpClient(stateDir, server) {
207
+ const doc = await readAuthJson(stateDir);
208
+ const prev = doc.mcpClients;
209
+ if (typeof prev !== 'object' || prev === null || Array.isArray(prev))
210
+ return;
211
+ const section = { ...prev };
212
+ if (!(server in section))
213
+ return;
214
+ delete section[server];
215
+ const path = credentialFilePath(stateDir);
216
+ await mkdir(credentialDir(stateDir), { recursive: true });
217
+ await writeFile(path, `${JSON.stringify({ ...doc, mcpClients: section }, null, 2)}
218
+ `, { mode: 0o600 });
219
+ try {
220
+ await chmod(path, 0o600);
221
+ }
222
+ catch { /* as above */ }
223
+ }
224
+ export async function writeMcpCredential(stateDir, server, cred) {
225
+ const doc = await readAuthJson(stateDir);
226
+ const section = isMcpAuthSection(doc.mcp) ? { ...doc.mcp } : {};
227
+ section[server] = cred;
228
+ const next = { ...doc, mcp: section };
229
+ const path = credentialFilePath(stateDir);
230
+ await mkdir(credentialDir(stateDir), { recursive: true });
231
+ await writeFile(path, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
232
+ try {
233
+ await chmod(path, 0o600);
234
+ }
235
+ catch {
236
+ /* best effort on platforms without POSIX modes, matching auth/store.ts */
237
+ }
238
+ }
239
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { AgentTool } from '@corenel/protocol';
2
+ import type { Policy } from '@corenel/protocol';
3
+ import type { McpRegistry } from './registry';
4
+ /**
5
+ * Every connected/primed MCP tool on `registry`, wrapped so a call is gated
6
+ * by `mcpCallAllowed(address, policy)` BEFORE the underlying tool ever runs.
7
+ * A tool whose server/tool pair cannot be encoded as a wire name is skipped
8
+ * (matching `asAgentTools`'s own refusal, already logged there -- not
9
+ * duplicated or silenced here).
10
+ */
11
+ export declare function gatedAgentTools(registry: McpRegistry, policy: Policy): AgentTool[];
12
+ //# sourceMappingURL=gatedAgentTools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gatedAgentTools.d.ts","sourceRoot":"","sources":["../src/gatedAgentTools.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAK9C;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,CAyBlF"}
@@ -0,0 +1,36 @@
1
+ import { asAgentTools } from './asAgentTools.js';
2
+ import { mcpCallAllowed } from './policy.js';
3
+ import { canEncodeMcpWireName, mcpToolAddress, mcpWireName } from './toolNames.js';
4
+ /**
5
+ * Every connected/primed MCP tool on `registry`, wrapped so a call is gated
6
+ * by `mcpCallAllowed(address, policy)` BEFORE the underlying tool ever runs.
7
+ * A tool whose server/tool pair cannot be encoded as a wire name is skipped
8
+ * (matching `asAgentTools`'s own refusal, already logged there -- not
9
+ * duplicated or silenced here).
10
+ */
11
+ export function gatedAgentTools(registry, policy) {
12
+ const base = asAgentTools(registry);
13
+ const byWireName = new Map(base.map((t) => [t.name, t]));
14
+ const wrapped = [];
15
+ for (const { server, tool } of registry.tools()) {
16
+ if (!canEncodeMcpWireName(server, tool.name))
17
+ continue;
18
+ const name = mcpWireName(server, tool.name);
19
+ const original = byWireName.get(name);
20
+ if (!original)
21
+ continue; // defensive: cannot happen, base and tools() agree by construction
22
+ // Built ONCE, from the connected {server, tool} pair -- never from a call.
23
+ const address = mcpToolAddress(server, tool.name);
24
+ wrapped.push({
25
+ ...original,
26
+ async run(args, ctx) {
27
+ const decision = mcpCallAllowed(address, policy);
28
+ if (!decision.ok)
29
+ return `Error: ${decision.reason}`;
30
+ return original.run(args, ctx);
31
+ },
32
+ });
33
+ }
34
+ return wrapped;
35
+ }
36
+ //# sourceMappingURL=gatedAgentTools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gatedAgentTools.js","sourceRoot":"","sources":["../src/gatedAgentTools.ts"],"names":[],"mappings":"AAgCA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEhF;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,QAAqB,EAAE,MAAc;IACnE,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAEzD,MAAM,OAAO,GAAgB,EAAE,CAAC;IAChC,KAAK,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC;QAChD,IAAI,CAAC,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QACvD,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,CAAC,QAAQ;YAAE,SAAS,CAAC,mEAAmE;QAE5F,2EAA2E;QAC3E,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAElD,OAAO,CAAC,IAAI,CAAC;YACX,GAAG,QAAQ;YACX,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG;gBACjB,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;gBACjD,IAAI,CAAC,QAAQ,CAAC,EAAE;oBAAE,OAAO,UAAU,QAAQ,CAAC,MAAM,EAAE,CAAC;gBACrD,OAAO,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACjC,CAAC;SACF,CAAC,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,47 @@
1
+ import type { McpServer } from './types.ts';
2
+ import type { AuthProvider } from './auth.ts';
3
+ export interface McpTool {
4
+ name: string;
5
+ description?: string;
6
+ inputSchema: unknown;
7
+ }
8
+ export declare class McpHttpClient {
9
+ private readonly server;
10
+ private readonly auth;
11
+ private readonly fetchImpl;
12
+ private nextRequestId;
13
+ private sessionId;
14
+ constructor(server: McpServer, auth: AuthProvider, fetchImpl?: typeof fetch);
15
+ initialize(): Promise<void>;
16
+ listTools(): Promise<McpTool[]>;
17
+ callTool(name: string, args: unknown): Promise<{
18
+ content: unknown;
19
+ isError: boolean;
20
+ }>;
21
+ /** Send one JSON-RPC request, retrying exactly once on a 401 that the
22
+ * AuthProvider says it recovered from. `isRetry` prevents a second retry
23
+ * loop -- a credential that fails twice is refused, not flaky. */
24
+ private send;
25
+ /** Read an SSE body until the JSON-RPC response for `id` arrives.
26
+ *
27
+ * Events are separated by a blank line; an event's `data:` lines are joined
28
+ * with newlines, per the SSE spec, and parsed as one JSON-RPC message.
29
+ * Notifications (no `id`) and responses to other ids are skipped. The reader
30
+ * is cancelled once the answer is in, so a server that keeps the stream open
31
+ * after replying cannot hold this call open with it. */
32
+ private readSseResponse;
33
+ /** The JSON-RPC response in one SSE event, if it answers `id`; else null. */
34
+ private matchSseEvent;
35
+ /** The bare `fetch()` call, wrapped -- every OTHER throw in `send` already
36
+ * names the server; this is the one call whose own failures (DNS miss,
37
+ * connection refused, TLS failure) never reach `send`'s response-handling
38
+ * at all. Left unguarded, node's bare `fetch failed` -- naming neither the
39
+ * URL nor the actual cause -- propagates straight out of `send`, into
40
+ * `registry.ts`'s `status().error`, and out of `doctor`, the one command
41
+ * whose entire job is diagnosis. `err.cause` carries the real reason
42
+ * (ECONNREFUSED, ENOTFOUND, a TLS alert); undici puts it there rather than
43
+ * in `err.message`. */
44
+ private request;
45
+ private buildHeaders;
46
+ }
47
+ //# sourceMappingURL=httpClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"httpClient.d.ts","sourceRoot":"","sources":["../src/httpClient.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAa9C,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;CACtB;AAcD,qBAAa,aAAa;IAKtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAN5B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,SAAS,CAAqB;gBAGnB,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,YAAY,EAClB,SAAS,GAAE,OAAO,KAAa;IAG5C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B,SAAS,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAU/B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAU5F;;uEAEmE;YACrD,IAAI;IAgDlB;;;;;;6DAMyD;YAC3C,eAAe;IAiC7B,6EAA6E;IAC7E,OAAO,CAAC,aAAa;IAkBrB;;;;;;;;4BAQwB;YACV,OAAO;YAeP,YAAY;CAQ3B"}
@@ -0,0 +1,196 @@
1
+ /** MCP spec revision this client speaks. Bumping it is a protocol decision,
2
+ * not a version-string typo -- do it deliberately. */
3
+ const PROTOCOL_VERSION = '2025-06-18';
4
+ const CLIENT_INFO = { name: 'corenel', version: '0.2.0' };
5
+ /** The `Mcp-Session-Id` header name, per the MCP Streamable HTTP transport
6
+ * spec. Servers are free to omit it (stateless servers have no session to
7
+ * track); when present it must be echoed on every later request. */
8
+ const SESSION_HEADER = 'Mcp-Session-Id';
9
+ export class McpHttpClient {
10
+ server;
11
+ auth;
12
+ fetchImpl;
13
+ nextRequestId = 1;
14
+ sessionId;
15
+ constructor(server, auth, fetchImpl = fetch) {
16
+ this.server = server;
17
+ this.auth = auth;
18
+ this.fetchImpl = fetchImpl;
19
+ }
20
+ async initialize() {
21
+ await this.send('initialize', {
22
+ protocolVersion: PROTOCOL_VERSION,
23
+ capabilities: {},
24
+ clientInfo: CLIENT_INFO,
25
+ });
26
+ }
27
+ async listTools() {
28
+ const result = await this.send('tools/list', {});
29
+ const tools = isRecord(result) ? result.tools : undefined;
30
+ // A server with zero tools is a legitimate, common answer -- not an
31
+ // error, and not something to confuse with the stream-response case
32
+ // above (which throws rather than reaching here at all).
33
+ if (!Array.isArray(tools))
34
+ return [];
35
+ return tools.filter(isMcpTool);
36
+ }
37
+ async callTool(name, args) {
38
+ const result = await this.send('tools/call', { name, arguments: args });
39
+ const record = isRecord(result) ? result : {};
40
+ // isError: true is the TOOL failing -- information for the model, not a
41
+ // transport problem, so it is returned rather than thrown. A transport
42
+ // failure (bad JSON-RPC, 401 refused twice, a stream response) throws
43
+ // out of `send` before we ever get here.
44
+ return { content: record.content, isError: record.isError === true };
45
+ }
46
+ /** Send one JSON-RPC request, retrying exactly once on a 401 that the
47
+ * AuthProvider says it recovered from. `isRetry` prevents a second retry
48
+ * loop -- a credential that fails twice is refused, not flaky. */
49
+ async send(method, params, isRetry = false) {
50
+ const headers = await this.buildHeaders();
51
+ // Allocated HERE rather than inside `request`, because an SSE reply has to be
52
+ // matched back to this id: a stream may carry notifications, or a response
53
+ // to some other request, ahead of ours.
54
+ const id = this.nextRequestId++;
55
+ const res = await this.request(headers, id, method, params);
56
+ if (res.status === 401) {
57
+ if (!isRetry && (await this.auth.onUnauthorized())) {
58
+ return this.send(method, params, true);
59
+ }
60
+ throw new Error(`MCP server "${this.server.name}" refused the credential (401) -- run: corenel mcp login ${this.server.name}`);
61
+ }
62
+ const sessionId = res.headers.get(SESSION_HEADER);
63
+ if (sessionId)
64
+ this.sessionId = sessionId;
65
+ if (!res.ok) {
66
+ throw new Error(`MCP server "${this.server.name}" returned HTTP ${res.status}`);
67
+ }
68
+ const contentType = res.headers.get('content-type') ?? '';
69
+ let body;
70
+ if (contentType.includes('text/event-stream')) {
71
+ /* STREAMABLE HTTP MAY ANSWER WITH SSE, and Notion, Asana, Atlassian and
72
+ * Dropbox all do. This client used to throw on sight, so those servers
73
+ * never connected at all. Notion's real reply is one `message` event
74
+ * carrying the JSON-RPC response, then a close -- but a server may also
75
+ * send notifications first, or hold the stream open afterwards, so the
76
+ * reader stops at OUR response rather than at the end of the stream. */
77
+ body = await this.readSseResponse(res, id);
78
+ }
79
+ else if (contentType.includes('application/json')) {
80
+ body = (await res.json());
81
+ }
82
+ else {
83
+ throw new Error(`MCP server "${this.server.name}" answered with an unexpected content-type: "${contentType || '(none)'}"`);
84
+ }
85
+ if (body.error) {
86
+ throw new Error(`MCP server "${this.server.name}" returned error ${body.error.code}: ${body.error.message}`);
87
+ }
88
+ return body.result;
89
+ }
90
+ /** Read an SSE body until the JSON-RPC response for `id` arrives.
91
+ *
92
+ * Events are separated by a blank line; an event's `data:` lines are joined
93
+ * with newlines, per the SSE spec, and parsed as one JSON-RPC message.
94
+ * Notifications (no `id`) and responses to other ids are skipped. The reader
95
+ * is cancelled once the answer is in, so a server that keeps the stream open
96
+ * after replying cannot hold this call open with it. */
97
+ async readSseResponse(res, id) {
98
+ if (!res.body) {
99
+ throw new Error(`MCP server "${this.server.name}" closed the stream without a response`);
100
+ }
101
+ const reader = res.body.getReader();
102
+ const decoder = new TextDecoder();
103
+ let buffer = '';
104
+ try {
105
+ for (;;) {
106
+ const { done, value } = await reader.read();
107
+ if (value)
108
+ buffer += decoder.decode(value, { stream: true });
109
+ // Normalise CRLF so one splitter handles both line endings.
110
+ buffer = buffer.replace(/\r\n/g, '\n');
111
+ let boundary = buffer.indexOf('\n\n');
112
+ while (boundary !== -1) {
113
+ const event = buffer.slice(0, boundary);
114
+ buffer = buffer.slice(boundary + 2);
115
+ const match = this.matchSseEvent(event, id);
116
+ if (match)
117
+ return match;
118
+ boundary = buffer.indexOf('\n\n');
119
+ }
120
+ if (done) {
121
+ // A final event the server did not terminate with a blank line.
122
+ const tail = buffer.trim() ? this.matchSseEvent(buffer, id) : null;
123
+ if (tail)
124
+ return tail;
125
+ throw new Error(`MCP server "${this.server.name}" closed the stream without a response`);
126
+ }
127
+ }
128
+ }
129
+ finally {
130
+ reader.cancel().catch(() => { });
131
+ }
132
+ }
133
+ /** The JSON-RPC response in one SSE event, if it answers `id`; else null. */
134
+ matchSseEvent(event, id) {
135
+ const data = event
136
+ .split('\n')
137
+ .filter((line) => line.startsWith('data:'))
138
+ .map((line) => line.slice(5).replace(/^ /, ''))
139
+ .join('\n');
140
+ if (!data)
141
+ return null;
142
+ let parsed;
143
+ try {
144
+ parsed = JSON.parse(data);
145
+ }
146
+ catch {
147
+ return null; // not a JSON-RPC message; SSE comments and keep-alives land here
148
+ }
149
+ if (!isRecord(parsed) || parsed.id !== id)
150
+ return null;
151
+ if (!('result' in parsed) && !('error' in parsed))
152
+ return null;
153
+ return parsed;
154
+ }
155
+ /** The bare `fetch()` call, wrapped -- every OTHER throw in `send` already
156
+ * names the server; this is the one call whose own failures (DNS miss,
157
+ * connection refused, TLS failure) never reach `send`'s response-handling
158
+ * at all. Left unguarded, node's bare `fetch failed` -- naming neither the
159
+ * URL nor the actual cause -- propagates straight out of `send`, into
160
+ * `registry.ts`'s `status().error`, and out of `doctor`, the one command
161
+ * whose entire job is diagnosis. `err.cause` carries the real reason
162
+ * (ECONNREFUSED, ENOTFOUND, a TLS alert); undici puts it there rather than
163
+ * in `err.message`. */
164
+ async request(headers, id, method, params) {
165
+ try {
166
+ return await this.fetchImpl(this.server.url, {
167
+ method: 'POST',
168
+ headers,
169
+ body: JSON.stringify({ jsonrpc: '2.0', id, method, params }),
170
+ });
171
+ }
172
+ catch (err) {
173
+ const cause = err instanceof Error && err.cause instanceof Error ? err.cause.message
174
+ : err instanceof Error ? err.message
175
+ : String(err);
176
+ throw new Error(`MCP server "${this.server.name}" (${this.server.url}) is unreachable: ${cause}`);
177
+ }
178
+ }
179
+ async buildHeaders() {
180
+ const headers = new Headers();
181
+ headers.set('Content-Type', 'application/json');
182
+ headers.set('Accept', 'application/json, text/event-stream');
183
+ for (const h of await this.auth.headers())
184
+ headers.set(h.name, h.value);
185
+ if (this.sessionId)
186
+ headers.set(SESSION_HEADER, this.sessionId);
187
+ return headers;
188
+ }
189
+ }
190
+ function isRecord(value) {
191
+ return typeof value === 'object' && value !== null;
192
+ }
193
+ function isMcpTool(value) {
194
+ return isRecord(value) && typeof value.name === 'string' && 'inputSchema' in value;
195
+ }
196
+ //# sourceMappingURL=httpClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"httpClient.js","sourceRoot":"","sources":["../src/httpClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export type { McpServer, McpCredential, McpHeadersCredential, McpOAuthCredential } from './types';
2
+ export type { McpTool } from './httpClient';
3
+ export { McpHttpClient } from './httpClient';
4
+ export type { McpRegisteredClient } from './config';
5
+ export { readMcpServers, writeMcpServers, readMcpCredential, writeMcpCredential, readMcpClient, writeMcpClient, clearMcpClient, } from './config';
6
+ export type { McpClient, McpClientFactory, ServerReport, ConnectAllOptions } from './registry';
7
+ export { McpRegistry, SERVER_CONNECT_TIMEOUT_MS, accountTokenFor } from './registry';
8
+ export type { McpCallDecision } from './policy';
9
+ export { mcpCallAllowed } from './policy';
10
+ export { asAgentTools } from './asAgentTools';
11
+ export { gatedAgentTools } from './gatedAgentTools';
12
+ export type { AuthProvider } from './auth';
13
+ export { authProviderFor } from './auth';
14
+ export { timedFetch, AUTH_REQUEST_TIMEOUT_MS } from './timedFetch';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClG,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EACL,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,cAAc,GACf,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/F,OAAO,EAAE,WAAW,EAAE,yBAAyB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACrF,YAAY,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { McpHttpClient } from './httpClient.js';
2
+ export { readMcpServers, writeMcpServers, readMcpCredential, writeMcpCredential, readMcpClient, writeMcpClient, clearMcpClient, } from './config.js';
3
+ export { McpRegistry, SERVER_CONNECT_TIMEOUT_MS, accountTokenFor } from './registry.js';
4
+ export { mcpCallAllowed } from './policy.js';
5
+ export { asAgentTools } from './asAgentTools.js';
6
+ export { gatedAgentTools } from './gatedAgentTools.js';
7
+ export { authProviderFor } from './auth.js';
8
+ export { timedFetch, AUTH_REQUEST_TIMEOUT_MS } from './timedFetch.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EACL,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,cAAc,GACf,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,WAAW,EAAE,yBAAyB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAErF,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC"}