@darkiceinteractive/mcp-conductor 1.0.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 (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +558 -0
  3. package/dist/bin/cli.d.ts +8 -0
  4. package/dist/bin/cli.d.ts.map +1 -0
  5. package/dist/bin/cli.js +940 -0
  6. package/dist/bin/cli.js.map +1 -0
  7. package/dist/bridge/http-server.d.ts +161 -0
  8. package/dist/bridge/http-server.d.ts.map +1 -0
  9. package/dist/bridge/http-server.js +367 -0
  10. package/dist/bridge/http-server.js.map +1 -0
  11. package/dist/bridge/index.d.ts +5 -0
  12. package/dist/bridge/index.d.ts.map +1 -0
  13. package/dist/bridge/index.js +5 -0
  14. package/dist/bridge/index.js.map +1 -0
  15. package/dist/config/defaults.d.ts +29 -0
  16. package/dist/config/defaults.d.ts.map +1 -0
  17. package/dist/config/defaults.js +60 -0
  18. package/dist/config/defaults.js.map +1 -0
  19. package/dist/config/index.d.ts +7 -0
  20. package/dist/config/index.d.ts.map +1 -0
  21. package/dist/config/index.js +7 -0
  22. package/dist/config/index.js.map +1 -0
  23. package/dist/config/loader.d.ts +49 -0
  24. package/dist/config/loader.d.ts.map +1 -0
  25. package/dist/config/loader.js +272 -0
  26. package/dist/config/loader.js.map +1 -0
  27. package/dist/config/schema.d.ts +93 -0
  28. package/dist/config/schema.d.ts.map +1 -0
  29. package/dist/config/schema.js +5 -0
  30. package/dist/config/schema.js.map +1 -0
  31. package/dist/hub/index.d.ts +5 -0
  32. package/dist/hub/index.d.ts.map +1 -0
  33. package/dist/hub/index.js +5 -0
  34. package/dist/hub/index.js.map +1 -0
  35. package/dist/hub/mcp-hub.d.ts +176 -0
  36. package/dist/hub/mcp-hub.d.ts.map +1 -0
  37. package/dist/hub/mcp-hub.js +550 -0
  38. package/dist/hub/mcp-hub.js.map +1 -0
  39. package/dist/index.d.ts +9 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +45 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/metrics/index.d.ts +5 -0
  44. package/dist/metrics/index.d.ts.map +1 -0
  45. package/dist/metrics/index.js +5 -0
  46. package/dist/metrics/index.js.map +1 -0
  47. package/dist/metrics/metrics-collector.d.ts +211 -0
  48. package/dist/metrics/metrics-collector.d.ts.map +1 -0
  49. package/dist/metrics/metrics-collector.js +437 -0
  50. package/dist/metrics/metrics-collector.js.map +1 -0
  51. package/dist/modes/index.d.ts +5 -0
  52. package/dist/modes/index.d.ts.map +1 -0
  53. package/dist/modes/index.js +5 -0
  54. package/dist/modes/index.js.map +1 -0
  55. package/dist/modes/mode-handler.d.ts +132 -0
  56. package/dist/modes/mode-handler.d.ts.map +1 -0
  57. package/dist/modes/mode-handler.js +252 -0
  58. package/dist/modes/mode-handler.js.map +1 -0
  59. package/dist/runtime/executor.d.ts +57 -0
  60. package/dist/runtime/executor.d.ts.map +1 -0
  61. package/dist/runtime/executor.js +700 -0
  62. package/dist/runtime/executor.js.map +1 -0
  63. package/dist/runtime/index.d.ts +5 -0
  64. package/dist/runtime/index.d.ts.map +1 -0
  65. package/dist/runtime/index.js +5 -0
  66. package/dist/runtime/index.js.map +1 -0
  67. package/dist/server/index.d.ts +5 -0
  68. package/dist/server/index.d.ts.map +1 -0
  69. package/dist/server/index.js +5 -0
  70. package/dist/server/index.js.map +1 -0
  71. package/dist/server/mcp-server.d.ts +62 -0
  72. package/dist/server/mcp-server.d.ts.map +1 -0
  73. package/dist/server/mcp-server.js +1272 -0
  74. package/dist/server/mcp-server.js.map +1 -0
  75. package/dist/skills/index.d.ts +5 -0
  76. package/dist/skills/index.d.ts.map +1 -0
  77. package/dist/skills/index.js +5 -0
  78. package/dist/skills/index.js.map +1 -0
  79. package/dist/skills/skills-engine.d.ts +157 -0
  80. package/dist/skills/skills-engine.d.ts.map +1 -0
  81. package/dist/skills/skills-engine.js +405 -0
  82. package/dist/skills/skills-engine.js.map +1 -0
  83. package/dist/streaming/execution-stream.d.ts +158 -0
  84. package/dist/streaming/execution-stream.d.ts.map +1 -0
  85. package/dist/streaming/execution-stream.js +320 -0
  86. package/dist/streaming/execution-stream.js.map +1 -0
  87. package/dist/streaming/index.d.ts +5 -0
  88. package/dist/streaming/index.d.ts.map +1 -0
  89. package/dist/streaming/index.js +5 -0
  90. package/dist/streaming/index.js.map +1 -0
  91. package/dist/utils/errors.d.ts +36 -0
  92. package/dist/utils/errors.d.ts.map +1 -0
  93. package/dist/utils/errors.js +68 -0
  94. package/dist/utils/errors.js.map +1 -0
  95. package/dist/utils/helpers.d.ts +44 -0
  96. package/dist/utils/helpers.d.ts.map +1 -0
  97. package/dist/utils/helpers.js +95 -0
  98. package/dist/utils/helpers.js.map +1 -0
  99. package/dist/utils/index.d.ts +9 -0
  100. package/dist/utils/index.d.ts.map +1 -0
  101. package/dist/utils/index.js +9 -0
  102. package/dist/utils/index.js.map +1 -0
  103. package/dist/utils/logger.d.ts +13 -0
  104. package/dist/utils/logger.d.ts.map +1 -0
  105. package/dist/utils/logger.js +48 -0
  106. package/dist/utils/logger.js.map +1 -0
  107. package/dist/utils/permissions.d.ts +97 -0
  108. package/dist/utils/permissions.d.ts.map +1 -0
  109. package/dist/utils/permissions.js +165 -0
  110. package/dist/utils/permissions.js.map +1 -0
  111. package/dist/utils/rate-limiter.d.ts +87 -0
  112. package/dist/utils/rate-limiter.d.ts.map +1 -0
  113. package/dist/utils/rate-limiter.js +187 -0
  114. package/dist/utils/rate-limiter.js.map +1 -0
  115. package/dist/watcher/config-watcher.d.ts +67 -0
  116. package/dist/watcher/config-watcher.d.ts.map +1 -0
  117. package/dist/watcher/config-watcher.js +150 -0
  118. package/dist/watcher/config-watcher.js.map +1 -0
  119. package/dist/watcher/index.d.ts +5 -0
  120. package/dist/watcher/index.d.ts.map +1 -0
  121. package/dist/watcher/index.js +5 -0
  122. package/dist/watcher/index.js.map +1 -0
  123. package/package.json +86 -0
  124. package/templates/CLAUDE.md +137 -0
  125. package/templates/skill-mcp-conductor.md +64 -0
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Token bucket rate limiter for MCP servers
3
+ */
4
+ import { EventEmitter } from 'node:events';
5
+ import { logger } from './logger.js';
6
+ /**
7
+ * Token bucket rate limiter with queue and reject modes
8
+ */
9
+ export class RateLimiter extends EventEmitter {
10
+ serverName;
11
+ requestsPerSecond;
12
+ maxTokens;
13
+ mode;
14
+ maxQueueTimeMs;
15
+ tokens;
16
+ lastRefill;
17
+ queue = [];
18
+ refillInterval = null;
19
+ // Stats
20
+ totalRequests = 0;
21
+ totalWaited = 0;
22
+ totalRejected = 0;
23
+ constructor(config, serverName) {
24
+ super();
25
+ this.serverName = serverName;
26
+ this.requestsPerSecond = config.requestsPerSecond;
27
+ this.maxTokens = config.burstSize ?? config.requestsPerSecond;
28
+ this.mode = config.onLimitExceeded ?? 'queue';
29
+ this.maxQueueTimeMs = config.maxQueueTimeMs ?? 30000;
30
+ // Start with full bucket
31
+ this.tokens = this.maxTokens;
32
+ this.lastRefill = Date.now();
33
+ // Start background token refill
34
+ this.startRefillTimer();
35
+ logger.debug(`RateLimiter created for ${serverName}`, {
36
+ requestsPerSecond: this.requestsPerSecond,
37
+ maxTokens: this.maxTokens,
38
+ mode: this.mode,
39
+ });
40
+ }
41
+ /**
42
+ * Acquire a token before making a request
43
+ * In queue mode: waits until a token is available
44
+ * In reject mode: throws immediately if no token available
45
+ */
46
+ async acquire() {
47
+ this.totalRequests++;
48
+ // Try to get a token immediately
49
+ if (this.tryAcquire()) {
50
+ this.emit('acquired', { serverName: this.serverName, waitedMs: 0 });
51
+ return;
52
+ }
53
+ // No token available
54
+ if (this.mode === 'reject') {
55
+ this.totalRejected++;
56
+ const error = new Error(`Rate limit exceeded for ${this.serverName}: ${this.requestsPerSecond} req/s`);
57
+ this.emit('rejected', { serverName: this.serverName, reason: 'no tokens available' });
58
+ throw error;
59
+ }
60
+ // Queue mode: wait for a token
61
+ return this.enqueue();
62
+ }
63
+ /**
64
+ * Release a token back (for cancelled requests before they actually execute)
65
+ */
66
+ release() {
67
+ this.tokens = Math.min(this.tokens + 1, this.maxTokens);
68
+ }
69
+ /**
70
+ * Get current rate limiter stats
71
+ */
72
+ getStats() {
73
+ return {
74
+ serverName: this.serverName,
75
+ availableTokens: this.tokens,
76
+ maxTokens: this.maxTokens,
77
+ requestsPerSecond: this.requestsPerSecond,
78
+ queueLength: this.queue.length,
79
+ totalRequests: this.totalRequests,
80
+ totalWaited: this.totalWaited,
81
+ totalRejected: this.totalRejected,
82
+ };
83
+ }
84
+ /**
85
+ * Clean up resources
86
+ */
87
+ destroy() {
88
+ if (this.refillInterval) {
89
+ clearInterval(this.refillInterval);
90
+ this.refillInterval = null;
91
+ }
92
+ // Reject all queued requests
93
+ for (const request of this.queue) {
94
+ clearTimeout(request.timeout);
95
+ request.reject(new Error(`RateLimiter for ${this.serverName} destroyed`));
96
+ }
97
+ this.queue = [];
98
+ logger.debug(`RateLimiter destroyed for ${this.serverName}`);
99
+ }
100
+ /**
101
+ * Try to acquire a token without waiting
102
+ */
103
+ tryAcquire() {
104
+ this.refillTokens();
105
+ if (this.tokens >= 1) {
106
+ this.tokens--;
107
+ return true;
108
+ }
109
+ return false;
110
+ }
111
+ /**
112
+ * Enqueue a request to wait for a token
113
+ */
114
+ enqueue() {
115
+ return new Promise((resolve, reject) => {
116
+ const queuePosition = this.queue.length + 1;
117
+ const estimatedWaitMs = Math.ceil((queuePosition / this.requestsPerSecond) * 1000);
118
+ this.emit('waiting', {
119
+ serverName: this.serverName,
120
+ queuePosition,
121
+ estimatedWaitMs,
122
+ });
123
+ logger.debug(`Request queued for ${this.serverName}`, {
124
+ queuePosition,
125
+ estimatedWaitMs,
126
+ });
127
+ const timeout = setTimeout(() => {
128
+ // Remove from queue
129
+ const index = this.queue.findIndex((r) => r.timeout === timeout);
130
+ if (index !== -1) {
131
+ this.queue.splice(index, 1);
132
+ }
133
+ this.totalRejected++;
134
+ const error = new Error(`Rate limit queue timeout for ${this.serverName} after ${this.maxQueueTimeMs}ms`);
135
+ this.emit('rejected', { serverName: this.serverName, reason: 'queue timeout' });
136
+ reject(error);
137
+ }, this.maxQueueTimeMs);
138
+ this.queue.push({
139
+ resolve,
140
+ reject,
141
+ timeout,
142
+ enqueuedAt: Date.now(),
143
+ });
144
+ });
145
+ }
146
+ /**
147
+ * Refill tokens based on elapsed time
148
+ */
149
+ refillTokens() {
150
+ const now = Date.now();
151
+ const elapsed = now - this.lastRefill;
152
+ // Calculate tokens to add based on elapsed time
153
+ const tokensToAdd = (elapsed / 1000) * this.requestsPerSecond;
154
+ if (tokensToAdd >= 1) {
155
+ this.tokens = Math.min(this.tokens + Math.floor(tokensToAdd), this.maxTokens);
156
+ this.lastRefill = now;
157
+ }
158
+ }
159
+ /**
160
+ * Start background timer to process queued requests
161
+ */
162
+ startRefillTimer() {
163
+ // Check queue every 50ms
164
+ this.refillInterval = setInterval(() => {
165
+ this.processQueue();
166
+ }, 50);
167
+ }
168
+ /**
169
+ * Process queued requests when tokens become available
170
+ */
171
+ processQueue() {
172
+ this.refillTokens();
173
+ while (this.queue.length > 0 && this.tokens >= 1) {
174
+ const request = this.queue.shift();
175
+ if (request) {
176
+ clearTimeout(request.timeout);
177
+ this.tokens--;
178
+ this.totalWaited++;
179
+ const waitedMs = Date.now() - request.enqueuedAt;
180
+ this.emit('acquired', { serverName: this.serverName, waitedMs });
181
+ logger.debug(`Request dequeued for ${this.serverName}`, { waitedMs });
182
+ request.resolve();
183
+ }
184
+ }
185
+ }
186
+ }
187
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Config File Watcher
3
+ *
4
+ * Watches Claude config files for changes and triggers hot-reload
5
+ * of MCP server connections.
6
+ */
7
+ import { EventEmitter } from 'node:events';
8
+ export interface WatcherConfig {
9
+ /** Path to watch (auto-detect if not specified) */
10
+ configPath?: string;
11
+ /** Debounce delay in milliseconds */
12
+ debounceMs?: number;
13
+ /** Whether to watch for changes */
14
+ enabled?: boolean;
15
+ }
16
+ export interface WatcherEvents {
17
+ configChanged: (path: string) => void;
18
+ watcherError: (error: Error) => void;
19
+ watcherStarted: (path: string) => void;
20
+ watcherStopped: () => void;
21
+ }
22
+ export declare class ConfigWatcher extends EventEmitter {
23
+ private config;
24
+ private watcher;
25
+ private debounceTimer;
26
+ private watchedPath;
27
+ private isRunning;
28
+ constructor(config?: WatcherConfig);
29
+ /**
30
+ * Start watching the config file
31
+ */
32
+ start(): Promise<boolean>;
33
+ /**
34
+ * Stop watching the config file
35
+ */
36
+ stop(): Promise<void>;
37
+ /**
38
+ * Handle file change event with debouncing
39
+ */
40
+ private handleChange;
41
+ /**
42
+ * Check if watcher is running
43
+ */
44
+ isWatching(): boolean;
45
+ /**
46
+ * Get the path being watched
47
+ */
48
+ getWatchedPath(): string | null;
49
+ /**
50
+ * Update the debounce delay
51
+ */
52
+ setDebounceMs(ms: number): void;
53
+ /**
54
+ * Trigger a manual reload (without file change)
55
+ */
56
+ triggerReload(): void;
57
+ }
58
+ /**
59
+ * Create a config watcher that integrates with an MCP Hub
60
+ */
61
+ export declare function createHubWatcher(hub: {
62
+ reload: () => Promise<{
63
+ added: string[];
64
+ removed: string[];
65
+ }>;
66
+ }, config?: WatcherConfig): ConfigWatcher;
67
+ //# sourceMappingURL=config-watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-watcher.d.ts","sourceRoot":"","sources":["../../src/watcher/config-watcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C,MAAM,WAAW,aAAa;IAC5B,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,YAAY,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACrC,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,cAAc,EAAE,MAAM,IAAI,CAAC;CAC5B;AAQD,qBAAa,aAAc,SAAQ,YAAY;IAC7C,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,SAAS,CAAS;gBAEd,MAAM,GAAE,aAAkB;IAKtC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC;IAkD/B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB3B;;OAEG;IACH,OAAO,CAAC,YAAY;IAcpB;;OAEG;IACH,UAAU,IAAI,OAAO;IAIrB;;OAEG;IACH,cAAc,IAAI,MAAM,GAAG,IAAI;IAI/B;;OAEG;IACH,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAI/B;;OAEG;IACH,aAAa,IAAI,IAAI;CAMtB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE;IAAE,MAAM,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;CAAE,EACtE,MAAM,GAAE,aAAkB,GACzB,aAAa,CAgBf"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Config File Watcher
3
+ *
4
+ * Watches Claude config files for changes and triggers hot-reload
5
+ * of MCP server connections.
6
+ */
7
+ import { watch } from 'chokidar';
8
+ import { EventEmitter } from 'node:events';
9
+ import { logger } from '../utils/index.js';
10
+ import { findClaudeConfig } from '../config/loader.js';
11
+ const DEFAULT_WATCHER_CONFIG = {
12
+ configPath: '',
13
+ debounceMs: 500,
14
+ enabled: true,
15
+ };
16
+ export class ConfigWatcher extends EventEmitter {
17
+ config;
18
+ watcher = null;
19
+ debounceTimer = null;
20
+ watchedPath = null;
21
+ isRunning = false;
22
+ constructor(config = {}) {
23
+ super();
24
+ this.config = { ...DEFAULT_WATCHER_CONFIG, ...config };
25
+ }
26
+ /**
27
+ * Start watching the config file
28
+ */
29
+ async start() {
30
+ if (!this.config.enabled) {
31
+ logger.debug('Config watcher disabled');
32
+ return false;
33
+ }
34
+ if (this.isRunning) {
35
+ logger.warn('Config watcher already running');
36
+ return true;
37
+ }
38
+ // Find the config file to watch
39
+ const configPath = this.config.configPath || findClaudeConfig();
40
+ if (!configPath) {
41
+ logger.warn('No Claude config file found to watch');
42
+ return false;
43
+ }
44
+ this.watchedPath = configPath;
45
+ try {
46
+ this.watcher = watch(configPath, {
47
+ persistent: true,
48
+ ignoreInitial: true,
49
+ awaitWriteFinish: {
50
+ stabilityThreshold: this.config.debounceMs,
51
+ pollInterval: 100,
52
+ },
53
+ });
54
+ this.watcher.on('change', (path) => {
55
+ this.handleChange(path);
56
+ });
57
+ this.watcher.on('error', (error) => {
58
+ logger.error('Config watcher error', { error: String(error) });
59
+ this.emit('watcherError', error);
60
+ });
61
+ this.isRunning = true;
62
+ logger.info(`Config watcher started`, { path: configPath });
63
+ this.emit('watcherStarted', configPath);
64
+ return true;
65
+ }
66
+ catch (error) {
67
+ logger.error('Failed to start config watcher', { error: String(error) });
68
+ return false;
69
+ }
70
+ }
71
+ /**
72
+ * Stop watching the config file
73
+ */
74
+ async stop() {
75
+ if (this.debounceTimer) {
76
+ clearTimeout(this.debounceTimer);
77
+ this.debounceTimer = null;
78
+ }
79
+ if (this.watcher) {
80
+ await this.watcher.close();
81
+ this.watcher = null;
82
+ }
83
+ this.isRunning = false;
84
+ this.watchedPath = null;
85
+ logger.info('Config watcher stopped');
86
+ this.emit('watcherStopped');
87
+ }
88
+ /**
89
+ * Handle file change event with debouncing
90
+ */
91
+ handleChange(path) {
92
+ // Clear any existing debounce timer
93
+ if (this.debounceTimer) {
94
+ clearTimeout(this.debounceTimer);
95
+ }
96
+ // Set up debounced callback
97
+ this.debounceTimer = setTimeout(() => {
98
+ logger.info('Config file changed, triggering reload', { path });
99
+ this.emit('configChanged', path);
100
+ this.debounceTimer = null;
101
+ }, this.config.debounceMs);
102
+ }
103
+ /**
104
+ * Check if watcher is running
105
+ */
106
+ isWatching() {
107
+ return this.isRunning;
108
+ }
109
+ /**
110
+ * Get the path being watched
111
+ */
112
+ getWatchedPath() {
113
+ return this.watchedPath;
114
+ }
115
+ /**
116
+ * Update the debounce delay
117
+ */
118
+ setDebounceMs(ms) {
119
+ this.config.debounceMs = ms;
120
+ }
121
+ /**
122
+ * Trigger a manual reload (without file change)
123
+ */
124
+ triggerReload() {
125
+ if (this.watchedPath) {
126
+ logger.info('Manual reload triggered', { path: this.watchedPath });
127
+ this.emit('configChanged', this.watchedPath);
128
+ }
129
+ }
130
+ }
131
+ /**
132
+ * Create a config watcher that integrates with an MCP Hub
133
+ */
134
+ export function createHubWatcher(hub, config = {}) {
135
+ const watcher = new ConfigWatcher(config);
136
+ watcher.on('configChanged', async () => {
137
+ try {
138
+ const result = await hub.reload();
139
+ logger.info('Hub reloaded after config change', {
140
+ added: result.added,
141
+ removed: result.removed,
142
+ });
143
+ }
144
+ catch (error) {
145
+ logger.error('Failed to reload hub after config change', { error: String(error) });
146
+ }
147
+ });
148
+ return watcher;
149
+ }
150
+ //# sourceMappingURL=config-watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-watcher.js","sourceRoot":"","sources":["../../src/watcher/config-watcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Watcher module exports
3
+ */
4
+ export { ConfigWatcher, createHubWatcher, type WatcherConfig, type WatcherEvents, } from './config-watcher.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/watcher/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Watcher module exports
3
+ */
4
+ export { ConfigWatcher, createHubWatcher, } from './config-watcher.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/watcher/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,aAAa,EACb,gBAAgB,GAGjB,MAAM,qBAAqB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@darkiceinteractive/mcp-conductor",
3
+ "version": "1.0.0",
4
+ "description": "MCP server that orchestrates code execution in a sandboxed Deno environment with access to all MCP servers",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "mcp-conductor": "./dist/bin/cli.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "templates",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "setup": "./scripts/setup.sh",
19
+ "build": "tsc",
20
+ "dev": "tsx watch src/index.ts",
21
+ "start": "node dist/index.js",
22
+ "test": "vitest",
23
+ "test:run": "vitest run",
24
+ "test:unit": "vitest run test/unit",
25
+ "test:integration": "vitest run test/integration",
26
+ "test:e2e": "vitest run test/e2e",
27
+ "test:benchmark": "vitest run test/benchmark",
28
+ "test:coverage": "vitest run --coverage",
29
+ "test:all": "vitest run",
30
+ "lint": "eslint src --ext .ts",
31
+ "lint:fix": "eslint src --ext .ts --fix",
32
+ "format": "prettier --write \"src/**/*.ts\"",
33
+ "clean": "rm -rf dist",
34
+ "prepublishOnly": "npm run build"
35
+ },
36
+ "keywords": [
37
+ "mcp",
38
+ "model-context-protocol",
39
+ "claude",
40
+ "code-execution",
41
+ "sandbox",
42
+ "deno"
43
+ ],
44
+ "author": "DarkIce Interactive",
45
+ "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/darkiceinteractive/mcp-conductor.git"
49
+ },
50
+ "homepage": "https://github.com/darkiceinteractive/mcp-conductor#readme",
51
+ "bugs": {
52
+ "url": "https://github.com/darkiceinteractive/mcp-conductor/issues"
53
+ },
54
+ "exports": {
55
+ ".": {
56
+ "types": "./dist/index.d.ts",
57
+ "import": "./dist/index.js"
58
+ }
59
+ },
60
+ "engines": {
61
+ "node": ">=18.0.0"
62
+ },
63
+ "dependencies": {
64
+ "@modelcontextprotocol/sdk": "^1.0.0",
65
+ "chalk": "^5.3.0",
66
+ "chokidar": "^3.5.3",
67
+ "commander": "^12.1.0",
68
+ "conf": "^13.0.1",
69
+ "gray-matter": "^4.0.3",
70
+ "ora": "^8.1.1",
71
+ "uuid": "^10.0.0",
72
+ "zod": "^3.23.8"
73
+ },
74
+ "devDependencies": {
75
+ "@types/node": "^22.10.2",
76
+ "@types/uuid": "^10.0.0",
77
+ "@typescript-eslint/eslint-plugin": "^8.18.0",
78
+ "@typescript-eslint/parser": "^8.18.0",
79
+ "@vitest/coverage-v8": "^2.1.8",
80
+ "eslint": "^9.17.0",
81
+ "prettier": "^3.4.2",
82
+ "tsx": "^4.19.2",
83
+ "typescript": "^5.7.2",
84
+ "vitest": "^2.1.8"
85
+ }
86
+ }
@@ -0,0 +1,137 @@
1
+ # MCP Conductor - Project Instructions
2
+
3
+ This project has MCP Conductor configured for token-efficient MCP operations.
4
+
5
+ ## Critical: Use execute_code for MCP Operations
6
+
7
+ **ALWAYS use `mcp-conductor`'s `execute_code` tool** instead of making direct MCP tool calls when:
8
+
9
+ 1. **Multiple tool calls needed** - Batch operations into a single execute_code call
10
+ 2. **Processing data** - Filter, transform, or aggregate results in the sandbox
11
+ 3. **Iterating over items** - Files, repos, search results, etc.
12
+ 4. **Large datasets** - Process data server-side, return only summaries
13
+
14
+ ### Why This Matters
15
+
16
+ Direct MCP calls consume tokens for BOTH request AND response in Claude's context. With execute_code:
17
+ - Write code once (small token cost)
18
+ - All intermediate data stays in sandbox (zero token cost)
19
+ - Only final return value enters context (minimal tokens)
20
+
21
+ **Token savings: 90%+ for multi-step operations**
22
+
23
+ ### Example: Reading Multiple Files
24
+
25
+ **BAD - Direct calls (high token usage):**
26
+ ```
27
+ 1. filesystem.list_directory → full response in context
28
+ 2. filesystem.read_file → full file in context
29
+ 3. filesystem.read_file → full file in context
30
+ 4. filesystem.read_file → full file in context
31
+ ... (each response consumes tokens)
32
+ ```
33
+
34
+ **GOOD - execute_code (minimal tokens):**
35
+ ```typescript
36
+ // Use mcp-conductor's execute_code tool with this code:
37
+ const fs = mcp.server('filesystem');
38
+ const files = await fs.call('list_directory', { path: '/src' });
39
+ const tsFiles = files.entries.filter(f => f.name.endsWith('.ts'));
40
+ const contents = await Promise.all(
41
+ tsFiles.map(f => fs.call('read_file', { path: '/src/' + f.name }))
42
+ );
43
+ // Only this summary enters Claude's context:
44
+ return {
45
+ count: tsFiles.length,
46
+ totalLines: contents.reduce((sum, c) => sum + c.split('\n').length, 0)
47
+ };
48
+ ```
49
+
50
+ ### When Direct MCP Calls Are OK
51
+
52
+ - Single, simple tool call with no processing
53
+ - Debugging or interactive exploration
54
+ - When you need streaming feedback
55
+
56
+ ## Web Search Optimisation (Optional)
57
+
58
+ If `brave-search` MCP server is configured, use `mcp.batchSearch()` for efficient batched searches:
59
+
60
+ **Native WebSearch (sequential, high tokens):**
61
+ ```
62
+ 1. WebSearch("query 1") → full results in context
63
+ 2. WebSearch("query 2") → full results in context
64
+ 3. WebSearch("query 3") → full results in context
65
+ ```
66
+
67
+ **mcp.batchSearch() (auto rate-limit handling, filtered):**
68
+ ```typescript
69
+ // Simple: auto handles rate limits, parses results, returns top 3 per query
70
+ const results = await mcp.batchSearch([
71
+ 'React hooks best practices',
72
+ 'Vue composition API tutorial',
73
+ 'Svelte vs React performance'
74
+ ], { topN: 3 });
75
+
76
+ return results;
77
+ // Returns: { "React hooks...": [{title, url, description}, ...], ... }
78
+ ```
79
+
80
+ **How it works:**
81
+ - Attempts parallel execution first (fastest)
82
+ - Auto-detects rate limits from errors
83
+ - Falls back to sequential with delays if needed
84
+ - Caches rate limit status for session
85
+ - Parses text response into structured data
86
+
87
+ **For other rate-limited APIs, use mcp.batch():**
88
+ ```typescript
89
+ // Generic batch with auto rate limit handling
90
+ const results = await mcp.batch([
91
+ { server: 'github', tool: 'search_repositories', params: { query: 'ai' } },
92
+ { server: 'github', tool: 'search_repositories', params: { query: 'ml' } },
93
+ ]);
94
+ ```
95
+
96
+ **Rate limit warnings (displayed in logs):**
97
+ ```
98
+ ⚠️ RATE LIMITED: brave-search - Free tier limit hit. Retrying with delays...
99
+ 💡 TIP: Upgrade your API plan for parallel execution: https://brave.com/search/api/
100
+ ```
101
+
102
+ **Benefits:**
103
+ - Automatic rate limit detection and handling
104
+ - No manual delays needed
105
+ - Parallel when possible, sequential when required
106
+ - ~80%+ token savings
107
+
108
+ **After upgrading API plan, use `forceParallel` to test:**
109
+ ```typescript
110
+ const results = await mcp.batchSearch([
111
+ 'query 1', 'query 2', 'query 3', 'query 4', 'query 5'
112
+ ], { topN: 3, forceParallel: true });
113
+ ```
114
+
115
+ **Note:** Use native WebSearch for single searches or when brave-search isn't configured.
116
+
117
+ ### Quick Reference
118
+
119
+ ```typescript
120
+ // Get a server client
121
+ const github = mcp.server('github');
122
+ // Or use attribute style
123
+ const github = mcp.github;
124
+
125
+ // Call a tool
126
+ const repos = await github.call('search_repositories', { query: 'ai' });
127
+
128
+ // Search for tools across all servers
129
+ const tools = await mcp.searchTools('file');
130
+
131
+ // Log (captured in response)
132
+ mcp.log('Processing...');
133
+ ```
134
+
135
+ ## Available MCP Servers
136
+
137
+ Run `mcp-conductor - list_servers` to see all connected servers and their tools.