orquesta-cli 0.2.108 → 0.2.111

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.
@@ -0,0 +1,201 @@
1
+ import { spawn } from 'child_process';
2
+ import * as path from 'path';
3
+ import { pathToFileURL, fileURLToPath } from 'url';
4
+ import { encodeMessage, MessageDecoder } from './jsonrpc.js';
5
+ import { logger } from '../../utils/logger.js';
6
+ const INIT_TIMEOUT_MS = Number(process.env['ORQUESTA_LSP_INIT_TIMEOUT_MS']) || 12000;
7
+ const DIAGNOSTICS_WAIT_MS = Number(process.env['ORQUESTA_LSP_DIAG_TIMEOUT_MS']) || 2500;
8
+ class LspClient {
9
+ def;
10
+ rootDir;
11
+ proc = null;
12
+ decoder = new MessageDecoder();
13
+ nextId = 1;
14
+ pendingRequests = new Map();
15
+ diagnostics = new Map();
16
+ diagWaiters = new Map();
17
+ openDocs = new Set();
18
+ initialized = false;
19
+ version = new Map();
20
+ constructor(def, rootDir) {
21
+ this.def = def;
22
+ this.rootDir = rootDir;
23
+ }
24
+ send(msg) {
25
+ if (!this.proc)
26
+ return;
27
+ this.proc.stdin.write(encodeMessage({ jsonrpc: '2.0', ...msg }));
28
+ }
29
+ request(method, params) {
30
+ const id = this.nextId++;
31
+ return new Promise((resolve, reject) => {
32
+ this.pendingRequests.set(id, { resolve: resolve, reject });
33
+ this.send({ id, method, params });
34
+ });
35
+ }
36
+ notify(method, params) {
37
+ this.send({ method, params });
38
+ }
39
+ handleMessage(msg) {
40
+ if (msg.id !== undefined && (msg.result !== undefined || msg.error !== undefined)) {
41
+ const pending = this.pendingRequests.get(msg.id);
42
+ if (pending) {
43
+ this.pendingRequests.delete(msg.id);
44
+ if (msg.error)
45
+ pending.reject(new Error(msg.error.message));
46
+ else
47
+ pending.resolve(msg.result);
48
+ }
49
+ return;
50
+ }
51
+ if (msg.method === 'textDocument/publishDiagnostics') {
52
+ const params = msg.params;
53
+ const diags = (params.diagnostics || []).map((d) => ({
54
+ severity: d.severity ?? 1,
55
+ line: d.range?.start?.line ?? 0,
56
+ character: d.range?.start?.character ?? 0,
57
+ message: d.message ?? '',
58
+ source: d.source,
59
+ }));
60
+ this.diagnostics.set(params.uri, diags);
61
+ const waiters = this.diagWaiters.get(params.uri);
62
+ if (waiters) {
63
+ this.diagWaiters.delete(params.uri);
64
+ for (const w of waiters)
65
+ w();
66
+ }
67
+ return;
68
+ }
69
+ if (msg.id !== undefined && msg.method) {
70
+ this.send({ id: msg.id, result: null });
71
+ }
72
+ }
73
+ async start() {
74
+ if (this.initialized)
75
+ return;
76
+ if (this.proc) {
77
+ await this.waitFor(() => this.initialized, INIT_TIMEOUT_MS);
78
+ return;
79
+ }
80
+ logger.flow('LSP starting server', { id: this.def.id, root: this.rootDir });
81
+ const proc = spawn(this.def.command, this.def.args, {
82
+ cwd: this.rootDir,
83
+ stdio: ['pipe', 'pipe', 'pipe'],
84
+ env: process.env,
85
+ });
86
+ this.proc = proc;
87
+ proc.stdout.on('data', (chunk) => {
88
+ for (const msg of this.decoder.push(chunk))
89
+ this.handleMessage(msg);
90
+ });
91
+ proc.stderr.on('data', () => {
92
+ });
93
+ proc.on('error', (err) => {
94
+ logger.warn('LSP server process error', { id: this.def.id, error: err.message });
95
+ this.teardown();
96
+ });
97
+ proc.on('exit', () => this.teardown());
98
+ await this.request('initialize', {
99
+ processId: process.pid,
100
+ rootUri: pathToFileURL(this.rootDir).toString(),
101
+ rootPath: this.rootDir,
102
+ capabilities: {
103
+ textDocument: {
104
+ synchronization: { didSave: true, dynamicRegistration: false },
105
+ publishDiagnostics: { relatedInformation: false },
106
+ },
107
+ },
108
+ workspaceFolders: [{ uri: pathToFileURL(this.rootDir).toString(), name: path.basename(this.rootDir) }],
109
+ });
110
+ this.notify('initialized', {});
111
+ this.initialized = true;
112
+ logger.flow('LSP server initialized', { id: this.def.id });
113
+ }
114
+ async getDiagnostics(absPath, text) {
115
+ await this.start();
116
+ const uri = pathToFileURL(absPath).toString();
117
+ this.diagnostics.delete(uri);
118
+ const arrived = this.onceDiagnostics(uri);
119
+ if (!this.openDocs.has(uri)) {
120
+ this.openDocs.add(uri);
121
+ this.version.set(uri, 1);
122
+ this.notify('textDocument/didOpen', {
123
+ textDocument: { uri, languageId: this.def.id, version: 1, text },
124
+ });
125
+ }
126
+ else {
127
+ const v = (this.version.get(uri) ?? 1) + 1;
128
+ this.version.set(uri, v);
129
+ this.notify('textDocument/didChange', {
130
+ textDocument: { uri, version: v },
131
+ contentChanges: [{ text }],
132
+ });
133
+ }
134
+ this.notify('textDocument/didSave', { textDocument: { uri }, text });
135
+ await Promise.race([arrived, this.delay(DIAGNOSTICS_WAIT_MS)]);
136
+ return this.diagnostics.get(uri) ?? [];
137
+ }
138
+ onceDiagnostics(uri) {
139
+ return new Promise((resolve) => {
140
+ const list = this.diagWaiters.get(uri) ?? [];
141
+ list.push(resolve);
142
+ this.diagWaiters.set(uri, list);
143
+ });
144
+ }
145
+ delay(ms) {
146
+ return new Promise((r) => {
147
+ const t = setTimeout(r, ms);
148
+ if (typeof t.unref === 'function')
149
+ t.unref();
150
+ });
151
+ }
152
+ async waitFor(cond, timeoutMs) {
153
+ const start = Date.now();
154
+ while (!cond() && Date.now() - start < timeoutMs) {
155
+ await this.delay(50);
156
+ }
157
+ }
158
+ teardown() {
159
+ this.initialized = false;
160
+ for (const [, p] of this.pendingRequests)
161
+ p.reject(new Error('LSP server exited'));
162
+ this.pendingRequests.clear();
163
+ this.proc = null;
164
+ }
165
+ async stop() {
166
+ if (!this.proc)
167
+ return;
168
+ try {
169
+ await Promise.race([this.request('shutdown', null), this.delay(1000)]);
170
+ this.notify('exit', null);
171
+ }
172
+ catch {
173
+ }
174
+ try {
175
+ this.proc.kill();
176
+ }
177
+ catch {
178
+ }
179
+ this.teardown();
180
+ }
181
+ }
182
+ const clients = new Map();
183
+ export function getOrCreateClient(def, rootDir) {
184
+ const key = `${def.id}::${rootDir}`;
185
+ let client = clients.get(key);
186
+ if (!client) {
187
+ client = new LspClient(def, rootDir);
188
+ clients.set(key, client);
189
+ }
190
+ return client;
191
+ }
192
+ export async function shutdownAllLspClients() {
193
+ const all = Array.from(clients.values());
194
+ clients.clear();
195
+ await Promise.all(all.map((c) => c.stop().catch(() => undefined)));
196
+ }
197
+ export function getActiveLspClientCount() {
198
+ return clients.size;
199
+ }
200
+ export { fileURLToPath };
201
+ //# sourceMappingURL=lsp-client.js.map
@@ -0,0 +1,14 @@
1
+ export interface LspServerDef {
2
+ id: string;
3
+ extensions: string[];
4
+ command: string;
5
+ args: string[];
6
+ }
7
+ export declare function resolveServerBinary(command: string): string | null;
8
+ export declare function getServerDefs(): LspServerDef[];
9
+ export declare function findServerForFile(absPath: string): {
10
+ def: LspServerDef;
11
+ binary: string;
12
+ } | null;
13
+ export declare function clearResolveCache(): void;
14
+ //# sourceMappingURL=server-registry.d.ts.map
@@ -0,0 +1,85 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { configManager } from '../config/config-manager.js';
4
+ const DEFAULT_SERVERS = [
5
+ {
6
+ id: 'typescript',
7
+ extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts'],
8
+ command: 'typescript-language-server',
9
+ args: ['--stdio'],
10
+ },
11
+ { id: 'python', extensions: ['.py', '.pyi'], command: 'pyright-langserver', args: ['--stdio'] },
12
+ { id: 'rust', extensions: ['.rs'], command: 'rust-analyzer', args: [] },
13
+ { id: 'go', extensions: ['.go'], command: 'gopls', args: [] },
14
+ { id: 'ruby', extensions: ['.rb'], command: 'solargraph', args: ['stdio'] },
15
+ ];
16
+ function execCandidates(command) {
17
+ if (path.isAbsolute(command))
18
+ return [command];
19
+ if (process.platform === 'win32') {
20
+ const exts = (process.env['PATHEXT'] || '.COM;.EXE;.BAT;.CMD').split(';');
21
+ return [command, ...exts.map((e) => command + e.toLowerCase()), ...exts.map((e) => command + e)];
22
+ }
23
+ return [command];
24
+ }
25
+ const resolveCache = new Map();
26
+ export function resolveServerBinary(command) {
27
+ if (resolveCache.has(command))
28
+ return resolveCache.get(command);
29
+ const tryFile = (p) => {
30
+ try {
31
+ const st = fs.statSync(p);
32
+ if (st.isFile())
33
+ return p;
34
+ }
35
+ catch {
36
+ }
37
+ return null;
38
+ };
39
+ let found = null;
40
+ if (path.isAbsolute(command) || command.includes('/') || command.includes('\\')) {
41
+ for (const cand of execCandidates(command)) {
42
+ found = tryFile(path.resolve(cand));
43
+ if (found)
44
+ break;
45
+ }
46
+ }
47
+ else {
48
+ const pathDirs = (process.env['PATH'] || '').split(path.delimiter).filter(Boolean);
49
+ outer: for (const dir of pathDirs) {
50
+ for (const cand of execCandidates(command)) {
51
+ found = tryFile(path.join(dir, cand));
52
+ if (found)
53
+ break outer;
54
+ }
55
+ }
56
+ }
57
+ resolveCache.set(command, found);
58
+ return found;
59
+ }
60
+ export function getServerDefs() {
61
+ let custom = [];
62
+ try {
63
+ custom = configManager.getLspServers();
64
+ }
65
+ catch {
66
+ }
67
+ return [...custom, ...DEFAULT_SERVERS];
68
+ }
69
+ export function findServerForFile(absPath) {
70
+ const ext = path.extname(absPath).toLowerCase();
71
+ if (!ext)
72
+ return null;
73
+ for (const def of getServerDefs()) {
74
+ if (!def.extensions.includes(ext))
75
+ continue;
76
+ const binary = resolveServerBinary(def.command);
77
+ if (binary)
78
+ return { def: { ...def, command: binary }, binary };
79
+ }
80
+ return null;
81
+ }
82
+ export function clearResolveCache() {
83
+ resolveCache.clear();
84
+ }
85
+ //# sourceMappingURL=server-registry.js.map
@@ -40,6 +40,12 @@ export class EvalRunner {
40
40
  if (input.working_dir) {
41
41
  process.chdir(input.working_dir);
42
42
  }
43
+ try {
44
+ const { initializeMcpServers } = await import('../tools/mcp/index.js');
45
+ await initializeMcpServers(process.cwd());
46
+ }
47
+ catch {
48
+ }
43
49
  const startEvent = {
44
50
  event: 'start',
45
51
  timestamp: now(),
@@ -58,6 +64,14 @@ export class EvalRunner {
58
64
  this.emitError(error instanceof Error ? error.message : String(error));
59
65
  this.emitEnd(false);
60
66
  }
67
+ finally {
68
+ try {
69
+ const { shutdownAllLspClients } = await import('../core/lsp/index.js');
70
+ await shutdownAllLspClients();
71
+ }
72
+ catch {
73
+ }
74
+ }
61
75
  }
62
76
  async execute(prompt) {
63
77
  if (!this.llmClient) {
@@ -2,6 +2,7 @@ import { PlanningLLM } from '../agents/planner/index.js';
2
2
  import { sessionManager } from '../core/session/session-manager.js';
3
3
  import { CompactManager, contextTracker, buildCompactedMessages, } from '../core/compact/index.js';
4
4
  import { configManager } from '../core/config/config-manager.js';
5
+ import { fileSnapshotStore } from '../core/file-snapshot-store.js';
5
6
  import { setTodoWriteCallback, clearTodoCallbacks, } from '../tools/llm/simple/todo-tools.js';
6
7
  import { setGetTodosCallback, setFinalResponseCallback, setMarkTodosCompletedCallback, clearFinalResponseCallbacks, } from '../tools/llm/simple/final-response-tool.js';
7
8
  import { eventBus, Events } from '../core/event-bus.js';
@@ -54,6 +55,7 @@ export class PlanExecutor {
54
55
  });
55
56
  this.currentLLMClient = llmClient;
56
57
  setDocsSearchLLMClientGetter(() => this.currentLLMClient);
58
+ fileSnapshotStore.beginTurn(userMessage);
57
59
  isInterruptedRef.current = false;
58
60
  callbacks.setIsInterrupted(false);
59
61
  callbacks.setTodos([]);
@@ -3,6 +3,8 @@ import * as path from 'path';
3
3
  import { logger } from '../../../utils/logger.js';
4
4
  import { shouldIgnore } from '../../../core/ignore-filter.js';
5
5
  import { getCachedFile, setCachedFile, invalidateCache } from '../../../core/file-cache.js';
6
+ import { fileSnapshotStore } from '../../../core/file-snapshot-store.js';
7
+ import { getDiagnosticsForFile } from '../../../core/lsp/index.js';
6
8
  const EXCLUDED_DIRS = new Set([
7
9
  'node_modules',
8
10
  '.git',
@@ -193,10 +195,12 @@ async function _executeCreateFile(args) {
193
195
  }
194
196
  const dir = path.dirname(resolvedPath);
195
197
  await fs.mkdir(dir, { recursive: true });
198
+ fileSnapshotStore.record(resolvedPath, null);
196
199
  await fs.writeFile(resolvedPath, content, 'utf-8');
197
200
  invalidateCache(resolvedPath);
198
201
  const lines = content.split('\n').length;
199
202
  logger.toolSuccess('create_file', args, { file: displayPath, lines }, 0);
203
+ const diagnostics = await getDiagnosticsForFile(resolvedPath);
200
204
  return {
201
205
  success: true,
202
206
  result: JSON.stringify({
@@ -204,7 +208,7 @@ async function _executeCreateFile(args) {
204
208
  file: displayPath,
205
209
  lines: lines,
206
210
  message: `Created ${displayPath} (${lines} lines)`,
207
- }),
211
+ }) + diagnostics,
208
212
  };
209
213
  }
210
214
  catch (error) {
@@ -356,6 +360,7 @@ async function _executeEditFile(args) {
356
360
  error: `Modified content too large (${(newContentSize / 1024 / 1024).toFixed(2)}MB). Maximum: ${MAX_WRITE_SIZE / 1024 / 1024}MB`,
357
361
  };
358
362
  }
363
+ fileSnapshotStore.record(resolvedPath, originalContent);
359
364
  await fs.writeFile(resolvedPath, newContent, 'utf-8');
360
365
  invalidateCache(resolvedPath);
361
366
  const oldLinesArr = oldString.split('\n');
@@ -371,6 +376,7 @@ async function _executeEditFile(args) {
371
376
  if (newLinesArr.length > 5)
372
377
  diffPreview.push('+ ...');
373
378
  logger.toolSuccess('edit_file', args, { file: displayPath, replacements, oldLines: oldLinesArr.length, newLines: newLinesArr.length }, 0);
379
+ const diagnostics = await getDiagnosticsForFile(resolvedPath);
374
380
  return {
375
381
  success: true,
376
382
  result: JSON.stringify({
@@ -383,7 +389,7 @@ async function _executeEditFile(args) {
383
389
  ? `Replaced ${replacements} occurrence(s) in ${displayPath}`
384
390
  : `Updated ${displayPath}`,
385
391
  diff: diffPreview,
386
- }),
392
+ }) + diagnostics,
387
393
  };
388
394
  }
389
395
  catch (error) {
@@ -0,0 +1,3 @@
1
+ export { initializeMcpServers, disconnectMcpServers, getConnectedMcpServers, namespacedToolName, type McpInitResult, } from './mcp-client.js';
2
+ export { loadMcpServerConfigs, normalizeSource } from './mcp-config.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ export { initializeMcpServers, disconnectMcpServers, getConnectedMcpServers, namespacedToolName, } from './mcp-client.js';
2
+ export { loadMcpServerConfigs, normalizeSource } from './mcp-config.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,16 @@
1
+ export declare function namespacedToolName(server: string, tool: string): string;
2
+ export interface McpInitResult {
3
+ servers: number;
4
+ tools: number;
5
+ errors: Array<{
6
+ server: string;
7
+ error: string;
8
+ }>;
9
+ }
10
+ export declare function initializeMcpServers(cwd?: string): Promise<McpInitResult>;
11
+ export declare function getConnectedMcpServers(): Array<{
12
+ name: string;
13
+ tools: string[];
14
+ }>;
15
+ export declare function disconnectMcpServers(): Promise<void>;
16
+ //# sourceMappingURL=mcp-client.d.ts.map
@@ -0,0 +1,180 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
3
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
4
+ import { createRequire } from 'module';
5
+ import { toolRegistry } from '../registry.js';
6
+ import { loadMcpServerConfigs } from './mcp-config.js';
7
+ import { logger } from '../../utils/logger.js';
8
+ const pkg = createRequire(import.meta.url)('../../../package.json');
9
+ const CONNECT_TIMEOUT_MS = Number(process.env['ORQUESTA_MCP_TIMEOUT_MS']) || 15000;
10
+ const CALL_TIMEOUT_MS = Number(process.env['ORQUESTA_MCP_CALL_TIMEOUT_MS']) || 120000;
11
+ const connected = [];
12
+ export function namespacedToolName(server, tool) {
13
+ const clean = (s) => s.replace(/[^a-zA-Z0-9_-]/g, '_');
14
+ return `mcp__${clean(server)}__${clean(tool)}`;
15
+ }
16
+ function flattenToolContent(content) {
17
+ if (!Array.isArray(content)) {
18
+ return typeof content === 'string' ? content : JSON.stringify(content ?? '');
19
+ }
20
+ const parts = [];
21
+ for (const item of content) {
22
+ if (!item || typeof item !== 'object')
23
+ continue;
24
+ const block = item;
25
+ if (block.type === 'text' && typeof block.text === 'string') {
26
+ parts.push(block.text);
27
+ }
28
+ else if (block.type === 'image') {
29
+ parts.push(`[image${block.mimeType ? ` ${block.mimeType}` : ''} — ${block.data ? `${block.data.length} b64 bytes` : 'no data'}]`);
30
+ }
31
+ else if (block.type === 'resource' && block.resource) {
32
+ const r = block.resource;
33
+ parts.push(r.text ?? `[resource ${r.uri ?? ''}]`);
34
+ }
35
+ else {
36
+ parts.push(JSON.stringify(block));
37
+ }
38
+ }
39
+ return parts.join('\n').trim();
40
+ }
41
+ function toToolParameters(inputSchema) {
42
+ const schema = (inputSchema && typeof inputSchema === 'object' ? inputSchema : {});
43
+ return {
44
+ type: 'object',
45
+ properties: schema.properties && typeof schema.properties === 'object' ? schema.properties : {},
46
+ ...(Array.isArray(schema.required) ? { required: schema.required } : {}),
47
+ };
48
+ }
49
+ function wrapMcpTool(client, serverName, tool) {
50
+ const fqName = namespacedToolName(serverName, tool.name);
51
+ const description = `[MCP:${serverName}] ${tool.description || tool.name}`.slice(0, 1024);
52
+ const definition = {
53
+ type: 'function',
54
+ function: {
55
+ name: fqName,
56
+ description,
57
+ parameters: toToolParameters(tool.inputSchema),
58
+ },
59
+ };
60
+ return {
61
+ definition,
62
+ categories: ['llm-simple', 'mcp'],
63
+ async execute(args) {
64
+ try {
65
+ const res = await client.callTool({ name: tool.name, arguments: args ?? {} }, undefined, { timeout: CALL_TIMEOUT_MS });
66
+ const text = flattenToolContent(res.content);
67
+ if (res.isError) {
68
+ return { success: false, error: text || `MCP tool ${fqName} reported an error` };
69
+ }
70
+ return { success: true, result: text || '(no output)' };
71
+ }
72
+ catch (err) {
73
+ return {
74
+ success: false,
75
+ error: `MCP tool ${fqName} failed: ${err instanceof Error ? err.message : String(err)}`,
76
+ };
77
+ }
78
+ },
79
+ };
80
+ }
81
+ function buildTransport(cfg) {
82
+ if (cfg.url) {
83
+ const options = cfg.headers
84
+ ? { requestInit: { headers: cfg.headers } }
85
+ : undefined;
86
+ return new StreamableHTTPClientTransport(new URL(cfg.url), options);
87
+ }
88
+ if (cfg.command) {
89
+ return new StdioClientTransport({
90
+ command: cfg.command,
91
+ args: cfg.args ?? [],
92
+ env: { ...getInheritedEnv(), ...(cfg.env ?? {}) },
93
+ ...(cfg.cwd ? { cwd: cfg.cwd } : {}),
94
+ stderr: 'ignore',
95
+ });
96
+ }
97
+ throw new Error(`MCP server "${cfg.name}" has no command or url`);
98
+ }
99
+ function getInheritedEnv() {
100
+ const out = {};
101
+ for (const [k, v] of Object.entries(process.env)) {
102
+ if (typeof v === 'string')
103
+ out[k] = v;
104
+ }
105
+ return out;
106
+ }
107
+ function withTimeout(promise, ms, label) {
108
+ return new Promise((resolve, reject) => {
109
+ const t = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
110
+ if (typeof t.unref === 'function')
111
+ t.unref();
112
+ promise.then((v) => {
113
+ clearTimeout(t);
114
+ resolve(v);
115
+ }, (e) => {
116
+ clearTimeout(t);
117
+ reject(e);
118
+ });
119
+ });
120
+ }
121
+ async function connectOne(cfg) {
122
+ const client = new Client({ name: 'orquesta-cli', version: pkg.version || '0.0.0' }, { capabilities: {} });
123
+ let transport;
124
+ try {
125
+ transport = buildTransport(cfg);
126
+ }
127
+ catch (err) {
128
+ logger.warn('MCP transport build failed', { server: cfg.name, error: err instanceof Error ? err.message : String(err) });
129
+ return 0;
130
+ }
131
+ await withTimeout(client.connect(transport), CONNECT_TIMEOUT_MS, `MCP connect (${cfg.name})`);
132
+ const listed = await withTimeout(client.listTools(), CONNECT_TIMEOUT_MS, `MCP listTools (${cfg.name})`);
133
+ const tools = Array.isArray(listed.tools) ? listed.tools : [];
134
+ const toolNames = [];
135
+ for (const t of tools) {
136
+ if (!t?.name)
137
+ continue;
138
+ const wrapped = wrapMcpTool(client, cfg.name, t);
139
+ toolRegistry.register(wrapped);
140
+ toolNames.push(wrapped.definition.function.name);
141
+ }
142
+ connected.push({ name: cfg.name, client, toolNames });
143
+ logger.info('MCP server connected', { server: cfg.name, tools: toolNames.length });
144
+ return toolNames.length;
145
+ }
146
+ export async function initializeMcpServers(cwd = process.cwd()) {
147
+ const configs = loadMcpServerConfigs(cwd);
148
+ const result = { servers: 0, tools: 0, errors: [] };
149
+ if (configs.length === 0)
150
+ return result;
151
+ logger.enter('initializeMcpServers', { count: configs.length });
152
+ await Promise.all(configs.map(async (cfg) => {
153
+ try {
154
+ const n = await connectOne(cfg);
155
+ result.servers += 1;
156
+ result.tools += n;
157
+ }
158
+ catch (err) {
159
+ const error = err instanceof Error ? err.message : String(err);
160
+ logger.warn('MCP server failed to connect', { server: cfg.name, error });
161
+ result.errors.push({ server: cfg.name, error });
162
+ }
163
+ }));
164
+ logger.exit('initializeMcpServers', result);
165
+ return result;
166
+ }
167
+ export function getConnectedMcpServers() {
168
+ return connected.map((c) => ({ name: c.name, tools: [...c.toolNames] }));
169
+ }
170
+ export async function disconnectMcpServers() {
171
+ await Promise.all(connected.map(async (c) => {
172
+ try {
173
+ await c.client.close();
174
+ }
175
+ catch {
176
+ }
177
+ }));
178
+ connected.length = 0;
179
+ }
180
+ //# sourceMappingURL=mcp-client.js.map
@@ -0,0 +1,4 @@
1
+ import { McpServerConfig } from '../../types/index.js';
2
+ export declare function normalizeSource(parsed: unknown): McpServerConfig[];
3
+ export declare function loadMcpServerConfigs(cwd?: string): McpServerConfig[];
4
+ //# sourceMappingURL=mcp-config.d.ts.map