92 lines
3.4 KiB
JavaScript
92 lines
3.4 KiB
JavaScript
|
|
/* eslint-disable no-undef */
|
||
|
|
function createConfigAndCleanupApi(ctx) {
|
||
|
|
with (ctx) {
|
||
|
|
function resolveMcpServerRuntimeCommand() {
|
||
|
|
const runtimeCommand = process.execPath;
|
||
|
|
const runtimeEnv = [];
|
||
|
|
|
||
|
|
if (runtimeCommand && existsSync(runtimeCommand)) {
|
||
|
|
const basename = path.basename(runtimeCommand).toLowerCase();
|
||
|
|
const isNodeBinary = basename === "node" || basename.startsWith("node.");
|
||
|
|
if (!isNodeBinary) {
|
||
|
|
runtimeEnv.push({ name: "ELECTRON_RUN_AS_NODE", value: "1" });
|
||
|
|
}
|
||
|
|
return { command: runtimeCommand, env: runtimeEnv };
|
||
|
|
}
|
||
|
|
|
||
|
|
return { command: "node", env: runtimeEnv };
|
||
|
|
}
|
||
|
|
|
||
|
|
function buildMcpServerConfig(port, scopedSessionIds, chatSessionId) {
|
||
|
|
// Use provided scoped IDs, or resolve them from chatSessionId.
|
||
|
|
const effectiveIds = (scopedSessionIds && scopedSessionIds.length > 0)
|
||
|
|
? scopedSessionIds
|
||
|
|
: getScopedSessionIds(chatSessionId);
|
||
|
|
|
||
|
|
const runtimePath = toUnpackedAsarPath(
|
||
|
|
path.join(__dirname, "..", "mcp", "netcatty-mcp-server.cjs"),
|
||
|
|
);
|
||
|
|
const runtime = resolveMcpServerRuntimeCommand();
|
||
|
|
|
||
|
|
const env = [
|
||
|
|
...runtime.env,
|
||
|
|
{ name: "NETCATTY_MCP_PORT", value: String(port) },
|
||
|
|
];
|
||
|
|
|
||
|
|
if (authToken) {
|
||
|
|
env.push({ name: "NETCATTY_MCP_TOKEN", value: authToken });
|
||
|
|
}
|
||
|
|
if (DEBUG_MCP) {
|
||
|
|
env.push({ name: "NETCATTY_MCP_DEBUG", value: "1" });
|
||
|
|
}
|
||
|
|
|
||
|
|
// When chatSessionId is present, the MCP subprocess resolves scope dynamically
|
||
|
|
// through main-process metadata, so avoid freezing session IDs at spawn time.
|
||
|
|
if (!chatSessionId && effectiveIds && effectiveIds.length > 0) {
|
||
|
|
env.push({ name: "NETCATTY_MCP_SESSION_IDS", value: effectiveIds.join(",") });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Pass chatSessionId so MCP server can scope getContext responses
|
||
|
|
if (chatSessionId) {
|
||
|
|
env.push({ name: "NETCATTY_MCP_CHAT_SESSION_ID", value: chatSessionId });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Pass permission mode so MCP server can enforce it locally (defense-in-depth)
|
||
|
|
env.push({ name: "NETCATTY_MCP_PERMISSION_MODE", value: permissionMode });
|
||
|
|
|
||
|
|
return {
|
||
|
|
name: "netcatty-remote-hosts",
|
||
|
|
type: "stdio",
|
||
|
|
command: runtime.command,
|
||
|
|
args: [runtimePath],
|
||
|
|
env,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Cleanup ──
|
||
|
|
|
||
|
|
async function cleanupScopedMetadata(chatSessionId) {
|
||
|
|
if (chatSessionId) {
|
||
|
|
scopedMetadata.delete(chatSessionId);
|
||
|
|
scopedAttachments.delete(chatSessionId);
|
||
|
|
preserveIdleSessionCleanup?.(chatSessionId);
|
||
|
|
clearOpenedSessionScope?.(chatSessionId);
|
||
|
|
cancelledChatSessions.delete(chatSessionId);
|
||
|
|
cancelBackgroundJobsForSession(chatSessionId);
|
||
|
|
cancelWorkerBackgroundJobsForSession(chatSessionId);
|
||
|
|
// Resolve any in-flight approval requests so dispatch()'s finally block
|
||
|
|
// releases its pendingSessionWriteApprovals entry. Without this, a chat
|
||
|
|
// deleted while an approval was pending would leave the per-session
|
||
|
|
// write lock held until the approval timeout expires.
|
||
|
|
clearPendingApprovals(chatSessionId);
|
||
|
|
await cancelSftpOpsForSession(chatSessionId);
|
||
|
|
sftpBridge.clearSftpEncodingStateByPrefix?.(`chat:${chatSessionId}:session:`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return { resolveMcpServerRuntimeCommand, buildMcpServerConfig, cleanupScopedMetadata };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = { createConfigAndCleanupApi };
|