@kirosnn/mosaic 0.73.0 → 0.74.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 +1 -1
- package/src/agent/prompts/systemPrompt.ts +1 -1
- package/src/agent/prompts/toolsPrompt.ts +64 -3
- package/src/components/Main.tsx +1480 -1459
- 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/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
|
@@ -1,148 +1,156 @@
|
|
|
1
|
-
import { createHash } from 'crypto';
|
|
2
|
-
import { requestApproval } from '../utils/approvalBridge';
|
|
3
|
-
import type { McpApprovalCacheEntry, McpRiskHint, McpServerConfig } from './types';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
request.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
const
|
|
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
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { requestApproval } from '../utils/approvalBridge';
|
|
3
|
+
import type { McpApprovalCacheEntry, McpRiskHint, McpServerConfig } from './types';
|
|
4
|
+
import { isNativeMcpServer } from './types';
|
|
5
|
+
|
|
6
|
+
const READ_KEYWORDS = ['read', 'get', 'list', 'search', 'find', 'show', 'describe', 'query', 'fetch', 'inspect', 'view', 'ls', 'cat'];
|
|
7
|
+
const WRITE_KEYWORDS = ['write', 'set', 'create', 'update', 'put', 'save', 'modify', 'patch', 'upsert', 'insert', 'append'];
|
|
8
|
+
const EXEC_KEYWORDS = ['exec', 'run', 'execute', 'spawn', 'shell', 'eval', 'invoke', 'call', 'process'];
|
|
9
|
+
const DELETE_KEYWORDS = ['delete', 'remove', 'destroy', 'drop', 'purge', 'clean', 'wipe', 'rm', 'unlink'];
|
|
10
|
+
const NET_KEYWORDS = ['http', 'request', 'download', 'upload', 'send', 'post', 'api', 'webhook', 'socket', 'connect'];
|
|
11
|
+
|
|
12
|
+
export class McpApprovalPolicy {
|
|
13
|
+
private cache = new Map<string, McpApprovalCacheEntry>();
|
|
14
|
+
|
|
15
|
+
inferRiskHint(toolName: string, _args: Record<string, unknown>): McpRiskHint {
|
|
16
|
+
const lower = toolName.toLowerCase();
|
|
17
|
+
|
|
18
|
+
for (const kw of DELETE_KEYWORDS) {
|
|
19
|
+
if (lower.includes(kw)) return 'execute';
|
|
20
|
+
}
|
|
21
|
+
for (const kw of EXEC_KEYWORDS) {
|
|
22
|
+
if (lower.includes(kw)) return 'execute';
|
|
23
|
+
}
|
|
24
|
+
for (const kw of WRITE_KEYWORDS) {
|
|
25
|
+
if (lower.includes(kw)) return 'write';
|
|
26
|
+
}
|
|
27
|
+
for (const kw of NET_KEYWORDS) {
|
|
28
|
+
if (lower.includes(kw)) return 'network';
|
|
29
|
+
}
|
|
30
|
+
for (const kw of READ_KEYWORDS) {
|
|
31
|
+
if (lower.includes(kw)) return 'read';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return 'unknown';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async requestMcpApproval(request: {
|
|
38
|
+
serverId: string;
|
|
39
|
+
serverName: string;
|
|
40
|
+
toolName: string;
|
|
41
|
+
canonicalId: string;
|
|
42
|
+
args: Record<string, unknown>;
|
|
43
|
+
approvalMode: McpServerConfig['approval'];
|
|
44
|
+
}): Promise<{ approved: boolean; customResponse?: string }> {
|
|
45
|
+
if (request.approvalMode === 'never') {
|
|
46
|
+
return { approved: true };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const riskHint = this.inferRiskHint(request.toolName, request.args);
|
|
50
|
+
|
|
51
|
+
if (this.checkCache(request.serverId, request.toolName, request.args)) {
|
|
52
|
+
return { approved: true };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const argsStr = formatArgs(request.args);
|
|
56
|
+
const payloadSize = JSON.stringify(request.args).length;
|
|
57
|
+
|
|
58
|
+
const isNative = isNativeMcpServer(request.serverId);
|
|
59
|
+
const preview = {
|
|
60
|
+
title: isNative ? request.toolName : `MCP: ${request.serverName} / ${request.toolName}`,
|
|
61
|
+
content: argsStr,
|
|
62
|
+
details: isNative
|
|
63
|
+
? [
|
|
64
|
+
`Tool: ${request.toolName}`,
|
|
65
|
+
`Risk: ${riskHint}`,
|
|
66
|
+
`Payload: ${payloadSize} bytes`,
|
|
67
|
+
]
|
|
68
|
+
: [
|
|
69
|
+
`Server: ${request.serverName} (${request.serverId})`,
|
|
70
|
+
`Tool: ${request.toolName}`,
|
|
71
|
+
`Risk: ${riskHint}`,
|
|
72
|
+
`Payload: ${payloadSize} bytes`,
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const mcpMeta = {
|
|
77
|
+
serverId: request.serverId,
|
|
78
|
+
serverName: request.serverName,
|
|
79
|
+
canonicalId: request.canonicalId,
|
|
80
|
+
riskHint,
|
|
81
|
+
payloadSize,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const result = await requestApproval(
|
|
85
|
+
request.canonicalId,
|
|
86
|
+
{ ...request.args, __mcpMeta: mcpMeta },
|
|
87
|
+
preview
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (result.approved && request.approvalMode !== 'always') {
|
|
91
|
+
this.addToCache(request.serverId, request.toolName, request.args, request.approvalMode);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private checkCache(serverId: string, toolName: string, args: Record<string, unknown>): boolean {
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
|
|
100
|
+
const serverKey = `server:${serverId}`;
|
|
101
|
+
const serverEntry = this.cache.get(serverKey);
|
|
102
|
+
if (serverEntry && serverEntry.expiresAt > now) return true;
|
|
103
|
+
|
|
104
|
+
const toolKey = `tool:${serverId}:${toolName}`;
|
|
105
|
+
const toolEntry = this.cache.get(toolKey);
|
|
106
|
+
if (toolEntry && toolEntry.expiresAt > now) return true;
|
|
107
|
+
|
|
108
|
+
const argsHash = hashArgs(args);
|
|
109
|
+
const argsKey = `toolArgs:${serverId}:${toolName}:${argsHash}`;
|
|
110
|
+
const argsEntry = this.cache.get(argsKey);
|
|
111
|
+
if (argsEntry && argsEntry.expiresAt > now) return true;
|
|
112
|
+
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private addToCache(serverId: string, toolName: string, _args: Record<string, unknown>, mode: McpServerConfig['approval']): void {
|
|
117
|
+
const ttl = 300000;
|
|
118
|
+
const expiresAt = Date.now() + ttl;
|
|
119
|
+
|
|
120
|
+
switch (mode) {
|
|
121
|
+
case 'once-per-server': {
|
|
122
|
+
const key = `server:${serverId}`;
|
|
123
|
+
this.cache.set(key, { scope: 'server', key, expiresAt });
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case 'once-per-tool': {
|
|
127
|
+
const key = `tool:${serverId}:${toolName}`;
|
|
128
|
+
this.cache.set(key, { scope: 'tool', key, expiresAt });
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
clearCache(): void {
|
|
135
|
+
this.cache.clear();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function hashArgs(args: Record<string, unknown>): string {
|
|
140
|
+
const str = JSON.stringify(args, Object.keys(args).sort());
|
|
141
|
+
return createHash('sha256').update(str).digest('hex').slice(0, 12);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function formatArgs(args: Record<string, unknown>): string {
|
|
145
|
+
const entries = Object.entries(args);
|
|
146
|
+
if (entries.length === 0) return '(no arguments)';
|
|
147
|
+
|
|
148
|
+
const lines: string[] = [];
|
|
149
|
+
for (const [key, value] of entries) {
|
|
150
|
+
const strValue = typeof value === 'string'
|
|
151
|
+
? (value.length > 100 ? value.slice(0, 100) + '...' : value)
|
|
152
|
+
: JSON.stringify(value);
|
|
153
|
+
lines.push(` ${key}: ${strValue}`);
|
|
154
|
+
}
|
|
155
|
+
return lines.join('\n');
|
|
148
156
|
}
|
package/src/mcp/cli/doctor.ts
CHANGED
|
@@ -23,10 +23,8 @@ export async function mcpDoctor(): Promise<void> {
|
|
|
23
23
|
for (const config of configs) {
|
|
24
24
|
console.log(`--- ${config.id} (${config.name}) ---`);
|
|
25
25
|
|
|
26
|
-
// 1. Config validation
|
|
27
26
|
console.log(' [config] OK');
|
|
28
27
|
|
|
29
|
-
// 2. Check command resolves
|
|
30
28
|
const commandExists = await checkCommand(config.command);
|
|
31
29
|
if (commandExists) {
|
|
32
30
|
console.log(` [command] "${config.command}" found`);
|
|
@@ -40,7 +38,6 @@ export async function mcpDoctor(): Promise<void> {
|
|
|
40
38
|
continue;
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
// 3. Try start + init + list tools
|
|
44
41
|
try {
|
|
45
42
|
const state = await manager.startServer(config);
|
|
46
43
|
|