feat(relay): expand to dev-c role + python/ts MCP fallback shims
queue.ts and server.ts now know about dev-c alongside pm/dev-a/dev-b so the four-role coordination paradigm works end-to-end. start.sh opens a fourth window for dev-c. call.py and call.ts are HTTP shims that agents can use when the MCP relay tools aren't registered in their session (the kickoff prompts reference call.py by path as a fallback). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -10,11 +10,6 @@ import { RelayQueue, isRole } from "./queue.ts";
|
||||
const PORT = 7331;
|
||||
const queue = new RelayQueue();
|
||||
|
||||
const mcpServer = new Server(
|
||||
{ name: "relay", version: "0.1.0" },
|
||||
{ capabilities: { tools: {} } }
|
||||
);
|
||||
|
||||
const TOOLS = [
|
||||
{
|
||||
name: "post_message",
|
||||
@@ -25,12 +20,12 @@ const TOOLS = [
|
||||
properties: {
|
||||
from: {
|
||||
type: "string",
|
||||
enum: ["pm", "dev-a", "dev-b"],
|
||||
enum: ["pm", "dev-a", "dev-b", "dev-c"],
|
||||
description: "Your role name",
|
||||
},
|
||||
to: {
|
||||
type: "string",
|
||||
enum: ["pm", "dev-a", "dev-b"],
|
||||
enum: ["pm", "dev-a", "dev-b", "dev-c"],
|
||||
description: "Recipient role name",
|
||||
},
|
||||
kind: {
|
||||
@@ -55,7 +50,7 @@ const TOOLS = [
|
||||
properties: {
|
||||
for: {
|
||||
type: "string",
|
||||
enum: ["pm", "dev-a", "dev-b"],
|
||||
enum: ["pm", "dev-a", "dev-b", "dev-c"],
|
||||
description: "Your role name",
|
||||
},
|
||||
},
|
||||
@@ -71,7 +66,7 @@ const TOOLS = [
|
||||
properties: {
|
||||
for: {
|
||||
type: "string",
|
||||
enum: ["pm", "dev-a", "dev-b"],
|
||||
enum: ["pm", "dev-a", "dev-b", "dev-c"],
|
||||
description: "Your role name",
|
||||
},
|
||||
},
|
||||
@@ -80,41 +75,36 @@ const TOOLS = [
|
||||
},
|
||||
];
|
||||
|
||||
mcpServer.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
||||
|
||||
mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const { name, arguments: args } = request.params;
|
||||
const a = args as Record<string, string>;
|
||||
|
||||
function handleToolCall(name: string, args: Record<string, string>) {
|
||||
if (name === "post_message") {
|
||||
if (!isRole(a.from)) {
|
||||
return { content: [{ type: "text" as const, text: `Error: unknown role "${a.from}"` }], isError: true };
|
||||
if (!isRole(args.from)) {
|
||||
return { content: [{ type: "text" as const, text: `Error: unknown role "${args.from}"` }], isError: true };
|
||||
}
|
||||
if (!isRole(a.to)) {
|
||||
return { content: [{ type: "text" as const, text: `Error: unknown role "${a.to}"` }], isError: true };
|
||||
if (!isRole(args.to)) {
|
||||
return { content: [{ type: "text" as const, text: `Error: unknown role "${args.to}"` }], isError: true };
|
||||
}
|
||||
const kind = a.kind as "status" | "question" | "directive" | "free";
|
||||
const msg = queue.post(a.from, a.to, kind, a.body);
|
||||
const kind = args.kind as "status" | "question" | "directive" | "free";
|
||||
const msg = queue.post(args.from, args.to, kind, args.body);
|
||||
const ts = new Date(msg.ts).toTimeString().slice(0, 8);
|
||||
const preview = a.body.slice(0, 60).replace(/\n/g, " ");
|
||||
const ellipsis = a.body.length > 60 ? "..." : "";
|
||||
process.stdout.write(`[${ts}] ${a.from} → ${a.to} [${kind}] "${preview}${ellipsis}"\n`);
|
||||
const preview = args.body.slice(0, 60).replace(/\n/g, " ");
|
||||
const ellipsis = args.body.length > 60 ? "..." : "";
|
||||
process.stdout.write(`[${ts}] ${args.from} → ${args.to} [${kind}] "${preview}${ellipsis}"\n`);
|
||||
return { content: [{ type: "text" as const, text: JSON.stringify({ id: msg.id }) }] };
|
||||
}
|
||||
|
||||
if (name === "read_messages") {
|
||||
if (!isRole(a.for)) {
|
||||
return { content: [{ type: "text" as const, text: `Error: unknown role "${a.for}"` }], isError: true };
|
||||
if (!isRole(args.for)) {
|
||||
return { content: [{ type: "text" as const, text: `Error: unknown role "${args.for}"` }], isError: true };
|
||||
}
|
||||
const messages = queue.read(a.for);
|
||||
const messages = queue.read(args.for);
|
||||
return { content: [{ type: "text" as const, text: JSON.stringify(messages) }] };
|
||||
}
|
||||
|
||||
if (name === "list_pending") {
|
||||
if (!isRole(a.for)) {
|
||||
return { content: [{ type: "text" as const, text: `Error: unknown role "${a.for}"` }], isError: true };
|
||||
if (!isRole(args.for)) {
|
||||
return { content: [{ type: "text" as const, text: `Error: unknown role "${args.for}"` }], isError: true };
|
||||
}
|
||||
const result = queue.pending(a.for);
|
||||
const result = queue.pending(args.for);
|
||||
return { content: [{ type: "text" as const, text: JSON.stringify(result) }] };
|
||||
}
|
||||
|
||||
@@ -122,7 +112,20 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
content: [{ type: "text" as const, text: `Error: unknown tool "${name}"` }],
|
||||
isError: true,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function makeServer() {
|
||||
const srv = new Server(
|
||||
{ name: "relay", version: "0.1.0" },
|
||||
{ capabilities: { tools: {} } }
|
||||
);
|
||||
srv.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
||||
srv.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const { name, arguments: args } = request.params;
|
||||
return handleToolCall(name, args as Record<string, string>);
|
||||
});
|
||||
return srv;
|
||||
}
|
||||
|
||||
const transports = new Map<string, SSEServerTransport>();
|
||||
|
||||
@@ -132,7 +135,8 @@ const httpServer = http.createServer(async (req, res) => {
|
||||
const transport = new SSEServerTransport("/message", res);
|
||||
transports.set(transport.sessionId, transport);
|
||||
transport.onclose = () => transports.delete(transport.sessionId);
|
||||
await mcpServer.connect(transport);
|
||||
// Each connection gets its own Server instance so multiple clients can coexist.
|
||||
await makeServer().connect(transport);
|
||||
} else if (req.method === "POST" && req.url?.startsWith("/message")) {
|
||||
const url = new URL(req.url, `http://127.0.0.1:${PORT}`);
|
||||
const sessionId = url.searchParams.get("sessionId") ?? "";
|
||||
|
||||
Reference in New Issue
Block a user