Wire validated core bridge, migrate daemon to it, verify live

This commit is contained in:
Fisto
2026-09-11 14:15:03 +02:00
parent e62d3007d8
commit 7d6650ce4e
27 changed files with 754 additions and 266 deletions
+27 -27
View File
@@ -1,13 +1,12 @@
/*
* Veskslop daemon — MCP control bridge over Chrome DevTools Protocol.
* Veskslop daemon — MCP control bridge over the validated core bridge.
*
* Talks to an unmodified Vesktop through --remote-debugging-port, evaluates
* actions against the real client, receives events through a CDP binding, and
* runs the trigger/whitelist/log logic locally.
* The daemon holds all logic. It sends validated actions to Vesktop and
* receives normalized events. See docs/core-bridge.md.
*
* Usage:
* VESKSLOP_TOKEN_FILE=... pnpm control:bridge
* CDP_URL=http://127.0.0.1:9222 MCP_PORT=8788 pnpm control:bridge
* pnpm control:bridge
* VESKSLOP_SOCKET=/path/to/vesktop-puppet.sock MCP_PORT=8788 pnpm control:bridge
*/
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
@@ -15,45 +14,43 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import * as z from "zod/v4";
import { CdpClient } from "./cdp-client.js";
import { Discord } from "./discord.js";
import { CoreClient, coreSocketPath } from "./core-client.js";
import { dedupe, getQueryCount, paced, sleep } from "./pacing.js";
import { Puppet } from "./puppet.js";
const HOST = process.env.MCP_HOST ?? "127.0.0.1";
const PORT = Number(process.env.MCP_PORT ?? 8788);
let discord: Discord | undefined;
let core: CoreClient | undefined;
let puppet: Puppet | undefined;
let cdpConnected = false;
let coreConnected = false;
async function connectLoop() {
for (;;) {
try {
const cdp = await CdpClient.connect();
const client = await CoreClient.connect();
discord = new Discord(cdp);
puppet = new Puppet(discord);
core = client;
puppet = new Puppet(client);
await discord.installEventBridge();
cdp.onEvent(event => {
client.onEvent(event => {
puppet!.handleEvent(event).catch(error => console.error("[daemon] event error:", error));
});
cdpConnected = true;
console.log("[daemon] connected to Vesktop via CDP");
coreConnected = true;
console.log(`[daemon] connected to the core bridge at ${coreSocketPath}`);
return;
} catch (error) {
console.warn(`[daemon] waiting for Vesktop CDP: ${(error as Error).message}`);
console.warn(`[daemon] waiting for the core bridge: ${(error as Error).message}`);
await sleep(2000);
}
}
}
function requireDiscord(): Discord {
if (!cdpConnected || !discord) throw new Error("Not connected to Vesktop (run it with --remote-debugging-port)");
return discord;
function requireCore(): CoreClient {
if (!coreConnected || !core) throw new Error("Not connected to Vesktop (is the core bridge running?)");
return core;
}
function requirePuppet(): Puppet {
@@ -73,7 +70,7 @@ function buildServer(): McpServer {
{
description: "Get the logged-in user, guilds and channels. Cache-only, no Discord queries."
},
async () => jsonText(await requireDiscord().getState())
async () => jsonText(await requireCore().getState())
);
server.registerTool(
@@ -86,7 +83,7 @@ function buildServer(): McpServer {
}
},
async ({ channelId, content }) => {
await paced(() => requireDiscord().sendMessage(channelId, content));
await paced(() => requireCore().sendMessage(channelId, content));
return jsonText({ channelId, sent: true, queriesUsed: getQueryCount() });
}
);
@@ -101,7 +98,7 @@ function buildServer(): McpServer {
}
},
async ({ userId, content }) => {
const result = await paced(() => requireDiscord().sendDm(userId, content));
const result = await paced(() => requireCore().sendDm(userId, content));
return jsonText({ ...result, userId, queriesUsed: getQueryCount() });
}
);
@@ -117,7 +114,7 @@ function buildServer(): McpServer {
},
async ({ channelId, limit }) => {
const result = await dedupe<{ cached: number; messages: unknown[] }>(`read:${channelId}:${limit}`, () =>
paced(() => requireDiscord().readMessages(channelId, limit))
paced(() => requireCore().readMessages(channelId, limit))
);
return jsonText({ ...result, channelId, queriesUsed: getQueryCount() });
}
@@ -203,7 +200,7 @@ app.delete("/mcp", (_req, res) => {
});
app.get("/health", (_req, res) => {
res.json({ ok: true, cdpConnected, ownerId: puppet?.owner, queries: getQueryCount() });
res.json({ ok: true, coreConnected, socketPath: coreSocketPath, ownerId: puppet?.owner, queries: getQueryCount() });
});
app.listen(PORT, HOST, (error?: Error) => {
@@ -214,4 +211,7 @@ app.listen(PORT, HOST, (error?: Error) => {
console.log(`[daemon] MCP listening on http://${HOST}:${PORT}/mcp`);
});
process.on("SIGINT", () => process.exit(0));
process.on("SIGINT", () => {
core?.close();
process.exit(0);
});