@nexrall/code-core 1.4.66 → 1.4.68
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/dist/agent/agentRegistry.d.ts +27 -0
- package/dist/agent/agentRegistry.d.ts.map +1 -1
- package/dist/agent/agentRegistry.js +45 -0
- package/dist/agent/agentTypes.d.ts +6 -2
- package/dist/agent/agentTypes.d.ts.map +1 -1
- package/dist/agent/agentTypes.js +13 -4
- package/dist/agent/askOnce.d.ts +12 -0
- package/dist/agent/askOnce.d.ts.map +1 -0
- package/dist/agent/askOnce.js +41 -0
- package/dist/agent/compaction.d.ts +244 -0
- package/dist/agent/compaction.d.ts.map +1 -0
- package/dist/agent/compaction.js +976 -0
- package/dist/agent/fileLocks.d.ts +34 -0
- package/dist/agent/fileLocks.d.ts.map +1 -0
- package/dist/agent/fileLocks.js +114 -0
- package/dist/agent/hooks.d.ts +331 -0
- package/dist/agent/hooks.d.ts.map +1 -0
- package/dist/agent/hooks.js +1239 -0
- package/dist/agent/iterationPolicy.d.ts +121 -0
- package/dist/agent/iterationPolicy.d.ts.map +1 -0
- package/dist/agent/iterationPolicy.js +297 -0
- package/dist/agent/lifecycleHost.d.ts +55 -0
- package/dist/agent/lifecycleHost.d.ts.map +1 -0
- package/dist/agent/lifecycleHost.js +294 -0
- package/dist/agent/loop.d.ts +39 -491
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +538 -3069
- package/dist/agent/planMode.d.ts.map +1 -1
- package/dist/agent/planMode.js +1 -0
- package/dist/agent/sharedTasks.d.ts +7 -0
- package/dist/agent/sharedTasks.d.ts.map +1 -1
- package/dist/agent/sharedTasks.js +16 -0
- package/dist/agent/subAgentBudget.d.ts +65 -0
- package/dist/agent/subAgentBudget.d.ts.map +1 -0
- package/dist/agent/subAgentBudget.js +269 -0
- package/dist/agent/subTask.d.ts +15 -0
- package/dist/agent/subTask.d.ts.map +1 -0
- package/dist/agent/subTask.js +730 -0
- package/dist/agent/subTaskSupport.d.ts +167 -0
- package/dist/agent/subTaskSupport.d.ts.map +1 -0
- package/dist/agent/subTaskSupport.js +425 -0
- package/dist/agent/toolDescriptions.d.ts +3 -0
- package/dist/agent/toolDescriptions.d.ts.map +1 -0
- package/dist/agent/toolDescriptions.js +116 -0
- package/dist/api/client.d.ts +41 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +173 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/mcp/client.d.ts +104 -0
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +136 -2
- package/dist/mcp/httpClient.d.ts +17 -1
- package/dist/mcp/httpClient.d.ts.map +1 -1
- package/dist/mcp/httpClient.js +120 -19
- package/dist/mcp/manager.d.ts +77 -2
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +275 -9
- package/dist/mcp/server.d.ts +39 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +189 -0
- package/dist/mcp/sseClient.d.ts +7 -1
- package/dist/mcp/sseClient.d.ts.map +1 -1
- package/dist/mcp/sseClient.js +45 -1
- package/dist/mcp/stats.d.ts +41 -0
- package/dist/mcp/stats.d.ts.map +1 -0
- package/dist/mcp/stats.js +108 -0
- package/dist/permissions/destructive.d.ts +2 -0
- package/dist/permissions/destructive.d.ts.map +1 -1
- package/dist/permissions/destructive.js +6 -2
- package/dist/permissions/destructiveTokens.d.ts +5 -0
- package/dist/permissions/destructiveTokens.d.ts.map +1 -1
- package/dist/permissions/destructiveTokens.js +9 -3
- package/dist/permissions/modePolicy.d.ts.map +1 -1
- package/dist/permissions/modePolicy.js +5 -2
- package/dist/permissions/rules.d.ts +4 -1
- package/dist/permissions/rules.d.ts.map +1 -1
- package/dist/permissions/rules.js +29 -0
- package/dist/plugins/data.d.ts +23 -0
- package/dist/plugins/data.d.ts.map +1 -0
- package/dist/plugins/data.js +141 -0
- package/dist/plugins/index.d.ts +14 -2
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +45 -4
- package/dist/plugins/installer.d.ts +83 -0
- package/dist/plugins/installer.d.ts.map +1 -1
- package/dist/plugins/installer.js +207 -1
- package/dist/types.d.ts +45 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/mcp/client.js
CHANGED
|
@@ -1,7 +1,102 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.McpClient = void 0;
|
|
3
|
+
exports.McpClient = exports.MCP_ELICITATION_CAPABILITY = void 0;
|
|
4
|
+
exports.flattenResourceContents = flattenResourceContents;
|
|
5
|
+
exports.flattenPromptMessages = flattenPromptMessages;
|
|
6
|
+
exports.elicitationCapability = elicitationCapability;
|
|
7
|
+
exports.normalizeElicitationRequest = normalizeElicitationRequest;
|
|
8
|
+
exports.serverRequestReply = serverRequestReply;
|
|
4
9
|
const child_process_1 = require("child_process");
|
|
10
|
+
/** Shared parsing of a resources/read reply: text parts joined, binary parts summarised, never inlined. */
|
|
11
|
+
function flattenResourceContents(result, maxChars = 100000) {
|
|
12
|
+
const contents = result?.contents ?? [];
|
|
13
|
+
const parts = contents.map((c) => {
|
|
14
|
+
if (typeof c.text === 'string')
|
|
15
|
+
return c.text;
|
|
16
|
+
if (typeof c.blob === 'string')
|
|
17
|
+
return `[binary resource ${c.uri ?? ''} (${c.mimeType ?? 'unknown type'}, ${Math.round(c.blob.length * 0.75)} bytes) not shown]`;
|
|
18
|
+
return '';
|
|
19
|
+
}).filter(Boolean);
|
|
20
|
+
const text = parts.join('\n');
|
|
21
|
+
return text.length > maxChars ? text.slice(0, maxChars) + `\n… [truncated, ${text.length - maxChars} more characters]` : text;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Shared parsing of a prompts/get reply: message texts joined in order, non-text
|
|
25
|
+
* parts summarised rather than inlined (the same rule as resources above). A
|
|
26
|
+
* prompt's `content` may be a single item or an array, so both shapes flatten to
|
|
27
|
+
* the same text; messages are separated by a blank line.
|
|
28
|
+
*/
|
|
29
|
+
function flattenPromptMessages(result, maxChars = 100000) {
|
|
30
|
+
const messages = result?.messages ?? [];
|
|
31
|
+
const parts = messages.map((m) => {
|
|
32
|
+
const content = m?.content;
|
|
33
|
+
const items = Array.isArray(content) ? content : content ? [content] : [];
|
|
34
|
+
return items.map((c) => {
|
|
35
|
+
const item = c;
|
|
36
|
+
if (item?.type === 'text' && typeof item.text === 'string')
|
|
37
|
+
return item.text;
|
|
38
|
+
if (item?.type === 'image')
|
|
39
|
+
return `[image prompt content (${item.mimeType ?? 'unknown type'}) not shown]`;
|
|
40
|
+
if (item?.type === 'audio')
|
|
41
|
+
return `[audio prompt content (${item.mimeType ?? 'unknown type'}) not shown]`;
|
|
42
|
+
if (item?.type === 'resource') {
|
|
43
|
+
if (typeof item.resource?.text === 'string')
|
|
44
|
+
return item.resource.text;
|
|
45
|
+
return `[resource prompt content (${item.resource?.uri ?? 'unknown uri'}) not shown]`;
|
|
46
|
+
}
|
|
47
|
+
return '';
|
|
48
|
+
}).filter(Boolean).join('\n');
|
|
49
|
+
}).filter(Boolean);
|
|
50
|
+
const text = parts.join('\n\n');
|
|
51
|
+
return text.length > maxChars ? text.slice(0, maxChars) + `\n… [truncated, ${text.length - maxChars} more characters]` : text;
|
|
52
|
+
}
|
|
53
|
+
/** Advertised on initialize: we can render both forms. Older servers just see `elicitation` present. */
|
|
54
|
+
exports.MCP_ELICITATION_CAPABILITY = { elicitation: { form: {}, url: {} } };
|
|
55
|
+
/**
|
|
56
|
+
* The `capabilities` object for initialize. `bare` (the per-server
|
|
57
|
+
* `bareElicitationCapability` config) sends the older empty shape — the same escape
|
|
58
|
+
* hatch Claude Code offers a server that refuses the `{form,url}` object.
|
|
59
|
+
*/
|
|
60
|
+
function elicitationCapability(bare) {
|
|
61
|
+
return bare ? { elicitation: {} } : { elicitation: { form: {}, url: {} } };
|
|
62
|
+
}
|
|
63
|
+
/** Normalise what a server sent (field names differ across protocol revisions). */
|
|
64
|
+
function normalizeElicitationRequest(params) {
|
|
65
|
+
const p = (params ?? {});
|
|
66
|
+
const url = typeof p.url === 'string' ? p.url : undefined;
|
|
67
|
+
const schema = (p.requestedSchema ?? p.schema);
|
|
68
|
+
return {
|
|
69
|
+
message: typeof p.message === 'string' && p.message.trim() ? p.message : 'An MCP server is asking for input.',
|
|
70
|
+
mode: p.mode === 'url' || (!p.mode && url) ? 'url' : 'form',
|
|
71
|
+
requestedSchema: schema && typeof schema === 'object' ? schema : undefined,
|
|
72
|
+
url,
|
|
73
|
+
elicitationId: typeof p.elicitationId === 'string' ? p.elicitationId
|
|
74
|
+
: typeof p.elicitation_id === 'string' ? p.elicitation_id : undefined,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The JSON-RPC body a transport sends back for a server→client request. Only
|
|
79
|
+
* elicitation is implemented; anything else gets a clean 'method not found' so the
|
|
80
|
+
* server fails fast instead of waiting out its own timeout.
|
|
81
|
+
*/
|
|
82
|
+
async function serverRequestReply(server, handler, method, params) {
|
|
83
|
+
if (method === 'elicitation/create') {
|
|
84
|
+
let resp = { action: 'decline' };
|
|
85
|
+
const request = normalizeElicitationRequest(params);
|
|
86
|
+
try {
|
|
87
|
+
if (handler)
|
|
88
|
+
resp = await handler(server, request);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
resp = { action: 'decline' };
|
|
92
|
+
}
|
|
93
|
+
const result = { action: resp.action };
|
|
94
|
+
if (resp.action === 'accept' && resp.content)
|
|
95
|
+
result.content = resp.content;
|
|
96
|
+
return { result };
|
|
97
|
+
}
|
|
98
|
+
return { error: { code: -32601, message: `Method not found: ${method}` } };
|
|
99
|
+
}
|
|
5
100
|
// ─── McpClient ────────────────────────────────────────────────────────────────
|
|
6
101
|
class McpClient {
|
|
7
102
|
constructor(name, config) {
|
|
@@ -11,9 +106,11 @@ class McpClient {
|
|
|
11
106
|
this._pending = new Map();
|
|
12
107
|
this._buf = '';
|
|
13
108
|
this._tools = [];
|
|
109
|
+
this._elicitationHandler = null;
|
|
14
110
|
this.name = name;
|
|
15
111
|
}
|
|
16
112
|
get tools() { return this._tools; }
|
|
113
|
+
setElicitationHandler(handler) { this._elicitationHandler = handler; }
|
|
17
114
|
async connect() {
|
|
18
115
|
if (!this.config.command)
|
|
19
116
|
throw new Error(`MCP server "${this.name}" has no command (stdio transport requires one).`);
|
|
@@ -42,7 +139,7 @@ class McpClient {
|
|
|
42
139
|
// Handshake: initialize
|
|
43
140
|
await this._request('initialize', {
|
|
44
141
|
protocolVersion: '2024-11-05',
|
|
45
|
-
capabilities:
|
|
142
|
+
capabilities: elicitationCapability(this.config.bareElicitationCapability),
|
|
46
143
|
clientInfo: { name: 'nexrall-code', version: '1.0' },
|
|
47
144
|
});
|
|
48
145
|
// Send initialized notification (no response expected)
|
|
@@ -51,6 +148,33 @@ class McpClient {
|
|
|
51
148
|
const result = await this._request('tools/list', {});
|
|
52
149
|
this._tools = result?.tools ?? [];
|
|
53
150
|
}
|
|
151
|
+
async listResources() {
|
|
152
|
+
try {
|
|
153
|
+
const res = (await this._request('resources/list', {}));
|
|
154
|
+
return Array.isArray(res?.resources) ? res.resources : [];
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return []; /* server has no resources capability */
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async readResource(uri) {
|
|
161
|
+
return flattenResourceContents(await this._request('resources/read', { uri }));
|
|
162
|
+
}
|
|
163
|
+
async listPrompts() {
|
|
164
|
+
try {
|
|
165
|
+
const res = (await this._request('prompts/list', {}));
|
|
166
|
+
return Array.isArray(res?.prompts) ? res.prompts : [];
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return []; /* server has no prompts capability */
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async getPrompt(name, args) {
|
|
173
|
+
const params = { name };
|
|
174
|
+
if (Object.keys(args).length)
|
|
175
|
+
params.arguments = args;
|
|
176
|
+
return flattenPromptMessages(await this._request('prompts/get', params));
|
|
177
|
+
}
|
|
54
178
|
async callTool(name, args) {
|
|
55
179
|
const result = await this._request('tools/call', { name, arguments: args });
|
|
56
180
|
if (!result)
|
|
@@ -105,6 +229,11 @@ class McpClient {
|
|
|
105
229
|
catch {
|
|
106
230
|
return;
|
|
107
231
|
}
|
|
232
|
+
// A server→client REQUEST carries both an id and a method (elicitation/create).
|
|
233
|
+
if (typeof msg.method === 'string' && msg.id != null) {
|
|
234
|
+
void this._handleServerRequest(msg.id, msg.method, msg.params);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
108
237
|
if (msg.id == null)
|
|
109
238
|
return; // notification, ignore
|
|
110
239
|
const pending = this._pending.get(msg.id);
|
|
@@ -118,6 +247,11 @@ class McpClient {
|
|
|
118
247
|
pending.resolve(msg.result);
|
|
119
248
|
}
|
|
120
249
|
}
|
|
250
|
+
/** Answer a server→client request. Errors are swallowed — the server must get SOME reply. */
|
|
251
|
+
async _handleServerRequest(id, method, params) {
|
|
252
|
+
const reply = await serverRequestReply(this.name, this._elicitationHandler, method, params);
|
|
253
|
+
this._send({ jsonrpc: '2.0', id, ...reply });
|
|
254
|
+
}
|
|
121
255
|
_rejectAll(err) {
|
|
122
256
|
for (const { reject } of this._pending.values())
|
|
123
257
|
reject(err);
|
package/dist/mcp/httpClient.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { McpServerConfig, McpToolDefinition, McpTransport } from './client';
|
|
1
|
+
import type { McpServerConfig, McpToolDefinition, McpTransport, McpResource, McpPrompt, McpElicitationHandler } from './client';
|
|
2
2
|
/** Thrown when a remote MCP server replies 401 — the caller should run the OAuth flow. */
|
|
3
3
|
export declare class McpOAuthRequiredError extends Error {
|
|
4
4
|
readonly serverName: string;
|
|
@@ -11,15 +11,31 @@ export declare class McpHttpClient implements McpTransport {
|
|
|
11
11
|
private _nextId;
|
|
12
12
|
private _tools;
|
|
13
13
|
private _sessionId;
|
|
14
|
+
private _elicitationHandler;
|
|
14
15
|
readonly name: string;
|
|
15
16
|
constructor(name: string, config: McpServerConfig);
|
|
16
17
|
get tools(): McpToolDefinition[];
|
|
18
|
+
setElicitationHandler(handler: McpElicitationHandler | null): void;
|
|
17
19
|
connect(): Promise<void>;
|
|
20
|
+
listResources(): Promise<McpResource[]>;
|
|
21
|
+
readResource(uri: string): Promise<string>;
|
|
22
|
+
listPrompts(): Promise<McpPrompt[]>;
|
|
23
|
+
getPrompt(name: string, args: Record<string, string>): Promise<string>;
|
|
18
24
|
callTool(name: string, args: Record<string, unknown>): Promise<string>;
|
|
19
25
|
disconnect(): void;
|
|
20
26
|
private _rpc;
|
|
21
27
|
private _notify;
|
|
22
28
|
private _post;
|
|
23
29
|
private _extractResult;
|
|
30
|
+
/**
|
|
31
|
+
* Read an SSE reply frame by frame. A server request (elicitation) is answered on the
|
|
32
|
+
* spot: the server WAITS for that answer before it sends our own result, so buffering
|
|
33
|
+
* the whole stream first would deadlock until the request timeout. As soon as our id
|
|
34
|
+
* arrives we stop and cancel the body (the stream may stay open for the session).
|
|
35
|
+
* Falls back to a buffered parse when the runtime hands us no readable body.
|
|
36
|
+
*/
|
|
37
|
+
private _consumeSse;
|
|
38
|
+
/** Reply to a server→client request. Fire-and-forget: the in-flight reader must not block. */
|
|
39
|
+
private _answerServerRequest;
|
|
24
40
|
}
|
|
25
41
|
//# sourceMappingURL=httpClient.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"httpClient.d.ts","sourceRoot":"","sources":["../../src/mcp/httpClient.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"httpClient.d.ts","sourceRoot":"","sources":["../../src/mcp/httpClient.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAqBhI,0FAA0F;AAC1F,qBAAa,qBAAsB,SAAQ,KAAK;aAE5B,UAAU,EAAE,MAAM;aAClB,SAAS,EAAE,MAAM;aACjB,eAAe,EAAE,MAAM;gBAFvB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM;CAK1C;AAED,qBAAa,aAAc,YAAW,YAAY;IAOtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IANjD,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,mBAAmB,CAAsC;IACjE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAmB,MAAM,EAAE,eAAe;IAIlE,IAAI,KAAK,IAAI,iBAAiB,EAAE,CAE/B;IAED,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,GAAG,IAAI;IAE5D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAYxB,aAAa,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAOvC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI1C,WAAW,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAOnC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAMtE,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAc5E,UAAU,IAAI,IAAI;YAaJ,IAAI;YAMJ,OAAO;YAKP,KAAK;YA2BL,cAAc;IAkB5B;;;;;;OAMG;YACW,WAAW;IAoDzB,8FAA8F;IAC9F,OAAO,CAAC,oBAAoB;CAM7B"}
|
package/dist/mcp/httpClient.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.McpHttpClient = exports.McpOAuthRequiredError = void 0;
|
|
7
7
|
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
8
|
+
const client_1 = require("./client");
|
|
8
9
|
const REQUEST_TIMEOUT_MS = 30000;
|
|
9
10
|
/** Thrown when a remote MCP server replies 401 — the caller should run the OAuth flow. */
|
|
10
11
|
class McpOAuthRequiredError extends Error {
|
|
@@ -23,23 +24,52 @@ class McpHttpClient {
|
|
|
23
24
|
this._nextId = 1;
|
|
24
25
|
this._tools = [];
|
|
25
26
|
this._sessionId = null;
|
|
27
|
+
this._elicitationHandler = null;
|
|
26
28
|
this.name = name;
|
|
27
29
|
}
|
|
28
30
|
get tools() {
|
|
29
31
|
return this._tools;
|
|
30
32
|
}
|
|
33
|
+
setElicitationHandler(handler) { this._elicitationHandler = handler; }
|
|
31
34
|
async connect() {
|
|
32
35
|
if (!this.config.url)
|
|
33
36
|
throw new Error(`MCP server "${this.name}" has no url (http/sse transport requires one).`);
|
|
34
37
|
await this._rpc('initialize', {
|
|
35
38
|
protocolVersion: '2024-11-05',
|
|
36
|
-
capabilities:
|
|
39
|
+
capabilities: (0, client_1.elicitationCapability)(this.config.bareElicitationCapability),
|
|
37
40
|
clientInfo: { name: 'nexrall-code', version: '1.0' },
|
|
38
41
|
});
|
|
39
42
|
await this._notify('notifications/initialized');
|
|
40
43
|
const res = (await this._rpc('tools/list', {}));
|
|
41
44
|
this._tools = res?.tools ?? [];
|
|
42
45
|
}
|
|
46
|
+
async listResources() {
|
|
47
|
+
try {
|
|
48
|
+
const res = (await this._rpc('resources/list', {}));
|
|
49
|
+
return Array.isArray(res?.resources) ? res.resources : [];
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return []; /* server has no resources capability */
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async readResource(uri) {
|
|
56
|
+
return (0, client_1.flattenResourceContents)(await this._rpc('resources/read', { uri }));
|
|
57
|
+
}
|
|
58
|
+
async listPrompts() {
|
|
59
|
+
try {
|
|
60
|
+
const res = (await this._rpc('prompts/list', {}));
|
|
61
|
+
return Array.isArray(res?.prompts) ? res.prompts : [];
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return []; /* server has no prompts capability */
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async getPrompt(name, args) {
|
|
68
|
+
const params = { name };
|
|
69
|
+
if (Object.keys(args).length)
|
|
70
|
+
params.arguments = args;
|
|
71
|
+
return (0, client_1.flattenPromptMessages)(await this._rpc('prompts/get', params));
|
|
72
|
+
}
|
|
43
73
|
async callTool(name, args) {
|
|
44
74
|
const result = (await this._rpc('tools/call', { name, arguments: args }));
|
|
45
75
|
if (!result)
|
|
@@ -108,35 +138,106 @@ class McpHttpClient {
|
|
|
108
138
|
throw new Error(`MCP HTTP ${resp.status} ${resp.statusText}${body ? `: ${body.slice(0, 200)}` : ''}`);
|
|
109
139
|
}
|
|
110
140
|
const contentType = resp.headers.get('content-type') ?? '';
|
|
141
|
+
if (contentType.includes('text/event-stream'))
|
|
142
|
+
return await this._consumeSse(resp, id);
|
|
111
143
|
const text = await resp.text();
|
|
112
144
|
if (!text.trim())
|
|
113
145
|
return null; // e.g. 202 ack for a notification
|
|
114
|
-
const
|
|
146
|
+
const msg = JSON.parse(text);
|
|
147
|
+
if (msg.error)
|
|
148
|
+
throw new Error(`MCP error ${msg.error.code}: ${msg.error.message}`);
|
|
149
|
+
return msg.result ?? null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Read an SSE reply frame by frame. A server request (elicitation) is answered on the
|
|
153
|
+
* spot: the server WAITS for that answer before it sends our own result, so buffering
|
|
154
|
+
* the whole stream first would deadlock until the request timeout. As soon as our id
|
|
155
|
+
* arrives we stop and cancel the body (the stream may stay open for the session).
|
|
156
|
+
* Falls back to a buffered parse when the runtime hands us no readable body.
|
|
157
|
+
*/
|
|
158
|
+
async _consumeSse(resp, id) {
|
|
159
|
+
const state = { have: false };
|
|
160
|
+
const handle = (msg) => {
|
|
161
|
+
if (typeof msg.method === 'string' && msg.id != null) {
|
|
162
|
+
this._answerServerRequest(msg.id, msg.method, msg.params);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (msg.id !== id)
|
|
166
|
+
return;
|
|
115
167
|
if (msg.error)
|
|
116
|
-
|
|
117
|
-
|
|
168
|
+
state.failure = new Error(`MCP error ${msg.error.code}: ${msg.error.message}`);
|
|
169
|
+
else {
|
|
170
|
+
state.result = msg.result ?? null;
|
|
171
|
+
state.have = true;
|
|
172
|
+
}
|
|
118
173
|
};
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
.filter((l) => l.startsWith('data:'))
|
|
125
|
-
.map((l) => l.slice(5).trim());
|
|
174
|
+
const body = resp.body;
|
|
175
|
+
if (body && typeof body[Symbol.asyncIterator] === 'function') {
|
|
176
|
+
let buf = '';
|
|
177
|
+
const dataLines = [];
|
|
178
|
+
const flush = () => {
|
|
126
179
|
if (!dataLines.length)
|
|
127
|
-
|
|
180
|
+
return;
|
|
181
|
+
const data = dataLines.join('\n');
|
|
182
|
+
dataLines.length = 0;
|
|
128
183
|
try {
|
|
129
|
-
|
|
130
|
-
if (msg.id === id)
|
|
131
|
-
return consume(msg);
|
|
184
|
+
handle(JSON.parse(data));
|
|
132
185
|
}
|
|
133
|
-
catch {
|
|
134
|
-
|
|
186
|
+
catch { /* comment/ping frame */ }
|
|
187
|
+
};
|
|
188
|
+
try {
|
|
189
|
+
for await (const chunk of body) {
|
|
190
|
+
buf += Buffer.from(chunk).toString('utf-8');
|
|
191
|
+
let nl;
|
|
192
|
+
while ((nl = buf.indexOf('\n')) !== -1) {
|
|
193
|
+
let line = buf.slice(0, nl);
|
|
194
|
+
buf = buf.slice(nl + 1);
|
|
195
|
+
if (line.endsWith('\r'))
|
|
196
|
+
line = line.slice(0, -1);
|
|
197
|
+
if (!line) {
|
|
198
|
+
flush();
|
|
199
|
+
if (state.have || state.failure)
|
|
200
|
+
break;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (line.startsWith('data:'))
|
|
204
|
+
dataLines.push(line.slice(5).replace(/^ /, ''));
|
|
205
|
+
}
|
|
206
|
+
if (state.have || state.failure)
|
|
207
|
+
break;
|
|
135
208
|
}
|
|
209
|
+
flush();
|
|
210
|
+
}
|
|
211
|
+
catch { /* a torn-down stream is not itself an error; the checks below decide */ }
|
|
212
|
+
try {
|
|
213
|
+
await body.cancel?.();
|
|
214
|
+
}
|
|
215
|
+
catch { /* best-effort */ }
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
const text = await resp.text();
|
|
219
|
+
for (const frame of text.split(/\r?\n\r?\n/)) {
|
|
220
|
+
const data = frame.split(/\r?\n/).filter((l) => l.startsWith('data:')).map((l) => l.slice(5).trim()).join('\n');
|
|
221
|
+
if (!data)
|
|
222
|
+
continue;
|
|
223
|
+
try {
|
|
224
|
+
handle(JSON.parse(data));
|
|
225
|
+
}
|
|
226
|
+
catch { /* skip non-JSON frames */ }
|
|
136
227
|
}
|
|
137
|
-
throw new Error('No matching JSON-RPC response found in SSE stream');
|
|
138
228
|
}
|
|
139
|
-
|
|
229
|
+
if (state.failure)
|
|
230
|
+
throw state.failure;
|
|
231
|
+
if (!state.have)
|
|
232
|
+
throw new Error('No matching JSON-RPC response found in SSE stream');
|
|
233
|
+
return state.result;
|
|
234
|
+
}
|
|
235
|
+
/** Reply to a server→client request. Fire-and-forget: the in-flight reader must not block. */
|
|
236
|
+
_answerServerRequest(id, method, params) {
|
|
237
|
+
void (async () => {
|
|
238
|
+
const reply = await (0, client_1.serverRequestReply)(this.name, this._elicitationHandler, method, params);
|
|
239
|
+
await this._post({ jsonrpc: '2.0', id, ...reply });
|
|
240
|
+
})().catch(() => { });
|
|
140
241
|
}
|
|
141
242
|
}
|
|
142
243
|
exports.McpHttpClient = McpHttpClient;
|
package/dist/mcp/manager.d.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import { McpServerConfig, McpToolDefinition } from './client';
|
|
1
|
+
import { McpServerConfig, McpToolDefinition, McpResource, McpPrompt } from './client';
|
|
2
|
+
import type { McpElicitationHandler } from './client';
|
|
3
|
+
import { ToolStats } from './stats';
|
|
2
4
|
export interface McpConfig {
|
|
3
5
|
mcpServers: Record<string, McpServerConfig>;
|
|
6
|
+
/**
|
|
7
|
+
* Names the user has switched off (spec §42). Stored as a list rather than a flag
|
|
8
|
+
* on the server so it also covers servers this file does not define — a plugin's
|
|
9
|
+
* mcp.json, for instance, which the user cannot edit but can perfectly well want
|
|
10
|
+
* to silence. Disabled servers are still listed by /mcp; they just never connect.
|
|
11
|
+
*/
|
|
12
|
+
disabledServers?: string[];
|
|
4
13
|
}
|
|
5
14
|
export interface AnthropicTool {
|
|
6
15
|
name: string;
|
|
@@ -14,9 +23,20 @@ export interface AnthropicTool {
|
|
|
14
23
|
export declare class McpManager {
|
|
15
24
|
private _clients;
|
|
16
25
|
private _toolMap;
|
|
26
|
+
private _promptCache;
|
|
17
27
|
private _configs;
|
|
18
28
|
private _errors;
|
|
19
29
|
private _needsAuth;
|
|
30
|
+
private _disabled;
|
|
31
|
+
/** Per-tool call counts and timings — the "is it slow or broken?" answer (spec §42). */
|
|
32
|
+
readonly stats: ToolStats;
|
|
33
|
+
private _elicitationHandler;
|
|
34
|
+
/**
|
|
35
|
+
* Route every server's elicitation (`elicitation/create`) through this handler —
|
|
36
|
+
* including servers that connect later, which is why it is also re-applied in
|
|
37
|
+
* connectAll/enable. Pass null to restore auto-decline.
|
|
38
|
+
*/
|
|
39
|
+
setElicitationHandler(handler: McpElicitationHandler | null): void;
|
|
20
40
|
/** Pick the transport for a server config: stdio (command), Streamable HTTP, or legacy SSE. */
|
|
21
41
|
private static makeClient;
|
|
22
42
|
/** Load config from ~/.nexrall/mcp.json, <workDir>/.nexrall/mcp.json, and plugin mcp.json files. */
|
|
@@ -45,28 +65,83 @@ export declare class McpManager {
|
|
|
45
65
|
callTool(prefixedName: string, input: Record<string, unknown>, abortSignal?: {
|
|
46
66
|
aborted: boolean;
|
|
47
67
|
}): Promise<string | null>;
|
|
68
|
+
/** Resources across connected, enabled servers. One server failing never hides the others. */
|
|
69
|
+
listResources(server?: string): Promise<Array<McpResource & {
|
|
70
|
+
server: string;
|
|
71
|
+
}>>;
|
|
72
|
+
/** Read one resource. Throws a clear error for an unknown server; bounded by the tool timeout. */
|
|
73
|
+
readResource(server: string, uri: string): Promise<string>;
|
|
74
|
+
/**
|
|
75
|
+
* Prompt metadata for one connected server. Fetched once per connection and
|
|
76
|
+
* cached — including an empty list (server has no prompts capability), so the
|
|
77
|
+
* `/` menu does not re-ask a server on every keystroke; reconnecting clears
|
|
78
|
+
* the entry. Returns [] for a server that is not connected.
|
|
79
|
+
*/
|
|
80
|
+
promptsFor(server: string): Promise<McpPrompt[]>;
|
|
81
|
+
/** Prompts across connected, enabled servers, tagged with the server they came from. */
|
|
82
|
+
listPrompts(server?: string): Promise<Array<McpPrompt & {
|
|
83
|
+
server: string;
|
|
84
|
+
}>>;
|
|
85
|
+
/**
|
|
86
|
+
* Expand one prompt to text. Positional values map, in order, onto the
|
|
87
|
+
* arguments the server declared in prompts/list — the same positional form
|
|
88
|
+
* Claude Code accepts (`/mcp__server__prompt first second`). Validation runs
|
|
89
|
+
* here, before the request, so mistakes read as sentences instead of raw
|
|
90
|
+
* JSON-RPC errors; the request itself is bounded by the same 60s timeout the
|
|
91
|
+
* resource reader uses.
|
|
92
|
+
*/
|
|
93
|
+
getPrompt(server: string, name: string, values?: string[]): Promise<string>;
|
|
48
94
|
/** True if this tool name belongs to an MCP server. */
|
|
49
95
|
isMcpTool(name: string): boolean;
|
|
50
96
|
get serverNames(): string[];
|
|
51
97
|
get toolCount(): number;
|
|
52
98
|
private static transportOf;
|
|
53
|
-
/** Returns per-server status for UI display, including failed / auth-required
|
|
99
|
+
/** Returns per-server status for UI display, including failed / auth-required / disabled. */
|
|
54
100
|
getStatus(): Array<{
|
|
55
101
|
name: string;
|
|
56
102
|
transport: 'stdio' | 'http' | 'sse';
|
|
57
103
|
tools: McpToolDefinition[];
|
|
58
104
|
connected: boolean;
|
|
105
|
+
disabled: boolean;
|
|
59
106
|
error?: string;
|
|
60
107
|
needsAuth?: boolean;
|
|
61
108
|
}>;
|
|
62
109
|
/** The WWW-Authenticate challenge a server returned on 401 (empty if none/unknown). */
|
|
63
110
|
authChallenge(name: string): string;
|
|
111
|
+
/** True when the user has switched this server off for the session. */
|
|
112
|
+
isDisabled(name: string): boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Switch a server off in THIS session: drop its client and forget its tool map, so
|
|
115
|
+
* the next turn's tool list no longer offers it. The caller persists the choice
|
|
116
|
+
* (McpManager.setServerDisabled) — this method deliberately does not touch disk.
|
|
117
|
+
*/
|
|
118
|
+
disable(name: string): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Switch a server back on and connect it, without a restart. Resolves to an error
|
|
121
|
+
* message when the connection fails, so the caller can print it and leave the
|
|
122
|
+
* server listed as broken instead of silently reporting success.
|
|
123
|
+
*/
|
|
124
|
+
enable(name: string): Promise<string | null>;
|
|
125
|
+
/** Disconnect one server for the session (its config stays; `enable` brings it back). */
|
|
126
|
+
disconnect(name: string): boolean;
|
|
127
|
+
/** Forgets one server's client and tools, leaving its config and status alone. */
|
|
128
|
+
private _dropClient;
|
|
64
129
|
/** Path to an mcp.json for the given scope. */
|
|
65
130
|
static configPath(workDir: string, scope: 'project' | 'user'): string;
|
|
66
131
|
private static readConfigFile;
|
|
67
132
|
private static writeConfigFile;
|
|
68
133
|
/** Add (or overwrite) a server in the given scope's mcp.json. */
|
|
69
134
|
static addServer(workDir: string, name: string, server: McpServerConfig, scope?: 'project' | 'user'): void;
|
|
135
|
+
/**
|
|
136
|
+
* Switch a server off/on in the config FILE, so the choice survives a restart.
|
|
137
|
+
* Written to the project scope even when the server came from elsewhere (a plugin
|
|
138
|
+
* or the user file), because the project is the only scope this session is sure it
|
|
139
|
+
* may edit — and `disabledServers` is a name list, so it works for all three.
|
|
140
|
+
* Returns the file that was written.
|
|
141
|
+
*/
|
|
142
|
+
static setServerDisabled(workDir: string, name: string, disabled: boolean, scope?: 'project' | 'user'): string;
|
|
143
|
+
/** Where a server's definition comes from, for messages that have to be honest about it. */
|
|
144
|
+
static serverScope(workDir: string, name: string): 'project' | 'user' | 'plugin' | 'none';
|
|
70
145
|
/** Remove a server from whichever scope(s) define it. Returns true if anything was removed. */
|
|
71
146
|
static removeServer(workDir: string, name: string): boolean;
|
|
72
147
|
disconnectAll(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/mcp/manager.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,eAAe,EAAE,iBAAiB,EAAgB,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/mcp/manager.ts"],"names":[],"mappings":"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"}
|