@lightcone-ai/daemon 0.9.79 → 0.10.1
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/index.js +13 -5
- package/mcp-servers/mysql/manifest.json +16 -0
- package/mcp-servers/official/company-fundamentals/index.js +34 -0
- package/mcp-servers/official/company-fundamentals/manifest.json +14 -0
- package/mcp-servers/official/compliance-check/index.js +49 -0
- package/mcp-servers/official/compliance-check/manifest.json +14 -0
- package/mcp-servers/official/industry-report/index.js +34 -0
- package/mcp-servers/official/industry-report/manifest.json +14 -0
- package/mcp-servers/official/market-data-query/index.js +34 -0
- package/mcp-servers/official/market-data-query/manifest.json +14 -0
- package/mcp-servers/official/portfolio-analysis/index.js +74 -0
- package/mcp-servers/official/portfolio-analysis/manifest.json +14 -0
- package/mcp-servers/official/portfolio-read/index.js +34 -0
- package/mcp-servers/official/portfolio-read/manifest.json +14 -0
- package/mcp-servers/official/research-fetch/index.js +35 -0
- package/mcp-servers/official/research-fetch/manifest.json +14 -0
- package/mcp-servers/official/risk-metrics/index.js +34 -0
- package/mcp-servers/official/risk-metrics/manifest.json +14 -0
- package/mcp-servers/official-common/fixtures.js +273 -0
- package/mcp-servers/official-common/server.js +34 -0
- package/mcp-servers/platform/manifest.json +15 -0
- package/mcp-servers/portfolio-analysis/core.js +592 -0
- package/mcp-servers/portfolio-analysis/index.js +45 -0
- package/mcp-servers/portfolio-analysis/package-lock.json +1139 -0
- package/mcp-servers/portfolio-analysis/package.json +10 -0
- package/mcp-servers/portfolio-read/core.js +330 -0
- package/mcp-servers/portfolio-read/index.js +127 -0
- package/mcp-servers/portfolio-read/package-lock.json +1243 -0
- package/mcp-servers/portfolio-read/package.json +11 -0
- package/mcp-servers/publisher/index.js +14 -14
- package/mcp-servers/publisher/manifest.json +16 -0
- package/package.json +4 -2
- package/src/_vendor/mcp/registry.js +327 -0
- package/src/agent-manager.js +761 -188
- package/src/chat-bridge.js +567 -92
- package/src/connection.js +1 -1
- package/src/drivers/claude.js +48 -45
- package/src/drivers/codex.js +110 -8
- package/src/drivers/kimi.js +80 -35
- package/src/governance-state.js +89 -0
- package/src/index.js +34 -16
- package/src/lease-window.js +8 -0
- package/src/mcp-config.js +52 -23
|
@@ -23,9 +23,9 @@ import { withProfileLock } from '../../src/profile-lock.js';
|
|
|
23
23
|
const SERVER_URL = process.env.SERVER_URL ?? '';
|
|
24
24
|
const MACHINE_API_KEY = process.env.MACHINE_API_KEY ?? '';
|
|
25
25
|
const AGENT_ID = process.env.AGENT_ID ?? '';
|
|
26
|
-
const
|
|
26
|
+
const WORKSPACE_ID = process.env.WORKSPACE_ID ?? '';
|
|
27
27
|
const WORKSPACE_DIR = process.env.WORKSPACE_DIR ?? process.cwd();
|
|
28
|
-
const
|
|
28
|
+
const WORKSPACE_ROOT_DIR = path.dirname(WORKSPACE_DIR);
|
|
29
29
|
|
|
30
30
|
// ── Platform registry ──────────────────────────────────────────────────────────
|
|
31
31
|
|
|
@@ -87,7 +87,7 @@ async function api(method, path, body) {
|
|
|
87
87
|
});
|
|
88
88
|
if (!res.ok) {
|
|
89
89
|
const text = await res.text();
|
|
90
|
-
throw new Error(`
|
|
90
|
+
throw new Error(`lightcone API ${method} ${path} failed (${res.status}): ${text}`);
|
|
91
91
|
}
|
|
92
92
|
return res.json();
|
|
93
93
|
}
|
|
@@ -139,15 +139,15 @@ function workspacePathFromMediaPath(filePath, approvalData) {
|
|
|
139
139
|
|
|
140
140
|
const normalized = filePath.replaceAll('\\', '/');
|
|
141
141
|
const virtualMatch = normalized.match(/^\/agent-workspace\/([^/]+)\/workspace\/(.+)$/);
|
|
142
|
-
if (virtualMatch) return {
|
|
142
|
+
if (virtualMatch) return { workspaceId: virtualMatch[1], relPath: virtualMatch[2] };
|
|
143
143
|
|
|
144
144
|
const workspaceSegmentMatch = normalized.match(/\/workspace\/((?:artifacts|notes|tmp)\/.+)$/);
|
|
145
145
|
if (workspaceSegmentMatch) {
|
|
146
|
-
return {
|
|
146
|
+
return { workspaceId: approvalData?.workspaceId ?? WORKSPACE_ID, relPath: workspaceSegmentMatch[1] };
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
if (!path.isAbsolute(filePath) && /^(artifacts|notes|tmp)\//.test(normalized)) {
|
|
150
|
-
return {
|
|
150
|
+
return { workspaceId: approvalData?.workspaceId ?? WORKSPACE_ID, relPath: normalized };
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
return null;
|
|
@@ -157,23 +157,23 @@ async function materializeWorkspaceMedia(filePath, approvalData) {
|
|
|
157
157
|
if (!filePath || existsSync(filePath)) return filePath;
|
|
158
158
|
|
|
159
159
|
const workspacePath = workspacePathFromMediaPath(filePath, approvalData);
|
|
160
|
-
if (!workspacePath?.
|
|
160
|
+
if (!workspacePath?.workspaceId || !workspacePath.relPath) return filePath;
|
|
161
161
|
|
|
162
|
-
const localPath = path.resolve(
|
|
163
|
-
const allowedRoots = ['artifacts', 'notes', 'tmp'].map(dir => path.join(
|
|
162
|
+
const localPath = path.resolve(WORKSPACE_ROOT_DIR, workspacePath.relPath);
|
|
163
|
+
const allowedRoots = ['artifacts', 'notes', 'tmp'].map(dir => path.join(WORKSPACE_ROOT_DIR, dir));
|
|
164
164
|
if (!allowedRoots.some(root => isInsideDir(localPath, root))) {
|
|
165
|
-
throw new Error(`workspace media path is outside allowed
|
|
165
|
+
throw new Error(`workspace media path is outside allowed workspace directories: ${filePath}`);
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
if (existsSync(localPath)) return localPath;
|
|
169
169
|
|
|
170
170
|
const data = await api(
|
|
171
171
|
'GET',
|
|
172
|
-
`/
|
|
172
|
+
`/workspace-memory?path=${encodeURIComponent(workspacePath.relPath)}&workspaceId=${encodeURIComponent(workspacePath.workspaceId)}`
|
|
173
173
|
);
|
|
174
174
|
mkdirSync(path.dirname(localPath), { recursive: true });
|
|
175
175
|
writeFileSync(localPath, decodeWorkspaceContent(data.content));
|
|
176
|
-
console.error(`[publisher] Materialized
|
|
176
|
+
console.error(`[publisher] Materialized workspace media ${workspacePath.relPath} -> ${localPath}`);
|
|
177
177
|
return localPath;
|
|
178
178
|
}
|
|
179
179
|
|
|
@@ -201,12 +201,12 @@ function validateLocalFile(filePath, { kind, required = false, allowedExts = []
|
|
|
201
201
|
const allowedRoots = [
|
|
202
202
|
realpathSync(WORKSPACE_DIR),
|
|
203
203
|
...['artifacts', 'notes', 'tmp']
|
|
204
|
-
.map(dir => path.join(
|
|
204
|
+
.map(dir => path.join(WORKSPACE_ROOT_DIR, dir))
|
|
205
205
|
.filter(existsSync)
|
|
206
206
|
.map(dir => realpathSync(dir)),
|
|
207
207
|
];
|
|
208
208
|
if (!allowedRoots.some(root => isInsideDir(realFile, root))) {
|
|
209
|
-
throw new Error(`${kind} file must be inside the agent workspace or
|
|
209
|
+
throw new Error(`${kind} file must be inside the agent workspace or workspace shared artifacts/notes/tmp directory: ${filePath}`);
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
const stat = statSync(realFile);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "publisher",
|
|
3
|
+
"name": "Publisher MCP Server",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"runtime": "node",
|
|
6
|
+
"entrypoint": "index.js",
|
|
7
|
+
"tool_declarations": [
|
|
8
|
+
{ "name": "get_platform_requirements", "classification": "cacheable" },
|
|
9
|
+
{ "name": "check_login_status", "classification": "mandatory" },
|
|
10
|
+
{ "name": "publish_content", "classification": "mandatory" }
|
|
11
|
+
],
|
|
12
|
+
"smoke_test": {
|
|
13
|
+
"tool": "get_platform_requirements",
|
|
14
|
+
"arguments": { "platform": "xhs", "content_type": "image_text" }
|
|
15
|
+
}
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightcone-ai/daemon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
15
|
"start": "node src/index.js",
|
|
16
|
-
"dev": "node --watch src/index.js"
|
|
16
|
+
"dev": "node --watch src/index.js",
|
|
17
|
+
"prepack": "node scripts/vendor-shared.js",
|
|
18
|
+
"postpack": "node scripts/unvendor-shared.js"
|
|
17
19
|
},
|
|
18
20
|
"dependencies": {
|
|
19
21
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const MANIFEST_FILE = 'manifest.json';
|
|
6
|
+
const ALLOWED_ROOT_RELATIVE_PATHS = Object.freeze([
|
|
7
|
+
'mcp-servers',
|
|
8
|
+
'daemon/mcp-servers',
|
|
9
|
+
]);
|
|
10
|
+
const VALID_TOOL_CLASSIFICATIONS = new Set(['mandatory', 'cacheable', 'local']);
|
|
11
|
+
|
|
12
|
+
let cachedRegistry = null;
|
|
13
|
+
|
|
14
|
+
function isPlainObject(value) {
|
|
15
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isPathInside(parentDir, targetPath) {
|
|
19
|
+
const relative = path.relative(parentDir, targetPath);
|
|
20
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeServerId(value, fallback = '') {
|
|
24
|
+
const normalized = String(value ?? fallback).trim().toLowerCase();
|
|
25
|
+
if (!normalized) return '';
|
|
26
|
+
if (!/^[a-z0-9][a-z0-9_-]*$/.test(normalized)) return '';
|
|
27
|
+
return normalized;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeToolDeclarations(manifest, serverId) {
|
|
31
|
+
const declarations = [];
|
|
32
|
+
|
|
33
|
+
const direct = Array.isArray(manifest.tool_declarations)
|
|
34
|
+
? manifest.tool_declarations
|
|
35
|
+
: Array.isArray(manifest.toolDeclarations)
|
|
36
|
+
? manifest.toolDeclarations
|
|
37
|
+
: [];
|
|
38
|
+
|
|
39
|
+
for (const item of direct) {
|
|
40
|
+
if (typeof item === 'string') {
|
|
41
|
+
const name = String(item).trim();
|
|
42
|
+
if (!name) continue;
|
|
43
|
+
declarations.push({ name, classification: null });
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (!isPlainObject(item)) continue;
|
|
47
|
+
const name = String(item.name ?? item.tool ?? '').trim();
|
|
48
|
+
if (!name) continue;
|
|
49
|
+
const classification = String(item.classification ?? item.tool_classification ?? '').trim().toLowerCase();
|
|
50
|
+
declarations.push({
|
|
51
|
+
name,
|
|
52
|
+
classification: VALID_TOOL_CLASSIFICATIONS.has(classification) ? classification : null,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (declarations.length > 0) {
|
|
57
|
+
return dedupeToolDeclarations(declarations);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const toolNames = Array.isArray(manifest.tool_names)
|
|
61
|
+
? manifest.tool_names
|
|
62
|
+
: Array.isArray(manifest.toolNames)
|
|
63
|
+
? manifest.toolNames
|
|
64
|
+
: [];
|
|
65
|
+
const declaredClassification = isPlainObject(manifest.tool_classification)
|
|
66
|
+
? manifest.tool_classification
|
|
67
|
+
: isPlainObject(manifest.toolClassification)
|
|
68
|
+
? manifest.toolClassification
|
|
69
|
+
: {};
|
|
70
|
+
|
|
71
|
+
for (const toolName of toolNames) {
|
|
72
|
+
const name = String(toolName ?? '').trim();
|
|
73
|
+
if (!name) continue;
|
|
74
|
+
const classification = String(declaredClassification[name] ?? '').trim().toLowerCase();
|
|
75
|
+
declarations.push({
|
|
76
|
+
name,
|
|
77
|
+
classification: VALID_TOOL_CLASSIFICATIONS.has(classification) ? classification : null,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (declarations.length === 0 && serverId === 'chat') {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return dedupeToolDeclarations(declarations);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function dedupeToolDeclarations(declarations) {
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
const output = [];
|
|
91
|
+
for (const declaration of declarations) {
|
|
92
|
+
const key = declaration.name;
|
|
93
|
+
if (seen.has(key)) continue;
|
|
94
|
+
seen.add(key);
|
|
95
|
+
output.push(declaration);
|
|
96
|
+
}
|
|
97
|
+
return output;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function normalizeSmokeTest(manifest) {
|
|
101
|
+
const raw = manifest.smoke_test ?? manifest.smokeTest;
|
|
102
|
+
if (!isPlainObject(raw)) return null;
|
|
103
|
+
const tool = String(raw.tool ?? raw.tool_name ?? '').trim();
|
|
104
|
+
if (!tool) return null;
|
|
105
|
+
const args = isPlainObject(raw.arguments) ? raw.arguments : {};
|
|
106
|
+
return { tool, arguments: args };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function collectManifestFiles(rootDir, maxDepth = 4) {
|
|
110
|
+
const files = [];
|
|
111
|
+
|
|
112
|
+
function walk(currentDir, depth) {
|
|
113
|
+
if (depth > maxDepth) return;
|
|
114
|
+
let entries = [];
|
|
115
|
+
try {
|
|
116
|
+
entries = readdirSync(currentDir, { withFileTypes: true });
|
|
117
|
+
} catch {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (const entry of entries) {
|
|
122
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
123
|
+
if (entry.isDirectory()) {
|
|
124
|
+
walk(fullPath, depth + 1);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (!entry.isFile() || entry.name !== MANIFEST_FILE) continue;
|
|
128
|
+
files.push(fullPath);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
walk(rootDir, 0);
|
|
133
|
+
return files;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function resolveAllowedRoots(repoRoot) {
|
|
137
|
+
return ALLOWED_ROOT_RELATIVE_PATHS
|
|
138
|
+
.map((relativePath) => path.resolve(repoRoot, relativePath))
|
|
139
|
+
.filter((absolutePath) => existsSync(absolutePath));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function normalizeManifestEntry({
|
|
143
|
+
rawManifest,
|
|
144
|
+
manifestPath,
|
|
145
|
+
repoRoot,
|
|
146
|
+
allowedRoots,
|
|
147
|
+
}) {
|
|
148
|
+
if (!isPlainObject(rawManifest)) {
|
|
149
|
+
throw new Error(`manifest root must be object: ${manifestPath}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const manifestDir = path.dirname(manifestPath);
|
|
153
|
+
const fallbackId = path.basename(manifestDir);
|
|
154
|
+
const id = normalizeServerId(rawManifest.id, fallbackId);
|
|
155
|
+
if (!id) {
|
|
156
|
+
throw new Error(`invalid server id in manifest: ${manifestPath}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const entrypointRaw = String(rawManifest.entrypoint ?? 'index.js').trim();
|
|
160
|
+
if (!entrypointRaw) {
|
|
161
|
+
throw new Error(`entrypoint is required: ${manifestPath}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const entrypointPath = path.resolve(manifestDir, entrypointRaw);
|
|
165
|
+
const insideAllowedBoundary = allowedRoots.some((root) => isPathInside(root, entrypointPath));
|
|
166
|
+
if (!insideAllowedBoundary) {
|
|
167
|
+
throw new Error(`entrypoint escapes allowed roots for '${id}': ${entrypointPath}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const entrypointExists = (() => {
|
|
171
|
+
try {
|
|
172
|
+
return statSync(entrypointPath).isFile();
|
|
173
|
+
} catch {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
})();
|
|
177
|
+
|
|
178
|
+
const toolDeclarations = normalizeToolDeclarations(rawManifest, id);
|
|
179
|
+
const toolClassification = {};
|
|
180
|
+
for (const declaration of toolDeclarations) {
|
|
181
|
+
if (!declaration.classification) continue;
|
|
182
|
+
toolClassification[declaration.name] = declaration.classification;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
id,
|
|
187
|
+
name: String(rawManifest.name ?? id).trim() || id,
|
|
188
|
+
version: String(rawManifest.version ?? '0.0.0').trim() || '0.0.0',
|
|
189
|
+
manifestPath,
|
|
190
|
+
manifestDir,
|
|
191
|
+
entrypointPath,
|
|
192
|
+
entrypointExists,
|
|
193
|
+
runtime: String(rawManifest.runtime ?? 'node').trim() || 'node',
|
|
194
|
+
toolDeclarations,
|
|
195
|
+
toolClassification,
|
|
196
|
+
smokeTest: normalizeSmokeTest(rawManifest),
|
|
197
|
+
rawManifest,
|
|
198
|
+
repoRoot,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function buildRegistry({ repoRoot }) {
|
|
203
|
+
const normalizedRepoRoot = path.resolve(repoRoot);
|
|
204
|
+
const allowedRoots = resolveAllowedRoots(normalizedRepoRoot);
|
|
205
|
+
const manifestFiles = allowedRoots.flatMap((root) => collectManifestFiles(root));
|
|
206
|
+
|
|
207
|
+
const errors = [];
|
|
208
|
+
const servers = new Map();
|
|
209
|
+
|
|
210
|
+
for (const manifestPath of manifestFiles) {
|
|
211
|
+
let rawManifest = null;
|
|
212
|
+
try {
|
|
213
|
+
rawManifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
214
|
+
} catch (error) {
|
|
215
|
+
errors.push({
|
|
216
|
+
manifestPath,
|
|
217
|
+
code: 'manifest_parse_failed',
|
|
218
|
+
message: error.message,
|
|
219
|
+
});
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let entry;
|
|
224
|
+
try {
|
|
225
|
+
entry = normalizeManifestEntry({
|
|
226
|
+
rawManifest,
|
|
227
|
+
manifestPath,
|
|
228
|
+
repoRoot: normalizedRepoRoot,
|
|
229
|
+
allowedRoots,
|
|
230
|
+
});
|
|
231
|
+
} catch (error) {
|
|
232
|
+
errors.push({
|
|
233
|
+
manifestPath,
|
|
234
|
+
code: 'manifest_invalid',
|
|
235
|
+
message: error.message,
|
|
236
|
+
});
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (servers.has(entry.id)) {
|
|
241
|
+
const previous = servers.get(entry.id);
|
|
242
|
+
errors.push({
|
|
243
|
+
manifestPath,
|
|
244
|
+
code: 'duplicate_server_id',
|
|
245
|
+
message: `duplicate server id '${entry.id}' (${previous.manifestPath} vs ${entry.manifestPath})`,
|
|
246
|
+
});
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
servers.set(entry.id, entry);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
repoRoot: normalizedRepoRoot,
|
|
255
|
+
allowedRoots,
|
|
256
|
+
manifestFiles,
|
|
257
|
+
servers,
|
|
258
|
+
errors,
|
|
259
|
+
loadedAt: new Date().toISOString(),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function defaultRepoRoot() {
|
|
264
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
265
|
+
return path.resolve(path.dirname(currentFile), '..', '..');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function loadMcpServerRegistry({
|
|
269
|
+
repoRoot = defaultRepoRoot(),
|
|
270
|
+
refresh = false,
|
|
271
|
+
strict = false,
|
|
272
|
+
} = {}) {
|
|
273
|
+
const normalizedRepoRoot = path.resolve(repoRoot);
|
|
274
|
+
|
|
275
|
+
if (!refresh && cachedRegistry && cachedRegistry.repoRoot === normalizedRepoRoot) {
|
|
276
|
+
if (strict && cachedRegistry.errors.length > 0) {
|
|
277
|
+
throw new Error(`mcp manifest registry has ${cachedRegistry.errors.length} error(s)`);
|
|
278
|
+
}
|
|
279
|
+
return cachedRegistry;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const registry = buildRegistry({ repoRoot: normalizedRepoRoot });
|
|
283
|
+
cachedRegistry = registry;
|
|
284
|
+
|
|
285
|
+
if (strict && registry.errors.length > 0) {
|
|
286
|
+
throw new Error(`mcp manifest registry has ${registry.errors.length} error(s)`);
|
|
287
|
+
}
|
|
288
|
+
return registry;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function getMcpServerManifest(serverId, options = {}) {
|
|
292
|
+
const id = normalizeServerId(serverId);
|
|
293
|
+
if (!id) return null;
|
|
294
|
+
const registry = loadMcpServerRegistry(options);
|
|
295
|
+
return registry.servers.get(id) ?? null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function resolveMcpServerEntrypoint(serverId, options = {}) {
|
|
299
|
+
const entry = getMcpServerManifest(serverId, options);
|
|
300
|
+
if (!entry) return null;
|
|
301
|
+
return entry.entrypointPath;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function getMcpToolClassificationMap(options = {}) {
|
|
305
|
+
const registry = loadMcpServerRegistry(options);
|
|
306
|
+
const classification = {};
|
|
307
|
+
|
|
308
|
+
for (const server of registry.servers.values()) {
|
|
309
|
+
for (const [toolName, toolClass] of Object.entries(server.toolClassification)) {
|
|
310
|
+
if (!VALID_TOOL_CLASSIFICATIONS.has(toolClass)) continue;
|
|
311
|
+
classification[toolName] = toolClass;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return classification;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function getMcpRegistryDiagnostics(options = {}) {
|
|
319
|
+
const registry = loadMcpServerRegistry(options);
|
|
320
|
+
return {
|
|
321
|
+
repoRoot: registry.repoRoot,
|
|
322
|
+
loadedAt: registry.loadedAt,
|
|
323
|
+
manifestCount: registry.manifestFiles.length,
|
|
324
|
+
serverCount: registry.servers.size,
|
|
325
|
+
errors: registry.errors,
|
|
326
|
+
};
|
|
327
|
+
}
|