funolio-agent 1.0.47 → 1.0.49
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/dist/agent-config.d.ts +9 -1
- package/dist/agent-config.d.ts.map +1 -1
- package/dist/agent-config.js +4 -1
- package/dist/agent-config.js.map +1 -1
- package/dist/auth/auto-detect.d.ts +1 -0
- package/dist/auth/auto-detect.d.ts.map +1 -1
- package/dist/auth/auto-detect.js +16 -13
- package/dist/auth/auto-detect.js.map +1 -1
- package/dist/auto-organizer.d.ts.map +1 -1
- package/dist/auto-organizer.js +4 -3
- package/dist/auto-organizer.js.map +1 -1
- package/dist/backfill.d.ts.map +1 -1
- package/dist/backfill.js +3 -2
- package/dist/backfill.js.map +1 -1
- package/dist/bot-manager.d.ts +8 -23
- package/dist/bot-manager.d.ts.map +1 -1
- package/dist/bot-manager.js +61 -388
- package/dist/bot-manager.js.map +1 -1
- package/dist/clerk-model.d.ts +5 -1
- package/dist/clerk-model.d.ts.map +1 -1
- package/dist/clerk-model.js +40 -28
- package/dist/clerk-model.js.map +1 -1
- package/dist/cli-session-epoch.d.ts +10 -0
- package/dist/cli-session-epoch.d.ts.map +1 -0
- package/dist/cli-session-epoch.js +61 -0
- package/dist/cli-session-epoch.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +30 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/pool.js +1 -1
- package/dist/commands/pool.js.map +1 -1
- package/dist/commands/setup.d.ts +37 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +154 -43
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +195 -164
- package/dist/commands/start.js.map +1 -1
- package/dist/config-cleanup.d.ts.map +1 -1
- package/dist/config-cleanup.js +2 -1
- package/dist/config-cleanup.js.map +1 -1
- package/dist/config.d.ts +6 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -30
- package/dist/config.js.map +1 -1
- package/dist/context-window.d.ts +33 -5
- package/dist/context-window.d.ts.map +1 -1
- package/dist/context-window.js +121 -20
- package/dist/context-window.js.map +1 -1
- package/dist/eval/orchestrator-front-door-replay.js +1 -1
- package/dist/eval/orchestrator-front-door-replay.js.map +1 -1
- package/dist/eval/policy-detection-replay.js +1 -1
- package/dist/eval/policy-detection-replay.js.map +1 -1
- package/dist/integration-tokens.d.ts +1 -6
- package/dist/integration-tokens.d.ts.map +1 -1
- package/dist/integration-tokens.js +38 -40
- package/dist/integration-tokens.js.map +1 -1
- package/dist/local-cli-pty-manager.d.ts +50 -0
- package/dist/local-cli-pty-manager.d.ts.map +1 -0
- package/dist/local-cli-pty-manager.js +645 -0
- package/dist/local-cli-pty-manager.js.map +1 -0
- package/dist/local-data.d.ts +30 -0
- package/dist/local-data.d.ts.map +1 -1
- package/dist/local-data.js +56 -1
- package/dist/local-data.js.map +1 -1
- package/dist/local-db.d.ts.map +1 -1
- package/dist/local-db.js +54 -1
- package/dist/local-db.js.map +1 -1
- package/dist/local-funnel.d.ts.map +1 -1
- package/dist/local-funnel.js +3 -2
- package/dist/local-funnel.js.map +1 -1
- package/dist/local-memory-search.d.ts +1 -0
- package/dist/local-memory-search.d.ts.map +1 -1
- package/dist/local-memory-search.js +101 -18
- package/dist/local-memory-search.js.map +1 -1
- package/dist/local-server.d.ts +0 -16
- package/dist/local-server.d.ts.map +1 -1
- package/dist/local-server.js +339 -287
- package/dist/local-server.js.map +1 -1
- package/dist/mcp/bridge-server.d.ts.map +1 -1
- package/dist/mcp/bridge-server.js +2 -1
- package/dist/mcp/bridge-server.js.map +1 -1
- package/dist/mcp/local-memory-server.d.ts +5 -0
- package/dist/mcp/local-memory-server.d.ts.map +1 -1
- package/dist/mcp/local-memory-server.js +15 -2
- package/dist/mcp/local-memory-server.js.map +1 -1
- package/dist/mcp/manager.d.ts +3 -22
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +66 -388
- package/dist/mcp/manager.js.map +1 -1
- package/dist/memory-extraction.d.ts +2 -0
- package/dist/memory-extraction.d.ts.map +1 -1
- package/dist/memory-extraction.js +3 -1
- package/dist/memory-extraction.js.map +1 -1
- package/dist/message-loop.d.ts +10 -6
- package/dist/message-loop.d.ts.map +1 -1
- package/dist/message-loop.js +241 -540
- package/dist/message-loop.js.map +1 -1
- package/dist/mqtt-client.d.ts +2 -31
- package/dist/mqtt-client.d.ts.map +1 -1
- package/dist/mqtt-client.js +2 -2
- package/dist/mqtt-client.js.map +1 -1
- package/dist/oauth.d.ts +6 -0
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +91 -0
- package/dist/oauth.js.map +1 -1
- package/dist/orchestration/front-door-policy.d.ts +5 -2
- package/dist/orchestration/front-door-policy.d.ts.map +1 -1
- package/dist/orchestration/front-door-policy.js +25 -28
- package/dist/orchestration/front-door-policy.js.map +1 -1
- package/dist/orchestration/orchestrator-blocked-prompt.js +1 -1
- package/dist/orchestration/orchestrator-final-response-prompt.js +1 -1
- package/dist/orchestration/orchestrator-operating-prompt.d.ts +11 -0
- package/dist/orchestration/orchestrator-operating-prompt.d.ts.map +1 -1
- package/dist/orchestration/orchestrator-operating-prompt.js +67 -44
- package/dist/orchestration/orchestrator-operating-prompt.js.map +1 -1
- package/dist/orchestration/worker-operating-prompt.js +3 -3
- package/dist/orchestration/worker-operating-prompt.js.map +1 -1
- package/dist/orchestrator.d.ts +5 -1
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +141 -81
- package/dist/orchestrator.js.map +1 -1
- package/dist/prompt-template.js +3 -3
- package/dist/prompt-template.js.map +1 -1
- package/dist/providers/claude-cli-prompt.d.ts.map +1 -1
- package/dist/providers/claude-cli-prompt.js +22 -6
- package/dist/providers/claude-cli-prompt.js.map +1 -1
- package/dist/providers/claude-cli.d.ts.map +1 -1
- package/dist/providers/claude-cli.js +20 -2
- package/dist/providers/claude-cli.js.map +1 -1
- package/dist/providers/codex-cli.d.ts.map +1 -1
- package/dist/providers/codex-cli.js +71 -16
- package/dist/providers/codex-cli.js.map +1 -1
- package/dist/providers/index.d.ts +11 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/runtime-context.d.ts +10 -0
- package/dist/runtime-context.d.ts.map +1 -0
- package/dist/runtime-context.js +30 -0
- package/dist/runtime-context.js.map +1 -0
- package/dist/subagent/queue.d.ts.map +1 -1
- package/dist/subagent/queue.js +1 -0
- package/dist/subagent/queue.js.map +1 -1
- package/dist/summarization-pipeline.d.ts +1 -0
- package/dist/summarization-pipeline.d.ts.map +1 -1
- package/dist/summarization-pipeline.js +94 -25
- package/dist/summarization-pipeline.js.map +1 -1
- package/dist/tool-permissions.d.ts +2 -0
- package/dist/tool-permissions.d.ts.map +1 -0
- package/dist/tool-permissions.js +25 -0
- package/dist/tool-permissions.js.map +1 -0
- package/dist/tools/index.d.ts +7 -8
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +70 -60
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/search-memory.d.ts.map +1 -1
- package/dist/tools/search-memory.js +9 -3
- package/dist/tools/search-memory.js.map +1 -1
- package/dist/tools/spawn-subagent.d.ts.map +1 -1
- package/dist/tools/spawn-subagent.js +1 -0
- package/dist/tools/spawn-subagent.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +1 -1
- package/dist/wizard-support.d.ts.map +1 -1
- package/dist/wizard-support.js +8 -6
- package/dist/wizard-support.js.map +1 -1
- package/dist/workflow-engine.d.ts +6 -2
- package/dist/workflow-engine.d.ts.map +1 -1
- package/dist/workflow-engine.js +254 -77
- package/dist/workflow-engine.js.map +1 -1
- package/package.json +2 -1
package/dist/mcp/manager.js
CHANGED
|
@@ -40,20 +40,22 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
40
40
|
return result;
|
|
41
41
|
};
|
|
42
42
|
})();
|
|
43
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
44
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
45
|
-
};
|
|
46
43
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
44
|
exports.MCPManager = void 0;
|
|
48
45
|
const child_process_1 = require("child_process");
|
|
49
46
|
const fs = __importStar(require("fs"));
|
|
50
47
|
const path = __importStar(require("path"));
|
|
51
48
|
const events_1 = require("events");
|
|
52
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
53
49
|
const config_1 = require("../config");
|
|
54
50
|
const registry_1 = require("./registry");
|
|
55
51
|
const stdio_protocol_1 = require("./stdio-protocol");
|
|
56
|
-
const
|
|
52
|
+
const GOOGLE_INTEGRATION_SERVER_IDS = new Set([
|
|
53
|
+
'google-workspace',
|
|
54
|
+
'google-drive',
|
|
55
|
+
'google-calendar',
|
|
56
|
+
'google-chat',
|
|
57
|
+
'gmail',
|
|
58
|
+
]);
|
|
57
59
|
/**
|
|
58
60
|
* MCP Manager — singleton per agent instance.
|
|
59
61
|
*/
|
|
@@ -140,104 +142,16 @@ class MCPManager extends events_1.EventEmitter {
|
|
|
140
142
|
? ['-y', config.npmPackage, ...config.args]
|
|
141
143
|
: ['run', config.pipPackage, ...config.args];
|
|
142
144
|
// Inject integration tokens for known MCP servers (e.g. google-drive → Google OAuth tokens)
|
|
143
|
-
const GOOGLE_MCP_IDS = new Set(['google-workspace', 'google-drive', 'google-calendar', 'gmail']);
|
|
144
145
|
let integrationEnv = {};
|
|
145
146
|
try {
|
|
146
|
-
const { getIntegrationEnvVars } = require('../integration-tokens');
|
|
147
|
-
if (
|
|
147
|
+
const { getIntegrationEnvVars, writeGoogleWorkspaceFiles } = require('../integration-tokens');
|
|
148
|
+
if (GOOGLE_INTEGRATION_SERVER_IDS.has(config.id)) {
|
|
149
|
+
writeGoogleWorkspaceFiles();
|
|
148
150
|
integrationEnv = getIntegrationEnvVars('google');
|
|
149
151
|
}
|
|
150
152
|
}
|
|
151
153
|
catch { }
|
|
152
|
-
|
|
153
|
-
const mergedEnv = { ...process.env, ...integrationEnv, ...config.envVars };
|
|
154
|
-
// For Google MCP servers that use file-based auth (e.g. @piotr-agier/google-drive-mcp),
|
|
155
|
-
// write the credentials and tokens to the expected file locations. These packages read
|
|
156
|
-
// from gcp-oauth.keys.json + ~/.config/google-drive-mcp/tokens.json rather than env vars.
|
|
157
|
-
if (GOOGLE_MCP_IDS.has(config.id)) {
|
|
158
|
-
const clientId = mergedEnv.GOOGLE_CLIENT_ID;
|
|
159
|
-
const clientSecret = mergedEnv.GOOGLE_CLIENT_SECRET;
|
|
160
|
-
const refreshToken = mergedEnv.GOOGLE_REFRESH_TOKEN;
|
|
161
|
-
if (clientId && clientSecret && refreshToken) {
|
|
162
|
-
try {
|
|
163
|
-
const os = require('os');
|
|
164
|
-
const configDir = path.join(os.homedir(), '.config', 'google-drive-mcp');
|
|
165
|
-
if (!fs.existsSync(configDir)) {
|
|
166
|
-
fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
167
|
-
}
|
|
168
|
-
// Write credentials file (gcp-oauth.keys.json)
|
|
169
|
-
const keysFile = path.join(configDir, 'gcp-oauth.keys.json');
|
|
170
|
-
fs.writeFileSync(keysFile, JSON.stringify({
|
|
171
|
-
installed: {
|
|
172
|
-
client_id: clientId,
|
|
173
|
-
client_secret: clientSecret,
|
|
174
|
-
redirect_uris: ['http://localhost:3000/oauth2callback'],
|
|
175
|
-
},
|
|
176
|
-
}, null, 2), { mode: 0o600 });
|
|
177
|
-
// Write tokens file — preserve existing valid access_token but
|
|
178
|
-
// always ensure scopes and refresh_token are up to date.
|
|
179
|
-
//
|
|
180
|
-
// The configured scopes are the source of truth. The tokens.json
|
|
181
|
-
// scope field is used by the MCP for internal checks (authGetStatus),
|
|
182
|
-
// so it must always match the configured scopes — not whatever the
|
|
183
|
-
// token file happened to have from a previous OAuth consent.
|
|
184
|
-
const tokensFile = path.join(configDir, 'tokens.json');
|
|
185
|
-
const incomingAccessToken = mergedEnv.GOOGLE_ACCESS_TOKEN;
|
|
186
|
-
const CONFIGURED_SCOPES = 'https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/gmail.modify https://www.googleapis.com/auth/chat.spaces https://www.googleapis.com/auth/chat.messages';
|
|
187
|
-
let tokenPayload = {
|
|
188
|
-
access_token: incomingAccessToken || 'needs-refresh',
|
|
189
|
-
refresh_token: refreshToken,
|
|
190
|
-
scope: CONFIGURED_SCOPES,
|
|
191
|
-
token_type: 'Bearer',
|
|
192
|
-
expiry_date: incomingAccessToken ? (Date.now() + 3600 * 1000) : (Date.now() - 1000),
|
|
193
|
-
};
|
|
194
|
-
// If we have no server-side token and an existing file has a real
|
|
195
|
-
// access_token, preserve it — but always update scope and refresh_token
|
|
196
|
-
// to match the current configuration.
|
|
197
|
-
if (!incomingAccessToken && fs.existsSync(tokensFile)) {
|
|
198
|
-
try {
|
|
199
|
-
const existing = JSON.parse(fs.readFileSync(tokensFile, 'utf-8'));
|
|
200
|
-
if (existing.access_token && existing.access_token !== 'needs-refresh' && existing.refresh_token) {
|
|
201
|
-
tokenPayload = {
|
|
202
|
-
...existing,
|
|
203
|
-
refresh_token: refreshToken, // Always use configured refresh token
|
|
204
|
-
scope: CONFIGURED_SCOPES, // Always use configured scopes
|
|
205
|
-
token_type: 'Bearer',
|
|
206
|
-
};
|
|
207
|
-
console.log(chalk_1.default.gray(` ℹ Preserving existing Google access_token, updated scope & refresh_token`));
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
catch {
|
|
211
|
-
// Can't parse existing file — use fresh payload
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
fs.writeFileSync(tokensFile, JSON.stringify(tokenPayload, null, 2), { mode: 0o600 });
|
|
215
|
-
// Also set the env var so the package can find its config
|
|
216
|
-
mergedEnv.GOOGLE_DRIVE_MCP_TOKEN_PATH = tokensFile;
|
|
217
|
-
mergedEnv.GOOGLE_CLIENT_SECRET_PATH = keysFile;
|
|
218
|
-
// Gmail MCP (@gongrzhe/server-gmail-autoauth-mcp) reads from ~/.gmail-mcp/
|
|
219
|
-
// instead of ~/.config/google-drive-mcp/, so duplicate the files there.
|
|
220
|
-
if (config.id === 'gmail') {
|
|
221
|
-
const gmailDir = path.join(os.homedir(), '.gmail-mcp');
|
|
222
|
-
if (!fs.existsSync(gmailDir)) {
|
|
223
|
-
fs.mkdirSync(gmailDir, { recursive: true, mode: 0o700 });
|
|
224
|
-
}
|
|
225
|
-
fs.copyFileSync(keysFile, path.join(gmailDir, 'gcp-oauth.keys.json'));
|
|
226
|
-
fs.copyFileSync(tokensFile, path.join(gmailDir, 'tokens.json'));
|
|
227
|
-
console.log(chalk_1.default.green(` ✓ Also wrote Gmail credentials to ${gmailDir}`));
|
|
228
|
-
}
|
|
229
|
-
console.log(chalk_1.default.green(` ✓ Wrote Google credentials for ${config.id} to ${configDir}`));
|
|
230
|
-
}
|
|
231
|
-
catch (err) {
|
|
232
|
-
console.error(chalk_1.default.red(` ✗ Failed to write Google credential files: ${err.message}`));
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
console.warn(chalk_1.default.yellow(` ⚠ Missing Google credentials for ${config.id} (need CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN)`));
|
|
237
|
-
console.warn(chalk_1.default.yellow(` Install via the Funolio marketplace and provide your Google OAuth credentials.`));
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
const env = mergedEnv;
|
|
154
|
+
const env = { ...process.env, ...integrationEnv, ...config.envVars };
|
|
241
155
|
const child = (0, child_process_1.spawn)(cmd, args, {
|
|
242
156
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
243
157
|
env,
|
|
@@ -250,7 +164,6 @@ class MCPManager extends events_1.EventEmitter {
|
|
|
250
164
|
ready: false,
|
|
251
165
|
requestId: 0,
|
|
252
166
|
pendingRequests: new Map(),
|
|
253
|
-
stderrBuffer: [],
|
|
254
167
|
};
|
|
255
168
|
this.servers.set(config.id, instance);
|
|
256
169
|
const reader = new stdio_protocol_1.McpStdioMessageReader((msg) => {
|
|
@@ -268,19 +181,11 @@ class MCPManager extends events_1.EventEmitter {
|
|
|
268
181
|
child.stdout?.on('data', (chunk) => {
|
|
269
182
|
reader.feed(chunk);
|
|
270
183
|
});
|
|
271
|
-
// Capture recent stderr for error enrichment (ring buffer, last 5 messages)
|
|
272
|
-
const stderrBuffer = [];
|
|
273
|
-
const MAX_STDERR_LINES = 5;
|
|
274
184
|
child.stderr?.on('data', (data) => {
|
|
275
185
|
const msg = data.toString().trim();
|
|
276
|
-
if (msg)
|
|
186
|
+
if (msg)
|
|
277
187
|
console.error(`[MCP:${config.id}] ${msg}`);
|
|
278
|
-
stderrBuffer.push(msg);
|
|
279
|
-
if (stderrBuffer.length > MAX_STDERR_LINES)
|
|
280
|
-
stderrBuffer.shift();
|
|
281
|
-
}
|
|
282
188
|
});
|
|
283
|
-
instance.stderrBuffer = stderrBuffer;
|
|
284
189
|
child.on('exit', (code) => {
|
|
285
190
|
const error = new Error(`MCP server ${config.id} exited with code ${code}`);
|
|
286
191
|
for (const [requestId, pending] of instance.pendingRequests.entries()) {
|
|
@@ -291,36 +196,6 @@ class MCPManager extends events_1.EventEmitter {
|
|
|
291
196
|
this.servers.delete(config.id);
|
|
292
197
|
this.rebuildToolList();
|
|
293
198
|
this.emit('server-stopped', config.id);
|
|
294
|
-
// Fix 2: MCP crash recovery — attempt one restart for always-on servers
|
|
295
|
-
// Track restart attempts to prevent infinite crash loops
|
|
296
|
-
if (config.alwaysOn && entry) {
|
|
297
|
-
const key = `restart_${config.id}`;
|
|
298
|
-
const now = Date.now();
|
|
299
|
-
const restartState = this[key] || { count: 0, firstAttempt: now };
|
|
300
|
-
restartState.count++;
|
|
301
|
-
this[key] = restartState;
|
|
302
|
-
// Max 3 restart attempts within 5 minutes, then give up
|
|
303
|
-
const MAX_RESTARTS = 3;
|
|
304
|
-
const RESTART_WINDOW_MS = 5 * 60 * 1000;
|
|
305
|
-
if (now - restartState.firstAttempt > RESTART_WINDOW_MS) {
|
|
306
|
-
restartState.count = 1;
|
|
307
|
-
restartState.firstAttempt = now;
|
|
308
|
-
}
|
|
309
|
-
if (restartState.count <= MAX_RESTARTS) {
|
|
310
|
-
console.log(`[MCP] Attempting auto-restart of ${config.id} (${restartState.count}/${MAX_RESTARTS})...`);
|
|
311
|
-
this.launchServer(config, entry).then((tools) => {
|
|
312
|
-
console.log(`[MCP] ✓ Auto-restarted ${config.id} — ${tools.length} tools recovered`);
|
|
313
|
-
restartState.count = 0; // Reset on success
|
|
314
|
-
this.emit('server-restarted', config.id, tools);
|
|
315
|
-
}).catch((err) => {
|
|
316
|
-
console.error(`[MCP] ✗ Auto-restart of ${config.id} failed: ${err.message}`);
|
|
317
|
-
this.emit('server-restart-failed', config.id, err.message);
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
else {
|
|
321
|
-
console.error(`[MCP] ✗ ${config.id} crashed ${restartState.count} times in ${Math.round((now - restartState.firstAttempt) / 1000)}s — giving up on auto-restart`);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
199
|
});
|
|
325
200
|
// MCP handshake: initialize
|
|
326
201
|
try {
|
|
@@ -339,15 +214,10 @@ class MCPManager extends events_1.EventEmitter {
|
|
|
339
214
|
inputSchema: t.inputSchema || {},
|
|
340
215
|
serverId: config.id,
|
|
341
216
|
}));
|
|
342
|
-
|
|
343
|
-
const groupedTools = (0, tool_grouping_1.groupMcpTools)(tools, config.id, entry.name);
|
|
344
|
-
if (groupedTools.length < tools.length) {
|
|
345
|
-
console.log(`[MCP] ${entry.name}: grouped ${tools.length} tools into ${groupedTools.length} meta-tool(s)`);
|
|
346
|
-
}
|
|
347
|
-
instance.tools = groupedTools;
|
|
217
|
+
instance.tools = tools;
|
|
348
218
|
instance.ready = true;
|
|
349
219
|
this.rebuildToolList();
|
|
350
|
-
console.log(`[MCP] ${entry.name} launched — ${tools.length} tools discovered
|
|
220
|
+
console.log(`[MCP] ${entry.name} launched — ${tools.length} tools discovered`);
|
|
351
221
|
this.emit('server-ready', config.id, tools);
|
|
352
222
|
return tools;
|
|
353
223
|
}
|
|
@@ -365,14 +235,13 @@ class MCPManager extends events_1.EventEmitter {
|
|
|
365
235
|
const msg = (0, stdio_protocol_1.encodeMcpStdioMessage)({ jsonrpc: '2.0', id, method, params });
|
|
366
236
|
instance.pendingRequests.set(id, { resolve, reject });
|
|
367
237
|
instance.process.stdin.write(msg);
|
|
368
|
-
// Timeout
|
|
369
|
-
const timeoutMs = method === 'initialize' ? 120_000 : 30_000;
|
|
238
|
+
// Timeout after 30s
|
|
370
239
|
setTimeout(() => {
|
|
371
240
|
if (instance.pendingRequests.has(id)) {
|
|
372
241
|
instance.pendingRequests.delete(id);
|
|
373
|
-
reject(new Error(`MCP request ${method} timed out
|
|
242
|
+
reject(new Error(`MCP request ${method} timed out`));
|
|
374
243
|
}
|
|
375
|
-
},
|
|
244
|
+
}, 30_000);
|
|
376
245
|
});
|
|
377
246
|
}
|
|
378
247
|
/** Send a JSON-RPC notification (no response expected) */
|
|
@@ -380,117 +249,8 @@ class MCPManager extends events_1.EventEmitter {
|
|
|
380
249
|
const msg = (0, stdio_protocol_1.encodeMcpStdioMessage)({ jsonrpc: '2.0', method, params });
|
|
381
250
|
instance.process.stdin.write(msg);
|
|
382
251
|
}
|
|
383
|
-
/**
|
|
384
|
-
enrichErrorResult(result, toolName, args) {
|
|
385
|
-
if (!result?.isError)
|
|
386
|
-
return result;
|
|
387
|
-
const contentText = result.content
|
|
388
|
-
? result.content.map((c) => c.text || JSON.stringify(c)).join('\n')
|
|
389
|
-
: '';
|
|
390
|
-
// Detect unhelpful errors and add context
|
|
391
|
-
const isUnhelpful = !contentText || contentText === 'Unknown error' || contentText.length < 20;
|
|
392
|
-
// Check for file-type-specific hints
|
|
393
|
-
const fileId = (args.fileId || args.file_id || '');
|
|
394
|
-
const mimeType = (args.mimeType || args.mime_type || '');
|
|
395
|
-
const actionName = toolName.replace(/^mcp_[^_]+_/, '');
|
|
396
|
-
let hint = '';
|
|
397
|
-
if (actionName === 'downloadFile' || actionName === 'readSpreadsheet' || actionName === 'readDocument') {
|
|
398
|
-
const uploadedFormats = ['.xlsx', '.docx', '.pptx', '.csv', '.pdf'];
|
|
399
|
-
const uploadedMimes = ['application/vnd.openxmlformats', 'application/msword', 'application/pdf'];
|
|
400
|
-
const isUploaded = uploadedFormats.some(f => mimeType.includes(f)) ||
|
|
401
|
-
uploadedMimes.some(m => mimeType.includes(m));
|
|
402
|
-
if (isUploaded && actionName === 'readSpreadsheet') {
|
|
403
|
-
hint = '\nHINT: This file appears to be an uploaded .xlsx file, NOT a native Google Sheet. ' +
|
|
404
|
-
'Use downloadFile to save it locally, then read with run_command (e.g., python with openpyxl).';
|
|
405
|
-
}
|
|
406
|
-
else if (isUploaded && actionName === 'downloadFile' && isUnhelpful) {
|
|
407
|
-
hint = '\nHINT: downloadFile failed. Try using copyFile with convert=true to convert to native Google format first, then use the appropriate read tool.';
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
if (hint && result.content) {
|
|
411
|
-
result.content = [
|
|
412
|
-
...result.content,
|
|
413
|
-
{ type: 'text', text: hint },
|
|
414
|
-
];
|
|
415
|
-
}
|
|
416
|
-
return result;
|
|
417
|
-
}
|
|
418
|
-
/** Call a tool on an MCP server (handles both regular and meta-tools) */
|
|
252
|
+
/** Call a tool on an MCP server */
|
|
419
253
|
async callTool(toolName, args) {
|
|
420
|
-
// Handle meta-tool calls: resolve to the real tool name and args
|
|
421
|
-
if ((0, tool_grouping_1.isMetaTool)(toolName)) {
|
|
422
|
-
const action = args.action;
|
|
423
|
-
// LLMs sometimes put params at the top level instead of inside "params".
|
|
424
|
-
// If "params" exists, use it. Otherwise, extract everything except "action"
|
|
425
|
-
// as the params object — this handles both { action, params: {...} } and
|
|
426
|
-
// { action, localPath, name, ... } formats.
|
|
427
|
-
let params;
|
|
428
|
-
if (args.params && typeof args.params === 'object') {
|
|
429
|
-
params = args.params;
|
|
430
|
-
}
|
|
431
|
-
else {
|
|
432
|
-
const { action: _a, ...rest } = args;
|
|
433
|
-
params = Object.keys(rest).length > 0 ? rest : {};
|
|
434
|
-
}
|
|
435
|
-
if (!action)
|
|
436
|
-
throw new Error(`Meta-tool "${toolName}" requires an "action" parameter`);
|
|
437
|
-
const { originalToolName, originalArgs } = (0, tool_grouping_1.resolveMetaTool)(toolName, action, params);
|
|
438
|
-
// Find the server for this meta-tool
|
|
439
|
-
const metaTool = this.allDiscoveredTools.find((t) => t.name === toolName);
|
|
440
|
-
if (!metaTool)
|
|
441
|
-
throw new Error(`Meta-tool not found: ${toolName}`);
|
|
442
|
-
const instance = this.servers.get(metaTool.serverId);
|
|
443
|
-
if (!instance || !instance.ready)
|
|
444
|
-
throw new Error(`MCP server ${metaTool.serverId} not running`);
|
|
445
|
-
// Strip prefix to get the raw MCP tool name
|
|
446
|
-
const rawName = originalToolName.replace(`mcp_${metaTool.serverId}_`, '');
|
|
447
|
-
// Auto-create parent directories for downloadFile to avoid "Parent directory does not exist"
|
|
448
|
-
if (rawName === 'downloadFile' || rawName === 'download_file') {
|
|
449
|
-
const localPath = originalArgs.localPath || originalArgs.local_path;
|
|
450
|
-
if (typeof localPath === 'string') {
|
|
451
|
-
const parentDir = path.dirname(localPath);
|
|
452
|
-
if (!fs.existsSync(parentDir)) {
|
|
453
|
-
fs.mkdirSync(parentDir, { recursive: true });
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
try {
|
|
458
|
-
const result = await this.sendRequest(instance, 'tools/call', { name: rawName, arguments: originalArgs });
|
|
459
|
-
// Bug 4: Enrich meta-tool errors with stderr
|
|
460
|
-
if (result?.isError) {
|
|
461
|
-
const recentStderr = instance.stderrBuffer?.slice(-3)
|
|
462
|
-
.filter((s) => s.includes('Error') || s.includes('error') || s.includes('fail') || s.includes('not exist') || s.includes('Invalid'))
|
|
463
|
-
.join(' | ');
|
|
464
|
-
if (recentStderr) {
|
|
465
|
-
const existingText = result.content?.map((c) => c.text || '').join(' ') || '';
|
|
466
|
-
result.content = [{ type: 'text', text: `${existingText || 'Unknown error'}. Server details: ${recentStderr}` }];
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
// Bug 1: Validate downloadFile results — detect Google sign-in pages
|
|
470
|
-
if ((rawName === 'downloadFile' || rawName === 'download_file') && !result?.isError) {
|
|
471
|
-
const localPath = (originalArgs.localPath || originalArgs.local_path);
|
|
472
|
-
if (localPath && fs.existsSync(localPath)) {
|
|
473
|
-
const header = Buffer.alloc(500);
|
|
474
|
-
const fd = fs.openSync(localPath, 'r');
|
|
475
|
-
fs.readSync(fd, header, 0, 500, 0);
|
|
476
|
-
fs.closeSync(fd);
|
|
477
|
-
const headerStr = header.toString('utf8').toLowerCase();
|
|
478
|
-
if (headerStr.includes('accounts.google.com') ||
|
|
479
|
-
(headerStr.startsWith('<!doctype html>') && !localPath.endsWith('.html'))) {
|
|
480
|
-
fs.unlinkSync(localPath);
|
|
481
|
-
return {
|
|
482
|
-
content: [{ type: 'text', text: 'ERROR: Download failed — received Google sign-in page instead of actual file content. The OAuth token may have expired. The file was NOT saved. Try using run_command to download via curl with a fresh token, or ask the user to re-authenticate Google Workspace.' }],
|
|
483
|
-
isError: true,
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
return this.enrichErrorResult(result, originalToolName, originalArgs);
|
|
489
|
-
}
|
|
490
|
-
catch (err) {
|
|
491
|
-
throw new Error(`MCP tool ${rawName} failed: ${err.message}${err.stack ? '\n' + err.stack.split('\n').slice(0, 3).join('\n') : ''}`);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
254
|
const tool = this.allDiscoveredTools.find((t) => t.name === toolName);
|
|
495
255
|
if (!tool)
|
|
496
256
|
throw new Error(`MCP tool not found: ${toolName}`);
|
|
@@ -499,45 +259,11 @@ class MCPManager extends events_1.EventEmitter {
|
|
|
499
259
|
throw new Error(`MCP server ${tool.serverId} not running`);
|
|
500
260
|
// Strip the mcp_{serverId}_ prefix to get the original tool name
|
|
501
261
|
const originalName = toolName.replace(`mcp_${tool.serverId}_`, '');
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
if (!fs.existsSync(parentDir)) {
|
|
508
|
-
fs.mkdirSync(parentDir, { recursive: true });
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
try {
|
|
513
|
-
const result = await this.sendRequest(instance, 'tools/call', {
|
|
514
|
-
name: originalName,
|
|
515
|
-
arguments: args,
|
|
516
|
-
});
|
|
517
|
-
// Bug 1: Validate downloadFile results — detect Google sign-in pages
|
|
518
|
-
if ((originalName === 'downloadFile' || originalName === 'download_file') && !result?.isError) {
|
|
519
|
-
const localPath = (args.localPath || args.local_path);
|
|
520
|
-
if (localPath && fs.existsSync(localPath)) {
|
|
521
|
-
const header = Buffer.alloc(500);
|
|
522
|
-
const fd = fs.openSync(localPath, 'r');
|
|
523
|
-
fs.readSync(fd, header, 0, 500, 0);
|
|
524
|
-
fs.closeSync(fd);
|
|
525
|
-
const headerStr = header.toString('utf8').toLowerCase();
|
|
526
|
-
if (headerStr.includes('accounts.google.com') ||
|
|
527
|
-
(headerStr.startsWith('<!doctype html>') && !localPath.endsWith('.html'))) {
|
|
528
|
-
fs.unlinkSync(localPath);
|
|
529
|
-
return {
|
|
530
|
-
content: [{ type: 'text', text: 'ERROR: Download failed — received Google sign-in page instead of actual file content. The OAuth token may have expired. The file was NOT saved. Try using run_command to download via curl with a fresh token, or ask the user to re-authenticate Google Workspace.' }],
|
|
531
|
-
isError: true,
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
return this.enrichErrorResult(result, toolName, args);
|
|
537
|
-
}
|
|
538
|
-
catch (err) {
|
|
539
|
-
throw new Error(`MCP tool ${originalName} failed: ${err.message}${err.stack ? '\n' + err.stack.split('\n').slice(0, 3).join('\n') : ''}`);
|
|
540
|
-
}
|
|
262
|
+
const result = await this.sendRequest(instance, 'tools/call', {
|
|
263
|
+
name: originalName,
|
|
264
|
+
arguments: args,
|
|
265
|
+
});
|
|
266
|
+
return result;
|
|
541
267
|
}
|
|
542
268
|
/** Rebuild the merged list of all discovered tools */
|
|
543
269
|
rebuildToolList() {
|
|
@@ -560,41 +286,22 @@ class MCPManager extends events_1.EventEmitter {
|
|
|
560
286
|
const output = result?.content
|
|
561
287
|
? result.content.map((c) => c.text || JSON.stringify(c)).join('\n')
|
|
562
288
|
: JSON.stringify(result);
|
|
563
|
-
let errorOutput = output;
|
|
564
|
-
if (result?.isError) {
|
|
565
|
-
// Enrich error with recent stderr from the MCP server
|
|
566
|
-
const srv = this.servers.get(t.serverId);
|
|
567
|
-
const recentStderr = srv?.stderrBuffer?.slice(-3)
|
|
568
|
-
.filter((s) => s.includes('Error') || s.includes('error') || s.includes('fail') || s.includes('not exist'))
|
|
569
|
-
.join('\n');
|
|
570
|
-
const parts = ['ERROR:'];
|
|
571
|
-
if (output && output !== 'Unknown error')
|
|
572
|
-
parts.push(output);
|
|
573
|
-
if (recentStderr)
|
|
574
|
-
parts.push(`Server details: ${recentStderr}`);
|
|
575
|
-
if (parts.length === 1)
|
|
576
|
-
parts.push('Unknown error from MCP server');
|
|
577
|
-
errorOutput = parts.join(' ');
|
|
578
|
-
}
|
|
579
289
|
return {
|
|
580
290
|
tool_call_id: '',
|
|
581
291
|
name: t.name,
|
|
582
292
|
success: !result?.isError,
|
|
583
|
-
output
|
|
584
|
-
error: result?.isError ? errorOutput : undefined,
|
|
293
|
+
output,
|
|
585
294
|
verified: true,
|
|
586
295
|
verification_details: 'MCP tool execution',
|
|
587
296
|
};
|
|
588
297
|
}
|
|
589
298
|
catch (err) {
|
|
590
|
-
const errorDetail = err.message || 'Unknown error';
|
|
591
|
-
const stackHint = err.stack ? '\n' + err.stack.split('\n').slice(1, 3).join('\n') : '';
|
|
592
299
|
return {
|
|
593
300
|
tool_call_id: '',
|
|
594
301
|
name: t.name,
|
|
595
302
|
success: false,
|
|
596
303
|
output: '',
|
|
597
|
-
error:
|
|
304
|
+
error: err.message,
|
|
598
305
|
verified: true,
|
|
599
306
|
verification_details: 'MCP tool execution failed',
|
|
600
307
|
};
|
|
@@ -617,6 +324,47 @@ class MCPManager extends events_1.EventEmitter {
|
|
|
617
324
|
alwaysOn: instance.config.alwaysOn,
|
|
618
325
|
};
|
|
619
326
|
}
|
|
327
|
+
async waitForServerExit(id, instance) {
|
|
328
|
+
await new Promise((resolve) => {
|
|
329
|
+
let settled = false;
|
|
330
|
+
const finish = () => {
|
|
331
|
+
if (settled)
|
|
332
|
+
return;
|
|
333
|
+
settled = true;
|
|
334
|
+
clearTimeout(timeout);
|
|
335
|
+
instance.process.removeListener('exit', finish);
|
|
336
|
+
this.removeListener('server-stopped', onStopped);
|
|
337
|
+
resolve();
|
|
338
|
+
};
|
|
339
|
+
const onStopped = (stoppedId) => {
|
|
340
|
+
if (stoppedId === id)
|
|
341
|
+
finish();
|
|
342
|
+
};
|
|
343
|
+
const timeout = setTimeout(finish, 5_000);
|
|
344
|
+
instance.process.once('exit', finish);
|
|
345
|
+
this.on('server-stopped', onStopped);
|
|
346
|
+
instance.process.kill();
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
async reloadIntegrationServers(integration) {
|
|
350
|
+
if (integration !== 'google')
|
|
351
|
+
return [];
|
|
352
|
+
const restarted = [];
|
|
353
|
+
const runningGoogleServers = Array.from(this.servers.entries())
|
|
354
|
+
.filter(([id]) => GOOGLE_INTEGRATION_SERVER_IDS.has(id));
|
|
355
|
+
for (const [id, instance] of runningGoogleServers) {
|
|
356
|
+
const config = {
|
|
357
|
+
...instance.config,
|
|
358
|
+
envVars: { ...instance.config.envVars },
|
|
359
|
+
args: [...instance.config.args],
|
|
360
|
+
};
|
|
361
|
+
const entry = instance.entry;
|
|
362
|
+
await this.waitForServerExit(id, instance);
|
|
363
|
+
await this.launchServer(config, entry);
|
|
364
|
+
restarted.push(id);
|
|
365
|
+
}
|
|
366
|
+
return restarted;
|
|
367
|
+
}
|
|
620
368
|
/** Stop a running MCP server */
|
|
621
369
|
async stopServer(id) {
|
|
622
370
|
const instance = this.servers.get(id);
|
|
@@ -654,76 +402,6 @@ class MCPManager extends events_1.EventEmitter {
|
|
|
654
402
|
this.saveConfigs();
|
|
655
403
|
}
|
|
656
404
|
}
|
|
657
|
-
/**
|
|
658
|
-
* Pre-warm an npm package by running `npx -y <package> --help` in the background.
|
|
659
|
-
* This downloads/caches the package so subsequent launches are fast.
|
|
660
|
-
*/
|
|
661
|
-
static preWarmPackage(npmPackage) {
|
|
662
|
-
console.log(`[MCP] Pre-warming package cache: ${npmPackage}`);
|
|
663
|
-
const child = (0, child_process_1.spawn)('npx', ['-y', npmPackage, '--help'], {
|
|
664
|
-
stdio: 'ignore',
|
|
665
|
-
detached: true,
|
|
666
|
-
env: { ...process.env },
|
|
667
|
-
});
|
|
668
|
-
child.unref();
|
|
669
|
-
child.on('exit', (code) => {
|
|
670
|
-
console.log(`[MCP] Pre-warm of ${npmPackage} finished (exit ${code})`);
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
/**
|
|
674
|
-
* Reload all running Google MCP servers after token refresh.
|
|
675
|
-
* Stops each server, then re-launches with fresh credentials from the token store.
|
|
676
|
-
*/
|
|
677
|
-
async reloadIntegrationServers(integration) {
|
|
678
|
-
if (integration !== 'google')
|
|
679
|
-
return [];
|
|
680
|
-
const GOOGLE_IDS = new Set(['google-workspace', 'google-drive', 'google-calendar', 'gmail']);
|
|
681
|
-
const restarted = [];
|
|
682
|
-
const runningGoogle = Array.from(this.servers.entries())
|
|
683
|
-
.filter(([id]) => GOOGLE_IDS.has(id));
|
|
684
|
-
for (const [id, instance] of runningGoogle) {
|
|
685
|
-
const config = { ...instance.config };
|
|
686
|
-
const entry = instance.entry;
|
|
687
|
-
console.log(`[MCP] Reloading ${id} after Google token refresh...`);
|
|
688
|
-
try {
|
|
689
|
-
// Stop current instance
|
|
690
|
-
instance.process.kill();
|
|
691
|
-
// Wait briefly for exit
|
|
692
|
-
await new Promise((resolve) => {
|
|
693
|
-
const timeout = setTimeout(resolve, 5000);
|
|
694
|
-
instance.process.once('exit', () => { clearTimeout(timeout); resolve(); });
|
|
695
|
-
});
|
|
696
|
-
this.servers.delete(id);
|
|
697
|
-
this.rebuildToolList();
|
|
698
|
-
// Re-launch with fresh tokens from the updated token store
|
|
699
|
-
await this.launchServer(config, entry);
|
|
700
|
-
restarted.push(id);
|
|
701
|
-
console.log(`[MCP] ✓ Reloaded ${id} successfully`);
|
|
702
|
-
}
|
|
703
|
-
catch (err) {
|
|
704
|
-
console.error(`[MCP] ✗ Failed to reload ${id}: ${err.message}`);
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
return restarted;
|
|
708
|
-
}
|
|
709
|
-
/**
|
|
710
|
-
* Fix 3: Check which configured (always-on) servers are NOT currently running.
|
|
711
|
-
* Returns names of dead servers for injection into system prompt / tool list.
|
|
712
|
-
*/
|
|
713
|
-
getDeadServers() {
|
|
714
|
-
const configs = this.loadPersistedConfigs();
|
|
715
|
-
const running = new Set(this.servers.keys());
|
|
716
|
-
return configs
|
|
717
|
-
.filter(c => c.alwaysOn && !running.has(c.id))
|
|
718
|
-
.map(c => c.id);
|
|
719
|
-
}
|
|
720
|
-
/**
|
|
721
|
-
* Check if a specific server is alive and responsive.
|
|
722
|
-
*/
|
|
723
|
-
isServerAlive(id) {
|
|
724
|
-
const instance = this.servers.get(id);
|
|
725
|
-
return !!(instance && instance.ready && instance.process && !instance.process.killed);
|
|
726
|
-
}
|
|
727
405
|
/** Shutdown all MCP servers */
|
|
728
406
|
async shutdown() {
|
|
729
407
|
for (const [id, instance] of this.servers) {
|