@enactprotocol/shared 2.1.23 → 2.1.28
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/dist/execution/index.d.ts +1 -1
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/types.d.ts +15 -0
- package/dist/execution/types.d.ts.map +1 -1
- package/dist/execution/types.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/manifest/index.d.ts +2 -2
- package/dist/manifest/index.d.ts.map +1 -1
- package/dist/manifest/index.js +1 -1
- package/dist/manifest/index.js.map +1 -1
- package/dist/manifest/loader.d.ts +14 -4
- package/dist/manifest/loader.d.ts.map +1 -1
- package/dist/manifest/loader.js +12 -8
- package/dist/manifest/loader.js.map +1 -1
- package/dist/manifest/validator.d.ts +21 -4
- package/dist/manifest/validator.d.ts.map +1 -1
- package/dist/manifest/validator.js +72 -47
- package/dist/manifest/validator.js.map +1 -1
- package/dist/resolver.d.ts +29 -0
- package/dist/resolver.d.ts.map +1 -1
- package/dist/resolver.js +115 -17
- package/dist/resolver.js.map +1 -1
- package/package.json +2 -2
- package/src/execution/index.ts +1 -0
- package/src/execution/types.ts +16 -0
- package/src/index.ts +27 -0
- package/src/manifest/index.ts +3 -0
- package/src/manifest/loader.ts +29 -9
- package/src/manifest/validator.ts +102 -57
- package/src/mcp-registry.ts +337 -0
- package/src/resolver.ts +153 -18
- package/tests/manifest/validator.test.ts +77 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -83,68 +83,85 @@ const SEMVER_REGEX =
|
|
|
83
83
|
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
|
-
* Tool name regex - hierarchical path format
|
|
86
|
+
* Tool name regex - hierarchical path format (required for publishing)
|
|
87
87
|
*/
|
|
88
88
|
const TOOL_NAME_REGEX = /^[a-z0-9_-]+(?:\/[a-z0-9_-]+)+$/;
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Tool name regex - simple format (allowed for local tools)
|
|
92
|
+
* Allows both hierarchical (org/tool) and simple (my-tool) names
|
|
93
|
+
*/
|
|
94
|
+
const TOOL_NAME_REGEX_LOCAL = /^[a-z0-9_-]+(?:\/[a-z0-9_-]+)*$/;
|
|
95
|
+
|
|
90
96
|
/**
|
|
91
97
|
* Go duration regex (used for timeout)
|
|
92
98
|
*/
|
|
93
99
|
const GO_DURATION_REGEX = /^(\d+)(ns|us|µs|ms|s|m|h)$/;
|
|
94
100
|
|
|
95
101
|
/**
|
|
96
|
-
*
|
|
102
|
+
* Create a tool manifest schema with configurable name validation
|
|
97
103
|
*/
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
name
|
|
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
|
-
|
|
104
|
+
function createToolManifestSchema(allowSimpleNames: boolean) {
|
|
105
|
+
const nameRegex = allowSimpleNames ? TOOL_NAME_REGEX_LOCAL : TOOL_NAME_REGEX;
|
|
106
|
+
const nameMessage = allowSimpleNames
|
|
107
|
+
? "Tool name must contain only lowercase letters, numbers, hyphens, and underscores"
|
|
108
|
+
: "Tool name must be hierarchical path format (e.g., 'org/tool' or 'org/category/tool')";
|
|
109
|
+
|
|
110
|
+
return z
|
|
111
|
+
.object({
|
|
112
|
+
// Required fields
|
|
113
|
+
name: z.string().min(1, "Tool name is required").regex(nameRegex, nameMessage),
|
|
114
|
+
|
|
115
|
+
description: z
|
|
116
|
+
.string()
|
|
117
|
+
.min(1, "Description is required")
|
|
118
|
+
.max(500, "Description should be 500 characters or less"),
|
|
119
|
+
|
|
120
|
+
// Recommended fields
|
|
121
|
+
enact: z.string().optional(),
|
|
122
|
+
version: z
|
|
123
|
+
.string()
|
|
124
|
+
.regex(SEMVER_REGEX, "Version must be valid semver (e.g., '1.0.0')")
|
|
125
|
+
.optional(),
|
|
126
|
+
from: z.string().optional(),
|
|
127
|
+
command: z.string().optional(),
|
|
128
|
+
timeout: z
|
|
129
|
+
.string()
|
|
130
|
+
.regex(GO_DURATION_REGEX, "Timeout must be Go duration format (e.g., '30s', '5m', '1h')")
|
|
131
|
+
.optional(),
|
|
132
|
+
license: z.string().optional(),
|
|
133
|
+
tags: z.array(z.string()).optional(),
|
|
134
|
+
|
|
135
|
+
// Schema fields
|
|
136
|
+
inputSchema: JsonSchemaSchema.optional(),
|
|
137
|
+
outputSchema: JsonSchemaSchema.optional(),
|
|
138
|
+
|
|
139
|
+
// Environment variables
|
|
140
|
+
env: z.record(z.string(), EnvVariableSchema).optional(),
|
|
141
|
+
|
|
142
|
+
// Behavior & Resources
|
|
143
|
+
annotations: ToolAnnotationsSchema.optional(),
|
|
144
|
+
resources: ResourceRequirementsSchema.optional(),
|
|
145
|
+
|
|
146
|
+
// Documentation
|
|
147
|
+
doc: z.string().optional(),
|
|
148
|
+
authors: z.array(AuthorSchema).optional(),
|
|
149
|
+
|
|
150
|
+
// Testing
|
|
151
|
+
examples: z.array(ToolExampleSchema).optional(),
|
|
152
|
+
})
|
|
153
|
+
.passthrough(); // Allow x-* custom fields
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Complete tool manifest schema (strict - requires hierarchical names)
|
|
158
|
+
*/
|
|
159
|
+
const ToolManifestSchema = createToolManifestSchema(false);
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Local tool manifest schema (relaxed - allows simple names)
|
|
163
|
+
*/
|
|
164
|
+
const ToolManifestSchemaLocal = createToolManifestSchema(true);
|
|
148
165
|
|
|
149
166
|
// ==================== Validation Functions ====================
|
|
150
167
|
|
|
@@ -239,14 +256,31 @@ function generateWarnings(manifest: ToolManifest): ValidationWarning[] {
|
|
|
239
256
|
return warnings;
|
|
240
257
|
}
|
|
241
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Options for manifest validation
|
|
261
|
+
*/
|
|
262
|
+
export interface ValidateManifestOptions {
|
|
263
|
+
/**
|
|
264
|
+
* Allow simple tool names without hierarchy (e.g., "my-tool" instead of "org/my-tool").
|
|
265
|
+
* Use this for local tools that won't be published.
|
|
266
|
+
* @default false
|
|
267
|
+
*/
|
|
268
|
+
allowSimpleNames?: boolean;
|
|
269
|
+
}
|
|
270
|
+
|
|
242
271
|
/**
|
|
243
272
|
* Validate a tool manifest
|
|
244
273
|
*
|
|
245
274
|
* @param manifest - The manifest to validate (parsed but unvalidated)
|
|
275
|
+
* @param options - Validation options
|
|
246
276
|
* @returns ValidationResult with valid flag, errors, and warnings
|
|
247
277
|
*/
|
|
248
|
-
export function validateManifest(
|
|
249
|
-
|
|
278
|
+
export function validateManifest(
|
|
279
|
+
manifest: unknown,
|
|
280
|
+
options: ValidateManifestOptions = {}
|
|
281
|
+
): ValidationResult {
|
|
282
|
+
const schema = options.allowSimpleNames ? ToolManifestSchemaLocal : ToolManifestSchema;
|
|
283
|
+
const result = schema.safeParse(manifest);
|
|
250
284
|
|
|
251
285
|
if (!result.success) {
|
|
252
286
|
return {
|
|
@@ -270,11 +304,15 @@ export function validateManifest(manifest: unknown): ValidationResult {
|
|
|
270
304
|
* Throws if validation fails
|
|
271
305
|
*
|
|
272
306
|
* @param manifest - The manifest to validate
|
|
307
|
+
* @param options - Validation options
|
|
273
308
|
* @returns The validated ToolManifest
|
|
274
309
|
* @throws Error if validation fails
|
|
275
310
|
*/
|
|
276
|
-
export function validateManifestStrict(
|
|
277
|
-
|
|
311
|
+
export function validateManifestStrict(
|
|
312
|
+
manifest: unknown,
|
|
313
|
+
options: ValidateManifestOptions = {}
|
|
314
|
+
): ToolManifest {
|
|
315
|
+
const result = validateManifest(manifest, options);
|
|
278
316
|
|
|
279
317
|
if (!result.valid) {
|
|
280
318
|
const errorMessages = result.errors?.map((e) => `${e.path}: ${e.message}`).join(", ");
|
|
@@ -285,12 +323,19 @@ export function validateManifestStrict(manifest: unknown): ToolManifest {
|
|
|
285
323
|
}
|
|
286
324
|
|
|
287
325
|
/**
|
|
288
|
-
* Check if a string is a valid tool name
|
|
326
|
+
* Check if a string is a valid tool name (hierarchical format for publishing)
|
|
289
327
|
*/
|
|
290
328
|
export function isValidToolName(name: string): boolean {
|
|
291
329
|
return TOOL_NAME_REGEX.test(name);
|
|
292
330
|
}
|
|
293
331
|
|
|
332
|
+
/**
|
|
333
|
+
* Check if a string is a valid local tool name (allows simple names)
|
|
334
|
+
*/
|
|
335
|
+
export function isValidLocalToolName(name: string): boolean {
|
|
336
|
+
return TOOL_NAME_REGEX_LOCAL.test(name);
|
|
337
|
+
}
|
|
338
|
+
|
|
294
339
|
/**
|
|
295
340
|
* Check if a string is a valid semver version
|
|
296
341
|
*/
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tools registry management
|
|
3
|
+
*
|
|
4
|
+
* Manages mcp.json for tracking tools exposed to MCP clients:
|
|
5
|
+
* - Global only: ~/.enact/mcp.json
|
|
6
|
+
*
|
|
7
|
+
* Similar to tools.json but adds toolset management for organizing
|
|
8
|
+
* which tools are exposed to MCP clients.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
import { getCacheDir, getEnactHome } from "./paths";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Structure of mcp.json file
|
|
17
|
+
*/
|
|
18
|
+
export interface McpRegistry {
|
|
19
|
+
/** Map of tool name to installed version */
|
|
20
|
+
tools: Record<string, string>;
|
|
21
|
+
/** Named collections of tools */
|
|
22
|
+
toolsets: Record<string, string[]>;
|
|
23
|
+
/** Currently active toolset (null = expose all tools) */
|
|
24
|
+
activeToolset: string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Information about an MCP-exposed tool
|
|
29
|
+
*/
|
|
30
|
+
export interface McpToolInfo {
|
|
31
|
+
name: string;
|
|
32
|
+
version: string;
|
|
33
|
+
cachePath: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get the path to mcp.json
|
|
38
|
+
*/
|
|
39
|
+
export function getMcpJsonPath(): string {
|
|
40
|
+
return join(getEnactHome(), "mcp.json");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load mcp.json
|
|
45
|
+
* Returns empty registry if file doesn't exist
|
|
46
|
+
*/
|
|
47
|
+
export function loadMcpRegistry(): McpRegistry {
|
|
48
|
+
const registryPath = getMcpJsonPath();
|
|
49
|
+
|
|
50
|
+
if (!existsSync(registryPath)) {
|
|
51
|
+
return { tools: {}, toolsets: {}, activeToolset: null };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const content = readFileSync(registryPath, "utf-8");
|
|
56
|
+
const parsed = JSON.parse(content);
|
|
57
|
+
return {
|
|
58
|
+
tools: parsed.tools ?? {},
|
|
59
|
+
toolsets: parsed.toolsets ?? {},
|
|
60
|
+
activeToolset: parsed.activeToolset ?? null,
|
|
61
|
+
};
|
|
62
|
+
} catch {
|
|
63
|
+
// Return empty registry on parse error
|
|
64
|
+
return { tools: {}, toolsets: {}, activeToolset: null };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Save mcp.json
|
|
70
|
+
*/
|
|
71
|
+
export function saveMcpRegistry(registry: McpRegistry): void {
|
|
72
|
+
const registryPath = getMcpJsonPath();
|
|
73
|
+
|
|
74
|
+
// Ensure directory exists
|
|
75
|
+
const dir = dirname(registryPath);
|
|
76
|
+
if (!existsSync(dir)) {
|
|
77
|
+
mkdirSync(dir, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const content = JSON.stringify(registry, null, 2);
|
|
81
|
+
writeFileSync(registryPath, content, "utf-8");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Add a tool to the MCP registry
|
|
86
|
+
*/
|
|
87
|
+
export function addMcpTool(toolName: string, version: string): void {
|
|
88
|
+
const registry = loadMcpRegistry();
|
|
89
|
+
registry.tools[toolName] = version;
|
|
90
|
+
saveMcpRegistry(registry);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Remove a tool from the MCP registry
|
|
95
|
+
*/
|
|
96
|
+
export function removeMcpTool(toolName: string): boolean {
|
|
97
|
+
const registry = loadMcpRegistry();
|
|
98
|
+
|
|
99
|
+
if (!(toolName in registry.tools)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
delete registry.tools[toolName];
|
|
104
|
+
|
|
105
|
+
// Also remove from all toolsets
|
|
106
|
+
for (const toolsetName of Object.keys(registry.toolsets)) {
|
|
107
|
+
const toolset = registry.toolsets[toolsetName];
|
|
108
|
+
if (toolset) {
|
|
109
|
+
registry.toolsets[toolsetName] = toolset.filter((t) => t !== toolName);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
saveMcpRegistry(registry);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Check if a tool is in the MCP registry
|
|
119
|
+
*/
|
|
120
|
+
export function isMcpToolInstalled(toolName: string): boolean {
|
|
121
|
+
const registry = loadMcpRegistry();
|
|
122
|
+
return toolName in registry.tools;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get the installed version of an MCP tool
|
|
127
|
+
* Returns null if not installed
|
|
128
|
+
*/
|
|
129
|
+
export function getMcpToolVersion(toolName: string): string | null {
|
|
130
|
+
const registry = loadMcpRegistry();
|
|
131
|
+
return registry.tools[toolName] ?? null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get the cache path for an MCP tool
|
|
136
|
+
*/
|
|
137
|
+
function getMcpToolCachePath(toolName: string, version: string): string {
|
|
138
|
+
const cacheDir = getCacheDir();
|
|
139
|
+
const normalizedVersion = version.startsWith("v") ? version.slice(1) : version;
|
|
140
|
+
return join(cacheDir, toolName, `v${normalizedVersion}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* List all tools that should be exposed to MCP clients
|
|
145
|
+
* If activeToolset is set, only returns tools in that toolset
|
|
146
|
+
* Otherwise returns all tools
|
|
147
|
+
*/
|
|
148
|
+
export function listMcpTools(): McpToolInfo[] {
|
|
149
|
+
const registry = loadMcpRegistry();
|
|
150
|
+
const tools: McpToolInfo[] = [];
|
|
151
|
+
|
|
152
|
+
// Determine which tools to expose
|
|
153
|
+
let toolsToExpose: string[];
|
|
154
|
+
|
|
155
|
+
const activeToolsetTools = registry.activeToolset
|
|
156
|
+
? registry.toolsets[registry.activeToolset]
|
|
157
|
+
: undefined;
|
|
158
|
+
|
|
159
|
+
if (activeToolsetTools) {
|
|
160
|
+
// Filter to only tools in the active toolset that are also installed
|
|
161
|
+
toolsToExpose = activeToolsetTools.filter((name) => name in registry.tools);
|
|
162
|
+
} else {
|
|
163
|
+
// Expose all installed tools
|
|
164
|
+
toolsToExpose = Object.keys(registry.tools);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const name of toolsToExpose) {
|
|
168
|
+
const version = registry.tools[name];
|
|
169
|
+
if (version) {
|
|
170
|
+
tools.push({
|
|
171
|
+
name,
|
|
172
|
+
version,
|
|
173
|
+
cachePath: getMcpToolCachePath(name, version),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return tools;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get info for a specific MCP tool if it's exposed
|
|
183
|
+
*/
|
|
184
|
+
export function getMcpToolInfo(toolName: string): McpToolInfo | null {
|
|
185
|
+
const registry = loadMcpRegistry();
|
|
186
|
+
const version = registry.tools[toolName];
|
|
187
|
+
|
|
188
|
+
if (!version) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Check if tool is in active toolset (if one is set)
|
|
193
|
+
if (registry.activeToolset) {
|
|
194
|
+
const activeToolsetTools = registry.toolsets[registry.activeToolset];
|
|
195
|
+
if (activeToolsetTools && !activeToolsetTools.includes(toolName)) {
|
|
196
|
+
return null; // Tool is installed but not in active toolset
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const cachePath = getMcpToolCachePath(toolName, version);
|
|
201
|
+
|
|
202
|
+
// Verify cache exists
|
|
203
|
+
if (!existsSync(cachePath)) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return { name: toolName, version, cachePath };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Toolset management
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Create a new toolset
|
|
214
|
+
*/
|
|
215
|
+
export function createToolset(name: string, tools: string[] = []): void {
|
|
216
|
+
const registry = loadMcpRegistry();
|
|
217
|
+
registry.toolsets[name] = tools;
|
|
218
|
+
saveMcpRegistry(registry);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Delete a toolset
|
|
223
|
+
*/
|
|
224
|
+
export function deleteToolset(name: string): boolean {
|
|
225
|
+
const registry = loadMcpRegistry();
|
|
226
|
+
|
|
227
|
+
if (!(name in registry.toolsets)) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
delete registry.toolsets[name];
|
|
232
|
+
|
|
233
|
+
// Clear active toolset if it was the deleted one
|
|
234
|
+
if (registry.activeToolset === name) {
|
|
235
|
+
registry.activeToolset = null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
saveMcpRegistry(registry);
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Add a tool to a toolset
|
|
244
|
+
*/
|
|
245
|
+
export function addToolToToolset(toolsetName: string, toolName: string): boolean {
|
|
246
|
+
const registry = loadMcpRegistry();
|
|
247
|
+
|
|
248
|
+
const toolset = registry.toolsets[toolsetName];
|
|
249
|
+
if (!toolset) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!toolset.includes(toolName)) {
|
|
254
|
+
toolset.push(toolName);
|
|
255
|
+
saveMcpRegistry(registry);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Remove a tool from a toolset
|
|
263
|
+
*/
|
|
264
|
+
export function removeToolFromToolset(toolsetName: string, toolName: string): boolean {
|
|
265
|
+
const registry = loadMcpRegistry();
|
|
266
|
+
|
|
267
|
+
const toolset = registry.toolsets[toolsetName];
|
|
268
|
+
if (!toolset) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const index = toolset.indexOf(toolName);
|
|
273
|
+
if (index === -1) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
toolset.splice(index, 1);
|
|
278
|
+
saveMcpRegistry(registry);
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Set the active toolset
|
|
284
|
+
*/
|
|
285
|
+
export function setActiveToolset(name: string | null): boolean {
|
|
286
|
+
const registry = loadMcpRegistry();
|
|
287
|
+
|
|
288
|
+
if (name !== null && !(name in registry.toolsets)) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
registry.activeToolset = name;
|
|
293
|
+
saveMcpRegistry(registry);
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Get the active toolset name
|
|
299
|
+
*/
|
|
300
|
+
export function getActiveToolset(): string | null {
|
|
301
|
+
const registry = loadMcpRegistry();
|
|
302
|
+
return registry.activeToolset;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* List all toolsets
|
|
307
|
+
*/
|
|
308
|
+
export function listToolsets(): Array<{ name: string; tools: string[]; isActive: boolean }> {
|
|
309
|
+
const registry = loadMcpRegistry();
|
|
310
|
+
|
|
311
|
+
return Object.entries(registry.toolsets).map(([name, tools]) => ({
|
|
312
|
+
name,
|
|
313
|
+
tools,
|
|
314
|
+
isActive: registry.activeToolset === name,
|
|
315
|
+
}));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Sync MCP registry with global tools.json
|
|
320
|
+
* Adds any tools from tools.json that aren't in mcp.json
|
|
321
|
+
* Does NOT remove tools (allows mcp.json to have subset of tools.json)
|
|
322
|
+
*/
|
|
323
|
+
export function syncMcpWithGlobalTools(globalTools: Record<string, string>): void {
|
|
324
|
+
const registry = loadMcpRegistry();
|
|
325
|
+
let changed = false;
|
|
326
|
+
|
|
327
|
+
for (const [name, version] of Object.entries(globalTools)) {
|
|
328
|
+
if (!(name in registry.tools)) {
|
|
329
|
+
registry.tools[name] = version;
|
|
330
|
+
changed = true;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (changed) {
|
|
335
|
+
saveMcpRegistry(registry);
|
|
336
|
+
}
|
|
337
|
+
}
|