Skip to content

Commit d4ac560

Browse files
committed
chore: missing file
1 parent 4e17081 commit d4ac560

File tree

1 file changed

+314
-0
lines changed

1 file changed

+314
-0
lines changed
Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
/**
2+
* MCP Meta-Tools for Module Discovery
3+
*
4+
* These tools allow LLMs to discover and load module-specific tools on-demand,
5+
* reducing the initial tool count and improving context efficiency.
6+
*
7+
* Meta-tools are always enabled and cannot be disabled.
8+
*/
9+
10+
import { z } from 'zod';
11+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
12+
import { MCPToolDefinition } from './types.js';
13+
import { META_MODULE, ToolRegistry } from './ToolRegistry.js';
14+
import { ConduitGrpcSdk } from '@conduitplatform/grpc-sdk';
15+
16+
/**
17+
* Create the list_modules meta-tool
18+
* Returns available modules with their tool counts and loaded status
19+
*/
20+
export function createListModulesTool(registry: ToolRegistry): MCPToolDefinition {
21+
return {
22+
name: 'list_modules',
23+
title: 'List Available Modules',
24+
description:
25+
'Lists all available modules with their tool counts. Use this to discover what capabilities are available before loading specific module tools.',
26+
module: META_MODULE,
27+
initiallyDisabled: false,
28+
inputSchema: {},
29+
outputSchema: {
30+
modules: z
31+
.array(
32+
z.object({
33+
name: z.string().describe('Module name'),
34+
toolCount: z.number().describe('Number of tools in this module'),
35+
loaded: z.boolean().describe('Whether the module tools are currently loaded'),
36+
}),
37+
)
38+
.describe('List of available modules'),
39+
},
40+
handler: async (): Promise<CallToolResult> => {
41+
const modules = registry.getModules();
42+
43+
const result = {
44+
modules,
45+
totalModules: modules.length,
46+
loadedModules: modules.filter(m => m.loaded).length,
47+
};
48+
49+
return {
50+
content: [
51+
{
52+
type: 'text',
53+
text: JSON.stringify(result, null, 2),
54+
},
55+
],
56+
structuredContent: result,
57+
};
58+
},
59+
};
60+
}
61+
62+
/**
63+
* Create the load_module meta-tool
64+
* Enables all tools for a specific module
65+
*/
66+
export function createLoadModuleTool(registry: ToolRegistry): MCPToolDefinition {
67+
return {
68+
name: 'load_module',
69+
title: 'Load Module Tools',
70+
description:
71+
'Loads (enables) all tools for a specific module. After loading, the module tools will appear in the tools list. Use list_modules first to see available modules.',
72+
module: META_MODULE,
73+
initiallyDisabled: false,
74+
inputSchema: {
75+
module: z
76+
.string()
77+
.describe(
78+
'The name of the module to load (e.g., "authentication", "storage", "email")',
79+
),
80+
},
81+
outputSchema: {
82+
success: z.boolean().describe('Whether the module was successfully loaded'),
83+
module: z.string().describe('The module that was loaded'),
84+
loadedTools: z.array(z.string()).describe('List of tool names that were enabled'),
85+
},
86+
handler: async (args): Promise<CallToolResult> => {
87+
const moduleName = args.module as string;
88+
89+
if (!moduleName) {
90+
return {
91+
content: [
92+
{
93+
type: 'text',
94+
text: 'Error: module parameter is required',
95+
},
96+
],
97+
isError: true,
98+
};
99+
}
100+
101+
// Check if module exists
102+
const modules = registry.getModules();
103+
const moduleExists = modules.some(m => m.name === moduleName);
104+
105+
if (!moduleExists) {
106+
const availableModules = modules.map(m => m.name).join(', ');
107+
return {
108+
content: [
109+
{
110+
type: 'text',
111+
text: `Error: Module "${moduleName}" not found. Available modules: ${availableModules}`,
112+
},
113+
],
114+
isError: true,
115+
};
116+
}
117+
118+
// Check if already loaded
119+
if (registry.isModuleLoaded(moduleName)) {
120+
const toolNames = registry.getModuleToolNames(moduleName);
121+
const result = {
122+
success: true,
123+
module: moduleName,
124+
loadedTools: toolNames,
125+
message: 'Module was already loaded',
126+
};
127+
128+
return {
129+
content: [
130+
{
131+
type: 'text',
132+
text: JSON.stringify(result, null, 2),
133+
},
134+
],
135+
structuredContent: result,
136+
};
137+
}
138+
139+
// Enable the module
140+
const enabledTools = registry.enableModule(moduleName);
141+
142+
const result = {
143+
success: true,
144+
module: moduleName,
145+
loadedTools: enabledTools,
146+
message: `Successfully loaded ${enabledTools.length} tools from module "${moduleName}"`,
147+
};
148+
149+
ConduitGrpcSdk.Logger.log(
150+
`MCP: Loaded module "${moduleName}" with ${enabledTools.length} tools`,
151+
);
152+
153+
return {
154+
content: [
155+
{
156+
type: 'text',
157+
text: JSON.stringify(result, null, 2),
158+
},
159+
],
160+
structuredContent: result,
161+
};
162+
},
163+
};
164+
}
165+
166+
/**
167+
* Create the unload_module meta-tool
168+
* Disables all tools for a specific module
169+
*/
170+
export function createUnloadModuleTool(registry: ToolRegistry): MCPToolDefinition {
171+
return {
172+
name: 'unload_module',
173+
title: 'Unload Module Tools',
174+
description:
175+
'Unloads (disables) all tools for a specific module. The tools will no longer appear in the tools list until loaded again.',
176+
module: META_MODULE,
177+
initiallyDisabled: false,
178+
inputSchema: {
179+
module: z
180+
.string()
181+
.describe('The name of the module to unload (e.g., "authentication", "storage")'),
182+
},
183+
outputSchema: {
184+
success: z.boolean().describe('Whether the module was successfully unloaded'),
185+
module: z.string().describe('The module that was unloaded'),
186+
unloadedTools: z
187+
.array(z.string())
188+
.describe('List of tool names that were disabled'),
189+
},
190+
handler: async (args): Promise<CallToolResult> => {
191+
const moduleName = args.module as string;
192+
193+
if (!moduleName) {
194+
return {
195+
content: [
196+
{
197+
type: 'text',
198+
text: 'Error: module parameter is required',
199+
},
200+
],
201+
isError: true,
202+
};
203+
}
204+
205+
// Check if module is loaded
206+
if (!registry.isModuleLoaded(moduleName)) {
207+
return {
208+
content: [
209+
{
210+
type: 'text',
211+
text: `Module "${moduleName}" is not currently loaded`,
212+
},
213+
],
214+
structuredContent: {
215+
success: true,
216+
module: moduleName,
217+
unloadedTools: [],
218+
message: 'Module was not loaded',
219+
},
220+
};
221+
}
222+
223+
// Disable the module
224+
const disabledTools = registry.disableModule(moduleName);
225+
226+
const result = {
227+
success: true,
228+
module: moduleName,
229+
unloadedTools: disabledTools,
230+
message: `Successfully unloaded ${disabledTools.length} tools from module "${moduleName}"`,
231+
};
232+
233+
ConduitGrpcSdk.Logger.log(`MCP: Unloaded module "${moduleName}"`);
234+
235+
return {
236+
content: [
237+
{
238+
type: 'text',
239+
text: JSON.stringify(result, null, 2),
240+
},
241+
],
242+
structuredContent: result,
243+
};
244+
},
245+
};
246+
}
247+
248+
/**
249+
* Create the list_loaded_modules meta-tool
250+
* Returns currently loaded modules with their tools
251+
*/
252+
export function createListLoadedModulesTool(registry: ToolRegistry): MCPToolDefinition {
253+
return {
254+
name: 'list_loaded_modules',
255+
title: 'List Loaded Modules',
256+
description:
257+
'Lists all currently loaded (enabled) modules and their tools. Use this to see what module tools are currently available.',
258+
module: META_MODULE,
259+
initiallyDisabled: false,
260+
inputSchema: {},
261+
outputSchema: {
262+
loadedModules: z
263+
.array(
264+
z.object({
265+
name: z.string().describe('Module name'),
266+
tools: z.array(z.string()).describe('Tool names in this module'),
267+
}),
268+
)
269+
.describe('List of loaded modules with their tools'),
270+
},
271+
handler: async (): Promise<CallToolResult> => {
272+
const loadedModuleNames = registry.getLoadedModules();
273+
274+
const loadedModules = loadedModuleNames.map(moduleName => ({
275+
name: moduleName,
276+
tools: registry.getModuleToolNames(moduleName),
277+
}));
278+
279+
const result = {
280+
loadedModules,
281+
totalLoadedModules: loadedModules.length,
282+
totalLoadedTools: loadedModules.reduce((sum, m) => sum + m.tools.length, 0),
283+
};
284+
285+
return {
286+
content: [
287+
{
288+
type: 'text',
289+
text: JSON.stringify(result, null, 2),
290+
},
291+
],
292+
structuredContent: result,
293+
};
294+
},
295+
};
296+
}
297+
298+
/**
299+
* Register all meta-tools with the registry
300+
*/
301+
export function registerMetaTools(registry: ToolRegistry): void {
302+
const metaTools = [
303+
createListModulesTool(registry),
304+
createLoadModuleTool(registry),
305+
createUnloadModuleTool(registry),
306+
createListLoadedModulesTool(registry),
307+
];
308+
309+
for (const tool of metaTools) {
310+
registry.registerTool(tool);
311+
}
312+
313+
ConduitGrpcSdk.Logger.log(`Registered ${metaTools.length} MCP meta-tools`);
314+
}

0 commit comments

Comments
 (0)