Files
NetMesh/electron/bridges/mcpServerBridge/capabilityRpcDispatch.cjs

180 lines
7.4 KiB
JavaScript
Raw Normal View History

"use strict";
const { CAPABILITY_STATUS, CAPABILITY_SURFACES } = require("../../capabilities/constants.cjs");
const { getCapabilityByRpcMethod } = require("../../capabilities/registry.cjs");
const { getMcpToolNameForRpcMethod } = require("../../capabilities/adapters/mcpAdapter.cjs");
const { createVaultService } = require("../../capabilities/services/vaultService.cjs");
const { createPortForwardService } = require("../../capabilities/services/portforwardService.cjs");
const { createSessionService } = require("../../capabilities/services/sessionService.cjs");
const UNROUTED = Symbol("capability-rpc-unrouted");
const SERVICE_BINDINGS = Object.freeze({
"vault.host.get": { domain: "vault", method: "getHost" },
"vault.host.list": { domain: "vault", method: "listHosts" },
"vault.host.open": { domain: "vault", method: "openHost" },
"vault.hosts.create": { domain: "vault", method: "createHosts" },
"vault.host.update": { domain: "vault", method: "updateHost" },
"vault.host.delete": { domain: "vault", method: "deleteHost" },
"vault.host.import": { domain: "vault", method: "importHosts" },
"vault.host.notes.get": { domain: "vault", method: "getHostNotes" },
"vault.host.notes.set": { domain: "vault", method: "setHostNotes" },
"vault.note.list": { domain: "vault", method: "listNotes" },
"vault.note.get": { domain: "vault", method: "getNote" },
"vault.note.create": { domain: "vault", method: "createNote" },
"vault.note.update": { domain: "vault", method: "updateNote" },
"vault.note.delete": { domain: "vault", method: "deleteNote" },
"vault.identity.list": { domain: "vault", method: "listIdentities" },
"vault.proxyProfile.list": { domain: "vault", method: "listProxyProfiles" },
"vault.group.list": { domain: "vault", method: "listGroups" },
"vault.group.create": { domain: "vault", method: "createGroup" },
"vault.group.update": { domain: "vault", method: "updateGroup" },
"vault.group.delete": { domain: "vault", method: "deleteGroup" },
"vault.snippets.list": { domain: "vault", method: "listSnippets" },
"vault.snippets.get": { domain: "vault", method: "getSnippet" },
"vault.snippets.run": { domain: "vault", method: "runSnippet" },
"vault.snippets.create": { domain: "vault", method: "createSnippet" },
"vault.snippets.update": { domain: "vault", method: "updateSnippet" },
"vault.snippets.delete": { domain: "vault", method: "deleteSnippet" },
"vault.scripts.list": { domain: "vault", method: "listScripts" },
"vault.scripts.get": { domain: "vault", method: "getScript" },
"vault.scripts.create": { domain: "vault", method: "createScript" },
"vault.scripts.update": { domain: "vault", method: "updateScript" },
"vault.scripts.delete": { domain: "vault", method: "deleteScript" },
"vault.scripts.run": { domain: "vault", method: "runScript" },
"vault.scripts.reference": { domain: "vault", method: "getScriptReference" },
"vault.scripts.runs.list": { domain: "vault", method: "listScriptRuns" },
"vault.scripts.run.stop": { domain: "vault", method: "stopScriptRun" },
"vault.scripts.run.pause": { domain: "vault", method: "pauseScriptRun" },
"vault.scripts.run.resume": { domain: "vault", method: "resumeScriptRun" },
"vault.scripts.targets.set": { domain: "vault", method: "setScriptTargets" },
"vault.host.connectScripts.list": { domain: "vault", method: "listHostConnectScripts" },
"vault.host.connectScripts.set": { domain: "vault", method: "setHostConnectScripts" },
"portforward.rules.list": { domain: "portforward", method: "listRules" },
"portforward.rules.create": { domain: "portforward", method: "createRule" },
"portforward.rules.update": { domain: "portforward", method: "updateRule" },
"portforward.rules.duplicate": { domain: "portforward", method: "duplicateRule" },
"portforward.rules.delete": { domain: "portforward", method: "deleteRule" },
"portforward.tunnels.list": { domain: "portforward", method: "listTunnels" },
"portforward.start": { domain: "portforward", method: "start" },
"portforward.stop": { domain: "portforward", method: "stop" },
"session.close": { domain: "session", method: "close" },
});
function resolveCapabilitySurface(rpcMethod) {
for (const surface of [
CAPABILITY_SURFACES.PUBLIC,
CAPABILITY_SURFACES.GLOBAL,
CAPABILITY_SURFACES.BUILTIN,
]) {
if (rpcMethod.startsWith("netcatty/") && surface !== CAPABILITY_SURFACES.BUILTIN) {
continue;
}
const capability = getCapabilityByRpcMethod(rpcMethod, surface);
if (capability) {
return { capability, surface };
}
}
return null;
}
function createCapabilityRpcDispatcher(deps) {
const {
invokeVaultAgent,
evaluatePermissionWithGrants,
isChatSessionCancelled,
requestApprovalFromRenderer,
USER_DENIED_MESSAGE,
} = deps;
const vaultService = createVaultService({ invokeVaultAgent });
const portforwardService = createPortForwardService({
invokeVaultAgent,
listPortForwards: deps.listPortForwards,
});
const sessionService = deps.sessionService || createSessionService({
invokeSessionAgent: invokeVaultAgent,
validateClose: deps.validateSessionClose,
beforeClose: deps.beforeSessionClose,
afterClose: deps.afterSessionClose,
onClosed: deps.onSessionClosed,
});
const services = {
vault: vaultService,
portforward: portforwardService,
session: sessionService,
};
return async function dispatchCapabilityRpc(rpcMethod, params = {}) {
if (typeof rpcMethod !== "string" || rpcMethod.startsWith("netcatty/")) {
return UNROUTED;
}
const resolved = resolveCapabilitySurface(rpcMethod);
if (!resolved) {
return UNROUTED;
}
const { capability, surface } = resolved;
if (capability.status !== CAPABILITY_STATUS.IMPLEMENTED) {
return {
ok: false,
code: "CAPABILITY_NOT_IMPLEMENTED",
error: `Capability "${capability.id}" is not implemented yet.`,
};
}
const binding = SERVICE_BINDINGS[capability.id];
if (!binding) {
return UNROUTED;
}
const permission = evaluatePermissionWithGrants({
rpcMethod,
surface,
permissionMode: deps.permissionMode,
params,
context: {
chatSessionCancelled: isChatSessionCancelled(params?.chatSessionId),
},
}, deps.permissionGrantsSnapshot);
if (!permission.allowed) {
return { ok: false, error: permission.error };
}
if (permission.requiresApproval) {
const { chatSessionId, ...toolArgs } = params || {};
const toolName = getMcpToolNameForRpcMethod(rpcMethod, surface) || capability.id;
const approved = await requestApprovalFromRenderer(toolName, toolArgs, chatSessionId);
if (!approved) {
return { ok: false, error: USER_DENIED_MESSAGE };
}
}
const service = services[binding.domain];
const handler = service?.[binding.method];
if (typeof handler !== "function") {
return {
ok: false,
error: `Capability handler "${capability.id}" is unavailable.`,
};
}
const hostOpenGeneration = capability.id === "vault.host.open"
? deps.captureHostOpenScope?.(params?.chatSessionId)
: null;
const result = await handler(params);
if (capability.id === "vault.host.open" && result?.ok !== false && result?.sessionId) {
await deps.onHostOpened?.(params?.chatSessionId, result.sessionId, hostOpenGeneration, result);
}
return result;
};
}
module.exports = {
UNROUTED,
createCapabilityRpcDispatcher,
SERVICE_BINDINGS,
};