@lightcone-ai/daemon 0.15.55 → 0.15.57
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/mcp-servers/mysql/core.js +17 -17
- package/mcp-servers/mysql/index.js +5 -5
- package/mcp-servers/mysql/manifest.json +1 -1
- package/package.json +1 -1
- package/src/agent-manager.js +44 -0
- package/src/chat-bridge.js +27 -2
|
@@ -111,9 +111,9 @@ export function createDataSourcePoolRegistry({ createPool, logger = () => {} } =
|
|
|
111
111
|
const pools = new Map();
|
|
112
112
|
|
|
113
113
|
return {
|
|
114
|
-
getOrCreate(
|
|
115
|
-
const id = String(
|
|
116
|
-
if (!id) throw new Error('
|
|
114
|
+
getOrCreate(dataSource, dbConfig) {
|
|
115
|
+
const id = String(dataSource ?? '').trim();
|
|
116
|
+
if (!id) throw new Error('data_source must be a non-empty string');
|
|
117
117
|
const nextFingerprint = fingerprintDbConfig(dbConfig);
|
|
118
118
|
const existing = pools.get(id);
|
|
119
119
|
|
|
@@ -123,7 +123,7 @@ export function createDataSourcePoolRegistry({ createPool, logger = () => {} } =
|
|
|
123
123
|
|
|
124
124
|
if (existing) {
|
|
125
125
|
Promise.resolve(existing.pool.end())
|
|
126
|
-
.catch((error) => logger(`[mysql-mcp] failed to close previous pool for
|
|
126
|
+
.catch((error) => logger(`[mysql-mcp] failed to close previous pool for data_source=${id}: ${error.message}`));
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
const pool = createPool(dbConfig);
|
|
@@ -172,10 +172,10 @@ export function createCredentialBrokerClient({
|
|
|
172
172
|
const normalizedWorkspaceId = String(workspaceId ?? '').trim();
|
|
173
173
|
const normalizedBundleId = String(bundleId ?? '').trim();
|
|
174
174
|
|
|
175
|
-
return async function fetchCredentialEnvVars(
|
|
176
|
-
const
|
|
177
|
-
if (!
|
|
178
|
-
throw new Error('
|
|
175
|
+
return async function fetchCredentialEnvVars(dataSource) {
|
|
176
|
+
const normalizedDataSource = String(dataSource ?? '').trim();
|
|
177
|
+
if (!normalizedDataSource) {
|
|
178
|
+
throw new Error('data_source is required');
|
|
179
179
|
}
|
|
180
180
|
if (!normalizedServerUrl || !normalizedMachineApiKey || !normalizedAgentId) {
|
|
181
181
|
throw new Error('mysql MCP missing SERVER_URL/MACHINE_API_KEY/AGENT_ID runtime context');
|
|
@@ -190,7 +190,7 @@ export function createCredentialBrokerClient({
|
|
|
190
190
|
body: JSON.stringify({
|
|
191
191
|
agent_id: normalizedAgentId,
|
|
192
192
|
workspace_id: normalizedWorkspaceId || null,
|
|
193
|
-
required_credentials: [
|
|
193
|
+
required_credentials: [normalizedDataSource],
|
|
194
194
|
bundle_id: normalizedBundleId || null,
|
|
195
195
|
}),
|
|
196
196
|
});
|
|
@@ -218,16 +218,16 @@ export function createCredentialBrokerClient({
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
export async function executeSql({
|
|
221
|
-
|
|
221
|
+
dataSource,
|
|
222
222
|
sql,
|
|
223
223
|
params = [],
|
|
224
224
|
limit,
|
|
225
225
|
fetchCredentialEnvVars,
|
|
226
226
|
poolRegistry,
|
|
227
227
|
} = {}) {
|
|
228
|
-
const
|
|
229
|
-
if (!
|
|
230
|
-
throw new Error('
|
|
228
|
+
const normalizedDataSource = String(dataSource ?? '').trim();
|
|
229
|
+
if (!normalizedDataSource) {
|
|
230
|
+
throw new Error('data_source is required');
|
|
231
231
|
}
|
|
232
232
|
if (!Array.isArray(params)) {
|
|
233
233
|
throw new Error('params must be an array');
|
|
@@ -240,9 +240,9 @@ export async function executeSql({
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
const prepared = prepareSql(sql, limit);
|
|
243
|
-
const credentialEnv = await fetchCredentialEnvVars(
|
|
243
|
+
const credentialEnv = await fetchCredentialEnvVars(normalizedDataSource);
|
|
244
244
|
const dbConfig = parseDbConfig(credentialEnv);
|
|
245
|
-
const pool = poolRegistry.getOrCreate(
|
|
245
|
+
const pool = poolRegistry.getOrCreate(normalizedDataSource, dbConfig);
|
|
246
246
|
|
|
247
247
|
const queryParams = [...params, ...prepared.limitParams];
|
|
248
248
|
const [result] = await pool.query(prepared.sql, queryParams);
|
|
@@ -252,7 +252,7 @@ export async function executeSql({
|
|
|
252
252
|
? result.slice(0, prepared.limit)
|
|
253
253
|
: result;
|
|
254
254
|
return {
|
|
255
|
-
|
|
255
|
+
data_source: normalizedDataSource,
|
|
256
256
|
row_count: rows.length,
|
|
257
257
|
limit: prepared.limit,
|
|
258
258
|
limit_applied: prepared.limitInjected || rows.length < result.length,
|
|
@@ -261,7 +261,7 @@ export async function executeSql({
|
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
return {
|
|
264
|
-
|
|
264
|
+
data_source: normalizedDataSource,
|
|
265
265
|
affected_rows: Number(result?.affectedRows ?? 0),
|
|
266
266
|
changed_rows: Number(result?.changedRows ?? 0),
|
|
267
267
|
insert_id: result?.insertId ?? null,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from './core.js';
|
|
12
12
|
|
|
13
13
|
const TOOL_SCHEMA = {
|
|
14
|
-
|
|
14
|
+
data_source: z.string().describe('工作区数据源名称,例如 sophon_cub。不要填写数据库密码或内部 ID。'),
|
|
15
15
|
sql: z.string().describe('要执行的 SQL。支持参数占位符 ?'),
|
|
16
16
|
params: z.array(z.any()).optional().describe('SQL 参数数组(按顺序对应 ? 占位符)'),
|
|
17
17
|
limit: z.number().int().positive().optional().describe('结果行数上限。默认 200,最大 1000'),
|
|
@@ -50,12 +50,12 @@ export function createMysqlMcpServer({
|
|
|
50
50
|
|
|
51
51
|
server.tool(
|
|
52
52
|
'query',
|
|
53
|
-
'执行通用 SQL
|
|
53
|
+
'执行通用 SQL。调用时通过数据源名称走 credential broker 动态获取 DB 凭证并路由连接。',
|
|
54
54
|
TOOL_SCHEMA,
|
|
55
|
-
async ({
|
|
55
|
+
async ({ data_source, sql, params = [], limit }) => {
|
|
56
56
|
try {
|
|
57
57
|
const payload = await executeSql({
|
|
58
|
-
|
|
58
|
+
dataSource: data_source,
|
|
59
59
|
sql,
|
|
60
60
|
params,
|
|
61
61
|
limit,
|
|
@@ -64,7 +64,7 @@ export function createMysqlMcpServer({
|
|
|
64
64
|
});
|
|
65
65
|
return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
|
|
66
66
|
} catch (error) {
|
|
67
|
-
logger(`[mysql-mcp] query failed for
|
|
67
|
+
logger(`[mysql-mcp] query failed for data_source=${data_source}: ${error.message}`);
|
|
68
68
|
return {
|
|
69
69
|
isError: true,
|
|
70
70
|
content: [{ type: 'text', text: `query failed: ${error.message}` }],
|
package/package.json
CHANGED
package/src/agent-manager.js
CHANGED
|
@@ -184,6 +184,7 @@ export class AgentManager {
|
|
|
184
184
|
case 'agent:start': return this._startAgent(msg, connection);
|
|
185
185
|
case 'agent:stop': return this._stopAgent(msg.agentId, msg.workspaceId, connection);
|
|
186
186
|
case 'agent:deliver': return this._deliverMessage(msg, connection);
|
|
187
|
+
case 'agent:reprobe': return this._reprobeCapability(msg, connection);
|
|
187
188
|
case 'publish:job': return this._handlePublishJob(msg, connection);
|
|
188
189
|
case 'browser:start_login': return this._startBrowserLogin(msg, connection);
|
|
189
190
|
case 'browser:stop_login': return this._stopBrowserLogin(msg);
|
|
@@ -504,6 +505,49 @@ export class AgentManager {
|
|
|
504
505
|
});
|
|
505
506
|
}
|
|
506
507
|
|
|
508
|
+
async _reprobeCapability({ agentId, workspaceId }, connection) {
|
|
509
|
+
const normalizedAgentId = String(agentId ?? '').trim();
|
|
510
|
+
if (!normalizedAgentId) {
|
|
511
|
+
console.log('[AgentManager] agent:reprobe ignored: missing agentId');
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const normalizedWorkspaceId = String(workspaceId ?? '').trim();
|
|
515
|
+
const workspaceDir = this._workspaceDir(normalizedAgentId, normalizedWorkspaceId);
|
|
516
|
+
const chatBridgePath = new URL('./chat-bridge.js', import.meta.url).pathname;
|
|
517
|
+
|
|
518
|
+
this._emitCapabilityProbe(connection, {
|
|
519
|
+
agentId: normalizedAgentId,
|
|
520
|
+
workspaceId: normalizedWorkspaceId,
|
|
521
|
+
capabilityId: CHAT_CAPABILITY_ID,
|
|
522
|
+
state: 'unknown',
|
|
523
|
+
reason: 'reprobe_pending',
|
|
524
|
+
details: { stage: 'reprobe' },
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
const result = await probeChatBridgeCapability({
|
|
528
|
+
chatBridgePath,
|
|
529
|
+
cwd: workspaceDir,
|
|
530
|
+
timeoutMs: CAPABILITY_PROBE_TIMEOUT_MS,
|
|
531
|
+
env: {
|
|
532
|
+
SERVER_URL: this.serverUrl,
|
|
533
|
+
MACHINE_API_KEY: this.machineApiKey,
|
|
534
|
+
AGENT_ID: normalizedAgentId,
|
|
535
|
+
WORKSPACE_ID: normalizedWorkspaceId,
|
|
536
|
+
WORKSPACE_DIR: workspaceDir,
|
|
537
|
+
},
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
this._emitCapabilityProbe(connection, {
|
|
541
|
+
agentId: normalizedAgentId,
|
|
542
|
+
workspaceId: normalizedWorkspaceId,
|
|
543
|
+
capabilityId: CHAT_CAPABILITY_ID,
|
|
544
|
+
state: result.state,
|
|
545
|
+
reason: result.reason,
|
|
546
|
+
details: { ...(result.details ?? {}), stage: 'reprobe' },
|
|
547
|
+
latencyMs: result.latencyMs,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
507
551
|
_emitCapabilityProbe(connection, {
|
|
508
552
|
agentId,
|
|
509
553
|
workspaceId,
|
package/src/chat-bridge.js
CHANGED
|
@@ -188,6 +188,7 @@ const DEFAULT_TOOL_CLASSIFICATION = {
|
|
|
188
188
|
query_user_history: 'cacheable',
|
|
189
189
|
list_accounts: 'cacheable',
|
|
190
190
|
list_credentials: 'cacheable',
|
|
191
|
+
list_data_sources: 'cacheable',
|
|
191
192
|
get_data_locations: 'cacheable',
|
|
192
193
|
explain_concept: 'cacheable',
|
|
193
194
|
get_release_notes: 'cacheable',
|
|
@@ -296,6 +297,7 @@ function inferToolForApi(method, apiPath, body) {
|
|
|
296
297
|
if (method === 'POST' && cleanPath === '/credential-auth/request') return 'request_credential_auth';
|
|
297
298
|
if (method === 'POST' && cleanPath === '/content-library/submit') return 'submit_to_library';
|
|
298
299
|
if (method === 'POST' && cleanPath === '/api/data-sources') return 'register_data_source';
|
|
300
|
+
if (method === 'GET' && cleanPath === '/api/data-sources') return 'list_data_sources';
|
|
299
301
|
if (method === 'POST' && cleanPath === '/orchestrate/decision') return 'write_governance_decision';
|
|
300
302
|
if (method === 'POST' && cleanPath === '/orchestrate/correction') return 'write_governance_correction';
|
|
301
303
|
if (method === 'GET' && cleanPath === '/orchestrate/context') return 'get_orchestrate_context';
|
|
@@ -933,6 +935,29 @@ server.tool('list_server', 'List workspaces, agents, and humans on the server',
|
|
|
933
935
|
return { content: [{ type: 'text', text: `Workspaces:\n${workspaces}\n\nAgents:\n${agents}\n\nHumans:\n${humans}` }] };
|
|
934
936
|
});
|
|
935
937
|
|
|
938
|
+
// ── list_data_sources ─────────────────────────────────────────────────────────
|
|
939
|
+
server.tool('list_data_sources', 'List named data sources available in the current workspace. Returns names and status only; no internal IDs or secrets.', {
|
|
940
|
+
workspace_id: z.string().optional().describe('Target workspace id. Defaults to current workspace context if omitted.'),
|
|
941
|
+
}, async ({ workspace_id }) => {
|
|
942
|
+
const targetWorkspaceId = (workspace_id ?? currentWorkspaceId ?? WORKSPACE_ID ?? '').trim();
|
|
943
|
+
if (!targetWorkspaceId) {
|
|
944
|
+
return { isError: true, content: [{ type: 'text', text: 'workspace_id is required (no current workspace context).' }] };
|
|
945
|
+
}
|
|
946
|
+
const params = new URLSearchParams({ workspace_id: targetWorkspaceId });
|
|
947
|
+
const data = await api('GET', `/api/data-sources?${params}`);
|
|
948
|
+
const sources = Array.isArray(data.data_sources) ? data.data_sources : [];
|
|
949
|
+
if (sources.length === 0) {
|
|
950
|
+
return { content: [{ type: 'text', text: 'No data sources are configured for this workspace.' }] };
|
|
951
|
+
}
|
|
952
|
+
const text = sources.map((source) => {
|
|
953
|
+
const name = String(source.name ?? '未命名数据源').trim();
|
|
954
|
+
const type = String(source.source_type ?? 'unknown').trim();
|
|
955
|
+
const status = String(source.credential_status ?? 'unknown').trim();
|
|
956
|
+
return `- ${name} (${type}) - connection ${status}`;
|
|
957
|
+
}).join('\n');
|
|
958
|
+
return { content: [{ type: 'text', text }] };
|
|
959
|
+
});
|
|
960
|
+
|
|
936
961
|
// ── list_tasks ────────────────────────────────────────────────────────────────
|
|
937
962
|
server.tool('list_tasks', 'List tasks in a workspace', {
|
|
938
963
|
workspace: z.string().describe('Target: #workspace-name'),
|
|
@@ -1457,7 +1482,7 @@ server.tool('submit_to_library',
|
|
|
1457
1482
|
|
|
1458
1483
|
// ── register_data_source ───────────────────────────────────────────────────────
|
|
1459
1484
|
server.tool('register_data_source',
|
|
1460
|
-
'Register a workspace data source without binding credential yet. Returns a one-time secure auth URL (10-minute expiry) that should be sent to the user via IM.',
|
|
1485
|
+
'Register a named workspace data source without binding credential yet. Returns a one-time secure auth URL (10-minute expiry) that should be sent to the user via IM.',
|
|
1461
1486
|
{
|
|
1462
1487
|
workspace_id: z.string().optional().describe('Target workspace id. Defaults to current workspace context if omitted.'),
|
|
1463
1488
|
display_name: z.string().describe('Human-readable data source name.'),
|
|
@@ -1484,7 +1509,7 @@ server.tool('register_data_source',
|
|
|
1484
1509
|
text:
|
|
1485
1510
|
`Data source registered.\n` +
|
|
1486
1511
|
`workspace_id=${data.workspace_id ?? targetWorkspaceId}\n` +
|
|
1487
|
-
`
|
|
1512
|
+
`display_name=${display_name}\n` +
|
|
1488
1513
|
`source_type=${data.source_type ?? source_type}\n` +
|
|
1489
1514
|
`expires_at=${data.expires_at ?? 'unknown'}\n` +
|
|
1490
1515
|
`secure_auth_url=${data.secure_auth_url}\n\n` +
|