orquesta-cli 0.2.107 → 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.
Files changed (38) hide show
  1. package/dist/cli.js +27 -4
  2. package/dist/core/commands/help.js +4 -0
  3. package/dist/core/commands/index.js +7 -0
  4. package/dist/core/commands/lsp.d.ts +3 -0
  5. package/dist/core/commands/lsp.js +37 -0
  6. package/dist/core/commands/mcp.d.ts +3 -0
  7. package/dist/core/commands/mcp.js +46 -0
  8. package/dist/core/commands/undo.d.ts +4 -0
  9. package/dist/core/commands/undo.js +45 -0
  10. package/dist/core/config/auto-detect.js +4 -4
  11. package/dist/core/config/config-manager.d.ts +7 -1
  12. package/dist/core/config/config-manager.js +36 -0
  13. package/dist/core/config/providers.d.ts +3 -0
  14. package/dist/core/config/providers.js +87 -3
  15. package/dist/core/file-snapshot-store.d.ts +25 -0
  16. package/dist/core/file-snapshot-store.js +104 -0
  17. package/dist/core/lsp/index.d.ts +6 -0
  18. package/dist/core/lsp/index.js +75 -0
  19. package/dist/core/lsp/jsonrpc.d.ts +18 -0
  20. package/dist/core/lsp/jsonrpc.js +38 -0
  21. package/dist/core/lsp/lsp-client.d.ts +40 -0
  22. package/dist/core/lsp/lsp-client.js +201 -0
  23. package/dist/core/lsp/server-registry.d.ts +14 -0
  24. package/dist/core/lsp/server-registry.js +85 -0
  25. package/dist/eval/eval-runner.js +14 -0
  26. package/dist/orchestration/plan-executor.js +2 -0
  27. package/dist/tools/llm/simple/file-tools.js +8 -2
  28. package/dist/tools/mcp/index.d.ts +3 -0
  29. package/dist/tools/mcp/index.js +3 -0
  30. package/dist/tools/mcp/mcp-client.d.ts +16 -0
  31. package/dist/tools/mcp/mcp-client.js +180 -0
  32. package/dist/tools/mcp/mcp-config.d.ts +4 -0
  33. package/dist/tools/mcp/mcp-config.js +87 -0
  34. package/dist/types/index.d.ts +21 -0
  35. package/dist/ui/hooks/slashCommandProcessor.js +17 -0
  36. package/package.json +2 -1
  37. package/dist/core/git-auto-updater.d.ts +0 -58
  38. package/dist/core/git-auto-updater.js +0 -374
@@ -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
@@ -0,0 +1,87 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { configManager } from '../../core/config/config-manager.js';
4
+ import { logger } from '../../utils/logger.js';
5
+ function normalizeEntry(name, raw) {
6
+ if (!name || typeof name !== 'string' || !raw || typeof raw !== 'object')
7
+ return null;
8
+ const e = raw;
9
+ const enabled = e.enabled === false || e.disabled === true ? false : true;
10
+ const cfg = {
11
+ name,
12
+ enabled,
13
+ ...(e.command ? { command: e.command } : {}),
14
+ ...(Array.isArray(e.args) ? { args: e.args } : {}),
15
+ ...(e.env && typeof e.env === 'object' ? { env: e.env } : {}),
16
+ ...(e.cwd ? { cwd: e.cwd } : {}),
17
+ ...(e.url ? { url: e.url } : {}),
18
+ ...(e.headers && typeof e.headers === 'object' ? { headers: e.headers } : {}),
19
+ };
20
+ if (!cfg.command && !cfg.url) {
21
+ logger.warn('MCP server entry has neither command nor url; skipping', { name });
22
+ return null;
23
+ }
24
+ return cfg;
25
+ }
26
+ function parseFile(filePath) {
27
+ let raw;
28
+ try {
29
+ raw = fs.readFileSync(filePath, 'utf8');
30
+ }
31
+ catch {
32
+ return [];
33
+ }
34
+ let parsed;
35
+ try {
36
+ parsed = JSON.parse(raw);
37
+ }
38
+ catch (err) {
39
+ logger.warn('MCP config file is not valid JSON; skipping', {
40
+ filePath,
41
+ error: err instanceof Error ? err.message : String(err),
42
+ });
43
+ return [];
44
+ }
45
+ return normalizeSource(parsed);
46
+ }
47
+ export function normalizeSource(parsed) {
48
+ if (parsed == null || typeof parsed !== 'object')
49
+ return [];
50
+ if (Array.isArray(parsed)) {
51
+ return parsed
52
+ .map((entry) => {
53
+ const name = entry?.name;
54
+ return name ? normalizeEntry(name, entry) : null;
55
+ })
56
+ .filter((x) => x !== null);
57
+ }
58
+ const obj = parsed;
59
+ const map = obj.mcpServers && typeof obj.mcpServers === 'object' ? obj.mcpServers : obj;
60
+ const out = [];
61
+ for (const [name, entry] of Object.entries(map)) {
62
+ if (!entry || typeof entry !== 'object')
63
+ continue;
64
+ const cfg = normalizeEntry(name, entry);
65
+ if (cfg)
66
+ out.push(cfg);
67
+ }
68
+ return out;
69
+ }
70
+ export function loadMcpServerConfigs(cwd = process.cwd()) {
71
+ const byName = new Map();
72
+ const add = (servers) => {
73
+ for (const s of servers)
74
+ byName.set(s.name, s);
75
+ };
76
+ try {
77
+ const global = configManager.getConfig().mcpServers;
78
+ if (Array.isArray(global))
79
+ add(normalizeSource(global));
80
+ }
81
+ catch {
82
+ }
83
+ add(parseFile(path.join(cwd, '.mcp.json')));
84
+ add(parseFile(path.join(cwd, '.orquesta', 'mcp.json')));
85
+ return Array.from(byName.values()).filter((s) => s.enabled !== false);
86
+ }
87
+ //# sourceMappingURL=mcp-config.js.map
@@ -128,6 +128,22 @@ export interface OrchestrationConfig {
128
128
  worktreeIsolation?: boolean;
129
129
  singleAgentMode?: boolean;
130
130
  }
131
+ export interface McpServerConfig {
132
+ name: string;
133
+ command?: string;
134
+ args?: string[];
135
+ env?: Record<string, string>;
136
+ cwd?: string;
137
+ url?: string;
138
+ headers?: Record<string, string>;
139
+ enabled?: boolean;
140
+ }
141
+ export interface LspServerConfig {
142
+ id: string;
143
+ extensions: string[];
144
+ command: string;
145
+ args: string[];
146
+ }
131
147
  export interface OpenConfig {
132
148
  version: string;
133
149
  currentEndpoint?: string;
@@ -143,6 +159,11 @@ export interface OpenConfig {
143
159
  safeEnvVars?: string[];
144
160
  orquesta?: OrquestaConfig;
145
161
  orchestration?: OrchestrationConfig;
162
+ mcpServers?: McpServerConfig[];
163
+ lspServers?: LspServerConfig[];
164
+ lsp?: {
165
+ enabled?: boolean;
166
+ };
146
167
  }
147
168
  export interface TodoItem {
148
169
  id: string;
@@ -8,6 +8,14 @@ export const SLASH_COMMANDS = [
8
8
  name: '/clear',
9
9
  description: 'Clear conversation and TODOs',
10
10
  },
11
+ {
12
+ name: '/undo',
13
+ description: 'Revert the file changes from the last turn',
14
+ },
15
+ {
16
+ name: '/redo',
17
+ description: 'Re-apply the changes undone by /undo',
18
+ },
11
19
  {
12
20
  name: '/compact',
13
21
  description: 'Compact conversation to free up context',
@@ -49,6 +57,15 @@ export const SLASH_COMMANDS = [
49
57
  description: 'Enable/disable optional tools (Browser, Background)',
50
58
  aliases: ['/tools'],
51
59
  },
60
+ {
61
+ name: '/mcp',
62
+ description: 'List connected MCP servers and their tools',
63
+ },
64
+ {
65
+ name: '/lsp',
66
+ description: 'LSP diagnostics after edits: status | on | off',
67
+ argsHint: 'on | off',
68
+ },
52
69
  {
53
70
  name: '/usage',
54
71
  description: 'Show token usage statistics',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orquesta-cli",
3
- "version": "0.2.107",
3
+ "version": "0.2.111",
4
4
  "description": "Orquesta CLI - AI-powered coding assistant with team collaboration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -92,6 +92,7 @@
92
92
  },
93
93
  "dependencies": {
94
94
  "@anthropic-ai/sdk": "^0.32.1",
95
+ "@modelcontextprotocol/sdk": "^1.29.0",
95
96
  "axios": "^1.6.2",
96
97
  "chalk": "^4.1.2",
97
98
  "commander": "^11.1.0",
@@ -1,58 +0,0 @@
1
- export type UpdateStatus = {
2
- type: 'checking';
3
- } | {
4
- type: 'no_update';
5
- } | {
6
- type: 'first_run';
7
- step: number;
8
- totalSteps: number;
9
- message: string;
10
- } | {
11
- type: 'updating';
12
- step: number;
13
- totalSteps: number;
14
- message: string;
15
- } | {
16
- type: 'complete';
17
- needsRestart: boolean;
18
- message: string;
19
- } | {
20
- type: 'error';
21
- message: string;
22
- } | {
23
- type: 'skipped';
24
- reason: string;
25
- };
26
- export type StatusCallback = (status: UpdateStatus) => void;
27
- export declare class GitAutoUpdater {
28
- private repoUrl;
29
- private repoDir;
30
- private commitFile;
31
- private enabled;
32
- private onStatus;
33
- constructor(options?: {
34
- repoUrl?: string;
35
- enabled?: boolean;
36
- onStatus?: StatusCallback;
37
- });
38
- setStatusCallback(callback: StatusCallback): void;
39
- private emitStatus;
40
- run(options?: {
41
- noUpdate?: boolean;
42
- }): Promise<boolean>;
43
- private runBinaryMode;
44
- private getRemoteCommit;
45
- private getSavedCommit;
46
- private saveCommit;
47
- private updateBinary;
48
- private initialSetup;
49
- private pullAndUpdate;
50
- private freshClone;
51
- private rebuildAndLink;
52
- private copyBinariesInternal;
53
- private ensurePathConfigured;
54
- private unlinkNpm;
55
- private cleanupRepo;
56
- }
57
- export default GitAutoUpdater;
58
- //# sourceMappingURL=git-auto-updater.d.ts.map