cli4ai 1.2.0 → 1.2.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.
Files changed (113) hide show
  1. package/README.md +39 -0
  2. package/dist/bin.d.ts +6 -0
  3. package/dist/bin.js +105 -0
  4. package/dist/cli.d.ts +5 -0
  5. package/dist/cli.js +335 -0
  6. package/dist/commands/add.d.ts +11 -0
  7. package/dist/commands/add.js +459 -0
  8. package/dist/commands/browse.d.ts +4 -0
  9. package/dist/commands/browse.js +379 -0
  10. package/dist/commands/config.d.ts +10 -0
  11. package/dist/commands/config.js +121 -0
  12. package/dist/commands/info.d.ts +9 -0
  13. package/dist/commands/info.js +122 -0
  14. package/dist/commands/init.d.ts +10 -0
  15. package/dist/commands/init.js +458 -0
  16. package/dist/commands/list.d.ts +10 -0
  17. package/dist/commands/list.js +76 -0
  18. package/dist/commands/mcp-config.d.ts +10 -0
  19. package/dist/commands/mcp-config.js +49 -0
  20. package/dist/commands/remotes.d.ts +22 -0
  21. package/dist/commands/remotes.js +196 -0
  22. package/dist/commands/remove.d.ts +8 -0
  23. package/dist/commands/remove.js +61 -0
  24. package/dist/commands/routines.d.ts +29 -0
  25. package/dist/commands/routines.js +363 -0
  26. package/dist/commands/run.d.ts +12 -0
  27. package/dist/commands/run.js +104 -0
  28. package/dist/commands/scheduler.d.ts +27 -0
  29. package/dist/commands/scheduler.js +350 -0
  30. package/dist/commands/search.d.ts +9 -0
  31. package/dist/commands/search.js +159 -0
  32. package/dist/commands/secrets.d.ts +28 -0
  33. package/dist/commands/secrets.js +236 -0
  34. package/dist/commands/serve.d.ts +13 -0
  35. package/dist/commands/serve.js +49 -0
  36. package/dist/commands/start.d.ts +8 -0
  37. package/dist/commands/start.js +27 -0
  38. package/dist/commands/update.d.ts +17 -0
  39. package/dist/commands/update.js +210 -0
  40. package/dist/core/config.d.ts +91 -0
  41. package/dist/core/config.js +738 -0
  42. package/dist/core/execute.d.ts +51 -0
  43. package/dist/core/execute.js +475 -0
  44. package/dist/core/link.d.ts +39 -0
  45. package/dist/core/link.js +214 -0
  46. package/dist/core/lockfile.d.ts +63 -0
  47. package/dist/core/lockfile.js +140 -0
  48. package/dist/core/manifest.d.ts +96 -0
  49. package/dist/core/manifest.js +224 -0
  50. package/dist/core/registry.d.ts +74 -0
  51. package/dist/core/registry.js +116 -0
  52. package/dist/core/remote-client.d.ts +98 -0
  53. package/dist/core/remote-client.js +252 -0
  54. package/dist/core/remotes.d.ts +88 -0
  55. package/dist/core/remotes.js +206 -0
  56. package/dist/core/routine-engine.d.ts +124 -0
  57. package/dist/core/routine-engine.js +699 -0
  58. package/dist/core/routines.d.ts +36 -0
  59. package/dist/core/routines.js +132 -0
  60. package/dist/core/scheduler-daemon.d.ts +10 -0
  61. package/dist/core/scheduler-daemon.js +77 -0
  62. package/dist/core/scheduler.d.ts +131 -0
  63. package/dist/core/scheduler.js +492 -0
  64. package/dist/core/secrets.d.ts +48 -0
  65. package/dist/core/secrets.js +384 -0
  66. package/dist/lib/cli.d.ts +84 -0
  67. package/dist/lib/cli.js +216 -0
  68. package/dist/mcp/adapter.d.ts +35 -0
  69. package/dist/mcp/adapter.js +94 -0
  70. package/dist/mcp/config-gen.d.ts +31 -0
  71. package/dist/mcp/config-gen.js +75 -0
  72. package/dist/mcp/server.d.ts +41 -0
  73. package/dist/mcp/server.js +296 -0
  74. package/dist/server/service.d.ts +85 -0
  75. package/dist/server/service.js +304 -0
  76. package/package.json +6 -3
  77. package/src/bin.ts +0 -118
  78. package/src/cli.ts +0 -412
  79. package/src/commands/add.ts +0 -562
  80. package/src/commands/browse.ts +0 -449
  81. package/src/commands/config.ts +0 -154
  82. package/src/commands/info.ts +0 -133
  83. package/src/commands/init.ts +0 -514
  84. package/src/commands/list.ts +0 -95
  85. package/src/commands/mcp-config.ts +0 -69
  86. package/src/commands/remotes.ts +0 -253
  87. package/src/commands/remove.ts +0 -78
  88. package/src/commands/routines.ts +0 -427
  89. package/src/commands/run.ts +0 -127
  90. package/src/commands/scheduler.ts +0 -438
  91. package/src/commands/search.ts +0 -185
  92. package/src/commands/secrets.ts +0 -292
  93. package/src/commands/serve.ts +0 -66
  94. package/src/commands/start.ts +0 -40
  95. package/src/commands/update.ts +0 -252
  96. package/src/core/config.ts +0 -845
  97. package/src/core/execute.ts +0 -569
  98. package/src/core/link.ts +0 -246
  99. package/src/core/lockfile.ts +0 -187
  100. package/src/core/manifest.ts +0 -327
  101. package/src/core/registry.ts +0 -165
  102. package/src/core/remote-client.ts +0 -419
  103. package/src/core/remotes.ts +0 -268
  104. package/src/core/routine-engine.ts +0 -895
  105. package/src/core/routines.ts +0 -171
  106. package/src/core/scheduler-daemon.ts +0 -94
  107. package/src/core/scheduler.ts +0 -606
  108. package/src/core/secrets.ts +0 -430
  109. package/src/lib/cli.ts +0 -261
  110. package/src/mcp/adapter.ts +0 -131
  111. package/src/mcp/config-gen.ts +0 -106
  112. package/src/mcp/server.ts +0 -365
  113. package/src/server/service.ts +0 -434
@@ -1,131 +0,0 @@
1
- /**
2
- * MCP Adapter - Converts CLI tools to MCP tools
3
- */
4
-
5
- import { spawn } from 'child_process';
6
- import { type Manifest, type CommandDef } from '../core/manifest.js';
7
-
8
- export interface McpTool {
9
- name: string;
10
- description: string;
11
- inputSchema: {
12
- type: 'object';
13
- properties: Record<string, { type: string; description?: string }>;
14
- required: string[];
15
- };
16
- }
17
-
18
- export interface McpToolResult {
19
- content: Array<{ type: 'text'; text: string }>;
20
- isError?: boolean;
21
- }
22
-
23
- /**
24
- * Convert a CLI command definition to MCP tool format
25
- */
26
- export function commandToMcpTool(manifest: Manifest, cmdName: string, cmd: CommandDef): McpTool {
27
- const properties: Record<string, { type: string; description?: string }> = {};
28
- const required: string[] = [];
29
-
30
- // Convert args to JSON Schema properties
31
- if (cmd.args) {
32
- for (const arg of cmd.args) {
33
- properties[arg.name] = {
34
- type: 'string',
35
- description: arg.description
36
- };
37
- if (arg.required) {
38
- required.push(arg.name);
39
- }
40
- }
41
- }
42
-
43
- return {
44
- name: `${manifest.name}_${cmdName}`,
45
- description: `${manifest.name}: ${cmd.description || cmdName}`,
46
- inputSchema: {
47
- type: 'object',
48
- properties,
49
- required
50
- }
51
- };
52
- }
53
-
54
- /**
55
- * Convert all commands in a manifest to MCP tools
56
- */
57
- export function manifestToMcpTools(manifest: Manifest): McpTool[] {
58
- const tools: McpTool[] = [];
59
-
60
- if (manifest.commands) {
61
- for (const [cmdName, cmdDef] of Object.entries(manifest.commands)) {
62
- tools.push(commandToMcpTool(manifest, cmdName, cmdDef));
63
- }
64
- }
65
-
66
- return tools;
67
- }
68
-
69
- /**
70
- * Execute a CLI tool command and return MCP-formatted result
71
- */
72
- export async function executeTool(
73
- entryPath: string,
74
- runtime: string,
75
- command: string,
76
- args: Record<string, string>,
77
- argOrder?: string[]
78
- ): Promise<McpToolResult> {
79
- return new Promise((resolve) => {
80
- // Build command arguments
81
- const cmdArgs = [command];
82
-
83
- // Prefer manifest-defined arg order (positional), fall back to stable ordering.
84
- const orderedKeys = argOrder ?? Object.keys(args).sort();
85
- for (const key of orderedKeys) {
86
- const value = args[key];
87
- if (value !== undefined && value !== '') cmdArgs.push(value);
88
- }
89
-
90
- const runtimeArgs =
91
- runtime === 'node'
92
- ? [entryPath, ...cmdArgs]
93
- : ['run', entryPath, ...cmdArgs];
94
-
95
- const proc = spawn(runtime, runtimeArgs, {
96
- stdio: ['pipe', 'pipe', 'pipe'],
97
- env: { ...process.env }
98
- });
99
-
100
- let stdout = '';
101
- let stderr = '';
102
-
103
- proc.stdout.on('data', (data) => {
104
- stdout += data.toString();
105
- });
106
-
107
- proc.stderr.on('data', (data) => {
108
- stderr += data.toString();
109
- });
110
-
111
- proc.on('close', (code) => {
112
- if (code !== 0) {
113
- resolve({
114
- content: [{ type: 'text', text: stderr || `Command failed with exit code ${code}` }],
115
- isError: true
116
- });
117
- } else {
118
- resolve({
119
- content: [{ type: 'text', text: stdout || 'Command completed successfully' }]
120
- });
121
- }
122
- });
123
-
124
- proc.on('error', (err) => {
125
- resolve({
126
- content: [{ type: 'text', text: `Failed to execute: ${err.message}` }],
127
- isError: true
128
- });
129
- });
130
- });
131
- }
@@ -1,106 +0,0 @@
1
- /**
2
- * MCP Config Generator - Generate Claude Code MCP configuration
3
- */
4
-
5
- import { resolve } from 'path';
6
- import { type Manifest, tryLoadManifest } from '../core/manifest.js';
7
- import { findPackage, getGlobalPackages, getLocalPackages, type InstalledPackage } from '../core/config.js';
8
-
9
- export interface McpServerConfig {
10
- command: string;
11
- args: string[];
12
- env?: Record<string, string>;
13
- }
14
-
15
- export interface ClaudeCodeConfig {
16
- mcpServers: Record<string, McpServerConfig>;
17
- }
18
-
19
- /**
20
- * Generate MCP server config for a single package
21
- */
22
- export function generateServerConfig(
23
- manifest: Manifest,
24
- _packagePath: string
25
- ): McpServerConfig {
26
- // Use cli4ai start command which handles the MCP server
27
- return {
28
- command: 'cli4ai',
29
- args: ['start', manifest.name]
30
- };
31
- }
32
-
33
- /**
34
- * Load manifest for an installed package
35
- */
36
- function loadPackageWithManifest(pkg: InstalledPackage): { name: string; path: string; manifest: Manifest } | null {
37
- const manifest = tryLoadManifest(pkg.path);
38
- if (!manifest) return null;
39
- return { name: pkg.name, path: pkg.path, manifest };
40
- }
41
-
42
- /**
43
- * Generate Claude Code config for installed packages
44
- */
45
- export function generateClaudeCodeConfig(
46
- cwd: string,
47
- options: { global?: boolean; packages?: string[] } = {}
48
- ): ClaudeCodeConfig {
49
- const mcpServers: Record<string, McpServerConfig> = {};
50
-
51
- // Get installed packages
52
- let installedPackages: InstalledPackage[] = [];
53
-
54
- if (options.packages && options.packages.length > 0) {
55
- // Specific packages requested
56
- for (const pkgName of options.packages) {
57
- const pkg = findPackage(pkgName, cwd);
58
- if (pkg) {
59
- installedPackages.push(pkg);
60
- }
61
- }
62
- } else {
63
- // All installed packages
64
- if (options.global) {
65
- installedPackages = getGlobalPackages();
66
- } else {
67
- installedPackages = [
68
- ...getLocalPackages(cwd),
69
- ...getGlobalPackages()
70
- ];
71
- }
72
- }
73
-
74
- // Load manifests and filter to MCP-enabled packages
75
- for (const pkg of installedPackages) {
76
- const pkgWithManifest = loadPackageWithManifest(pkg);
77
- if (pkgWithManifest && pkgWithManifest.manifest.mcp?.enabled) {
78
- mcpServers[`cli4ai-${pkgWithManifest.name}`] = generateServerConfig(
79
- pkgWithManifest.manifest,
80
- pkgWithManifest.path
81
- );
82
- }
83
- }
84
-
85
- return { mcpServers };
86
- }
87
-
88
- /**
89
- * Format config as JSON for Claude Code
90
- */
91
- export function formatClaudeCodeConfig(config: ClaudeCodeConfig): string {
92
- return JSON.stringify(config, null, 2);
93
- }
94
-
95
- /**
96
- * Generate config snippet for adding to existing claude_desktop_config.json
97
- */
98
- export function generateConfigSnippet(
99
- manifest: Manifest,
100
- packagePath: string
101
- ): string {
102
- const serverConfig = generateServerConfig(manifest, packagePath);
103
- const serverName = `cli4ai-${manifest.name}`;
104
-
105
- return `"${serverName}": ${JSON.stringify(serverConfig, null, 2)}`;
106
- }
package/src/mcp/server.ts DELETED
@@ -1,365 +0,0 @@
1
- /**
2
- * MCP Server - Expose CLI tools as MCP tools
3
- *
4
- * Implements the Model Context Protocol (MCP) over stdio
5
- * https://modelcontextprotocol.io/
6
- *
7
- * SECURITY: Includes execution safeguards:
8
- * - Audit logging of all tool calls
9
- * - Rate limiting to prevent abuse
10
- * - Trust level tracking for packages
11
- */
12
-
13
- import { spawn } from 'child_process';
14
- import { resolve } from 'path';
15
- import { appendFileSync, existsSync, mkdirSync } from 'fs';
16
- import { homedir } from 'os';
17
- import { type Manifest } from '../core/manifest.js';
18
- import { manifestToMcpTools, type McpTool } from './adapter.js';
19
- import { getSecret } from '../core/secrets.js';
20
- import { loadConfig } from '../core/config.js';
21
-
22
- // ═══════════════════════════════════════════════════════════════════════════
23
- // SECURITY: Audit logging and rate limiting
24
- // ═══════════════════════════════════════════════════════════════════════════
25
-
26
- const AUDIT_LOG_DIR = resolve(homedir(), '.cli4ai', 'logs');
27
- const RATE_LIMIT_WINDOW_MS = 60000; // 1 minute
28
- const RATE_LIMIT_MAX_CALLS = 100; // Max calls per minute per tool
29
-
30
- interface RateLimitEntry {
31
- count: number;
32
- windowStart: number;
33
- }
34
-
35
- /**
36
- * Audit log an MCP tool call for security tracking
37
- * Can be disabled via `cli4ai config set audit.enabled false`
38
- */
39
- function auditLog(
40
- packageName: string,
41
- toolName: string,
42
- args: Record<string, unknown>,
43
- result: 'success' | 'error' | 'rate_limited',
44
- errorMessage?: string
45
- ): void {
46
- try {
47
- // Check if audit logging is enabled
48
- const config = loadConfig();
49
- if (!config.audit?.enabled) {
50
- return;
51
- }
52
-
53
- if (!existsSync(AUDIT_LOG_DIR)) {
54
- mkdirSync(AUDIT_LOG_DIR, { recursive: true });
55
- }
56
-
57
- const logEntry = {
58
- timestamp: new Date().toISOString(),
59
- package: packageName,
60
- tool: toolName,
61
- // Redact potentially sensitive argument values, keep keys
62
- argKeys: Object.keys(args),
63
- result,
64
- error: errorMessage,
65
- pid: process.pid
66
- };
67
-
68
- const logFile = resolve(AUDIT_LOG_DIR, `mcp-audit-${new Date().toISOString().slice(0, 10)}.log`);
69
- appendFileSync(logFile, JSON.stringify(logEntry) + '\n');
70
- } catch {
71
- // Don't fail tool execution if logging fails
72
- }
73
- }
74
-
75
- interface JsonRpcRequest {
76
- jsonrpc: '2.0';
77
- id: number | string;
78
- method: string;
79
- params?: Record<string, unknown>;
80
- }
81
-
82
- interface JsonRpcResponse {
83
- jsonrpc: '2.0';
84
- id: number | string;
85
- result?: unknown;
86
- error?: { code: number; message: string; data?: unknown };
87
- }
88
-
89
- /**
90
- * MCP Server that wraps a CLI tool
91
- */
92
- export class McpServer {
93
- private manifest: Manifest;
94
- private packagePath: string;
95
- private tools: McpTool[];
96
- private rateLimits: Map<string, RateLimitEntry> = new Map();
97
- private totalCallCount: number = 0;
98
-
99
- constructor(manifest: Manifest, packagePath: string) {
100
- this.manifest = manifest;
101
- this.packagePath = packagePath;
102
- this.tools = manifestToMcpTools(manifest);
103
- }
104
-
105
- /**
106
- * SECURITY: Check if a tool call should be rate limited
107
- */
108
- private checkRateLimit(toolName: string): { allowed: boolean; retryAfterMs?: number } {
109
- const now = Date.now();
110
- const entry = this.rateLimits.get(toolName);
111
-
112
- if (!entry || now - entry.windowStart >= RATE_LIMIT_WINDOW_MS) {
113
- // Start new window
114
- this.rateLimits.set(toolName, { count: 1, windowStart: now });
115
- return { allowed: true };
116
- }
117
-
118
- if (entry.count >= RATE_LIMIT_MAX_CALLS) {
119
- const retryAfterMs = RATE_LIMIT_WINDOW_MS - (now - entry.windowStart);
120
- return { allowed: false, retryAfterMs };
121
- }
122
-
123
- entry.count++;
124
- return { allowed: true };
125
- }
126
-
127
- /**
128
- * Start the MCP server (stdio mode)
129
- */
130
- async start(): Promise<void> {
131
- process.stdin.setEncoding('utf8');
132
-
133
- let buffer = '';
134
-
135
- process.stdin.on('data', (chunk: string) => {
136
- buffer += chunk;
137
-
138
- // Try to parse complete JSON-RPC messages
139
- const lines = buffer.split('\n');
140
- buffer = lines.pop() || '';
141
-
142
- for (const line of lines) {
143
- if (line.trim()) {
144
- this.handleMessage(line.trim());
145
- }
146
- }
147
- });
148
-
149
- process.stdin.on('end', () => {
150
- process.exit(0);
151
- });
152
- }
153
-
154
- private handleMessage(message: string): void {
155
- try {
156
- const request = JSON.parse(message) as JsonRpcRequest;
157
- this.handleRequest(request);
158
- } catch (err) {
159
- this.sendError(null, -32700, 'Parse error');
160
- }
161
- }
162
-
163
- private async handleRequest(request: JsonRpcRequest): Promise<void> {
164
- const { id, method, params } = request;
165
-
166
- try {
167
- switch (method) {
168
- case 'initialize':
169
- this.sendResult(id, {
170
- protocolVersion: '2024-11-05',
171
- capabilities: {
172
- tools: {}
173
- },
174
- serverInfo: {
175
- name: `cli4ai-${this.manifest.name}`,
176
- version: this.manifest.version
177
- }
178
- });
179
- break;
180
-
181
- case 'initialized':
182
- // No response needed
183
- break;
184
-
185
- case 'tools/list':
186
- this.sendResult(id, {
187
- tools: this.tools.map(t => ({
188
- name: t.name,
189
- description: t.description,
190
- inputSchema: t.inputSchema
191
- }))
192
- });
193
- break;
194
-
195
- case 'tools/call':
196
- await this.handleToolCall(id, params as { name: string; arguments?: Record<string, string> });
197
- break;
198
-
199
- case 'ping':
200
- this.sendResult(id, {});
201
- break;
202
-
203
- default:
204
- this.sendError(id, -32601, `Method not found: ${method}`);
205
- }
206
- } catch (err) {
207
- this.sendError(id, -32603, (err as Error).message);
208
- }
209
- }
210
-
211
- private async handleToolCall(
212
- id: number | string,
213
- params: { name: string; arguments?: Record<string, string> }
214
- ): Promise<void> {
215
- const { name, arguments: args = {} } = params;
216
-
217
- // SECURITY: Rate limiting
218
- const rateCheck = this.checkRateLimit(name);
219
- if (!rateCheck.allowed) {
220
- auditLog(this.manifest.name, name, args, 'rate_limited');
221
- this.sendError(id, -32000, `Rate limit exceeded. Retry after ${Math.ceil((rateCheck.retryAfterMs || 0) / 1000)}s`);
222
- return;
223
- }
224
-
225
- // Track total calls for monitoring
226
- this.totalCallCount++;
227
-
228
- // Parse tool name: package_command
229
- const parts = name.split('_');
230
- if (parts.length < 2 || parts[0] !== this.manifest.name) {
231
- auditLog(this.manifest.name, name, args, 'error', 'Unknown tool');
232
- this.sendError(id, -32602, `Unknown tool: ${name}`);
233
- return;
234
- }
235
-
236
- const command = parts.slice(1).join('_');
237
- const cmdDef = this.manifest.commands?.[command];
238
-
239
- if (!cmdDef) {
240
- auditLog(this.manifest.name, name, args, 'error', 'Unknown command');
241
- this.sendError(id, -32602, `Unknown command: ${command}`);
242
- return;
243
- }
244
-
245
- // Build command arguments in order
246
- const cmdArgs: string[] = [command];
247
- if (cmdDef.args) {
248
- for (const argDef of cmdDef.args) {
249
- const value = args[argDef.name];
250
- if (value !== undefined && value !== '') {
251
- cmdArgs.push(String(value));
252
- } else if (argDef.required) {
253
- auditLog(this.manifest.name, name, args, 'error', `Missing required argument: ${argDef.name}`);
254
- this.sendError(id, -32602, `Missing required argument: ${argDef.name}`);
255
- return;
256
- }
257
- }
258
- }
259
-
260
- // Execute the CLI tool
261
- const entryPath = resolve(this.packagePath, this.manifest.entry);
262
-
263
- try {
264
- const result = await this.executeCommand(entryPath, cmdArgs);
265
- auditLog(this.manifest.name, name, args, 'success');
266
- this.sendResult(id, {
267
- content: [{ type: 'text', text: result }]
268
- });
269
- } catch (err) {
270
- const errorMessage = (err as Error).message;
271
- auditLog(this.manifest.name, name, args, 'error', errorMessage);
272
- this.sendResult(id, {
273
- content: [{ type: 'text', text: errorMessage }],
274
- isError: true
275
- });
276
- }
277
- }
278
-
279
- private executeCommand(entryPath: string, args: string[]): Promise<string> {
280
- return new Promise((resolve, reject) => {
281
- // Use tsx for TypeScript files, node for JavaScript
282
- let cmd: string;
283
- let cmdArgs: string[];
284
- if (entryPath.endsWith('.ts') || entryPath.endsWith('.tsx')) {
285
- cmd = 'npx';
286
- cmdArgs = ['tsx', entryPath, ...args];
287
- } else {
288
- cmd = 'node';
289
- cmdArgs = [entryPath, ...args];
290
- }
291
-
292
- // Inject secrets from manifest env definitions
293
- // SECURITY: Use package-scoped secret lookup (tries scoped first, then global)
294
- const secretsEnv: Record<string, string> = {};
295
- if (this.manifest.env) {
296
- for (const key of Object.keys(this.manifest.env)) {
297
- const value = getSecret(key, this.manifest.name);
298
- if (value) {
299
- secretsEnv[key] = value;
300
- }
301
- }
302
- }
303
-
304
- const proc = spawn(cmd, cmdArgs, {
305
- stdio: ['pipe', 'pipe', 'pipe'],
306
- env: { ...process.env, ...secretsEnv }
307
- });
308
-
309
- let stdout = '';
310
- let stderr = '';
311
-
312
- proc.stdout.on('data', (data) => {
313
- stdout += data.toString();
314
- });
315
-
316
- proc.stderr.on('data', (data) => {
317
- stderr += data.toString();
318
- });
319
-
320
- proc.on('close', (code) => {
321
- if (code !== 0) {
322
- reject(new Error(stderr || `Exit code ${code}`));
323
- } else {
324
- resolve(stdout);
325
- }
326
- });
327
-
328
- proc.on('error', (err) => {
329
- reject(err);
330
- });
331
- });
332
- }
333
-
334
- private sendResult(id: number | string | null, result: unknown): void {
335
- if (id === null) return;
336
-
337
- const response: JsonRpcResponse = {
338
- jsonrpc: '2.0',
339
- id,
340
- result
341
- };
342
- console.log(JSON.stringify(response));
343
- }
344
-
345
- private sendError(id: number | string | null, code: number, message: string): void {
346
- // Per JSON-RPC 2.0 spec, error responses for parse errors should include id: null
347
- // For other errors where id is null (shouldn't happen), we skip the response
348
- if (id === null && code !== -32700) return;
349
-
350
- const response: JsonRpcResponse = {
351
- jsonrpc: '2.0',
352
- id: id as number | string, // For parse errors (-32700), this will be cast from null
353
- error: { code, message }
354
- };
355
- console.log(JSON.stringify(response));
356
- }
357
- }
358
-
359
- /**
360
- * Start MCP server for a package
361
- */
362
- export async function startMcpServer(manifest: Manifest, packagePath: string): Promise<void> {
363
- const server = new McpServer(manifest, packagePath);
364
- await server.start();
365
- }