Wire validated core bridge, migrate daemon to it, verify live
This commit is contained in:
+27
-27
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user