@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.
Files changed (91) hide show
  1. package/dist/agent/agentRegistry.d.ts +27 -0
  2. package/dist/agent/agentRegistry.d.ts.map +1 -1
  3. package/dist/agent/agentRegistry.js +45 -0
  4. package/dist/agent/agentTypes.d.ts +6 -2
  5. package/dist/agent/agentTypes.d.ts.map +1 -1
  6. package/dist/agent/agentTypes.js +13 -4
  7. package/dist/agent/askOnce.d.ts +12 -0
  8. package/dist/agent/askOnce.d.ts.map +1 -0
  9. package/dist/agent/askOnce.js +41 -0
  10. package/dist/agent/compaction.d.ts +244 -0
  11. package/dist/agent/compaction.d.ts.map +1 -0
  12. package/dist/agent/compaction.js +976 -0
  13. package/dist/agent/fileLocks.d.ts +34 -0
  14. package/dist/agent/fileLocks.d.ts.map +1 -0
  15. package/dist/agent/fileLocks.js +114 -0
  16. package/dist/agent/hooks.d.ts +331 -0
  17. package/dist/agent/hooks.d.ts.map +1 -0
  18. package/dist/agent/hooks.js +1239 -0
  19. package/dist/agent/iterationPolicy.d.ts +121 -0
  20. package/dist/agent/iterationPolicy.d.ts.map +1 -0
  21. package/dist/agent/iterationPolicy.js +297 -0
  22. package/dist/agent/lifecycleHost.d.ts +55 -0
  23. package/dist/agent/lifecycleHost.d.ts.map +1 -0
  24. package/dist/agent/lifecycleHost.js +294 -0
  25. package/dist/agent/loop.d.ts +39 -491
  26. package/dist/agent/loop.d.ts.map +1 -1
  27. package/dist/agent/loop.js +538 -3069
  28. package/dist/agent/planMode.d.ts.map +1 -1
  29. package/dist/agent/planMode.js +1 -0
  30. package/dist/agent/sharedTasks.d.ts +7 -0
  31. package/dist/agent/sharedTasks.d.ts.map +1 -1
  32. package/dist/agent/sharedTasks.js +16 -0
  33. package/dist/agent/subAgentBudget.d.ts +65 -0
  34. package/dist/agent/subAgentBudget.d.ts.map +1 -0
  35. package/dist/agent/subAgentBudget.js +269 -0
  36. package/dist/agent/subTask.d.ts +15 -0
  37. package/dist/agent/subTask.d.ts.map +1 -0
  38. package/dist/agent/subTask.js +730 -0
  39. package/dist/agent/subTaskSupport.d.ts +167 -0
  40. package/dist/agent/subTaskSupport.d.ts.map +1 -0
  41. package/dist/agent/subTaskSupport.js +425 -0
  42. package/dist/agent/toolDescriptions.d.ts +3 -0
  43. package/dist/agent/toolDescriptions.d.ts.map +1 -0
  44. package/dist/agent/toolDescriptions.js +116 -0
  45. package/dist/api/client.d.ts +41 -0
  46. package/dist/api/client.d.ts.map +1 -1
  47. package/dist/api/client.js +173 -1
  48. package/dist/index.d.ts +5 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +5 -0
  51. package/dist/mcp/client.d.ts +104 -0
  52. package/dist/mcp/client.d.ts.map +1 -1
  53. package/dist/mcp/client.js +136 -2
  54. package/dist/mcp/httpClient.d.ts +17 -1
  55. package/dist/mcp/httpClient.d.ts.map +1 -1
  56. package/dist/mcp/httpClient.js +120 -19
  57. package/dist/mcp/manager.d.ts +77 -2
  58. package/dist/mcp/manager.d.ts.map +1 -1
  59. package/dist/mcp/manager.js +275 -9
  60. package/dist/mcp/server.d.ts +39 -0
  61. package/dist/mcp/server.d.ts.map +1 -0
  62. package/dist/mcp/server.js +189 -0
  63. package/dist/mcp/sseClient.d.ts +7 -1
  64. package/dist/mcp/sseClient.d.ts.map +1 -1
  65. package/dist/mcp/sseClient.js +45 -1
  66. package/dist/mcp/stats.d.ts +41 -0
  67. package/dist/mcp/stats.d.ts.map +1 -0
  68. package/dist/mcp/stats.js +108 -0
  69. package/dist/permissions/destructive.d.ts +2 -0
  70. package/dist/permissions/destructive.d.ts.map +1 -1
  71. package/dist/permissions/destructive.js +6 -2
  72. package/dist/permissions/destructiveTokens.d.ts +5 -0
  73. package/dist/permissions/destructiveTokens.d.ts.map +1 -1
  74. package/dist/permissions/destructiveTokens.js +9 -3
  75. package/dist/permissions/modePolicy.d.ts.map +1 -1
  76. package/dist/permissions/modePolicy.js +5 -2
  77. package/dist/permissions/rules.d.ts +4 -1
  78. package/dist/permissions/rules.d.ts.map +1 -1
  79. package/dist/permissions/rules.js +29 -0
  80. package/dist/plugins/data.d.ts +23 -0
  81. package/dist/plugins/data.d.ts.map +1 -0
  82. package/dist/plugins/data.js +141 -0
  83. package/dist/plugins/index.d.ts +14 -2
  84. package/dist/plugins/index.d.ts.map +1 -1
  85. package/dist/plugins/index.js +45 -4
  86. package/dist/plugins/installer.d.ts +83 -0
  87. package/dist/plugins/installer.d.ts.map +1 -1
  88. package/dist/plugins/installer.js +207 -1
  89. package/dist/types.d.ts +45 -1
  90. package/dist/types.d.ts.map +1 -1
  91. package/package.json +1 -1
@@ -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);
@@ -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":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAqBjF,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;IAMtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IALjD,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,UAAU,CAAuB;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAmB,MAAM,EAAE,eAAe;IAIlE,IAAI,KAAK,IAAI,iBAAiB,EAAE,CAE/B;IAEK,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAYxB,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;CAqC7B"}
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"}
@@ -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 consume = (msg) => {
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
- throw new Error(`MCP error ${msg.error.code}: ${msg.error.message}`);
117
- return msg.result ?? null;
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
- if (contentType.includes('text/event-stream')) {
120
- // Walk SSE frames; return the JSON-RPC message matching our request id.
121
- for (const frame of text.split(/\r?\n\r?\n/)) {
122
- const dataLines = frame
123
- .split(/\r?\n/)
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
- continue;
180
+ return;
181
+ const data = dataLines.join('\n');
182
+ dataLines.length = 0;
128
183
  try {
129
- const msg = JSON.parse(dataLines.join('\n'));
130
- if (msg.id === id)
131
- return consume(msg);
184
+ handle(JSON.parse(data));
132
185
  }
133
- catch {
134
- /* skip non-JSON frames (comments, pings) */
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
- return consume(JSON.parse(text));
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;
@@ -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 connections. */
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;AAOvF,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CAC7C;AAGD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE;QACZ,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH;AAID,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAmC;IAEnD,OAAO,CAAC,QAAQ,CAA6B;IAE7C,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,OAAO,CAA6B;IAE5C,OAAO,CAAC,UAAU,CAA6B;IAE/C,+FAA+F;IAC/F,OAAO,CAAC,MAAM,CAAC,UAAU;IAMzB,oGAAoG;IACpG,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS;IA2B7C,mFAAmF;IAC7E,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BlD,sEAAsE;IACtE,iBAAiB,IAAI,aAAa,EAAE;IAkBpC;;;;;;;;;;;;;;;;OAgBG;IACG,QAAQ,CACZ,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,GACjC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAoCzB,uDAAuD;IACvD,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIhC,IAAI,WAAW,IAAI,MAAM,EAAE,CAE1B;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,OAAO,CAAC,MAAM,CAAC,WAAW;IAK1B,8FAA8F;IAC9F,SAAS,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;QAAC,KAAK,EAAE,iBAAiB,EAAE,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAY9J,uFAAuF;IACvF,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAMnC,+CAA+C;IAC/C,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM;IAMrE,OAAO,CAAC,MAAM,CAAC,cAAc;IAS7B,OAAO,CAAC,MAAM,CAAC,eAAe;IAK9B,iEAAiE;IACjE,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,KAAK,GAAE,SAAS,GAAG,MAAkB,GAAG,IAAI;IAOrH,+FAA+F;IAC/F,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IAc3D,aAAa,IAAI,IAAI;CAQtB"}
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/mcp/manager.ts"],"names":[],"mappings":"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"}