@kirosnn/mosaic 0.0.91 → 0.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +2 -6
- package/package.json +55 -48
- package/src/agent/Agent.ts +353 -131
- package/src/agent/context.ts +4 -4
- package/src/agent/prompts/systemPrompt.ts +209 -70
- package/src/agent/prompts/toolsPrompt.ts +285 -138
- package/src/agent/provider/anthropic.ts +109 -105
- package/src/agent/provider/google.ts +111 -107
- package/src/agent/provider/mistral.ts +95 -95
- package/src/agent/provider/ollama.ts +73 -17
- package/src/agent/provider/openai.ts +146 -102
- package/src/agent/provider/rateLimit.ts +178 -0
- package/src/agent/provider/reasoning.ts +29 -0
- package/src/agent/provider/xai.ts +108 -104
- package/src/agent/tools/definitions.ts +15 -1
- package/src/agent/tools/executor.ts +717 -98
- package/src/agent/tools/exploreExecutor.ts +20 -22
- package/src/agent/tools/fetch.ts +58 -0
- package/src/agent/tools/glob.ts +20 -4
- package/src/agent/tools/grep.ts +64 -9
- package/src/agent/tools/plan.ts +27 -0
- package/src/agent/tools/question.ts +7 -1
- package/src/agent/tools/read.ts +2 -0
- package/src/agent/types.ts +15 -14
- package/src/components/App.tsx +50 -8
- package/src/components/CustomInput.tsx +461 -77
- package/src/components/Main.tsx +1459 -1112
- package/src/components/Setup.tsx +1 -1
- package/src/components/ShortcutsModal.tsx +11 -8
- package/src/components/Welcome.tsx +1 -1
- package/src/components/main/ApprovalPanel.tsx +4 -3
- package/src/components/main/ChatPage.tsx +858 -516
- package/src/components/main/HomePage.tsx +58 -39
- package/src/components/main/QuestionPanel.tsx +52 -7
- package/src/components/main/ThinkingIndicator.tsx +13 -2
- package/src/components/main/types.ts +11 -10
- package/src/index.tsx +53 -25
- package/src/mcp/approvalPolicy.ts +148 -0
- package/src/mcp/cli/add.ts +185 -0
- package/src/mcp/cli/doctor.ts +77 -0
- package/src/mcp/cli/index.ts +85 -0
- package/src/mcp/cli/list.ts +50 -0
- package/src/mcp/cli/logs.ts +24 -0
- package/src/mcp/cli/manage.ts +99 -0
- package/src/mcp/cli/show.ts +53 -0
- package/src/mcp/cli/tools.ts +77 -0
- package/src/mcp/config.ts +223 -0
- package/src/mcp/index.ts +80 -0
- package/src/mcp/processManager.ts +299 -0
- package/src/mcp/rateLimiter.ts +50 -0
- package/src/mcp/registry.ts +151 -0
- package/src/mcp/schemaConverter.ts +100 -0
- package/src/mcp/servers/navigation.ts +854 -0
- package/src/mcp/toolCatalog.ts +169 -0
- package/src/mcp/types.ts +95 -0
- package/src/utils/approvalBridge.ts +45 -12
- package/src/utils/approvalModeBridge.ts +17 -0
- package/src/utils/commands/approvals.ts +48 -0
- package/src/utils/commands/compact.ts +30 -0
- package/src/utils/commands/echo.ts +1 -1
- package/src/utils/commands/image.ts +109 -0
- package/src/utils/commands/index.ts +9 -7
- package/src/utils/commands/new.ts +15 -0
- package/src/utils/commands/types.ts +3 -0
- package/src/utils/config.ts +3 -1
- package/src/utils/diffRendering.tsx +13 -16
- package/src/utils/exploreBridge.ts +10 -0
- package/src/utils/history.ts +82 -40
- package/src/utils/imageBridge.ts +28 -0
- package/src/utils/images.ts +31 -0
- package/src/utils/markdown.tsx +163 -99
- package/src/utils/models.ts +31 -16
- package/src/utils/notificationBridge.ts +23 -0
- package/src/utils/questionBridge.ts +36 -1
- package/src/utils/tokenEstimator.ts +32 -0
- package/src/utils/toolFormatting.ts +428 -48
- package/src/web/app.tsx +65 -5
- package/src/web/assets/css/ChatPage.css +102 -30
- package/src/web/assets/css/MessageItem.css +26 -29
- package/src/web/assets/css/ThinkingIndicator.css +44 -6
- package/src/web/assets/css/ToolMessage.css +36 -14
- package/src/web/components/ChatPage.tsx +228 -105
- package/src/web/components/HomePage.tsx +3 -3
- package/src/web/components/MessageItem.tsx +80 -81
- package/src/web/components/QuestionPanel.tsx +72 -12
- package/src/web/components/Setup.tsx +1 -1
- package/src/web/components/Sidebar.tsx +1 -3
- package/src/web/components/ThinkingIndicator.tsx +41 -21
- package/src/web/router.ts +1 -1
- package/src/web/server.tsx +894 -662
- package/src/web/storage.ts +23 -1
- package/src/web/types.ts +7 -6
- package/src/utils/commands/redo.ts +0 -74
- package/src/utils/commands/sessions.ts +0 -129
- package/src/utils/commands/undo.ts +0 -75
- package/src/utils/undoRedo.ts +0 -429
- package/src/utils/undoRedoBridge.ts +0 -45
- package/src/utils/undoRedoDb.ts +0 -338
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { createInterface } from 'readline';
|
|
2
|
+
import { saveServerConfig, loadMcpConfig } from '../config';
|
|
3
|
+
import { MCP_REGISTRY, findRegistryEntry, type McpRegistryEntry } from '../registry';
|
|
4
|
+
import type { McpServerConfig } from '../types';
|
|
5
|
+
|
|
6
|
+
function ask(rl: ReturnType<typeof createInterface>, question: string, defaultValue?: string): Promise<string> {
|
|
7
|
+
const suffix = defaultValue ? ` [${defaultValue}]` : '';
|
|
8
|
+
return new Promise(resolve => {
|
|
9
|
+
rl.question(`${question}${suffix}: `, (answer: string) => {
|
|
10
|
+
resolve(answer.trim() || defaultValue || '');
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function mcpAdd(nameArg?: string): Promise<void> {
|
|
16
|
+
const existing = loadMcpConfig();
|
|
17
|
+
const existingIds = new Set(existing.map(c => c.id));
|
|
18
|
+
|
|
19
|
+
if (nameArg) {
|
|
20
|
+
const entry = findRegistryEntry(nameArg);
|
|
21
|
+
if (entry) {
|
|
22
|
+
if (existingIds.has(entry.id)) {
|
|
23
|
+
console.log(`Server "${entry.id}" is already configured.`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
await addFromRegistry(entry);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
console.log(`"${nameArg}" not found in the registry.\n`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
console.log('Available MCP servers:\n');
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < MCP_REGISTRY.length; i++) {
|
|
38
|
+
const entry = MCP_REGISTRY[i]!;
|
|
39
|
+
const installed = existingIds.has(entry.id) ? ' (installed)' : '';
|
|
40
|
+
console.log(` ${String(i + 1).padStart(2)}. ${entry.name.padEnd(22)} ${entry.description}${installed}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log(`\n 0. Custom server`);
|
|
44
|
+
|
|
45
|
+
const choice = await ask(rl, '\nChoose a server (number or name)', '');
|
|
46
|
+
rl.close();
|
|
47
|
+
|
|
48
|
+
if (!choice) return;
|
|
49
|
+
|
|
50
|
+
if (choice === '0' || choice.toLowerCase() === 'custom') {
|
|
51
|
+
await addCustom();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const num = parseInt(choice, 10);
|
|
56
|
+
let entry: McpRegistryEntry | null = null;
|
|
57
|
+
|
|
58
|
+
if (!isNaN(num) && num >= 1 && num <= MCP_REGISTRY.length) {
|
|
59
|
+
entry = MCP_REGISTRY[num - 1]!;
|
|
60
|
+
} else {
|
|
61
|
+
entry = findRegistryEntry(choice);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!entry) {
|
|
65
|
+
console.log(`"${choice}" not found.`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (existingIds.has(entry.id)) {
|
|
70
|
+
console.log(`Server "${entry.id}" is already configured.`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await addFromRegistry(entry);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if ((error as any)?.code === 'ERR_USE_AFTER_CLOSE') return;
|
|
77
|
+
throw error;
|
|
78
|
+
} finally {
|
|
79
|
+
try { rl.close(); } catch {}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function addFromRegistry(entry: McpRegistryEntry): Promise<void> {
|
|
84
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
console.log(`\nAdding ${entry.name}: ${entry.description}\n`);
|
|
88
|
+
|
|
89
|
+
const args = [...entry.args];
|
|
90
|
+
const env: Record<string, string> = {};
|
|
91
|
+
|
|
92
|
+
if (entry.prompts) {
|
|
93
|
+
for (const prompt of entry.prompts) {
|
|
94
|
+
const value = await ask(rl, prompt.question);
|
|
95
|
+
if (!value) {
|
|
96
|
+
console.log('Cancelled.');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (prompt.argIndex !== undefined) {
|
|
100
|
+
args[prompt.argIndex] = value;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (entry.env) {
|
|
106
|
+
for (const [key, meta] of Object.entries(entry.env)) {
|
|
107
|
+
const existing = process.env[key];
|
|
108
|
+
if (existing) {
|
|
109
|
+
console.log(` ${key}: using value from environment`);
|
|
110
|
+
env[key] = existing;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const suffix = meta.required ? '' : ' (optional, press Enter to skip)';
|
|
115
|
+
const value = await ask(rl, `${meta.description}${suffix}`);
|
|
116
|
+
|
|
117
|
+
if (!value && meta.required) {
|
|
118
|
+
console.log(`${key} is required. Cancelled.`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (value) {
|
|
123
|
+
env[key] = value;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const config: Partial<McpServerConfig> = {
|
|
129
|
+
id: entry.id,
|
|
130
|
+
name: entry.name,
|
|
131
|
+
command: entry.command,
|
|
132
|
+
args,
|
|
133
|
+
enabled: true,
|
|
134
|
+
autostart: 'startup',
|
|
135
|
+
approval: 'always',
|
|
136
|
+
...(Object.keys(env).length > 0 && { env }),
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
saveServerConfig(config);
|
|
140
|
+
console.log(`\n"${entry.id}" added successfully.`);
|
|
141
|
+
console.log(`Run "mosaic mcp doctor" to test connectivity.`);
|
|
142
|
+
} finally {
|
|
143
|
+
rl.close();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function addCustom(): Promise<void> {
|
|
148
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
console.log('\nCustom MCP server\n');
|
|
152
|
+
|
|
153
|
+
const id = await ask(rl, 'Server ID');
|
|
154
|
+
if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {
|
|
155
|
+
console.log('Invalid server ID.');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const name = await ask(rl, 'Display name', id);
|
|
160
|
+
const command = await ask(rl, 'Command (e.g., npx, node, python)');
|
|
161
|
+
if (!command) {
|
|
162
|
+
console.log('Command is required.');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const argsStr = await ask(rl, 'Arguments (space-separated)', '');
|
|
167
|
+
const args = argsStr ? argsStr.split(/\s+/) : [];
|
|
168
|
+
|
|
169
|
+
const config: Partial<McpServerConfig> = {
|
|
170
|
+
id,
|
|
171
|
+
name,
|
|
172
|
+
command,
|
|
173
|
+
args,
|
|
174
|
+
enabled: true,
|
|
175
|
+
autostart: 'startup',
|
|
176
|
+
approval: 'always',
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
saveServerConfig(config);
|
|
180
|
+
console.log(`\n"${id}" added successfully.`);
|
|
181
|
+
console.log(`Run "mosaic mcp doctor" to test connectivity.`);
|
|
182
|
+
} finally {
|
|
183
|
+
rl.close();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { loadMcpConfig } from '../config';
|
|
2
|
+
import { McpProcessManager } from '../processManager';
|
|
3
|
+
import { platform } from 'os';
|
|
4
|
+
import { exec } from 'child_process';
|
|
5
|
+
import { promisify } from 'util';
|
|
6
|
+
|
|
7
|
+
const execAsync = promisify(exec);
|
|
8
|
+
|
|
9
|
+
export async function mcpDoctor(): Promise<void> {
|
|
10
|
+
const configs = loadMcpConfig();
|
|
11
|
+
|
|
12
|
+
if (configs.length === 0) {
|
|
13
|
+
console.log('No MCP servers configured. Nothing to diagnose.');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
console.log(`Diagnosing ${configs.length} MCP server(s)...\n`);
|
|
18
|
+
|
|
19
|
+
const manager = new McpProcessManager();
|
|
20
|
+
let pass = 0;
|
|
21
|
+
let fail = 0;
|
|
22
|
+
|
|
23
|
+
for (const config of configs) {
|
|
24
|
+
console.log(`--- ${config.id} (${config.name}) ---`);
|
|
25
|
+
|
|
26
|
+
// 1. Config validation
|
|
27
|
+
console.log(' [config] OK');
|
|
28
|
+
|
|
29
|
+
// 2. Check command resolves
|
|
30
|
+
const commandExists = await checkCommand(config.command);
|
|
31
|
+
if (commandExists) {
|
|
32
|
+
console.log(` [command] "${config.command}" found`);
|
|
33
|
+
} else {
|
|
34
|
+
console.log(` [command] WARNING: "${config.command}" not found on PATH`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!config.enabled) {
|
|
38
|
+
console.log(' [status] DISABLED - skipping connectivity test');
|
|
39
|
+
console.log('');
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 3. Try start + init + list tools
|
|
44
|
+
try {
|
|
45
|
+
const state = await manager.startServer(config);
|
|
46
|
+
|
|
47
|
+
if (state.status === 'running') {
|
|
48
|
+
console.log(` [connect] OK (${state.initLatencyMs}ms)`);
|
|
49
|
+
console.log(` [tools] ${state.toolCount} tool(s) discovered`);
|
|
50
|
+
pass++;
|
|
51
|
+
} else {
|
|
52
|
+
console.log(` [connect] FAILED: ${state.lastError || 'unknown error'}`);
|
|
53
|
+
fail++;
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
57
|
+
console.log(` [connect] FAILED: ${message}`);
|
|
58
|
+
fail++;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log('');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await manager.shutdownAll();
|
|
65
|
+
|
|
66
|
+
console.log(`\nResults: ${pass} passed, ${fail} failed, ${configs.length - pass - fail} skipped`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function checkCommand(command: string): Promise<boolean> {
|
|
70
|
+
const which = platform() === 'win32' ? 'where' : 'which';
|
|
71
|
+
try {
|
|
72
|
+
await execAsync(`${which} ${command}`);
|
|
73
|
+
return true;
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export async function runMcpCli(args: string[]): Promise<void> {
|
|
2
|
+
const command = args[0] || 'help';
|
|
3
|
+
const rest = args.slice(1);
|
|
4
|
+
|
|
5
|
+
switch (command) {
|
|
6
|
+
case 'list':
|
|
7
|
+
case 'ls': {
|
|
8
|
+
const { mcpList } = await import('./list');
|
|
9
|
+
await mcpList();
|
|
10
|
+
break;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
case 'tools': {
|
|
14
|
+
const { mcpTools } = await import('./tools');
|
|
15
|
+
await mcpTools(rest[0]);
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
case 'doctor': {
|
|
20
|
+
const { mcpDoctor } = await import('./doctor');
|
|
21
|
+
await mcpDoctor();
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
case 'logs': {
|
|
26
|
+
const { mcpLogs } = await import('./logs');
|
|
27
|
+
await mcpLogs(rest[0]);
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
case 'show': {
|
|
32
|
+
const { mcpShow } = await import('./show');
|
|
33
|
+
await mcpShow(rest[0]);
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
case 'add': {
|
|
38
|
+
const { mcpAdd } = await import('./add');
|
|
39
|
+
await mcpAdd(rest[0]);
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
case 'remove':
|
|
44
|
+
case 'enable':
|
|
45
|
+
case 'disable':
|
|
46
|
+
case 'restart':
|
|
47
|
+
case 'start':
|
|
48
|
+
case 'stop':
|
|
49
|
+
case 'refresh': {
|
|
50
|
+
const { mcpManage } = await import('./manage');
|
|
51
|
+
await mcpManage(command, rest[0]);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
case 'help':
|
|
56
|
+
default:
|
|
57
|
+
showMcpHelp();
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function showMcpHelp(): void {
|
|
63
|
+
console.log(`
|
|
64
|
+
Mosaic MCP - Model Context Protocol client
|
|
65
|
+
|
|
66
|
+
Usage:
|
|
67
|
+
mosaic mcp <command> [options]
|
|
68
|
+
|
|
69
|
+
Commands:
|
|
70
|
+
list List configured MCP servers
|
|
71
|
+
tools [serverId] List available MCP tools
|
|
72
|
+
doctor Run diagnostics on MCP servers
|
|
73
|
+
logs <serverId> Show server logs
|
|
74
|
+
show <serverId> Show server config and state
|
|
75
|
+
add [name] Add an MCP server (by name or from the list)
|
|
76
|
+
remove <serverId> Remove a server
|
|
77
|
+
enable <serverId> Enable a server
|
|
78
|
+
disable <serverId> Disable a server
|
|
79
|
+
start <serverId> Start a server
|
|
80
|
+
stop <serverId> Stop a server
|
|
81
|
+
restart <serverId> Restart a server
|
|
82
|
+
refresh [serverId] Refresh tool catalog
|
|
83
|
+
help Show this help
|
|
84
|
+
`);
|
|
85
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { loadMcpConfig } from '../config';
|
|
2
|
+
import { getMcpManager } from '../index';
|
|
3
|
+
|
|
4
|
+
export async function mcpList(): Promise<void> {
|
|
5
|
+
const configs = loadMcpConfig();
|
|
6
|
+
|
|
7
|
+
if (configs.length === 0) {
|
|
8
|
+
console.log('No MCP servers configured.');
|
|
9
|
+
console.log('Use "mosaic mcp add" to add a server.');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const manager = getMcpManager();
|
|
14
|
+
|
|
15
|
+
const header = [
|
|
16
|
+
pad('ID', 20),
|
|
17
|
+
pad('Name', 20),
|
|
18
|
+
pad('Enabled', 8),
|
|
19
|
+
pad('Autostart', 10),
|
|
20
|
+
pad('Status', 10),
|
|
21
|
+
pad('Tools', 6),
|
|
22
|
+
'Last Error',
|
|
23
|
+
].join(' | ');
|
|
24
|
+
|
|
25
|
+
console.log(header);
|
|
26
|
+
console.log('-'.repeat(header.length));
|
|
27
|
+
|
|
28
|
+
for (const config of configs) {
|
|
29
|
+
const state = manager.getState(config.id);
|
|
30
|
+
const status = state?.status || 'stopped';
|
|
31
|
+
const toolCount = state?.toolCount ?? 0;
|
|
32
|
+
const lastError = state?.lastError || '';
|
|
33
|
+
|
|
34
|
+
const row = [
|
|
35
|
+
pad(config.id, 20),
|
|
36
|
+
pad(config.name, 20),
|
|
37
|
+
pad(config.enabled ? 'yes' : 'no', 8),
|
|
38
|
+
pad(config.autostart, 10),
|
|
39
|
+
pad(status, 10),
|
|
40
|
+
pad(String(toolCount), 6),
|
|
41
|
+
lastError.length > 40 ? lastError.slice(0, 40) + '...' : lastError,
|
|
42
|
+
].join(' | ');
|
|
43
|
+
|
|
44
|
+
console.log(row);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function pad(str: string, len: number): string {
|
|
49
|
+
return str.length >= len ? str.slice(0, len) : str + ' '.repeat(len - str.length);
|
|
50
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getMcpManager } from '../index';
|
|
2
|
+
|
|
3
|
+
export async function mcpLogs(serverId?: string): Promise<void> {
|
|
4
|
+
if (!serverId) {
|
|
5
|
+
console.log('Usage: mosaic mcp logs <serverId>');
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const manager = getMcpManager();
|
|
10
|
+
const logs = manager.getLogs(serverId);
|
|
11
|
+
|
|
12
|
+
if (logs.length === 0) {
|
|
13
|
+
console.log(`No logs for server "${serverId}".`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
console.log(`Logs for ${serverId} (${logs.length} entries):\n`);
|
|
18
|
+
|
|
19
|
+
for (const entry of logs) {
|
|
20
|
+
const time = new Date(entry.timestamp).toISOString().slice(11, 23);
|
|
21
|
+
const level = entry.level.toUpperCase().padEnd(5);
|
|
22
|
+
console.log(`[${time}] ${level} ${entry.message}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { loadMcpConfig, removeServerConfig, updateServerConfig } from '../config';
|
|
2
|
+
import { getMcpManager, initializeMcp } from '../index';
|
|
3
|
+
|
|
4
|
+
export async function mcpManage(command: string, serverId?: string): Promise<void> {
|
|
5
|
+
if (!serverId && command !== 'refresh') {
|
|
6
|
+
console.log(`Usage: mosaic mcp ${command} <serverId>`);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
switch (command) {
|
|
11
|
+
case 'remove': {
|
|
12
|
+
const removed = removeServerConfig(serverId!);
|
|
13
|
+
if (removed) {
|
|
14
|
+
console.log(`Server "${serverId}" removed.`);
|
|
15
|
+
} else {
|
|
16
|
+
console.log(`Server "${serverId}" not found.`);
|
|
17
|
+
}
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
case 'enable': {
|
|
22
|
+
const result = updateServerConfig(serverId!, { enabled: true });
|
|
23
|
+
if (result) {
|
|
24
|
+
console.log(`Server "${serverId}" enabled.`);
|
|
25
|
+
} else {
|
|
26
|
+
console.log(`Server "${serverId}" not found.`);
|
|
27
|
+
}
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
case 'disable': {
|
|
32
|
+
const result = updateServerConfig(serverId!, { enabled: false });
|
|
33
|
+
if (result) {
|
|
34
|
+
console.log(`Server "${serverId}" disabled.`);
|
|
35
|
+
} else {
|
|
36
|
+
console.log(`Server "${serverId}" not found.`);
|
|
37
|
+
}
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
case 'start': {
|
|
42
|
+
const configs = loadMcpConfig();
|
|
43
|
+
const config = configs.find(c => c.id === serverId);
|
|
44
|
+
if (!config) {
|
|
45
|
+
console.log(`Server "${serverId}" not found.`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const manager = getMcpManager();
|
|
50
|
+
console.log(`Starting server "${serverId}"...`);
|
|
51
|
+
const state = await manager.startServer(config);
|
|
52
|
+
console.log(`Status: ${state.status}`);
|
|
53
|
+
if (state.lastError) console.log(`Error: ${state.lastError}`);
|
|
54
|
+
if (state.toolCount > 0) console.log(`Tools: ${state.toolCount}`);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
case 'stop': {
|
|
59
|
+
const manager = getMcpManager();
|
|
60
|
+
console.log(`Stopping server "${serverId}"...`);
|
|
61
|
+
await manager.stopServer(serverId!);
|
|
62
|
+
console.log(`Server "${serverId}" stopped.`);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
case 'restart': {
|
|
67
|
+
const manager = getMcpManager();
|
|
68
|
+
console.log(`Restarting server "${serverId}"...`);
|
|
69
|
+
const state = await manager.restartServer(serverId!);
|
|
70
|
+
if (state) {
|
|
71
|
+
console.log(`Status: ${state.status}`);
|
|
72
|
+
if (state.toolCount > 0) console.log(`Tools: ${state.toolCount}`);
|
|
73
|
+
} else {
|
|
74
|
+
console.log(`Server "${serverId}" not found.`);
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
case 'refresh': {
|
|
80
|
+
await initializeMcp();
|
|
81
|
+
const { getMcpCatalog } = await import('../index');
|
|
82
|
+
try {
|
|
83
|
+
const catalog = getMcpCatalog();
|
|
84
|
+
catalog.refreshTools(serverId);
|
|
85
|
+
const tools = catalog.getMcpToolInfos();
|
|
86
|
+
const count = serverId
|
|
87
|
+
? tools.filter(t => t.serverId === serverId).length
|
|
88
|
+
: tools.length;
|
|
89
|
+
console.log(`Refreshed. ${count} MCP tool(s) available.`);
|
|
90
|
+
} catch {
|
|
91
|
+
console.log('MCP not initialized. No servers configured or all failed.');
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
default:
|
|
97
|
+
console.log(`Unknown command: ${command}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { loadMcpConfig } from '../config';
|
|
2
|
+
import { getMcpManager } from '../index';
|
|
3
|
+
|
|
4
|
+
export async function mcpShow(serverId?: string): Promise<void> {
|
|
5
|
+
if (!serverId) {
|
|
6
|
+
console.log('Usage: mosaic mcp show <serverId>');
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const configs = loadMcpConfig();
|
|
11
|
+
const config = configs.find(c => c.id === serverId);
|
|
12
|
+
|
|
13
|
+
if (!config) {
|
|
14
|
+
console.log(`Server "${serverId}" not found.`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
console.log(`Server: ${config.id}`);
|
|
19
|
+
console.log(` Name: ${config.name}`);
|
|
20
|
+
console.log(` Enabled: ${config.enabled}`);
|
|
21
|
+
console.log(` Command: ${config.command} ${config.args.join(' ')}`);
|
|
22
|
+
if (config.cwd) console.log(` CWD: ${config.cwd}`);
|
|
23
|
+
console.log(` Autostart: ${config.autostart}`);
|
|
24
|
+
console.log(` Approval: ${config.approval}`);
|
|
25
|
+
console.log(` Timeouts: init=${config.timeouts.initialize}ms, call=${config.timeouts.call}ms`);
|
|
26
|
+
console.log(` Limits: ${config.limits.maxCallsPerMinute} calls/min, ${config.limits.maxPayloadBytes} bytes max`);
|
|
27
|
+
console.log(` Logs: persist=${config.logs.persist}, buffer=${config.logs.bufferSize}`);
|
|
28
|
+
|
|
29
|
+
if (config.tools.allow) console.log(` Allow: ${config.tools.allow.join(', ')}`);
|
|
30
|
+
if (config.tools.deny) console.log(` Deny: ${config.tools.deny.join(', ')}`);
|
|
31
|
+
|
|
32
|
+
if (config.env) {
|
|
33
|
+
console.log(` Env:`);
|
|
34
|
+
for (const [key, value] of Object.entries(config.env)) {
|
|
35
|
+
console.log(` ${key}=${value.length > 30 ? value.slice(0, 30) + '...' : value}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const manager = getMcpManager();
|
|
40
|
+
const state = manager.getState(serverId);
|
|
41
|
+
|
|
42
|
+
if (state) {
|
|
43
|
+
console.log(`\nRuntime State:`);
|
|
44
|
+
console.log(` Status: ${state.status}`);
|
|
45
|
+
if (state.pid) console.log(` PID: ${state.pid}`);
|
|
46
|
+
if (state.initLatencyMs) console.log(` Init: ${state.initLatencyMs}ms`);
|
|
47
|
+
console.log(` Tools: ${state.toolCount}`);
|
|
48
|
+
if (state.lastError) console.log(` Last Error: ${state.lastError}`);
|
|
49
|
+
if (state.lastCallAt) console.log(` Last Call: ${new Date(state.lastCallAt).toISOString()}`);
|
|
50
|
+
} else {
|
|
51
|
+
console.log(`\nRuntime State: not started`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { loadMcpConfig } from '../config';
|
|
2
|
+
import { getMcpManager } from '../index';
|
|
3
|
+
|
|
4
|
+
export async function mcpTools(serverId?: string): Promise<void> {
|
|
5
|
+
const configs = loadMcpConfig();
|
|
6
|
+
|
|
7
|
+
if (configs.length === 0) {
|
|
8
|
+
console.log('No MCP servers configured.');
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const manager = getMcpManager();
|
|
13
|
+
const targetConfigs = serverId ? configs.filter(c => c.id === serverId) : configs;
|
|
14
|
+
|
|
15
|
+
if (serverId && targetConfigs.length === 0) {
|
|
16
|
+
console.log(`Server "${serverId}" not found.`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let totalTools = 0;
|
|
21
|
+
|
|
22
|
+
for (const config of targetConfigs) {
|
|
23
|
+
const state = manager.getState(config.id);
|
|
24
|
+
if (!state || state.status !== 'running') {
|
|
25
|
+
console.log(`\n[${config.id}] (${state?.status || 'not started'})`);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const tools = manager.listTools(config.id);
|
|
30
|
+
console.log(`\n[${config.id}] ${config.name} - ${tools.length} tools`);
|
|
31
|
+
|
|
32
|
+
if (tools.length === 0) {
|
|
33
|
+
console.log(' (no tools)');
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const deny = config.tools.deny || [];
|
|
38
|
+
const allow = config.tools.allow || [];
|
|
39
|
+
|
|
40
|
+
for (const t of tools) {
|
|
41
|
+
let status = 'exposed';
|
|
42
|
+
if (deny.length > 0 && deny.some(p => matchPattern(t.name, p))) {
|
|
43
|
+
status = 'denied';
|
|
44
|
+
} else if (allow.length > 0 && !allow.some(p => matchPattern(t.name, p))) {
|
|
45
|
+
status = 'denied';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const desc = t.description ? ` - ${t.description.slice(0, 60)}` : '';
|
|
49
|
+
console.log(` ${status === 'denied' ? 'x' : '+'} ${t.canonicalId}${desc}`);
|
|
50
|
+
|
|
51
|
+
if (t.inputSchema && typeof t.inputSchema === 'object') {
|
|
52
|
+
const props = (t.inputSchema as any).properties;
|
|
53
|
+
if (props && typeof props === 'object') {
|
|
54
|
+
const required = ((t.inputSchema as any).required || []) as string[];
|
|
55
|
+
for (const [key, schema] of Object.entries(props as Record<string, any>)) {
|
|
56
|
+
const type = schema.type || 'unknown';
|
|
57
|
+
const req = required.includes(key) ? 'required' : 'optional';
|
|
58
|
+
console.log(` - ${key} (${type}, ${req})`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
totalTools++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(`\nTotal: ${totalTools} tools`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function matchPattern(name: string, pattern: string): boolean {
|
|
71
|
+
if (pattern === '*') return true;
|
|
72
|
+
if (pattern === name) return true;
|
|
73
|
+
const regex = new RegExp(
|
|
74
|
+
'^' + pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.') + '$'
|
|
75
|
+
);
|
|
76
|
+
return regex.test(name);
|
|
77
|
+
}
|