branch:
mcp-server-agent.ts
4570 bytesRaw
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { McpAgent } from "agents/mcp";
import { z } from "zod";
type State = { totalCalls: number };
const IDLE_TIMEOUT_SECONDS = 15 * 60;
const IDLE_CALLBACK = "onIdleTimeout";
/**
* MCP servers use HTTP (not WebSocket), so they can't use PlaygroundAgent's
* onConnect/onClose idle timeout. Instead, each tool call resets a 15-minute
* self-destruct schedule. If no tools are called before it fires, the agent
* destroys itself to free storage from abandoned demo sessions.
*/
export class PlaygroundMcpServer extends McpAgent<Env, State, {}> {
server = new McpServer({
name: "Playground MCP Server",
version: "1.0.0"
});
initialState: State = { totalCalls: 0 };
private resetIdleTimer() {
for (const schedule of this.getSchedules()) {
if (schedule.callback === IDLE_CALLBACK) {
this.cancelSchedule(schedule.id);
}
}
this.schedule(IDLE_TIMEOUT_SECONDS, IDLE_CALLBACK, {});
}
async onIdleTimeout() {
await this.destroy();
}
async init() {
this.server.registerTool(
"roll_dice",
{
description: "Roll one or more dice with a given number of sides",
inputSchema: {
sides: z.number().min(2).max(100).default(6),
count: z.number().min(1).max(20).default(1)
}
},
async ({ sides, count }) => {
this.resetIdleTimer();
const rolls = Array.from(
{ length: count },
() => Math.floor(Math.random() * sides) + 1
);
this.setState({
totalCalls: this.state.totalCalls + 1
});
return {
content: [
{
type: "text" as const,
text: JSON.stringify({
rolls,
total: rolls.reduce((a, b) => a + b, 0),
sides,
count
})
}
]
};
}
);
this.server.registerTool(
"generate_uuid",
{
description: "Generate one or more random UUIDs",
inputSchema: {
count: z.number().min(1).max(10).default(1)
}
},
async ({ count }) => {
this.resetIdleTimer();
const uuids = Array.from({ length: count }, () => crypto.randomUUID());
this.setState({
totalCalls: this.state.totalCalls + 1
});
return {
content: [
{
type: "text" as const,
text: JSON.stringify({ uuids })
}
]
};
}
);
this.server.registerTool(
"word_count",
{
description: "Count words, characters, and lines in text",
inputSchema: {
text: z.string()
}
},
async ({ text }) => {
this.resetIdleTimer();
const words = text.trim().split(/\s+/).filter(Boolean).length;
const characters = text.length;
const lines = text.split("\n").length;
this.setState({
totalCalls: this.state.totalCalls + 1
});
return {
content: [
{
type: "text" as const,
text: JSON.stringify({ words, characters, lines })
}
]
};
}
);
this.server.registerTool(
"hash_text",
{
description: "Compute SHA-256 hash of text",
inputSchema: {
text: z.string()
}
},
async ({ text }) => {
this.resetIdleTimer();
const encoder = new TextEncoder();
const data = encoder.encode(text);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hash = Array.from(new Uint8Array(hashBuffer))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
this.setState({
totalCalls: this.state.totalCalls + 1
});
return {
content: [{ type: "text" as const, text: JSON.stringify({ hash }) }]
};
}
);
this.server.resource(
"server_info",
"playground://server-info",
async (uri) => ({
contents: [
{
uri: uri.href,
text: JSON.stringify(
{
name: "Playground MCP Server",
version: "1.0.0",
totalCalls: this.state.totalCalls,
tools: ["roll_dice", "generate_uuid", "word_count", "hash_text"]
},
null,
2
)
}
]
})
);
}
}