@kirosnn/mosaic 0.73.0 → 0.75.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/README.md +1 -1
- package/package.json +2 -2
- package/src/agent/prompts/systemPrompt.ts +1 -1
- package/src/agent/prompts/toolsPrompt.ts +75 -5
- package/src/agent/tools/explore.ts +4 -4
- package/src/agent/tools/exploreExecutor.ts +56 -4
- package/src/components/Main.tsx +1480 -1459
- package/src/components/main/ChatPage.tsx +858 -858
- package/src/index.tsx +32 -5
- package/src/mcp/approvalPolicy.ts +155 -147
- package/src/mcp/cli/doctor.ts +0 -3
- package/src/mcp/config.ts +234 -223
- package/src/mcp/processManager.ts +303 -298
- package/src/mcp/servers/navigation/browser.ts +151 -0
- package/src/mcp/servers/navigation/index.ts +23 -0
- package/src/mcp/servers/navigation/tools.ts +263 -0
- package/src/mcp/servers/navigation/types.ts +17 -0
- package/src/mcp/servers/navigation/utils.ts +20 -0
- package/src/mcp/toolCatalog.ts +181 -168
- package/src/mcp/types.ts +115 -94
- package/src/utils/history.ts +82 -82
- package/src/utils/markdown.tsx +60 -26
- package/src/utils/toolFormatting.ts +55 -6
- package/src/web/components/MessageItem.tsx +44 -13
- package/src/mcp/servers/navigation.ts +0 -854
package/src/mcp/config.ts
CHANGED
|
@@ -1,223 +1,234 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { homedir } from 'os';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
import type { McpServerConfig, McpGlobalConfig } from './types';
|
|
6
|
-
|
|
7
|
-
const MCP_DIR = join(homedir(), '.mosaic', 'mcp');
|
|
8
|
-
const CONFIG_FILE = join(MCP_DIR, 'config.json');
|
|
9
|
-
const SERVERS_DIR = join(MCP_DIR, 'servers');
|
|
10
|
-
|
|
11
|
-
function ensureDirs(): void {
|
|
12
|
-
if (!existsSync(MCP_DIR)) mkdirSync(MCP_DIR, { recursive: true });
|
|
13
|
-
if (!existsSync(SERVERS_DIR)) mkdirSync(SERVERS_DIR, { recursive: true });
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function getDefaultServerConfig(): Partial<McpServerConfig> {
|
|
17
|
-
return {
|
|
18
|
-
enabled: true,
|
|
19
|
-
transport: { type: 'stdio' },
|
|
20
|
-
args: [],
|
|
21
|
-
autostart: 'startup',
|
|
22
|
-
timeouts: {
|
|
23
|
-
initialize: 30000,
|
|
24
|
-
call: 60000,
|
|
25
|
-
},
|
|
26
|
-
limits: {
|
|
27
|
-
maxCallsPerMinute: 60,
|
|
28
|
-
maxPayloadBytes: 1024 * 1024,
|
|
29
|
-
},
|
|
30
|
-
logs: {
|
|
31
|
-
persist: false,
|
|
32
|
-
bufferSize: 200,
|
|
33
|
-
},
|
|
34
|
-
tools: {},
|
|
35
|
-
approval: 'always',
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function getNavigationServerConfig(): Partial<McpServerConfig> {
|
|
40
|
-
const serverPath = fileURLToPath(new URL('./servers/navigation.ts', import.meta.url));
|
|
41
|
-
return {
|
|
42
|
-
id: 'navigation',
|
|
43
|
-
name: 'Navigation',
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
configMap.set(server.id, server);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import type { McpServerConfig, McpGlobalConfig } from './types';
|
|
6
|
+
|
|
7
|
+
const MCP_DIR = join(homedir(), '.mosaic', 'mcp');
|
|
8
|
+
const CONFIG_FILE = join(MCP_DIR, 'config.json');
|
|
9
|
+
const SERVERS_DIR = join(MCP_DIR, 'servers');
|
|
10
|
+
|
|
11
|
+
function ensureDirs(): void {
|
|
12
|
+
if (!existsSync(MCP_DIR)) mkdirSync(MCP_DIR, { recursive: true });
|
|
13
|
+
if (!existsSync(SERVERS_DIR)) mkdirSync(SERVERS_DIR, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getDefaultServerConfig(): Partial<McpServerConfig> {
|
|
17
|
+
return {
|
|
18
|
+
enabled: true,
|
|
19
|
+
transport: { type: 'stdio' },
|
|
20
|
+
args: [],
|
|
21
|
+
autostart: 'startup',
|
|
22
|
+
timeouts: {
|
|
23
|
+
initialize: 30000,
|
|
24
|
+
call: 60000,
|
|
25
|
+
},
|
|
26
|
+
limits: {
|
|
27
|
+
maxCallsPerMinute: 60,
|
|
28
|
+
maxPayloadBytes: 1024 * 1024,
|
|
29
|
+
},
|
|
30
|
+
logs: {
|
|
31
|
+
persist: false,
|
|
32
|
+
bufferSize: 200,
|
|
33
|
+
},
|
|
34
|
+
tools: {},
|
|
35
|
+
approval: 'always',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getNavigationServerConfig(): Partial<McpServerConfig> {
|
|
40
|
+
const serverPath = fileURLToPath(new URL('./servers/navigation/index.ts', import.meta.url));
|
|
41
|
+
return {
|
|
42
|
+
id: 'navigation',
|
|
43
|
+
name: 'Navigation',
|
|
44
|
+
native: true,
|
|
45
|
+
command: 'npx',
|
|
46
|
+
args: ['tsx', serverPath],
|
|
47
|
+
enabled: true,
|
|
48
|
+
autostart: 'startup',
|
|
49
|
+
approval: 'never',
|
|
50
|
+
toolApproval: {
|
|
51
|
+
navigation_cookies: 'always',
|
|
52
|
+
navigation_headers: 'always',
|
|
53
|
+
},
|
|
54
|
+
timeouts: {
|
|
55
|
+
initialize: 30000,
|
|
56
|
+
call: 60000,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function validateServerConfig(config: Partial<McpServerConfig>): string[] {
|
|
62
|
+
const errors: string[] = [];
|
|
63
|
+
|
|
64
|
+
if (!config.id || typeof config.id !== 'string') {
|
|
65
|
+
errors.push('Server id is required and must be a string');
|
|
66
|
+
} else if (!/^[a-zA-Z0-9_-]+$/.test(config.id)) {
|
|
67
|
+
errors.push('Server id must contain only alphanumeric characters, hyphens, and underscores');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!config.name || typeof config.name !== 'string') {
|
|
71
|
+
errors.push('Server name is required and must be a string');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!config.command || typeof config.command !== 'string') {
|
|
75
|
+
errors.push('Server command is required and must be a string');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (config.args && !Array.isArray(config.args)) {
|
|
79
|
+
errors.push('Server args must be an array of strings');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (config.autostart && !['startup', 'on-demand', 'never'].includes(config.autostart)) {
|
|
83
|
+
errors.push('Server autostart must be "startup", "on-demand", or "never"');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (config.approval && !['always', 'once-per-tool', 'once-per-server', 'never'].includes(config.approval)) {
|
|
87
|
+
errors.push('Server approval must be "always", "once-per-tool", "once-per-server", or "never"');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return errors;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function mergeWithDefaults(partial: Partial<McpServerConfig>): McpServerConfig {
|
|
94
|
+
const defaults = getDefaultServerConfig();
|
|
95
|
+
return {
|
|
96
|
+
id: partial.id!,
|
|
97
|
+
name: partial.name || partial.id!,
|
|
98
|
+
enabled: partial.enabled ?? defaults.enabled!,
|
|
99
|
+
native: partial.native,
|
|
100
|
+
transport: partial.transport || defaults.transport!,
|
|
101
|
+
command: partial.command!,
|
|
102
|
+
args: partial.args || defaults.args!,
|
|
103
|
+
cwd: partial.cwd,
|
|
104
|
+
env: partial.env,
|
|
105
|
+
autostart: partial.autostart || defaults.autostart!,
|
|
106
|
+
timeouts: { ...defaults.timeouts!, ...partial.timeouts },
|
|
107
|
+
limits: { ...defaults.limits!, ...partial.limits },
|
|
108
|
+
logs: { ...defaults.logs!, ...partial.logs },
|
|
109
|
+
tools: { ...defaults.tools, ...partial.tools },
|
|
110
|
+
approval: partial.approval || defaults.approval!,
|
|
111
|
+
toolApproval: partial.toolApproval,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function loadGlobalConfigFile(): Partial<McpGlobalConfig> {
|
|
116
|
+
if (!existsSync(CONFIG_FILE)) return {};
|
|
117
|
+
try {
|
|
118
|
+
const content = readFileSync(CONFIG_FILE, 'utf-8');
|
|
119
|
+
return JSON.parse(content);
|
|
120
|
+
} catch {
|
|
121
|
+
return {};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function loadServerFiles(): Partial<McpServerConfig>[] {
|
|
126
|
+
if (!existsSync(SERVERS_DIR)) return [];
|
|
127
|
+
const files = readdirSync(SERVERS_DIR).filter(f => f.endsWith('.json'));
|
|
128
|
+
const configs: Partial<McpServerConfig>[] = [];
|
|
129
|
+
|
|
130
|
+
for (const file of files) {
|
|
131
|
+
try {
|
|
132
|
+
const content = readFileSync(join(SERVERS_DIR, file), 'utf-8');
|
|
133
|
+
const parsed = JSON.parse(content);
|
|
134
|
+
if (!parsed.id) {
|
|
135
|
+
parsed.id = file.replace(/\.json$/, '');
|
|
136
|
+
}
|
|
137
|
+
configs.push(parsed);
|
|
138
|
+
} catch {
|
|
139
|
+
// skip invalid files
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return configs;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function loadMcpConfig(): McpServerConfig[] {
|
|
147
|
+
ensureDirs();
|
|
148
|
+
|
|
149
|
+
const globalConfig = loadGlobalConfigFile();
|
|
150
|
+
const serverFiles = loadServerFiles();
|
|
151
|
+
|
|
152
|
+
const configMap = new Map<string, Partial<McpServerConfig>>();
|
|
153
|
+
|
|
154
|
+
if (globalConfig.servers) {
|
|
155
|
+
for (const server of globalConfig.servers) {
|
|
156
|
+
if (server.id) {
|
|
157
|
+
configMap.set(server.id, server);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const server of serverFiles) {
|
|
163
|
+
if (server.id) {
|
|
164
|
+
const existing = configMap.get(server.id);
|
|
165
|
+
if (existing) {
|
|
166
|
+
configMap.set(server.id, { ...existing, ...server });
|
|
167
|
+
} else {
|
|
168
|
+
configMap.set(server.id, server);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!configMap.has('navigation')) {
|
|
174
|
+
configMap.set('navigation', getNavigationServerConfig());
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const results: McpServerConfig[] = [];
|
|
178
|
+
for (const [, partial] of configMap) {
|
|
179
|
+
const errors = validateServerConfig(partial);
|
|
180
|
+
if (errors.length === 0) {
|
|
181
|
+
results.push(mergeWithDefaults(partial));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return results;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function saveServerConfig(config: Partial<McpServerConfig>): void {
|
|
189
|
+
ensureDirs();
|
|
190
|
+
const errors = validateServerConfig(config);
|
|
191
|
+
if (errors.length > 0) {
|
|
192
|
+
throw new Error(`Invalid server config: ${errors.join(', ')}`);
|
|
193
|
+
}
|
|
194
|
+
const filePath = join(SERVERS_DIR, `${config.id}.json`);
|
|
195
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2), 'utf-8');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function removeServerConfig(id: string): boolean {
|
|
199
|
+
const filePath = join(SERVERS_DIR, `${id}.json`);
|
|
200
|
+
if (existsSync(filePath)) {
|
|
201
|
+
unlinkSync(filePath);
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const globalConfig = loadGlobalConfigFile();
|
|
206
|
+
if (globalConfig.servers) {
|
|
207
|
+
const idx = globalConfig.servers.findIndex(s => s.id === id);
|
|
208
|
+
if (idx !== -1) {
|
|
209
|
+
globalConfig.servers.splice(idx, 1);
|
|
210
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(globalConfig, null, 2), 'utf-8');
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function updateServerConfig(id: string, updates: Partial<McpServerConfig>): McpServerConfig | null {
|
|
219
|
+
const configs = loadMcpConfig();
|
|
220
|
+
const existing = configs.find(c => c.id === id);
|
|
221
|
+
if (!existing) return null;
|
|
222
|
+
|
|
223
|
+
const updated = { ...existing, ...updates, id };
|
|
224
|
+
saveServerConfig(updated);
|
|
225
|
+
return updated;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function getMcpConfigDir(): string {
|
|
229
|
+
return MCP_DIR;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function getServersDir(): string {
|
|
233
|
+
return SERVERS_DIR;
|
|
234
|
+
}
|