kubeview-mcp 1.9.0 → 1.9.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/CHANGELOG.md +6 -0
- package/bin/kubeview-mcp.js +19 -2
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +4 -1
- package/scripts/build-version.js +19 -0
- package/src/agent/bridge/MCPBridge.ts +176 -0
- package/src/agent/codegen/CodegenManager.ts +334 -0
- package/src/agent/codegen/SchemaToTypeScriptMapper.ts +98 -0
- package/src/agent/codegen/ToolDescriptionBuilder.ts +258 -0
- package/src/agent/codegen/ToolSchemaIntrospector.ts +95 -0
- package/src/agent/codegen/types.ts +38 -0
- package/src/agent/config/CodeModeConfig.ts +48 -0
- package/src/agent/runtime/code-executor/CodeExecutor.ts +127 -0
- package/src/agent/runtime/code-executor/NodeVmCodeExecutor.ts +117 -0
- package/src/agent/runtime/createSandboxManager.ts +40 -0
- package/src/agent/runtime/ivm/IVMSandboxManager.ts +371 -0
- package/src/agent/runtime/types.ts +13 -0
- package/src/agent/runtime/vm/VmSandboxManager.ts +284 -0
- package/src/agent/security/PIITokenizer.ts +65 -0
- package/src/cli/cli.ts +25 -0
- package/src/cli/code-mode.ts +254 -0
- package/src/cli/run-command.js +682 -0
- package/src/index.ts +178 -0
- package/src/kubernetes/BaseResourceOperations.ts +286 -0
- package/src/kubernetes/CircuitBreaker.ts +485 -0
- package/src/kubernetes/ConnectionManager.ts +114 -0
- package/src/kubernetes/ConnectionPool.ts +551 -0
- package/src/kubernetes/ErrorHandler.ts +436 -0
- package/src/kubernetes/ErrorHandling.ts +401 -0
- package/src/kubernetes/KubernetesClient.ts +653 -0
- package/src/kubernetes/README.md +349 -0
- package/src/kubernetes/ResourceOperations.ts +116 -0
- package/src/kubernetes/RetryStrategy.ts +372 -0
- package/src/kubernetes/RetryableOperation.ts +313 -0
- package/src/kubernetes/docs/ResourceOperations.md +606 -0
- package/src/kubernetes/index.ts +116 -0
- package/src/kubernetes/resources/ConfigMapOperations.ts +368 -0
- package/src/kubernetes/resources/CustomResourceOperations.ts +392 -0
- package/src/kubernetes/resources/DeploymentOperations.ts +294 -0
- package/src/kubernetes/resources/HelmReleaseOperations.ts +536 -0
- package/src/kubernetes/resources/IngressOperations.ts +277 -0
- package/src/kubernetes/resources/MetricOperations.ts +1734 -0
- package/src/kubernetes/resources/NamespaceOperations.ts +129 -0
- package/src/kubernetes/resources/PersistentVolumeClaimOperations.ts +516 -0
- package/src/kubernetes/resources/PersistentVolumeOperations.ts +438 -0
- package/src/kubernetes/resources/PodOperations.ts +424 -0
- package/src/kubernetes/resources/SecretOperations.ts +314 -0
- package/src/kubernetes/resources/ServiceOperations.ts +283 -0
- package/src/kubernetes/utils/ResourceUtils.ts +553 -0
- package/src/plugins/ArgoCDToolsPlugin.ts +129 -0
- package/src/plugins/ArgoToolsPlugin.ts +130 -0
- package/src/plugins/BaseToolsPlugin.ts +148 -0
- package/src/plugins/HelmToolsPlugin.ts +129 -0
- package/src/plugins/KubernetesToolsPlugin.ts +232 -0
- package/src/plugins/SamplePlugin.ts +59 -0
- package/src/plugins/index.ts +4 -0
- package/src/server/MCPServer.ts +900 -0
- package/src/server/StreamableHttpRuntime.ts +391 -0
- package/src/server/TransportConfig.ts +95 -0
- package/src/tools/RunCodeTool.ts +777 -0
- package/src/tools/argo/ArgoCronListTool.ts +140 -0
- package/src/tools/argo/ArgoGetTool.ts +138 -0
- package/src/tools/argo/ArgoListTool.ts +262 -0
- package/src/tools/argo/ArgoLogsTool.ts +307 -0
- package/src/tools/argo/BaseTool.ts +104 -0
- package/src/tools/argo/index.ts +11 -0
- package/src/tools/argocd/ArgoCDAppGetTool.ts +109 -0
- package/src/tools/argocd/ArgoCDAppHistoryTool.ts +91 -0
- package/src/tools/argocd/ArgoCDAppListTool.ts +144 -0
- package/src/tools/argocd/ArgoCDAppResourcesTool.ts +139 -0
- package/src/tools/argocd/ArgoCDAppTool.ts +819 -0
- package/src/tools/argocd/BaseTool.ts +89 -0
- package/src/tools/argocd/index.ts +3 -0
- package/src/tools/helm/BaseTool.ts +61 -0
- package/src/tools/helm/HelmDebugTool.ts +486 -0
- package/src/tools/helm/HelmGetHooksTool.ts +45 -0
- package/src/tools/helm/HelmGetManifestTool.ts +45 -0
- package/src/tools/helm/HelmGetNotesTool.ts +45 -0
- package/src/tools/helm/HelmGetResourcesTool.ts +141 -0
- package/src/tools/helm/HelmGetTool.ts +260 -0
- package/src/tools/helm/HelmGetValuesTool.ts +71 -0
- package/src/tools/helm/HelmHistoryTool.ts +57 -0
- package/src/tools/helm/HelmListTool.ts +144 -0
- package/src/tools/helm/HelmListWithResourcesTool.ts +195 -0
- package/src/tools/helm/HelmStatusTool.ts +63 -0
- package/src/tools/helm/index.ts +5 -0
- package/src/tools/kubernetes/BaseTool.ts +76 -0
- package/src/tools/kubernetes/ExecTool.ts +308 -0
- package/src/tools/kubernetes/GetConfigMapTool.ts +33 -0
- package/src/tools/kubernetes/GetContainerLogsTool.ts +155 -0
- package/src/tools/kubernetes/GetCronJobsTool.ts +47 -0
- package/src/tools/kubernetes/GetDaemonSetsTool.ts +55 -0
- package/src/tools/kubernetes/GetDeploymentsTool.ts +33 -0
- package/src/tools/kubernetes/GetEndpointSlicesTool.ts +57 -0
- package/src/tools/kubernetes/GetEndpointsTool.ts +53 -0
- package/src/tools/kubernetes/GetEventsTool.ts +133 -0
- package/src/tools/kubernetes/GetHPATool.ts +48 -0
- package/src/tools/kubernetes/GetIngressTool.ts +39 -0
- package/src/tools/kubernetes/GetJobsTool.ts +54 -0
- package/src/tools/kubernetes/GetLimitRangesTool.ts +40 -0
- package/src/tools/kubernetes/GetMetricsTool.ts +61 -0
- package/src/tools/kubernetes/GetNamespacesTool.ts +36 -0
- package/src/tools/kubernetes/GetNodesTool.ts +55 -0
- package/src/tools/kubernetes/GetPDBTool.ts +48 -0
- package/src/tools/kubernetes/GetPersistentVolumeClaimsTool.ts +113 -0
- package/src/tools/kubernetes/GetPersistentVolumesTool.ts +107 -0
- package/src/tools/kubernetes/GetPodMetricsTool.ts +113 -0
- package/src/tools/kubernetes/GetPodsTool.ts +39 -0
- package/src/tools/kubernetes/GetReplicaSetsTool.ts +51 -0
- package/src/tools/kubernetes/GetResourceQuotaTool.ts +55 -0
- package/src/tools/kubernetes/GetResourceTool.ts +1296 -0
- package/src/tools/kubernetes/GetSecretsTool.ts +55 -0
- package/src/tools/kubernetes/GetServicesTool.ts +39 -0
- package/src/tools/kubernetes/GetStatefulSetsTool.ts +55 -0
- package/src/tools/kubernetes/KubeListTool.ts +1708 -0
- package/src/tools/kubernetes/KubeLogTool.ts +1063 -0
- package/src/tools/kubernetes/KubeMetricsTool.ts +220 -0
- package/src/tools/kubernetes/KubeNetTool.ts +481 -0
- package/src/tools/kubernetes/PortForwardTool.ts +307 -0
- package/src/tools/kubernetes/index.ts +10 -0
- package/src/tools/meta/PlanStepTool.ts +130 -0
- package/src/utils/CliUtils.ts +218 -0
- package/src/utils/CodeModeConfig.ts +47 -0
- package/src/utils/HelmDataParser.ts +230 -0
- package/src/utils/HelmLiveResources.ts +607 -0
- package/src/utils/McpToolResult.ts +32 -0
- package/src/utils/SensitiveData.ts +148 -0
- package/src/utils/toolNamespaces.ts +41 -0
- package/src/version.ts +2 -0
- package/tsconfig.json +37 -0
package/CHANGELOG.md
CHANGED
package/bin/kubeview-mcp.js
CHANGED
|
@@ -52,11 +52,11 @@ class KubeMCPCLI {
|
|
|
52
52
|
const nodeModulesPath = path.join(this.projectRoot, 'node_modules');
|
|
53
53
|
if (!fs.existsSync(nodeModulesPath)) {
|
|
54
54
|
this.log('📦 Installing dependencies...', colors.blue);
|
|
55
|
-
|
|
55
|
+
this.runCommandForMcpStartup('npm install');
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// Build the project
|
|
59
|
-
|
|
59
|
+
this.runCommandForMcpStartup('npm run build');
|
|
60
60
|
this.log('✅ Build completed successfully', colors.green);
|
|
61
61
|
} catch (buildError) {
|
|
62
62
|
this.error('❌ Build failed:');
|
|
@@ -66,6 +66,23 @@ class KubeMCPCLI {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
runCommandForMcpStartup(command) {
|
|
70
|
+
try {
|
|
71
|
+
execSync(command, {
|
|
72
|
+
cwd: this.projectRoot,
|
|
73
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
74
|
+
});
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (error.stdout) {
|
|
77
|
+
process.stderr.write(error.stdout);
|
|
78
|
+
}
|
|
79
|
+
if (error.stderr) {
|
|
80
|
+
process.stderr.write(error.stderr);
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
69
86
|
// Display help information
|
|
70
87
|
displayHelp() {
|
|
71
88
|
this.log('Kube MCP - Kubernetes MCP Server', colors.cyan);
|
package/dist/src/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "1.9.
|
|
1
|
+
export declare const VERSION = "1.9.1";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/src/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kubeview-mcp",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "Read-only MCP server enabling code-driven AI analysis of Kubernetes clusters",
|
|
5
5
|
"homepage": "https://github.com/mikhae1/kubeview-mcp",
|
|
6
6
|
"mcpName": "io.github.mikhae1/kubeview",
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
"files": [
|
|
21
21
|
"bin/",
|
|
22
22
|
"dist/",
|
|
23
|
+
"scripts/build-version.js",
|
|
24
|
+
"src/",
|
|
25
|
+
"tsconfig.json",
|
|
23
26
|
"*.md"
|
|
24
27
|
],
|
|
25
28
|
"scripts": {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generates src/version.ts from package.json version.
|
|
4
|
+
* Run before TypeScript compilation to bake version into the build.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { dirname, join } from 'path';
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const rootDir = join(__dirname, '..');
|
|
13
|
+
|
|
14
|
+
const packageJson = JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf8'));
|
|
15
|
+
const versionTs = `// Auto-generated by scripts/build-version.js - DO NOT EDIT
|
|
16
|
+
export const VERSION = '${packageJson.version}';\n`;
|
|
17
|
+
|
|
18
|
+
writeFileSync(join(rootDir, 'src', 'version.ts'), versionTs);
|
|
19
|
+
console.error(`Generated src/version.ts with version ${packageJson.version}`);
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
|
+
import type { ListToolsResult, Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import type { Logger } from 'winston';
|
|
5
|
+
import { PIITokenizer } from '../security/PIITokenizer.js';
|
|
6
|
+
|
|
7
|
+
export interface MCPServerConfig {
|
|
8
|
+
name: string;
|
|
9
|
+
command: string;
|
|
10
|
+
args?: string[];
|
|
11
|
+
env?: Record<string, string>;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ToolRegistration {
|
|
16
|
+
qualifiedName: string;
|
|
17
|
+
server: string;
|
|
18
|
+
toolName: string;
|
|
19
|
+
tool: Tool;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface MCPBridgeOptions {
|
|
23
|
+
enablePII?: boolean;
|
|
24
|
+
logger?: Logger;
|
|
25
|
+
defaultTimeoutMs?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class MCPBridge {
|
|
29
|
+
private clients: Map<string, Client> = new Map();
|
|
30
|
+
private toolRegistry: Map<string, ToolRegistration> = new Map();
|
|
31
|
+
private piiTokenizer?: PIITokenizer;
|
|
32
|
+
private toolDiscoveryCache: Map<string, Tool[]> = new Map();
|
|
33
|
+
private configByServer: Map<string, MCPServerConfig> = new Map();
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
private readonly configs: MCPServerConfig[],
|
|
37
|
+
private readonly options: MCPBridgeOptions = {},
|
|
38
|
+
) {
|
|
39
|
+
if (options.enablePII) {
|
|
40
|
+
this.piiTokenizer = new PIITokenizer();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public async initialize(): Promise<void> {
|
|
45
|
+
for (const config of this.configs) {
|
|
46
|
+
if (this.configByServer.has(config.name)) {
|
|
47
|
+
throw new Error(`Duplicate MCP server name: ${config.name}`);
|
|
48
|
+
}
|
|
49
|
+
this.configByServer.set(config.name, config);
|
|
50
|
+
await this.initializeServer(config);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private async initializeServer(config: MCPServerConfig): Promise<void> {
|
|
55
|
+
const envEntries = Object.entries({
|
|
56
|
+
...process.env,
|
|
57
|
+
...config.env,
|
|
58
|
+
}).filter((entry): entry is [string, string] => typeof entry[1] === 'string');
|
|
59
|
+
|
|
60
|
+
const mergedEnv = Object.fromEntries(envEntries);
|
|
61
|
+
|
|
62
|
+
const transport = new StdioClientTransport({
|
|
63
|
+
command: config.command,
|
|
64
|
+
args: config.args ?? [],
|
|
65
|
+
env: mergedEnv,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const client = new Client(
|
|
69
|
+
{
|
|
70
|
+
name: 'kube-mcp-code-mode-client',
|
|
71
|
+
version: '1.0.0',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
capabilities: {},
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (this.options.logger) {
|
|
79
|
+
this.options.logger.info(`Connecting to MCP server '${config.name}'`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await client.connect(transport);
|
|
83
|
+
this.clients.set(config.name, client);
|
|
84
|
+
|
|
85
|
+
const toolsResponse = await client.listTools();
|
|
86
|
+
this.cacheToolMetadata(config.name, toolsResponse);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private cacheToolMetadata(serverName: string, toolsResponse: ListToolsResult): void {
|
|
90
|
+
this.toolDiscoveryCache.set(serverName, toolsResponse.tools);
|
|
91
|
+
|
|
92
|
+
for (const tool of toolsResponse.tools) {
|
|
93
|
+
const qualifiedName = `${serverName}__${tool.name}`;
|
|
94
|
+
this.toolRegistry.set(qualifiedName, {
|
|
95
|
+
qualifiedName,
|
|
96
|
+
server: serverName,
|
|
97
|
+
toolName: tool.name,
|
|
98
|
+
tool,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public getRegisteredTools(): ToolRegistration[] {
|
|
104
|
+
return Array.from(this.toolRegistry.values());
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public getToolMetadata(qualifiedName: string): ToolRegistration | undefined {
|
|
108
|
+
return this.toolRegistry.get(qualifiedName);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public listServers(): string[] {
|
|
112
|
+
return Array.from(this.clients.keys());
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public listToolsForServer(serverName: string): Tool[] {
|
|
116
|
+
return this.toolDiscoveryCache.get(serverName) ?? [];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public async callTool<T = any>(qualifiedName: string, args: any): Promise<T> {
|
|
120
|
+
const registration = this.toolRegistry.get(qualifiedName);
|
|
121
|
+
if (!registration) {
|
|
122
|
+
throw new Error(`Tool ${qualifiedName} not found`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const client = this.clients.get(registration.server);
|
|
126
|
+
if (!client) {
|
|
127
|
+
throw new Error(`Server ${registration.server} not connected`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const sanitizedArgs = this.piiTokenizer ? this.piiTokenizer.tokenize(args) : args;
|
|
131
|
+
|
|
132
|
+
const timeoutMs =
|
|
133
|
+
this.configByServer.get(registration.server)?.timeoutMs ?? this.options.defaultTimeoutMs;
|
|
134
|
+
const callPromise = client.callTool({
|
|
135
|
+
name: registration.toolName,
|
|
136
|
+
arguments: sanitizedArgs,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
let result: Awaited<ReturnType<typeof client.callTool>>;
|
|
140
|
+
if (timeoutMs && timeoutMs > 0) {
|
|
141
|
+
result = await this.withTimeout(callPromise, timeoutMs, qualifiedName);
|
|
142
|
+
} else {
|
|
143
|
+
result = await callPromise;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const detokenized = this.piiTokenizer ? this.piiTokenizer.detokenize(result) : result;
|
|
147
|
+
return detokenized as T;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public async close(): Promise<void> {
|
|
151
|
+
for (const client of this.clients.values()) {
|
|
152
|
+
await client.close();
|
|
153
|
+
}
|
|
154
|
+
this.clients.clear();
|
|
155
|
+
this.toolRegistry.clear();
|
|
156
|
+
this.toolDiscoveryCache.clear();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private async withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {
|
|
160
|
+
return new Promise<T>((resolve, reject) => {
|
|
161
|
+
const timer = setTimeout(() => {
|
|
162
|
+
reject(new Error(`${label} timed out after ${timeoutMs}ms`));
|
|
163
|
+
}, timeoutMs);
|
|
164
|
+
|
|
165
|
+
promise
|
|
166
|
+
.then((value) => {
|
|
167
|
+
clearTimeout(timer);
|
|
168
|
+
resolve(value);
|
|
169
|
+
})
|
|
170
|
+
.catch((err) => {
|
|
171
|
+
clearTimeout(timer);
|
|
172
|
+
reject(err);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
import { ToolSchemaIntrospector } from './ToolSchemaIntrospector.js';
|
|
5
|
+
import { SchemaToTypeScriptMapper } from './SchemaToTypeScriptMapper.js';
|
|
6
|
+
import type { ToolSchemaSummary } from './types.js';
|
|
7
|
+
|
|
8
|
+
export interface CodegenManagerOptions {
|
|
9
|
+
outputDir: string;
|
|
10
|
+
runtimeImportPath?: string;
|
|
11
|
+
manifestPath?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DEFAULT_OPTIONS: CodegenManagerOptions = {
|
|
15
|
+
outputDir: path.resolve(process.cwd(), 'generated/servers'),
|
|
16
|
+
runtimeImportPath: '../../runtime/callMCPTool.ts',
|
|
17
|
+
manifestPath: path.resolve(process.cwd(), 'generated/servers/manifest.json'),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class CodegenManager {
|
|
21
|
+
private readonly mapper = new SchemaToTypeScriptMapper();
|
|
22
|
+
private readonly printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
23
|
+
private readonly options: CodegenManagerOptions;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
private readonly introspector: ToolSchemaIntrospector,
|
|
27
|
+
options?: Partial<CodegenManagerOptions>,
|
|
28
|
+
) {
|
|
29
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public async generate(): Promise<void> {
|
|
33
|
+
const summaries = this.introspector.collectToolSchemas();
|
|
34
|
+
if (!summaries.length) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await fs.mkdir(this.options.outputDir, { recursive: true });
|
|
39
|
+
await this.ensureRuntimeHelpers();
|
|
40
|
+
|
|
41
|
+
const grouped = this.groupByServer(summaries);
|
|
42
|
+
for (const [server, tools] of grouped.entries()) {
|
|
43
|
+
const serverDir = path.join(this.options.outputDir, server);
|
|
44
|
+
await fs.rm(serverDir, { recursive: true, force: true });
|
|
45
|
+
await fs.mkdir(serverDir, { recursive: true });
|
|
46
|
+
await Promise.all(tools.map((tool) => this.writeToolFile(serverDir, server, tool)));
|
|
47
|
+
await this.writeServerIndex(serverDir, tools);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await this.writeRootIndex(Array.from(grouped.keys()));
|
|
51
|
+
await this.writeManifest(grouped);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private groupByServer(summaries: ToolSchemaSummary[]): Map<string, ToolSchemaSummary[]> {
|
|
55
|
+
const grouped = new Map<string, ToolSchemaSummary[]>();
|
|
56
|
+
for (const summary of summaries) {
|
|
57
|
+
if (!grouped.has(summary.server)) {
|
|
58
|
+
grouped.set(summary.server, []);
|
|
59
|
+
}
|
|
60
|
+
grouped.get(summary.server)!.push(summary);
|
|
61
|
+
}
|
|
62
|
+
return grouped;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async writeToolFile(
|
|
66
|
+
baseDir: string,
|
|
67
|
+
_server: string,
|
|
68
|
+
tool: ToolSchemaSummary,
|
|
69
|
+
): Promise<void> {
|
|
70
|
+
const statements: ts.Statement[] = [];
|
|
71
|
+
statements.push(this.createRuntimeImport());
|
|
72
|
+
|
|
73
|
+
const pascalName = this.toPascalCase(tool.toolName);
|
|
74
|
+
const inputTypeName = `${pascalName}Input`;
|
|
75
|
+
const resultTypeName = `${pascalName}Result`;
|
|
76
|
+
|
|
77
|
+
statements.push(this.mapper.createTypeAliasDeclaration(inputTypeName, tool.inputSchema));
|
|
78
|
+
statements.push(this.mapper.createTypeAliasDeclaration(resultTypeName, undefined));
|
|
79
|
+
|
|
80
|
+
statements.push(this.createToolFunction(tool, inputTypeName, resultTypeName));
|
|
81
|
+
|
|
82
|
+
const content = this.printStatements(statements);
|
|
83
|
+
const filePath = path.join(baseDir, `${tool.toolName}.ts`);
|
|
84
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private createRuntimeImport(): ts.ImportDeclaration {
|
|
88
|
+
const importPath = this.options.runtimeImportPath ?? DEFAULT_OPTIONS.runtimeImportPath!;
|
|
89
|
+
return ts.factory.createImportDeclaration(
|
|
90
|
+
undefined,
|
|
91
|
+
ts.factory.createImportClause(
|
|
92
|
+
false,
|
|
93
|
+
undefined,
|
|
94
|
+
ts.factory.createNamedImports([
|
|
95
|
+
ts.factory.createImportSpecifier(
|
|
96
|
+
false,
|
|
97
|
+
undefined,
|
|
98
|
+
ts.factory.createIdentifier('callMCPTool'),
|
|
99
|
+
),
|
|
100
|
+
]),
|
|
101
|
+
),
|
|
102
|
+
ts.factory.createStringLiteral(importPath),
|
|
103
|
+
undefined,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private createToolFunction(
|
|
108
|
+
tool: ToolSchemaSummary,
|
|
109
|
+
inputTypeName: string,
|
|
110
|
+
resultTypeName: string,
|
|
111
|
+
): ts.FunctionDeclaration {
|
|
112
|
+
const docComment = tool.description
|
|
113
|
+
? ts.factory.createJSDocComment(tool.description)
|
|
114
|
+
: undefined;
|
|
115
|
+
|
|
116
|
+
const fn = ts.factory.createFunctionDeclaration(
|
|
117
|
+
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
|
|
118
|
+
undefined,
|
|
119
|
+
ts.factory.createIdentifier(this.toCamelCase(tool.toolName)),
|
|
120
|
+
undefined,
|
|
121
|
+
[
|
|
122
|
+
ts.factory.createParameterDeclaration(
|
|
123
|
+
undefined,
|
|
124
|
+
undefined,
|
|
125
|
+
ts.factory.createIdentifier('input'),
|
|
126
|
+
undefined,
|
|
127
|
+
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(inputTypeName)),
|
|
128
|
+
undefined,
|
|
129
|
+
),
|
|
130
|
+
],
|
|
131
|
+
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Promise'), [
|
|
132
|
+
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(resultTypeName)),
|
|
133
|
+
]),
|
|
134
|
+
ts.factory.createBlock(
|
|
135
|
+
[
|
|
136
|
+
ts.factory.createReturnStatement(
|
|
137
|
+
ts.factory.createCallExpression(
|
|
138
|
+
ts.factory.createIdentifier('callMCPTool'),
|
|
139
|
+
[ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(resultTypeName))],
|
|
140
|
+
[
|
|
141
|
+
ts.factory.createStringLiteral(tool.qualifiedName),
|
|
142
|
+
ts.factory.createIdentifier('input'),
|
|
143
|
+
],
|
|
144
|
+
),
|
|
145
|
+
),
|
|
146
|
+
],
|
|
147
|
+
true,
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
if (docComment) {
|
|
152
|
+
ts.addSyntheticLeadingComment(
|
|
153
|
+
fn,
|
|
154
|
+
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
155
|
+
`*\n * ${tool.description}\n `,
|
|
156
|
+
true,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return fn;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private async writeServerIndex(serverDir: string, tools: ToolSchemaSummary[]): Promise<void> {
|
|
164
|
+
const statements = tools.map((tool) =>
|
|
165
|
+
ts.factory.createExportDeclaration(
|
|
166
|
+
undefined,
|
|
167
|
+
false,
|
|
168
|
+
ts.factory.createNamedExports([
|
|
169
|
+
ts.factory.createExportSpecifier(
|
|
170
|
+
false,
|
|
171
|
+
undefined,
|
|
172
|
+
ts.factory.createIdentifier(this.toCamelCase(tool.toolName)),
|
|
173
|
+
),
|
|
174
|
+
]),
|
|
175
|
+
ts.factory.createStringLiteral(`./${tool.toolName}.ts`),
|
|
176
|
+
undefined,
|
|
177
|
+
),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const content = this.printStatements(statements);
|
|
181
|
+
await fs.writeFile(path.join(serverDir, 'index.ts'), content, 'utf-8');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private async writeRootIndex(servers: string[]): Promise<void> {
|
|
185
|
+
const statements = servers.map((server) =>
|
|
186
|
+
ts.factory.createExportDeclaration(
|
|
187
|
+
undefined,
|
|
188
|
+
false,
|
|
189
|
+
undefined,
|
|
190
|
+
ts.factory.createStringLiteral(`./${server}/index.ts`),
|
|
191
|
+
undefined,
|
|
192
|
+
),
|
|
193
|
+
);
|
|
194
|
+
const content = this.printStatements(statements);
|
|
195
|
+
await fs.writeFile(path.join(this.options.outputDir, 'index.ts'), content, 'utf-8');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private async writeManifest(grouped: Map<string, ToolSchemaSummary[]>): Promise<void> {
|
|
199
|
+
const manifest = Array.from(grouped.entries()).map(([server, tools]) => ({
|
|
200
|
+
server,
|
|
201
|
+
tools: tools.map((tool) => ({
|
|
202
|
+
name: tool.toolName,
|
|
203
|
+
qualifiedName: tool.qualifiedName,
|
|
204
|
+
description: tool.description,
|
|
205
|
+
inputSchema: tool.inputSchema, // Include full schema for runtime introspection
|
|
206
|
+
})),
|
|
207
|
+
}));
|
|
208
|
+
|
|
209
|
+
if (!this.options.manifestPath) return;
|
|
210
|
+
await fs.writeFile(this.options.manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private async ensureRuntimeHelpers(): Promise<void> {
|
|
214
|
+
const runtimeDir = path.resolve(this.options.outputDir, '../runtime');
|
|
215
|
+
await fs.mkdir(runtimeDir, { recursive: true });
|
|
216
|
+
const filePath = path.join(runtimeDir, 'callMCPTool.ts');
|
|
217
|
+
const content = `declare global {
|
|
218
|
+
// eslint-disable-next-line no-var
|
|
219
|
+
var __callMCPTool: ((qualifiedName: string, args: unknown) => Promise<unknown>) | undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export async function callMCPTool<T = unknown>(qualifiedName: string, args: unknown): Promise<T> {
|
|
223
|
+
if (typeof globalThis.__callMCPTool !== 'function') {
|
|
224
|
+
throw new Error('callMCPTool bridge not initialized');
|
|
225
|
+
}
|
|
226
|
+
const fn = globalThis.__callMCPTool as (name: string, params: unknown) => Promise<T>;
|
|
227
|
+
return fn(qualifiedName, args);
|
|
228
|
+
}
|
|
229
|
+
`;
|
|
230
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
231
|
+
|
|
232
|
+
const searchHelperPath = path.join(runtimeDir, 'toolSearch.ts');
|
|
233
|
+
const searchContent = `import manifest from '../servers/manifest.json';
|
|
234
|
+
|
|
235
|
+
export interface ToolManifestEntry {
|
|
236
|
+
server: string;
|
|
237
|
+
name: string;
|
|
238
|
+
qualifiedName: string;
|
|
239
|
+
description?: string;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const tools: ToolManifestEntry[] = manifest.flatMap((serverEntry) =>
|
|
243
|
+
serverEntry.tools.map((tool) => ({
|
|
244
|
+
server: serverEntry.server,
|
|
245
|
+
...tool,
|
|
246
|
+
})),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
export function listServers(): string[] {
|
|
250
|
+
return manifest.map((entry) => entry.server);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function listTools(server?: string): ToolManifestEntry[] {
|
|
254
|
+
if (!server) return tools;
|
|
255
|
+
return tools.filter((tool) => tool.server === server);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function searchTools(query: string, limit = 10): ToolManifestEntry[] {
|
|
259
|
+
const normalized = query.toLowerCase();
|
|
260
|
+
return tools
|
|
261
|
+
.filter(
|
|
262
|
+
(tool) =>
|
|
263
|
+
tool.name.toLowerCase().includes(normalized) ||
|
|
264
|
+
(tool.description ?? '').toLowerCase().includes(normalized),
|
|
265
|
+
)
|
|
266
|
+
.slice(0, limit);
|
|
267
|
+
}
|
|
268
|
+
`;
|
|
269
|
+
await fs.writeFile(searchHelperPath, searchContent, 'utf-8');
|
|
270
|
+
|
|
271
|
+
const workspaceHelperPath = path.join(runtimeDir, 'workspaceFs.ts');
|
|
272
|
+
const workspaceContent = `declare global {
|
|
273
|
+
// eslint-disable-next-line no-var
|
|
274
|
+
var __workspaceFs:
|
|
275
|
+
| {
|
|
276
|
+
readFile(path: string): Promise<string>;
|
|
277
|
+
writeFile(path: string, data: string): Promise<void>;
|
|
278
|
+
listDir(path?: string): Promise<string[]>;
|
|
279
|
+
exists(path: string): Promise<boolean>;
|
|
280
|
+
}
|
|
281
|
+
| undefined;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function ensureWorkspaceFs() {
|
|
285
|
+
if (!globalThis.__workspaceFs) {
|
|
286
|
+
throw new Error('Workspace filesystem bridge not initialized');
|
|
287
|
+
}
|
|
288
|
+
return globalThis.__workspaceFs;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export async function readWorkspaceFile(path: string): Promise<string> {
|
|
292
|
+
return ensureWorkspaceFs().readFile(path);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export async function writeWorkspaceFile(path: string, data: string): Promise<void> {
|
|
296
|
+
return ensureWorkspaceFs().writeFile(path, data);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export async function listWorkspaceDir(path?: string): Promise<string[]> {
|
|
300
|
+
return ensureWorkspaceFs().listDir(path);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export async function workspacePathExists(path: string): Promise<boolean> {
|
|
304
|
+
return ensureWorkspaceFs().exists(path);
|
|
305
|
+
}
|
|
306
|
+
`;
|
|
307
|
+
await fs.writeFile(workspaceHelperPath, workspaceContent, 'utf-8');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private printStatements(statements: ts.Statement[]): string {
|
|
311
|
+
const sourceFile = ts.createSourceFile(
|
|
312
|
+
'temp.ts',
|
|
313
|
+
'',
|
|
314
|
+
ts.ScriptTarget.ES2020,
|
|
315
|
+
false,
|
|
316
|
+
ts.ScriptKind.TS,
|
|
317
|
+
);
|
|
318
|
+
return statements
|
|
319
|
+
.map((statement) => this.printer.printNode(ts.EmitHint.Unspecified, statement, sourceFile))
|
|
320
|
+
.join('\n\n');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private toPascalCase(value: string): string {
|
|
324
|
+
return value
|
|
325
|
+
.replace(/[_-]+/g, ' ')
|
|
326
|
+
.replace(/(?:^\w|[A-Z]|\b\w)/g, (word) => word.toUpperCase())
|
|
327
|
+
.replace(/\s+/g, '');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private toCamelCase(value: string): string {
|
|
331
|
+
const pascal = this.toPascalCase(value);
|
|
332
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
333
|
+
}
|
|
334
|
+
}
|