@inkeep/agents-cli 0.1.0 → 0.1.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/LICENSE.md +22 -17
- package/README.md +1 -13
- package/dist/__tests__/config-validation.test.js +0 -8
- package/dist/__tests__/utils/ts-loader.test.js +0 -1
- package/dist/commands/pull.js +0 -8
- package/dist/config.d.ts +6 -36
- package/dist/config.js +9 -292
- package/dist/index.d.ts +2 -2
- package/dist/index.js +37398 -96
- package/dist/index.js.map +7 -0
- package/dist/utils/ts-loader.js +0 -1
- package/package.json +20 -19
- package/dist/__tests__/commands/pull-retry.test.d.ts +0 -1
- package/dist/__tests__/commands/pull-retry.test.js +0 -156
- package/dist/__tests__/utils/port-manager.test.d.ts +0 -1
- package/dist/__tests__/utils/port-manager.test.js +0 -144
- package/dist/commands/mcp-list.d.ts +0 -4
- package/dist/commands/mcp-list.js +0 -156
- package/dist/commands/mcp-start-simple.d.ts +0 -5
- package/dist/commands/mcp-start-simple.js +0 -193
- package/dist/commands/mcp-start.d.ts +0 -5
- package/dist/commands/mcp-start.js +0 -217
- package/dist/commands/mcp-status.d.ts +0 -1
- package/dist/commands/mcp-status.js +0 -96
- package/dist/commands/mcp-stop.d.ts +0 -5
- package/dist/commands/mcp-stop.js +0 -160
- package/dist/utils/port-manager.d.ts +0 -43
- package/dist/utils/port-manager.js +0 -92
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import { execSync, spawn } from 'node:child_process';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
3
|
-
import { dirname, resolve } from 'node:path';
|
|
4
|
-
import { pathToFileURL } from 'node:url';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
import ora from 'ora';
|
|
7
|
-
export async function mcpStartCommand(graphPath, options) {
|
|
8
|
-
let spinner = ora('Loading graph configuration...').start();
|
|
9
|
-
try {
|
|
10
|
-
// Resolve the graph path
|
|
11
|
-
const fullPath = resolve(graphPath);
|
|
12
|
-
if (!existsSync(fullPath)) {
|
|
13
|
-
spinner.fail(`Graph file not found: ${graphPath}`);
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
16
|
-
spinner.text = 'Starting MCP servers...';
|
|
17
|
-
// Get the directory of the graph file for proper module resolution
|
|
18
|
-
const graphDir = dirname(fullPath);
|
|
19
|
-
// Check if it's a TypeScript file
|
|
20
|
-
if (fullPath.endsWith('.ts')) {
|
|
21
|
-
spinner.text = 'Checking TypeScript runtime...';
|
|
22
|
-
// Check if tsx is available in the graph directory
|
|
23
|
-
try {
|
|
24
|
-
execSync('npx tsx --version', {
|
|
25
|
-
stdio: 'pipe',
|
|
26
|
-
cwd: graphDir,
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
spinner.fail('TypeScript runtime not found');
|
|
31
|
-
console.log(chalk.yellow('\nTo use TypeScript graph files:'));
|
|
32
|
-
console.log(chalk.gray(' Option 1: Compile your graph first'));
|
|
33
|
-
console.log(chalk.gray(' cd inkeep-chat && pnpm build'));
|
|
34
|
-
console.log(chalk.gray(' inkeep mcp start inkeep-chat/dist/examples/graph.graph.js'));
|
|
35
|
-
console.log(chalk.gray('\n Option 2: Ensure tsx is installed in the graph directory'));
|
|
36
|
-
console.log(chalk.gray(' cd inkeep-chat && pnpm add -D tsx'));
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
// Convert to file URL for import
|
|
41
|
-
const fileUrl = pathToFileURL(fullPath).href;
|
|
42
|
-
// Spawn tsx process to run the runner with the graph file
|
|
43
|
-
const child = spawn('npx', [
|
|
44
|
-
'tsx',
|
|
45
|
-
'--eval',
|
|
46
|
-
`
|
|
47
|
-
import('${fileUrl}').then(async module => {
|
|
48
|
-
const servers = module.servers || module.tools || [];
|
|
49
|
-
const graph = module.graph;
|
|
50
|
-
|
|
51
|
-
let graphId = 'unknown';
|
|
52
|
-
if (graph && typeof graph.getId === 'function') {
|
|
53
|
-
graphId = graph.getId();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const startedServers = [];
|
|
57
|
-
let nextPort = 3100;
|
|
58
|
-
|
|
59
|
-
for (const server of servers) {
|
|
60
|
-
if (!server) continue;
|
|
61
|
-
|
|
62
|
-
// Try to get name using getName() method or direct property access
|
|
63
|
-
let name = 'unnamed';
|
|
64
|
-
if (typeof server.getName === 'function') {
|
|
65
|
-
name = server.getName();
|
|
66
|
-
} else if (server.config && server.config.name) {
|
|
67
|
-
name = server.config.name;
|
|
68
|
-
} else if (server.name) {
|
|
69
|
-
name = server.name;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Try to get server URL
|
|
73
|
-
let serverUrl = undefined;
|
|
74
|
-
if (typeof server.getServerUrl === 'function') {
|
|
75
|
-
serverUrl = server.getServerUrl();
|
|
76
|
-
} else if (server.config && server.config.serverUrl) {
|
|
77
|
-
serverUrl = server.config.serverUrl;
|
|
78
|
-
} else if (server.serverUrl) {
|
|
79
|
-
serverUrl = server.serverUrl;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const isLocal = !serverUrl;
|
|
83
|
-
const port = isLocal ? (server.port || server.config?.port || nextPort++) : undefined;
|
|
84
|
-
|
|
85
|
-
console.log(JSON.stringify({
|
|
86
|
-
type: 'server_started',
|
|
87
|
-
name,
|
|
88
|
-
port,
|
|
89
|
-
serverUrl,
|
|
90
|
-
deployment: isLocal ? 'local' : 'remote'
|
|
91
|
-
}));
|
|
92
|
-
|
|
93
|
-
startedServers.push(name);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
console.log(JSON.stringify({
|
|
97
|
-
type: 'all_started',
|
|
98
|
-
count: startedServers.length,
|
|
99
|
-
graphId
|
|
100
|
-
}));
|
|
101
|
-
|
|
102
|
-
if (!${options.detached || false}) {
|
|
103
|
-
// Keep process alive
|
|
104
|
-
process.stdin.resume();
|
|
105
|
-
|
|
106
|
-
process.on('SIGINT', () => {
|
|
107
|
-
console.log(JSON.stringify({ type: 'shutting_down' }));
|
|
108
|
-
process.exit(0);
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
}).catch(error => {
|
|
112
|
-
console.error(JSON.stringify({
|
|
113
|
-
type: 'error',
|
|
114
|
-
message: error.message
|
|
115
|
-
}));
|
|
116
|
-
process.exit(1);
|
|
117
|
-
});
|
|
118
|
-
`,
|
|
119
|
-
], {
|
|
120
|
-
stdio: ['inherit', 'pipe', 'pipe'],
|
|
121
|
-
cwd: graphDir, // Set working directory to graph file's directory
|
|
122
|
-
env: {
|
|
123
|
-
...process.env,
|
|
124
|
-
ENVIRONMENT: process.env.ENVIRONMENT || 'test',
|
|
125
|
-
DB_FILE_NAME: process.env.DB_FILE_NAME || ':memory:',
|
|
126
|
-
INKEEP_TENANT_ID: process.env.INKEEP_TENANT_ID || 'test-tenant',
|
|
127
|
-
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || 'test-key',
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
let serverCount = 0;
|
|
131
|
-
let graphId = 'unknown';
|
|
132
|
-
// Handle stdout
|
|
133
|
-
child.stdout?.on('data', (data) => {
|
|
134
|
-
const lines = data.toString().split('\n').filter(Boolean);
|
|
135
|
-
for (const line of lines) {
|
|
136
|
-
try {
|
|
137
|
-
const msg = JSON.parse(line);
|
|
138
|
-
if (msg.type === 'server_started') {
|
|
139
|
-
const icon = msg.deployment === 'local' ? '🏠' : '☁️';
|
|
140
|
-
const location = msg.deployment === 'local'
|
|
141
|
-
? `on port ${chalk.cyan(msg.port)}`
|
|
142
|
-
: `at ${chalk.cyan(msg.serverUrl)}`;
|
|
143
|
-
spinner.succeed(`${icon} Started ${chalk.green(msg.name)} ${location}`);
|
|
144
|
-
spinner = ora().start(); // Create new spinner for next server
|
|
145
|
-
serverCount++;
|
|
146
|
-
}
|
|
147
|
-
else if (msg.type === 'all_started') {
|
|
148
|
-
spinner.stop();
|
|
149
|
-
console.log(chalk.green(`\n✅ Started ${msg.count} MCP server(s)`));
|
|
150
|
-
graphId = msg.graphId || 'unknown';
|
|
151
|
-
if (!options.detached) {
|
|
152
|
-
console.log(chalk.gray('\nServers are running. Press Ctrl+C to stop all servers.\n'));
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
else if (msg.type === 'shutting_down') {
|
|
156
|
-
console.log(chalk.yellow('\n\nShutting down MCP servers...'));
|
|
157
|
-
}
|
|
158
|
-
else if (msg.type === 'error') {
|
|
159
|
-
spinner.fail(`Error: ${msg.message}`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
catch {
|
|
163
|
-
// Not JSON, just print it
|
|
164
|
-
if (options.verbose) {
|
|
165
|
-
console.log(chalk.gray(line));
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
// Handle stderr
|
|
171
|
-
child.stderr?.on('data', (data) => {
|
|
172
|
-
if (options.verbose) {
|
|
173
|
-
console.error(chalk.red(data.toString()));
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
// Handle exit
|
|
177
|
-
child.on('exit', (code) => {
|
|
178
|
-
if (code !== 0 && code !== null) {
|
|
179
|
-
spinner.fail('Failed to start MCP servers');
|
|
180
|
-
process.exit(code);
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
// Forward SIGINT to child
|
|
184
|
-
process.on('SIGINT', () => {
|
|
185
|
-
child.kill('SIGINT');
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
catch (error) {
|
|
189
|
-
spinner.fail('Failed to start MCP servers');
|
|
190
|
-
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
|
|
191
|
-
process.exit(1);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join, resolve } from 'node:path';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
import ora from 'ora';
|
|
6
|
-
import { PortManager } from '../utils/port-manager.js';
|
|
7
|
-
import { loadTypeScriptModule } from '../utils/ts-loader.js';
|
|
8
|
-
const MCP_DIR = join(homedir(), '.inkeep', 'mcp');
|
|
9
|
-
const REGISTRY_FILE = join(MCP_DIR, 'servers.json');
|
|
10
|
-
// Ensure MCP directory exists
|
|
11
|
-
if (!existsSync(MCP_DIR)) {
|
|
12
|
-
mkdirSync(MCP_DIR, { recursive: true });
|
|
13
|
-
}
|
|
14
|
-
function loadRegistry() {
|
|
15
|
-
if (!existsSync(REGISTRY_FILE)) {
|
|
16
|
-
return { servers: [] };
|
|
17
|
-
}
|
|
18
|
-
try {
|
|
19
|
-
return JSON.parse(readFileSync(REGISTRY_FILE, 'utf-8'));
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
return { servers: [] };
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function saveRegistry(registry) {
|
|
26
|
-
writeFileSync(REGISTRY_FILE, JSON.stringify(registry, null, 2));
|
|
27
|
-
}
|
|
28
|
-
function cleanupStaleServers(registry) {
|
|
29
|
-
// Remove servers whose processes are no longer running
|
|
30
|
-
const activeServers = registry.servers.filter((server) => {
|
|
31
|
-
try {
|
|
32
|
-
// Check if process is still running
|
|
33
|
-
process.kill(server.pid, 0);
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
return { servers: activeServers };
|
|
41
|
-
}
|
|
42
|
-
export async function mcpStartCommand(graphPath, options) {
|
|
43
|
-
const spinner = ora('Loading graph configuration...').start();
|
|
44
|
-
try {
|
|
45
|
-
// Resolve the graph path
|
|
46
|
-
const fullPath = resolve(graphPath);
|
|
47
|
-
if (!existsSync(fullPath)) {
|
|
48
|
-
spinner.fail(`Graph file not found: ${graphPath}`);
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
spinner.text = 'Importing graph configuration...';
|
|
52
|
-
// Import the graph module
|
|
53
|
-
let module;
|
|
54
|
-
if (fullPath.endsWith('.ts')) {
|
|
55
|
-
// For TypeScript files, use our loader
|
|
56
|
-
try {
|
|
57
|
-
module = await loadTypeScriptModule(fullPath);
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
spinner.fail('Failed to load TypeScript graph configuration');
|
|
61
|
-
console.error(chalk.red('Error:'), error.message);
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
// For JavaScript files, import directly
|
|
67
|
-
module = await import(fullPath);
|
|
68
|
-
}
|
|
69
|
-
// Find exported tools (both regular exports and default export)
|
|
70
|
-
const tools = [];
|
|
71
|
-
const toolMap = new Map();
|
|
72
|
-
// Check for exported servers (new convention)
|
|
73
|
-
if (module.servers && module.servers.__type === 'array' && module.servers.items) {
|
|
74
|
-
tools.push(...module.servers.items);
|
|
75
|
-
}
|
|
76
|
-
// Check for exported tools (backwards compatibility)
|
|
77
|
-
if (module.tools && module.tools.__type === 'array' && module.tools.items) {
|
|
78
|
-
tools.push(...module.tools.items);
|
|
79
|
-
}
|
|
80
|
-
// Check for individual tool exports
|
|
81
|
-
for (const [key, value] of Object.entries(module)) {
|
|
82
|
-
if (key === 'default' || key === 'graph' || key === 'tools' || key === 'servers')
|
|
83
|
-
continue;
|
|
84
|
-
// Check if it looks like a tool (has execute function or is an IPC tool)
|
|
85
|
-
if (value && typeof value === 'object') {
|
|
86
|
-
const hasExecute = value.hasExecute === true;
|
|
87
|
-
const hasInit = value.hasInit === true;
|
|
88
|
-
const hasGetServerUrl = value.hasGetServerUrl === true;
|
|
89
|
-
if (hasExecute || (hasInit && hasGetServerUrl)) {
|
|
90
|
-
tools.push(value);
|
|
91
|
-
toolMap.set(key, value);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
// Get graph ID
|
|
96
|
-
let graphId = 'unknown';
|
|
97
|
-
if (module.graph && module.graph.graphId) {
|
|
98
|
-
graphId = module.graph.graphId;
|
|
99
|
-
}
|
|
100
|
-
if (tools.length === 0) {
|
|
101
|
-
spinner.warn('No MCP tools found in graph configuration');
|
|
102
|
-
console.log(chalk.gray('\nTo use MCP tools, export them from your graph file:'));
|
|
103
|
-
console.log(chalk.gray(' export const myTool = ipcTool({ ... });'));
|
|
104
|
-
console.log(chalk.gray(' export const tools = [myTool, anotherTool];'));
|
|
105
|
-
process.exit(0);
|
|
106
|
-
}
|
|
107
|
-
spinner.succeed(`Found ${tools.length} MCP tool(s) in graph`);
|
|
108
|
-
// Load and clean registry
|
|
109
|
-
const registry = cleanupStaleServers(loadRegistry());
|
|
110
|
-
// Start each tool
|
|
111
|
-
const startedServers = [];
|
|
112
|
-
const portManager = PortManager.getInstance();
|
|
113
|
-
for (const tool of tools) {
|
|
114
|
-
const toolSpinner = ora(`Starting MCP server: ${tool.name || 'unnamed'}`).start();
|
|
115
|
-
try {
|
|
116
|
-
// Check if tool is already running
|
|
117
|
-
const existingServer = registry.servers.find((s) => s.graphId === graphId && s.toolId === (tool.id || tool.name));
|
|
118
|
-
if (existingServer) {
|
|
119
|
-
// Check if process is still alive
|
|
120
|
-
try {
|
|
121
|
-
process.kill(existingServer.pid, 0);
|
|
122
|
-
toolSpinner.warn(`Server ${tool.name} already running (PID: ${existingServer.pid})`);
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
// Process is dead, remove from registry
|
|
127
|
-
registry.servers = registry.servers.filter((s) => s.pid !== existingServer.pid);
|
|
128
|
-
portManager.releasePort(existingServer.port);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
// Determine deployment type
|
|
132
|
-
const isLocal = tool.hasExecute === true ||
|
|
133
|
-
tool.hasInit === true ||
|
|
134
|
-
(!tool.serverUrl && !tool.deployment);
|
|
135
|
-
const deployment = tool.deployment || (isLocal ? 'local' : 'remote');
|
|
136
|
-
// For TypeScript files, we can't actually initialize or call methods
|
|
137
|
-
// We just use the metadata we extracted
|
|
138
|
-
// Get server details
|
|
139
|
-
let port;
|
|
140
|
-
let serverUrl;
|
|
141
|
-
if (deployment === 'local') {
|
|
142
|
-
// Allocate port for local server
|
|
143
|
-
if (tool.port) {
|
|
144
|
-
port = await portManager.allocatePort(tool.port);
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
port = await portManager.allocatePort();
|
|
148
|
-
}
|
|
149
|
-
serverUrl = `http://localhost:${port}/mcp`;
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
// Remote server
|
|
153
|
-
serverUrl = tool.serverUrl;
|
|
154
|
-
}
|
|
155
|
-
const server = {
|
|
156
|
-
pid: process.pid, // Will be updated if we spawn a subprocess
|
|
157
|
-
graphId,
|
|
158
|
-
toolId: tool.id || tool.name || 'unknown',
|
|
159
|
-
name: tool.name || 'unnamed',
|
|
160
|
-
port,
|
|
161
|
-
serverUrl,
|
|
162
|
-
deployment,
|
|
163
|
-
transport: tool.transport || 'ipc',
|
|
164
|
-
command: graphPath,
|
|
165
|
-
startedAt: new Date().toISOString(),
|
|
166
|
-
description: tool.description,
|
|
167
|
-
};
|
|
168
|
-
startedServers.push(server);
|
|
169
|
-
registry.servers.push(server);
|
|
170
|
-
const deploymentIcon = deployment === 'local' ? '🏠' : '☁️';
|
|
171
|
-
toolSpinner.succeed(`${deploymentIcon} Started ${chalk.green(tool.name || 'unnamed')} ` +
|
|
172
|
-
(deployment === 'local' ? `on port ${chalk.cyan(port)}` : `at ${chalk.cyan(serverUrl)}`));
|
|
173
|
-
if (options.verbose) {
|
|
174
|
-
console.log(chalk.gray(` ID: ${tool.id}`));
|
|
175
|
-
console.log(chalk.gray(` Type: ${deployment} MCP server`));
|
|
176
|
-
console.log(chalk.gray(` Description: ${tool.description || 'N/A'}`));
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
catch (error) {
|
|
180
|
-
toolSpinner.fail(`Failed to start ${tool.name || 'unnamed'}: ${error}`);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// Save updated registry
|
|
184
|
-
saveRegistry(registry);
|
|
185
|
-
console.log(chalk.green(`\n✅ Started ${startedServers.length} MCP server(s)`));
|
|
186
|
-
if (!options.detached) {
|
|
187
|
-
console.log(chalk.gray('\nServers are running. Press Ctrl+C to stop all servers.\n'));
|
|
188
|
-
// Handle graceful shutdown
|
|
189
|
-
process.on('SIGINT', () => {
|
|
190
|
-
console.log(chalk.yellow('\n\nShutting down MCP servers...'));
|
|
191
|
-
// Stop all tools
|
|
192
|
-
for (const tool of tools) {
|
|
193
|
-
if (typeof tool.stop === 'function') {
|
|
194
|
-
try {
|
|
195
|
-
tool.stop();
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
console.error(chalk.red(`Error stopping tool: ${error}`));
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
// Remove from registry
|
|
203
|
-
const updatedRegistry = loadRegistry();
|
|
204
|
-
updatedRegistry.servers = updatedRegistry.servers.filter((s) => !startedServers.some((started) => started.pid === s.pid));
|
|
205
|
-
saveRegistry(updatedRegistry);
|
|
206
|
-
process.exit(0);
|
|
207
|
-
});
|
|
208
|
-
// Keep process alive
|
|
209
|
-
process.stdin.resume();
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
catch (error) {
|
|
213
|
-
spinner.fail('Failed to start MCP servers');
|
|
214
|
-
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
|
|
215
|
-
process.exit(1);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function mcpStatusCommand(): Promise<void>;
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
import Table from 'cli-table3';
|
|
6
|
-
const MCP_DIR = join(homedir(), '.inkeep', 'mcp');
|
|
7
|
-
const REGISTRY_FILE = join(MCP_DIR, 'servers.json');
|
|
8
|
-
function loadRegistry() {
|
|
9
|
-
if (!existsSync(REGISTRY_FILE)) {
|
|
10
|
-
return { servers: [] };
|
|
11
|
-
}
|
|
12
|
-
try {
|
|
13
|
-
return JSON.parse(readFileSync(REGISTRY_FILE, 'utf-8'));
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
return { servers: [] };
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function isProcessRunning(pid) {
|
|
20
|
-
try {
|
|
21
|
-
process.kill(pid, 0);
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function formatUptime(ms) {
|
|
29
|
-
const seconds = Math.floor(ms / 1000);
|
|
30
|
-
const minutes = Math.floor(seconds / 60);
|
|
31
|
-
const hours = Math.floor(minutes / 60);
|
|
32
|
-
const days = Math.floor(hours / 24);
|
|
33
|
-
if (days > 0)
|
|
34
|
-
return `${days}d ${hours % 24}h`;
|
|
35
|
-
if (hours > 0)
|
|
36
|
-
return `${hours}h ${minutes % 60}m`;
|
|
37
|
-
if (minutes > 0)
|
|
38
|
-
return `${minutes}m ${seconds % 60}s`;
|
|
39
|
-
return `${seconds}s`;
|
|
40
|
-
}
|
|
41
|
-
export async function mcpStatusCommand() {
|
|
42
|
-
const registry = loadRegistry();
|
|
43
|
-
if (registry.servers.length === 0) {
|
|
44
|
-
console.log(chalk.gray('No MCP servers registered'));
|
|
45
|
-
console.log(chalk.gray('\nStart servers with: inkeep mcp start <graph-file>'));
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
// Create table
|
|
49
|
-
const table = new Table({
|
|
50
|
-
head: [
|
|
51
|
-
chalk.cyan('PID'),
|
|
52
|
-
chalk.cyan('Graph'),
|
|
53
|
-
chalk.cyan('Tool'),
|
|
54
|
-
chalk.cyan('Port'),
|
|
55
|
-
chalk.cyan('Status'),
|
|
56
|
-
chalk.cyan('Uptime'),
|
|
57
|
-
],
|
|
58
|
-
style: {
|
|
59
|
-
head: [],
|
|
60
|
-
border: ['gray'],
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
let activeCount = 0;
|
|
64
|
-
let staleCount = 0;
|
|
65
|
-
for (const server of registry.servers) {
|
|
66
|
-
const isRunning = isProcessRunning(server.pid);
|
|
67
|
-
const uptime = new Date().getTime() - new Date(server.startedAt).getTime();
|
|
68
|
-
const uptimeStr = formatUptime(uptime);
|
|
69
|
-
if (isRunning) {
|
|
70
|
-
activeCount++;
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
staleCount++;
|
|
74
|
-
}
|
|
75
|
-
table.push([
|
|
76
|
-
isRunning ? chalk.green(server.pid.toString()) : chalk.red(server.pid.toString()),
|
|
77
|
-
server.graphId,
|
|
78
|
-
server.toolId,
|
|
79
|
-
server.port ? chalk.cyan(server.port.toString()) : chalk.gray('N/A'),
|
|
80
|
-
isRunning ? chalk.green('● Running') : chalk.red('● Stopped'),
|
|
81
|
-
isRunning ? uptimeStr : chalk.gray('N/A'),
|
|
82
|
-
]);
|
|
83
|
-
}
|
|
84
|
-
console.log(chalk.bold('\n📡 MCP Server Status\n'));
|
|
85
|
-
console.log(table.toString());
|
|
86
|
-
console.log();
|
|
87
|
-
console.log(chalk.gray('Summary:'));
|
|
88
|
-
console.log(chalk.green(` ● ${activeCount} active`), chalk.red(` ● ${staleCount} stopped`));
|
|
89
|
-
if (staleCount > 0) {
|
|
90
|
-
console.log(chalk.yellow('\n⚠️ Some servers have stopped. Run "inkeep mcp stop --all" to clean up.'));
|
|
91
|
-
}
|
|
92
|
-
console.log(chalk.gray('\nCommands:'));
|
|
93
|
-
console.log(chalk.gray(' • Start servers: inkeep mcp start <graph-file>'));
|
|
94
|
-
console.log(chalk.gray(' • Stop servers: inkeep mcp stop'));
|
|
95
|
-
console.log(chalk.gray(' • Stop all: inkeep mcp stop --all'));
|
|
96
|
-
}
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
import inquirer from 'inquirer';
|
|
6
|
-
import ora from 'ora';
|
|
7
|
-
const MCP_DIR = join(homedir(), '.inkeep', 'mcp');
|
|
8
|
-
const REGISTRY_FILE = join(MCP_DIR, 'servers.json');
|
|
9
|
-
function loadRegistry() {
|
|
10
|
-
if (!existsSync(REGISTRY_FILE)) {
|
|
11
|
-
return { servers: [] };
|
|
12
|
-
}
|
|
13
|
-
try {
|
|
14
|
-
return JSON.parse(readFileSync(REGISTRY_FILE, 'utf-8'));
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
return { servers: [] };
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function saveRegistry(registry) {
|
|
21
|
-
writeFileSync(REGISTRY_FILE, JSON.stringify(registry, null, 2));
|
|
22
|
-
}
|
|
23
|
-
function stopServer(server) {
|
|
24
|
-
try {
|
|
25
|
-
process.kill(server.pid, 'SIGTERM');
|
|
26
|
-
// Give it a moment to terminate gracefully
|
|
27
|
-
setTimeout(() => {
|
|
28
|
-
try {
|
|
29
|
-
// Check if still running and force kill if needed
|
|
30
|
-
process.kill(server.pid, 0);
|
|
31
|
-
process.kill(server.pid, 'SIGKILL');
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
// Process already terminated
|
|
35
|
-
}
|
|
36
|
-
}, 1000);
|
|
37
|
-
return true;
|
|
38
|
-
}
|
|
39
|
-
catch (error) {
|
|
40
|
-
// Process might already be dead
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
export async function mcpStopCommand(options) {
|
|
45
|
-
const spinner = ora('Loading MCP server registry...').start();
|
|
46
|
-
try {
|
|
47
|
-
const registry = loadRegistry();
|
|
48
|
-
if (registry.servers.length === 0) {
|
|
49
|
-
spinner.info('No MCP servers are currently running');
|
|
50
|
-
process.exit(0);
|
|
51
|
-
}
|
|
52
|
-
spinner.stop();
|
|
53
|
-
let serversToStop = [];
|
|
54
|
-
if (options.all) {
|
|
55
|
-
// Stop all servers
|
|
56
|
-
serversToStop = registry.servers;
|
|
57
|
-
}
|
|
58
|
-
else if (options.graph) {
|
|
59
|
-
// Stop servers for specific graph
|
|
60
|
-
serversToStop = registry.servers.filter((s) => s.graphId === options.graph);
|
|
61
|
-
if (serversToStop.length === 0) {
|
|
62
|
-
console.log(chalk.yellow(`No servers found for graph: ${options.graph}`));
|
|
63
|
-
process.exit(0);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
// Interactive selection
|
|
68
|
-
const groupedServers = new Map();
|
|
69
|
-
// Group servers by graph
|
|
70
|
-
for (const server of registry.servers) {
|
|
71
|
-
const servers = groupedServers.get(server.graphId) || [];
|
|
72
|
-
servers.push(server);
|
|
73
|
-
groupedServers.set(server.graphId, servers);
|
|
74
|
-
}
|
|
75
|
-
// Create choices
|
|
76
|
-
const choices = [
|
|
77
|
-
{ name: chalk.red('Stop all servers'), value: 'all' },
|
|
78
|
-
new inquirer.Separator(),
|
|
79
|
-
];
|
|
80
|
-
for (const [graphId, servers] of groupedServers) {
|
|
81
|
-
choices.push({
|
|
82
|
-
name: `${chalk.cyan(graphId)} (${servers.length} server${servers.length > 1 ? 's' : ''})`,
|
|
83
|
-
value: graphId,
|
|
84
|
-
});
|
|
85
|
-
for (const server of servers) {
|
|
86
|
-
const uptime = new Date().getTime() - new Date(server.startedAt).getTime();
|
|
87
|
-
const uptimeStr = formatUptime(uptime);
|
|
88
|
-
choices.push({
|
|
89
|
-
name: chalk.gray(` └─ ${server.toolId} `) +
|
|
90
|
-
(server.port ? chalk.gray(`(port ${server.port}) `) : '') +
|
|
91
|
-
chalk.gray(`[${uptimeStr}]`),
|
|
92
|
-
value: `tool:${server.pid}`,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
const answer = await inquirer.prompt([
|
|
97
|
-
{
|
|
98
|
-
type: 'list',
|
|
99
|
-
name: 'selection',
|
|
100
|
-
message: 'Select servers to stop:',
|
|
101
|
-
choices,
|
|
102
|
-
pageSize: 15,
|
|
103
|
-
},
|
|
104
|
-
]);
|
|
105
|
-
if (answer.selection === 'all') {
|
|
106
|
-
serversToStop = registry.servers;
|
|
107
|
-
}
|
|
108
|
-
else if (answer.selection.startsWith('tool:')) {
|
|
109
|
-
const pid = parseInt(answer.selection.replace('tool:', ''));
|
|
110
|
-
serversToStop = registry.servers.filter((s) => s.pid === pid);
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
serversToStop = registry.servers.filter((s) => s.graphId === answer.selection);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
if (serversToStop.length === 0) {
|
|
117
|
-
console.log(chalk.yellow('No servers selected'));
|
|
118
|
-
process.exit(0);
|
|
119
|
-
}
|
|
120
|
-
// Stop the servers
|
|
121
|
-
const stopSpinner = ora(`Stopping ${serversToStop.length} server(s)...`).start();
|
|
122
|
-
let stoppedCount = 0;
|
|
123
|
-
for (const server of serversToStop) {
|
|
124
|
-
const stopped = stopServer(server);
|
|
125
|
-
if (stopped) {
|
|
126
|
-
stoppedCount++;
|
|
127
|
-
if (options.all || options.graph) {
|
|
128
|
-
console.log(chalk.gray(` • Stopped ${server.toolId} (PID: ${server.pid})`));
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
// Update registry
|
|
133
|
-
registry.servers = registry.servers.filter((s) => !serversToStop.some((toStop) => toStop.pid === s.pid));
|
|
134
|
-
saveRegistry(registry);
|
|
135
|
-
if (stoppedCount > 0) {
|
|
136
|
-
stopSpinner.succeed(`Stopped ${stoppedCount} MCP server(s)`);
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
stopSpinner.warn('No servers were stopped (they may have already terminated)');
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
catch (error) {
|
|
143
|
-
spinner.fail('Failed to stop MCP servers');
|
|
144
|
-
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
|
|
145
|
-
process.exit(1);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
function formatUptime(ms) {
|
|
149
|
-
const seconds = Math.floor(ms / 1000);
|
|
150
|
-
const minutes = Math.floor(seconds / 60);
|
|
151
|
-
const hours = Math.floor(minutes / 60);
|
|
152
|
-
const days = Math.floor(hours / 24);
|
|
153
|
-
if (days > 0)
|
|
154
|
-
return `${days}d ${hours % 24}h`;
|
|
155
|
-
if (hours > 0)
|
|
156
|
-
return `${hours}h ${minutes % 60}m`;
|
|
157
|
-
if (minutes > 0)
|
|
158
|
-
return `${minutes}m ${seconds % 60}s`;
|
|
159
|
-
return `${seconds}s`;
|
|
160
|
-
}
|