@inkeep/agents-cli 0.1.0

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 (75) hide show
  1. package/LICENSE.md +51 -0
  2. package/README.md +512 -0
  3. package/dist/__tests__/api.test.d.ts +1 -0
  4. package/dist/__tests__/api.test.js +257 -0
  5. package/dist/__tests__/cli.test.d.ts +1 -0
  6. package/dist/__tests__/cli.test.js +153 -0
  7. package/dist/__tests__/commands/config.test.d.ts +1 -0
  8. package/dist/__tests__/commands/config.test.js +154 -0
  9. package/dist/__tests__/commands/init.test.d.ts +1 -0
  10. package/dist/__tests__/commands/init.test.js +186 -0
  11. package/dist/__tests__/commands/pull-retry.test.d.ts +1 -0
  12. package/dist/__tests__/commands/pull-retry.test.js +156 -0
  13. package/dist/__tests__/commands/pull.test.d.ts +1 -0
  14. package/dist/__tests__/commands/pull.test.js +54 -0
  15. package/dist/__tests__/commands/push-spinner.test.d.ts +1 -0
  16. package/dist/__tests__/commands/push-spinner.test.js +127 -0
  17. package/dist/__tests__/commands/push.test.d.ts +1 -0
  18. package/dist/__tests__/commands/push.test.js +265 -0
  19. package/dist/__tests__/config-validation.test.d.ts +1 -0
  20. package/dist/__tests__/config-validation.test.js +106 -0
  21. package/dist/__tests__/package.test.d.ts +1 -0
  22. package/dist/__tests__/package.test.js +82 -0
  23. package/dist/__tests__/utils/json-comparator.test.d.ts +1 -0
  24. package/dist/__tests__/utils/json-comparator.test.js +174 -0
  25. package/dist/__tests__/utils/port-manager.test.d.ts +1 -0
  26. package/dist/__tests__/utils/port-manager.test.js +144 -0
  27. package/dist/__tests__/utils/ts-loader.test.d.ts +1 -0
  28. package/dist/__tests__/utils/ts-loader.test.js +233 -0
  29. package/dist/api.d.ts +23 -0
  30. package/dist/api.js +140 -0
  31. package/dist/commands/chat-enhanced.d.ts +7 -0
  32. package/dist/commands/chat-enhanced.js +396 -0
  33. package/dist/commands/chat.d.ts +5 -0
  34. package/dist/commands/chat.js +125 -0
  35. package/dist/commands/config.d.ts +6 -0
  36. package/dist/commands/config.js +128 -0
  37. package/dist/commands/init.d.ts +5 -0
  38. package/dist/commands/init.js +171 -0
  39. package/dist/commands/list-graphs.d.ts +6 -0
  40. package/dist/commands/list-graphs.js +131 -0
  41. package/dist/commands/mcp-list.d.ts +4 -0
  42. package/dist/commands/mcp-list.js +156 -0
  43. package/dist/commands/mcp-start-simple.d.ts +5 -0
  44. package/dist/commands/mcp-start-simple.js +193 -0
  45. package/dist/commands/mcp-start.d.ts +5 -0
  46. package/dist/commands/mcp-start.js +217 -0
  47. package/dist/commands/mcp-status.d.ts +1 -0
  48. package/dist/commands/mcp-status.js +96 -0
  49. package/dist/commands/mcp-stop.d.ts +5 -0
  50. package/dist/commands/mcp-stop.js +160 -0
  51. package/dist/commands/pull.d.ts +15 -0
  52. package/dist/commands/pull.js +313 -0
  53. package/dist/commands/pull.llm-generate.d.ts +10 -0
  54. package/dist/commands/pull.llm-generate.js +184 -0
  55. package/dist/commands/push.d.ts +6 -0
  56. package/dist/commands/push.js +268 -0
  57. package/dist/config.d.ts +43 -0
  58. package/dist/config.js +292 -0
  59. package/dist/exports.d.ts +2 -0
  60. package/dist/exports.js +2 -0
  61. package/dist/index.d.ts +2 -0
  62. package/dist/index.js +98 -0
  63. package/dist/types/config.d.ts +9 -0
  64. package/dist/types/config.js +3 -0
  65. package/dist/types/graph.d.ts +10 -0
  66. package/dist/types/graph.js +1 -0
  67. package/dist/utils/json-comparator.d.ts +60 -0
  68. package/dist/utils/json-comparator.js +222 -0
  69. package/dist/utils/mcp-runner.d.ts +6 -0
  70. package/dist/utils/mcp-runner.js +147 -0
  71. package/dist/utils/port-manager.d.ts +43 -0
  72. package/dist/utils/port-manager.js +92 -0
  73. package/dist/utils/ts-loader.d.ts +5 -0
  74. package/dist/utils/ts-loader.js +146 -0
  75. package/package.json +77 -0
package/dist/index.js ADDED
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { Command } from 'commander';
6
+ import { configGetCommand, configListCommand, configSetCommand } from './commands/config.js';
7
+ import { initCommand } from './commands/init.js';
8
+ import { listGraphsCommand } from './commands/list-graphs.js';
9
+ import { pullCommand } from './commands/pull.js';
10
+ import { pushCommand } from './commands/push.js';
11
+ // Get the current directory for ESM
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+ // Read package.json to get version
15
+ const packageJsonPath = join(__dirname, '..', 'package.json');
16
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
17
+ const program = new Command();
18
+ program
19
+ .name('inkeep')
20
+ .description('CLI tool for Inkeep Agent Framework')
21
+ .version(packageJson.version);
22
+ // Init command
23
+ program
24
+ .command('init [path]')
25
+ .description('Initialize a new Inkeep configuration file')
26
+ .option('--no-interactive', 'Skip interactive path selection')
27
+ .action(async (path, options) => {
28
+ await initCommand({ path, ...options });
29
+ });
30
+ // Config command with subcommands
31
+ const configCommand = program.command('config').description('Manage Inkeep configuration');
32
+ configCommand
33
+ .command('get [key]')
34
+ .description('Get configuration value(s)')
35
+ .option('--config-file-path <path>', 'Path to configuration file')
36
+ .action(async (key, options) => {
37
+ await configGetCommand(key, options);
38
+ });
39
+ configCommand
40
+ .command('set <key> <value>')
41
+ .description('Set a configuration value')
42
+ .option('--config-file-path <path>', 'Path to configuration file')
43
+ .action(async (key, value, options) => {
44
+ await configSetCommand(key, value, options);
45
+ });
46
+ configCommand
47
+ .command('list')
48
+ .description('List all configuration values')
49
+ .option('--config-file-path <path>', 'Path to configuration file')
50
+ .action(async (options) => {
51
+ await configListCommand(options);
52
+ });
53
+ // Push command
54
+ program
55
+ .command('push <graph-path>')
56
+ .description('Push a graph configuration to the backend')
57
+ .option('--tenant-id <tenant-id>', 'Tenant ID (use with --api-url)')
58
+ .option('--api-url <api-url>', 'API URL (use with --tenant-id or alone to override config)')
59
+ .option('--config-file-path <path>', 'Path to configuration file (alternative to --tenant-id/--api-url)')
60
+ .action(async (graphPath, options) => {
61
+ await pushCommand(graphPath, options);
62
+ });
63
+ // Pull command
64
+ program
65
+ .command('pull <graph-id>')
66
+ .description('Pull a graph configuration from the backend and generate TypeScript file')
67
+ .option('--tenant-id <tenant-id>', 'Tenant ID (use with --api-url)')
68
+ .option('--api-url <api-url>', 'API URL (use with --tenant-id or alone to override config)')
69
+ .option('--config-file-path <path>', 'Path to configuration file (alternative to --tenant-id/--api-url)')
70
+ .option('--output-path <path>', 'Output directory for the generated file (overrides config)')
71
+ .option('--json', 'Output as JSON file instead of TypeScript')
72
+ .action(async (graphId, options) => {
73
+ await pullCommand(graphId, options);
74
+ });
75
+ // Chat command
76
+ program
77
+ .command('chat [graph-id]')
78
+ .description('Start an interactive chat session with a graph (interactive selection if no ID provided)')
79
+ .option('--tenant-id <tenant-id>', 'Tenant ID (use with --api-url)')
80
+ .option('--api-url <api-url>', 'API URL (use with --tenant-id or alone to override config)')
81
+ .option('--config-file-path <path>', 'Path to configuration file (alternative to --tenant-id/--api-url)')
82
+ .action(async (graphId, options) => {
83
+ // Import the enhanced version with autocomplete
84
+ const { chatCommandEnhanced } = await import('./commands/chat-enhanced.js');
85
+ await chatCommandEnhanced(graphId, options);
86
+ });
87
+ // List graphs command
88
+ program
89
+ .command('list-graphs')
90
+ .description('List all available graphs for the current tenant')
91
+ .option('--tenant-id <tenant-id>', 'Tenant ID (use with --api-url)')
92
+ .option('--api-url <api-url>', 'API URL (use with --tenant-id or alone to override config)')
93
+ .option('--config-file-path <path>', 'Path to configuration file (alternative to --tenant-id/--api-url)')
94
+ .action(async (options) => {
95
+ await listGraphsCommand(options);
96
+ });
97
+ // Parse command line arguments
98
+ program.parse();
@@ -0,0 +1,9 @@
1
+ import type { ModelSettings } from '@inkeep/agents-core';
2
+ export interface InkeepConfig {
3
+ tenantId: string;
4
+ projectId: string;
5
+ apiUrl?: string;
6
+ outputDirectory?: string;
7
+ modelSettings?: ModelSettings;
8
+ }
9
+ export declare function defineConfig(config: InkeepConfig): InkeepConfig;
@@ -0,0 +1,3 @@
1
+ export function defineConfig(config) {
2
+ return config;
3
+ }
@@ -0,0 +1,10 @@
1
+ export interface GraphData {
2
+ id: string;
3
+ name: string;
4
+ description?: string;
5
+ agents: Record<string, any>;
6
+ tools: Record<string, any>;
7
+ contextConfigs: any[];
8
+ credentialReferences: any[];
9
+ }
10
+ export type FullGraphDefinition = GraphData;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Options for JSON comparison
3
+ */
4
+ export interface ComparisonOptions {
5
+ /** Whether to ignore order in arrays (default: true) */
6
+ ignoreArrayOrder?: boolean;
7
+ /** Whether to ignore case in string comparisons (default: false) */
8
+ ignoreCase?: boolean;
9
+ /** Whether to ignore whitespace differences (default: false) */
10
+ ignoreWhitespace?: boolean;
11
+ /** Custom key paths to ignore during comparison */
12
+ ignorePaths?: string[];
13
+ /** Whether to show detailed differences (default: false) */
14
+ showDetails?: boolean;
15
+ }
16
+ /**
17
+ * Result of JSON comparison
18
+ */
19
+ export interface ComparisonResult {
20
+ /** Whether the objects are equivalent */
21
+ isEqual: boolean;
22
+ /** List of differences found */
23
+ differences: Difference[];
24
+ /** Summary statistics */
25
+ stats: {
26
+ totalKeys: number;
27
+ differentKeys: number;
28
+ missingKeys: number;
29
+ extraKeys: number;
30
+ };
31
+ }
32
+ /**
33
+ * Represents a difference between two objects
34
+ */
35
+ export interface Difference {
36
+ /** Path to the differing value */
37
+ path: string;
38
+ /** Type of difference */
39
+ type: 'different' | 'missing' | 'extra' | 'type_mismatch';
40
+ /** Value in the first object */
41
+ value1?: any;
42
+ /** Value in the second object */
43
+ value2?: any;
44
+ /** Description of the difference */
45
+ description: string;
46
+ }
47
+ /**
48
+ * Compare two JSON objects for structural equivalence
49
+ * Handles arrays with different ordering and nested objects
50
+ */
51
+ export declare function compareJsonObjects(obj1: any, obj2: any, options?: ComparisonOptions): ComparisonResult;
52
+ /**
53
+ * Create a normalized version of a JSON object for comparison
54
+ * This can be useful for creating a canonical representation
55
+ */
56
+ export declare function normalizeJsonObject(obj: any, options?: ComparisonOptions): any;
57
+ /**
58
+ * Get a summary of differences in a human-readable format
59
+ */
60
+ export declare function getDifferenceSummary(result: ComparisonResult): string;
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Compare two JSON objects for structural equivalence
3
+ * Handles arrays with different ordering and nested objects
4
+ */
5
+ export function compareJsonObjects(obj1, obj2, options = {}) {
6
+ const { ignoreArrayOrder = true, ignoreCase = false, ignoreWhitespace = false, ignorePaths = [], } = options;
7
+ const differences = [];
8
+ const stats = {
9
+ totalKeys: 0,
10
+ differentKeys: 0,
11
+ missingKeys: 0,
12
+ extraKeys: 0,
13
+ };
14
+ function normalizeValue(value) {
15
+ if (typeof value === 'string') {
16
+ let normalized = value;
17
+ if (ignoreCase) {
18
+ normalized = normalized.toLowerCase();
19
+ }
20
+ if (ignoreWhitespace) {
21
+ normalized = normalized.trim().replace(/\s+/g, ' ');
22
+ }
23
+ return normalized;
24
+ }
25
+ return value;
26
+ }
27
+ function shouldIgnorePath(path) {
28
+ return ignorePaths.some((ignorePath) => {
29
+ if (ignorePath.endsWith('*')) {
30
+ return path.startsWith(ignorePath.slice(0, -1));
31
+ }
32
+ return path === ignorePath;
33
+ });
34
+ }
35
+ function compareValues(value1, value2, path = '') {
36
+ if (shouldIgnorePath(path)) {
37
+ return true;
38
+ }
39
+ // Handle null/undefined
40
+ if (value1 === null || value1 === undefined) {
41
+ if (value2 === null || value2 === undefined) {
42
+ return true;
43
+ }
44
+ differences.push({
45
+ path,
46
+ type: 'different',
47
+ value1,
48
+ value2,
49
+ description: `Null/undefined mismatch: ${value1} vs ${value2}`,
50
+ });
51
+ return false;
52
+ }
53
+ if (value2 === null || value2 === undefined) {
54
+ differences.push({
55
+ path,
56
+ type: 'different',
57
+ value1,
58
+ value2,
59
+ description: `Null/undefined mismatch: ${value1} vs ${value2}`,
60
+ });
61
+ return false;
62
+ }
63
+ // Handle different types
64
+ if (typeof value1 !== typeof value2) {
65
+ differences.push({
66
+ path,
67
+ type: 'type_mismatch',
68
+ value1,
69
+ value2,
70
+ description: `Type mismatch: ${typeof value1} vs ${typeof value2}`,
71
+ });
72
+ return false;
73
+ }
74
+ // Handle primitives
75
+ if (typeof value1 !== 'object') {
76
+ const normalized1 = normalizeValue(value1);
77
+ const normalized2 = normalizeValue(value2);
78
+ if (normalized1 !== normalized2) {
79
+ differences.push({
80
+ path,
81
+ type: 'different',
82
+ value1,
83
+ value2,
84
+ description: `Value mismatch: ${value1} vs ${value2}`,
85
+ });
86
+ return false;
87
+ }
88
+ return true;
89
+ }
90
+ // Handle arrays
91
+ if (Array.isArray(value1) && Array.isArray(value2)) {
92
+ if (value1.length !== value2.length) {
93
+ differences.push({
94
+ path,
95
+ type: 'different',
96
+ value1: value1.length,
97
+ value2: value2.length,
98
+ description: `Array length mismatch: ${value1.length} vs ${value2.length}`,
99
+ });
100
+ return false; // Array length mismatch is a fundamental difference
101
+ }
102
+ if (ignoreArrayOrder) {
103
+ // Compare arrays ignoring order
104
+ const sorted1 = [...value1].sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
105
+ const sorted2 = [...value2].sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
106
+ for (let i = 0; i < sorted1.length; i++) {
107
+ compareValues(sorted1[i], sorted2[i], `${path}[${i}]`); // Don't return false, just collect differences
108
+ }
109
+ }
110
+ else {
111
+ // Compare arrays in order
112
+ for (let i = 0; i < value1.length; i++) {
113
+ compareValues(value1[i], value2[i], `${path}[${i}]`); // Don't return false, just collect differences
114
+ }
115
+ }
116
+ return true;
117
+ }
118
+ // Handle objects
119
+ if (typeof value1 === 'object' && typeof value2 === 'object') {
120
+ const keys1 = Object.keys(value1);
121
+ const keys2 = Object.keys(value2);
122
+ const allKeys = new Set([...keys1, ...keys2]);
123
+ stats.totalKeys += allKeys.size;
124
+ for (const key of allKeys) {
125
+ const currentPath = path ? `${path}.${key}` : key;
126
+ if (!keys1.includes(key)) {
127
+ differences.push({
128
+ path: currentPath,
129
+ type: 'missing',
130
+ value2: value2[key],
131
+ description: `Missing key in first object: ${key}`,
132
+ });
133
+ stats.missingKeys++;
134
+ continue; // Continue checking other keys instead of returning false
135
+ }
136
+ if (!keys2.includes(key)) {
137
+ differences.push({
138
+ path: currentPath,
139
+ type: 'extra',
140
+ value1: value1[key],
141
+ description: `Extra key in first object: ${key}`,
142
+ });
143
+ stats.extraKeys++;
144
+ continue; // Continue checking other keys instead of returning false
145
+ }
146
+ if (!compareValues(value1[key], value2[key], currentPath)) {
147
+ stats.differentKeys++;
148
+ // Don't return false here, continue checking other keys
149
+ }
150
+ }
151
+ return true;
152
+ }
153
+ return true;
154
+ }
155
+ compareValues(obj1, obj2);
156
+ return {
157
+ isEqual: differences.length === 0,
158
+ differences,
159
+ stats,
160
+ };
161
+ }
162
+ /**
163
+ * Create a normalized version of a JSON object for comparison
164
+ * This can be useful for creating a canonical representation
165
+ */
166
+ export function normalizeJsonObject(obj, options = {}) {
167
+ const { ignoreArrayOrder = true, ignoreCase = false, ignoreWhitespace = false } = options;
168
+ function normalizeValue(value) {
169
+ if (typeof value === 'string') {
170
+ let normalized = value;
171
+ if (ignoreCase) {
172
+ normalized = normalized.toLowerCase();
173
+ }
174
+ if (ignoreWhitespace) {
175
+ normalized = normalized.trim().replace(/\s+/g, ' ');
176
+ }
177
+ return normalized;
178
+ }
179
+ if (Array.isArray(value)) {
180
+ const normalizedArray = value.map(normalizeValue);
181
+ if (ignoreArrayOrder) {
182
+ return normalizedArray.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
183
+ }
184
+ return normalizedArray;
185
+ }
186
+ if (typeof value === 'object' && value !== null) {
187
+ const normalizedObj = {};
188
+ const sortedKeys = Object.keys(value).sort();
189
+ for (const key of sortedKeys) {
190
+ normalizedObj[key] = normalizeValue(value[key]);
191
+ }
192
+ return normalizedObj;
193
+ }
194
+ return value;
195
+ }
196
+ return normalizeValue(obj);
197
+ }
198
+ /**
199
+ * Get a summary of differences in a human-readable format
200
+ */
201
+ export function getDifferenceSummary(result) {
202
+ if (result.isEqual) {
203
+ return '✅ Objects are equivalent';
204
+ }
205
+ const { differences, stats } = result;
206
+ const summary = [`❌ Objects differ (${differences.length} differences found)`];
207
+ summary.push(`📊 Statistics:`);
208
+ summary.push(` • Total keys: ${stats.totalKeys}`);
209
+ summary.push(` • Different values: ${stats.differentKeys}`);
210
+ summary.push(` • Missing keys: ${stats.missingKeys}`);
211
+ summary.push(` • Extra keys: ${stats.extraKeys}`);
212
+ if (differences.length > 0) {
213
+ summary.push(`\n🔍 Differences:`);
214
+ differences.slice(0, 10).forEach((diff, index) => {
215
+ summary.push(` ${index + 1}. ${diff.path}: ${diff.description}`);
216
+ });
217
+ if (differences.length > 10) {
218
+ summary.push(` ... and ${differences.length - 10} more differences`);
219
+ }
220
+ }
221
+ return summary.join('\n');
222
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Runner - Loads and starts MCP servers from a graph file
4
+ * This is executed as a subprocess by the CLI
5
+ */
6
+ export {};
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Runner - Loads and starts MCP servers from a graph file
4
+ * This is executed as a subprocess by the CLI
5
+ */
6
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
7
+ import http from 'node:http';
8
+ import { homedir } from 'node:os';
9
+ import { join } from 'node:path';
10
+ const MCP_DIR = join(homedir(), '.inkeep', 'mcp');
11
+ const REGISTRY_FILE = join(MCP_DIR, 'servers.json');
12
+ // Ensure MCP directory exists
13
+ if (!existsSync(MCP_DIR)) {
14
+ mkdirSync(MCP_DIR, { recursive: true });
15
+ }
16
+ async function startServers(graphPath) {
17
+ try {
18
+ // Import the graph module
19
+ const module = await import(graphPath);
20
+ // Get servers
21
+ const servers = module.servers || module.tools || [];
22
+ // Get graph ID
23
+ let graphId = 'unknown';
24
+ if (module.graph && typeof module.graph.getId === 'function') {
25
+ graphId = module.graph.getId();
26
+ }
27
+ const registeredServers = [];
28
+ let nextPort = 3100;
29
+ // Start each server
30
+ for (const server of servers) {
31
+ if (!server)
32
+ continue;
33
+ // Get server metadata
34
+ const name = server.name || 'unnamed';
35
+ const id = server.id || name;
36
+ const description = server.description || '';
37
+ // Check deployment type
38
+ const isLocal = typeof server.execute === 'function' ||
39
+ typeof server.init === 'function' ||
40
+ !server.serverUrl;
41
+ if (isLocal) {
42
+ // Start local server
43
+ const port = server.port || nextPort++;
44
+ // Initialize if needed
45
+ if (typeof server.init === 'function') {
46
+ await server.init();
47
+ }
48
+ // Create HTTP server for local MCP
49
+ if (typeof server.execute === 'function') {
50
+ const httpServer = http.createServer(async (req, res) => {
51
+ if (req.method === 'POST' && req.url === '/mcp') {
52
+ let body = '';
53
+ req.on('data', (chunk) => (body += chunk));
54
+ req.on('end', async () => {
55
+ try {
56
+ const params = JSON.parse(body);
57
+ const result = await server.execute(params);
58
+ res.writeHead(200, { 'Content-Type': 'application/json' });
59
+ res.end(JSON.stringify({ result }));
60
+ }
61
+ catch (error) {
62
+ res.writeHead(500, { 'Content-Type': 'application/json' });
63
+ res.end(JSON.stringify({ error: error.message }));
64
+ }
65
+ });
66
+ }
67
+ else {
68
+ res.writeHead(404);
69
+ res.end('Not Found');
70
+ }
71
+ });
72
+ httpServer.listen(port, () => {
73
+ console.log(JSON.stringify({
74
+ type: 'server_started',
75
+ name,
76
+ port,
77
+ deployment: 'local',
78
+ }));
79
+ });
80
+ }
81
+ registeredServers.push({
82
+ pid: process.pid,
83
+ graphId,
84
+ toolId: id,
85
+ name,
86
+ port,
87
+ deployment: 'local',
88
+ transport: 'http',
89
+ command: graphPath,
90
+ startedAt: new Date().toISOString(),
91
+ description,
92
+ });
93
+ }
94
+ else {
95
+ // Register remote server
96
+ registeredServers.push({
97
+ pid: process.pid,
98
+ graphId,
99
+ toolId: id,
100
+ name,
101
+ serverUrl: server.serverUrl || server.getServerUrl?.(),
102
+ deployment: 'remote',
103
+ transport: server.transport || 'http',
104
+ command: graphPath,
105
+ startedAt: new Date().toISOString(),
106
+ description,
107
+ });
108
+ console.log(JSON.stringify({
109
+ type: 'server_registered',
110
+ name,
111
+ serverUrl: server.serverUrl,
112
+ deployment: 'remote',
113
+ }));
114
+ }
115
+ }
116
+ // Save to registry
117
+ writeFileSync(REGISTRY_FILE, JSON.stringify({ servers: registeredServers }, null, 2));
118
+ console.log(JSON.stringify({
119
+ type: 'all_started',
120
+ count: registeredServers.length,
121
+ }));
122
+ // Keep process alive
123
+ process.stdin.resume();
124
+ // Handle shutdown
125
+ process.on('SIGINT', () => {
126
+ console.log(JSON.stringify({ type: 'shutting_down' }));
127
+ process.exit(0);
128
+ });
129
+ }
130
+ catch (error) {
131
+ console.error(JSON.stringify({
132
+ type: 'error',
133
+ message: error.message,
134
+ }));
135
+ process.exit(1);
136
+ }
137
+ }
138
+ // Get graph path from command line
139
+ const graphPath = process.argv[2];
140
+ if (!graphPath) {
141
+ console.error(JSON.stringify({
142
+ type: 'error',
143
+ message: 'Graph path is required',
144
+ }));
145
+ process.exit(1);
146
+ }
147
+ startServers(graphPath);
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Manages dynamic port allocation for local MCP servers
3
+ */
4
+ export declare class PortManager {
5
+ private static instance;
6
+ private static readonly BASE_PORT;
7
+ private static readonly MAX_PORT;
8
+ private allocatedPorts;
9
+ private constructor();
10
+ static getInstance(): PortManager;
11
+ /**
12
+ * Allocate an available port in the configured range
13
+ */
14
+ allocatePort(preferredPort?: number): Promise<number>;
15
+ /**
16
+ * Release a previously allocated port
17
+ */
18
+ releasePort(port: number): void;
19
+ /**
20
+ * Release all allocated ports
21
+ */
22
+ releaseAll(): void;
23
+ /**
24
+ * Check if a specific port is available
25
+ */
26
+ private isPortAvailable;
27
+ /**
28
+ * Get list of currently allocated ports
29
+ */
30
+ getAllocatedPorts(): number[];
31
+ /**
32
+ * Get port allocation statistics
33
+ */
34
+ getStats(): {
35
+ allocated: number;
36
+ available: number;
37
+ range: {
38
+ min: number;
39
+ max: number;
40
+ };
41
+ ports: number[];
42
+ };
43
+ }