@lightcone-ai/daemon 0.15.56 → 0.15.58

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.
@@ -4,6 +4,7 @@ export const DEFAULT_LIMIT = 200;
4
4
  export const MAX_LIMIT = 1000;
5
5
 
6
6
  const READ_QUERY_PREFIX = /^(SELECT|SHOW|DESCRIBE|DESC|EXPLAIN|WITH)\b/i;
7
+ const AUTO_LIMIT_PREFIX = /^(SELECT|WITH)\b/i;
7
8
  const REQUIRED_DB_KEYS = ['DB_HOST', 'DB_PORT', 'DB_USER', 'DB_PASSWORD', 'DB_NAME'];
8
9
 
9
10
  function trimTrailingSemicolons(sql) {
@@ -27,6 +28,10 @@ export function isReadQuery(sql) {
27
28
  return READ_QUERY_PREFIX.test(sql);
28
29
  }
29
30
 
31
+ export function supportsAutoLimit(sql) {
32
+ return AUTO_LIMIT_PREFIX.test(sql);
33
+ }
34
+
30
35
  export function hasLimitClause(sql) {
31
36
  return /\bLIMIT\b/i.test(sql);
32
37
  }
@@ -36,7 +41,7 @@ export function prepareSql(sql, limit) {
36
41
  if (!normalizedSql) throw new Error('sql must be a non-empty string');
37
42
 
38
43
  const effectiveLimit = normalizeLimit(limit);
39
- if (!isReadQuery(normalizedSql) || hasLimitClause(normalizedSql)) {
44
+ if (!supportsAutoLimit(normalizedSql) || hasLimitClause(normalizedSql)) {
40
45
  return {
41
46
  sql: normalizedSql,
42
47
  limit: effectiveLimit,
@@ -111,9 +116,9 @@ export function createDataSourcePoolRegistry({ createPool, logger = () => {} } =
111
116
  const pools = new Map();
112
117
 
113
118
  return {
114
- getOrCreate(dataSourceId, dbConfig) {
115
- const id = String(dataSourceId ?? '').trim();
116
- if (!id) throw new Error('data_source_id must be a non-empty string');
119
+ getOrCreate(dataSource, dbConfig) {
120
+ const id = String(dataSource ?? '').trim();
121
+ if (!id) throw new Error('data_source must be a non-empty string');
117
122
  const nextFingerprint = fingerprintDbConfig(dbConfig);
118
123
  const existing = pools.get(id);
119
124
 
@@ -123,7 +128,7 @@ export function createDataSourcePoolRegistry({ createPool, logger = () => {} } =
123
128
 
124
129
  if (existing) {
125
130
  Promise.resolve(existing.pool.end())
126
- .catch((error) => logger(`[mysql-mcp] failed to close previous pool for data_source_id=${id}: ${error.message}`));
131
+ .catch((error) => logger(`[mysql-mcp] failed to close previous pool for data_source=${id}: ${error.message}`));
127
132
  }
128
133
 
129
134
  const pool = createPool(dbConfig);
@@ -172,10 +177,10 @@ export function createCredentialBrokerClient({
172
177
  const normalizedWorkspaceId = String(workspaceId ?? '').trim();
173
178
  const normalizedBundleId = String(bundleId ?? '').trim();
174
179
 
175
- return async function fetchCredentialEnvVars(dataSourceId) {
176
- const normalizedDataSourceId = String(dataSourceId ?? '').trim();
177
- if (!normalizedDataSourceId) {
178
- throw new Error('data_source_id is required');
180
+ return async function fetchCredentialEnvVars(dataSource) {
181
+ const normalizedDataSource = String(dataSource ?? '').trim();
182
+ if (!normalizedDataSource) {
183
+ throw new Error('data_source is required');
179
184
  }
180
185
  if (!normalizedServerUrl || !normalizedMachineApiKey || !normalizedAgentId) {
181
186
  throw new Error('mysql MCP missing SERVER_URL/MACHINE_API_KEY/AGENT_ID runtime context');
@@ -190,7 +195,7 @@ export function createCredentialBrokerClient({
190
195
  body: JSON.stringify({
191
196
  agent_id: normalizedAgentId,
192
197
  workspace_id: normalizedWorkspaceId || null,
193
- required_credentials: [normalizedDataSourceId],
198
+ required_credentials: [normalizedDataSource],
194
199
  bundle_id: normalizedBundleId || null,
195
200
  }),
196
201
  });
@@ -218,16 +223,16 @@ export function createCredentialBrokerClient({
218
223
  }
219
224
 
220
225
  export async function executeSql({
221
- dataSourceId,
226
+ dataSource,
222
227
  sql,
223
228
  params = [],
224
229
  limit,
225
230
  fetchCredentialEnvVars,
226
231
  poolRegistry,
227
232
  } = {}) {
228
- const normalizedDataSourceId = String(dataSourceId ?? '').trim();
229
- if (!normalizedDataSourceId) {
230
- throw new Error('data_source_id is required');
233
+ const normalizedDataSource = String(dataSource ?? '').trim();
234
+ if (!normalizedDataSource) {
235
+ throw new Error('data_source is required');
231
236
  }
232
237
  if (!Array.isArray(params)) {
233
238
  throw new Error('params must be an array');
@@ -240,9 +245,9 @@ export async function executeSql({
240
245
  }
241
246
 
242
247
  const prepared = prepareSql(sql, limit);
243
- const credentialEnv = await fetchCredentialEnvVars(normalizedDataSourceId);
248
+ const credentialEnv = await fetchCredentialEnvVars(normalizedDataSource);
244
249
  const dbConfig = parseDbConfig(credentialEnv);
245
- const pool = poolRegistry.getOrCreate(normalizedDataSourceId, dbConfig);
250
+ const pool = poolRegistry.getOrCreate(normalizedDataSource, dbConfig);
246
251
 
247
252
  const queryParams = [...params, ...prepared.limitParams];
248
253
  const [result] = await pool.query(prepared.sql, queryParams);
@@ -252,7 +257,7 @@ export async function executeSql({
252
257
  ? result.slice(0, prepared.limit)
253
258
  : result;
254
259
  return {
255
- data_source_id: normalizedDataSourceId,
260
+ data_source: normalizedDataSource,
256
261
  row_count: rows.length,
257
262
  limit: prepared.limit,
258
263
  limit_applied: prepared.limitInjected || rows.length < result.length,
@@ -261,7 +266,7 @@ export async function executeSql({
261
266
  }
262
267
 
263
268
  return {
264
- data_source_id: normalizedDataSourceId,
269
+ data_source: normalizedDataSource,
265
270
  affected_rows: Number(result?.affectedRows ?? 0),
266
271
  changed_rows: Number(result?.changedRows ?? 0),
267
272
  insert_id: result?.insertId ?? null,
@@ -11,7 +11,7 @@ import {
11
11
  } from './core.js';
12
12
 
13
13
  const TOOL_SCHEMA = {
14
- data_source_id: z.string().describe('工作区数据源 ID。credential broker 会在运行时解析到绑定凭据。'),
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。调用时通过 data_source_id 走 credential broker 动态获取 DB 凭证并路由连接。',
53
+ '执行通用 SQL。调用时通过数据源名称走 credential broker 动态获取 DB 凭证并路由连接。',
54
54
  TOOL_SCHEMA,
55
- async ({ data_source_id, sql, params = [], limit }) => {
55
+ async ({ data_source, sql, params = [], limit }) => {
56
56
  try {
57
57
  const payload = await executeSql({
58
- dataSourceId: data_source_id,
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 data_source_id=${data_source_id}: ${error.message}`);
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}` }],
@@ -10,7 +10,7 @@
10
10
  "smoke_test": {
11
11
  "tool": "query",
12
12
  "arguments": {
13
- "data_source_id": "smoke-test-data-source",
13
+ "data_source": "smoke-test-data-source",
14
14
  "sql": "SELECT 1",
15
15
  "limit": 1
16
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightcone-ai/daemon",
3
- "version": "0.15.56",
3
+ "version": "0.15.58",
4
4
  "type": "module",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -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
- `data_source_id=${data.data_source_id}\n` +
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` +