@poolzin/pool-bot 2026.4.33 → 2026.4.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"vps-security-tool.d.ts","sourceRoot":"","sources":["../../../src/agents/tools/vps-security-tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAG5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,aAAa,GAAG,YAAY,CAkDtE"}
1
+ {"version":3,"file":"vps-security-tool.d.ts","sourceRoot":"","sources":["../../../src/agents/tools/vps-security-tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAG5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,aAAa,GAAG,YAAY,CAsDtE"}
@@ -158,7 +158,12 @@ async function unbanIP(ip) {
158
158
  const stillBanned = status.includes(ip);
159
159
  if (stillBanned) {
160
160
  return {
161
- content: [{ type: "text", text: `❌ Failed to unban ${ip}. IP may not be banned or fail2ban error.` }],
161
+ content: [
162
+ {
163
+ type: "text",
164
+ text: `❌ Failed to unban ${ip}. IP may not be banned or fail2ban error.`,
165
+ },
166
+ ],
162
167
  };
163
168
  }
164
169
  return {
@@ -187,7 +192,13 @@ ${rules || "No rules configured"}`;
187
192
  async function checkSSH() {
188
193
  const config = await runCommand("cat /etc/ssh/sshd_config 2>/dev/null");
189
194
  const settings = {};
190
- const keys = ["PermitRootLogin", "PasswordAuthentication", "LoginGraceTime", "MaxAuthTries", "PubkeyAuthentication"];
195
+ const keys = [
196
+ "PermitRootLogin",
197
+ "PasswordAuthentication",
198
+ "LoginGraceTime",
199
+ "MaxAuthTries",
200
+ "PubkeyAuthentication",
201
+ ];
191
202
  for (const key of keys) {
192
203
  const match = config.match(new RegExp(`^${key}\\s+(\\w+)`, "m"));
193
204
  settings[key] = match?.[1] ?? "not set";
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2026.4.33",
3
- "commit": "606183f32882c21ddbc7bc39effa20a3a5c759f8",
4
- "builtAt": "2026-04-07T01:22:38.798Z"
2
+ "version": "2026.4.35",
3
+ "commit": "713ea950b901906b93c4bd9d8a313731671f40e5",
4
+ "builtAt": "2026-04-07T11:40:47.952Z"
5
5
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * PoolBot MCP CLI
3
+ *
4
+ * Usage:
5
+ * poolbot mcp serve # Start MCP server on stdio (for Claude Desktop)
6
+ * poolbot mcp serve --http # Start MCP server on HTTP (port 3000)
7
+ * poolbot mcp serve --http --port 3001
8
+ */
9
+ import type { Command } from "commander";
10
+ export declare function registerMCPCli(program: Command): void;
11
+ //# sourceMappingURL=mcp-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-cli.d.ts","sourceRoot":"","sources":["../../src/cli/mcp-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASzC,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,QAqD9C"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * PoolBot MCP CLI
3
+ *
4
+ * Usage:
5
+ * poolbot mcp serve # Start MCP server on stdio (for Claude Desktop)
6
+ * poolbot mcp serve --http # Start MCP server on HTTP (port 3000)
7
+ * poolbot mcp serve --http --port 3001
8
+ */
9
+ import { runMCPServer } from "../mcp/server.js";
10
+ import { loadConfig } from "../config/config.js";
11
+ import { resolveDefaultAgentId } from "../agents/agent-scope.js";
12
+ import { runCommandWithRuntime } from "./cli-utils.js";
13
+ import { defaultRuntime } from "../runtime.js";
14
+ import { theme } from "../terminal/theme.js";
15
+ import { formatDocsLink } from "../terminal/links.js";
16
+ export function registerMCPCli(program) {
17
+ const mcp = program
18
+ .command("mcp")
19
+ .description("Model Context Protocol server for PoolBot")
20
+ .addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/mcp-server", "docs.molt.bot/mcp-server")}\n`);
21
+ mcp
22
+ .command("serve")
23
+ .description("Start MCP server")
24
+ .option("--http", "Use HTTP transport instead of stdio")
25
+ .option("--port <number>", "HTTP port (default: 3000)")
26
+ .option("--read-only", "Disable send_message tool")
27
+ .action(async (opts) => {
28
+ const config = loadConfig();
29
+ const agentId = resolveDefaultAgentId(config);
30
+ const isHTTP = opts.http === true;
31
+ const port = opts.port ? parseInt(opts.port, 10) : undefined;
32
+ const readOnly = opts.readOnly === true;
33
+ defaultRuntime.log("Starting PoolBot MCP Server...");
34
+ defaultRuntime.log(` Agent: ${agentId}`);
35
+ defaultRuntime.log(` Mode: ${isHTTP ? "HTTP" : "stdio"}`);
36
+ if (isHTTP) {
37
+ defaultRuntime.log(` Port: ${port || 3000}`);
38
+ }
39
+ if (readOnly) {
40
+ defaultRuntime.log(" Read-only mode: enabled");
41
+ }
42
+ defaultRuntime.log("");
43
+ defaultRuntime.log("For Claude Desktop, add to claude_desktop_config.json:");
44
+ defaultRuntime.log(JSON.stringify({
45
+ mcpServers: {
46
+ poolbot: {
47
+ command: "poolbot",
48
+ args: ["mcp", "serve"],
49
+ },
50
+ },
51
+ }, null, 2));
52
+ defaultRuntime.log("");
53
+ await runCommandWithRuntime(defaultRuntime, async () => {
54
+ await runMCPServer({
55
+ agentId,
56
+ httpPort: port,
57
+ readOnly,
58
+ });
59
+ });
60
+ });
61
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"register.subclis.d.ts","sourceRoot":"","sources":["../../../src/cli/program/register.subclis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOzC,KAAK,eAAe,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAElE,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,eAAe,CAAC;CAC3B,CAAC;AA8RF,wBAAgB,gBAAgB,IAAI,WAAW,EAAE,CAEhD;AAED,wBAAgB,gCAAgC,IAAI,MAAM,EAAE,CAE3D;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ3F;AAaD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,MAAM,EAAiB,QAkBrF"}
1
+ {"version":3,"file":"register.subclis.d.ts","sourceRoot":"","sources":["../../../src/cli/program/register.subclis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOzC,KAAK,eAAe,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAElE,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,eAAe,CAAC;CAC3B,CAAC;AAuSF,wBAAgB,gBAAgB,IAAI,WAAW,EAAE,CAEhD;AAED,wBAAgB,gCAAgC,IAAI,MAAM,EAAE,CAE3D;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ3F;AAaD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,MAAM,EAAiB,QAkBrF"}
@@ -263,6 +263,15 @@ const entries = [
263
263
  mod.registerUpdateCli(program);
264
264
  },
265
265
  },
266
+ {
267
+ name: "mcp",
268
+ description: "Model Context Protocol server",
269
+ hasSubcommands: true,
270
+ register: async (program) => {
271
+ const mod = await import("../mcp-cli.js");
272
+ mod.registerMCPCli(program);
273
+ },
274
+ },
266
275
  {
267
276
  name: "completion",
268
277
  description: "Generate shell completion script",
@@ -0,0 +1,39 @@
1
+ /**
2
+ * MCP (Model Context Protocol) Server for PoolBot.
3
+ *
4
+ * Exposes PoolBot sessions and conversations to MCP-compatible clients
5
+ * like Claude Desktop, Cursor, VS Code, etc.
6
+ *
7
+ * Supports both stdio and Streamable HTTP transports.
8
+ *
9
+ * @see https://modelcontextprotocol.io/
10
+ */
11
+ interface MCPServerOptions {
12
+ agentId?: string;
13
+ workspaceDir?: string;
14
+ httpPort?: number;
15
+ readOnly?: boolean;
16
+ }
17
+ export declare class PoolBotMCPServer {
18
+ private server;
19
+ private options;
20
+ private workspaceDir;
21
+ private agentId;
22
+ constructor(options?: MCPServerOptions);
23
+ private setupHandlers;
24
+ /**
25
+ * Start MCP server with stdio transport (for Claude Desktop, etc.)
26
+ */
27
+ startStdio(): Promise<void>;
28
+ /**
29
+ * Start MCP server with HTTP transport (for web clients)
30
+ */
31
+ startHTTP(port?: number): Promise<void>;
32
+ /**
33
+ * Stop the MCP server
34
+ */
35
+ stop(): Promise<void>;
36
+ }
37
+ export declare function runMCPServer(options?: MCPServerOptions): Promise<void>;
38
+ export {};
39
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAqBH,UAAU,gBAAgB;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA+HD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,GAAE,gBAAqB;IA2B1C,OAAO,CAAC,aAAa;IAySrB;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAMjC;;OAEG;IACG,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB7C;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAG5B;AAID,wBAAsB,YAAY,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BhF"}
@@ -0,0 +1,466 @@
1
+ /**
2
+ * MCP (Model Context Protocol) Server for PoolBot.
3
+ *
4
+ * Exposes PoolBot sessions and conversations to MCP-compatible clients
5
+ * like Claude Desktop, Cursor, VS Code, etc.
6
+ *
7
+ * Supports both stdio and Streamable HTTP transports.
8
+ *
9
+ * @see https://modelcontextprotocol.io/
10
+ */
11
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
12
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
14
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
15
+ import { createServer } from "node:http";
16
+ import { parse as parseUrl } from "node:url";
17
+ import { existsSync, readFileSync } from "node:fs";
18
+ import { join } from "node:path";
19
+ import { loadConfig } from "../config/config.js";
20
+ import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
21
+ // ── Session Database Helpers ─────────────────────────────────────────
22
+ function getSessionStorePath(workspaceDir, agentId) {
23
+ return join(workspaceDir, "memory", agentId, "sessions.json");
24
+ }
25
+ function loadSessions(workspaceDir, agentId) {
26
+ const storePath = getSessionStorePath(workspaceDir, agentId);
27
+ if (!existsSync(storePath)) {
28
+ return [];
29
+ }
30
+ try {
31
+ const content = readFileSync(storePath, "utf-8");
32
+ const store = JSON.parse(content);
33
+ return Object.entries(store.sessions || {})
34
+ .map(([key, value]) => {
35
+ const session = value;
36
+ return {
37
+ sessionId: session.sessionId || key,
38
+ sessionKey: key,
39
+ createdAt: session.createdAt || 0,
40
+ updatedAt: session.updatedAt || 0,
41
+ messageCount: session.messageCount || 0,
42
+ model: session.model,
43
+ };
44
+ })
45
+ .sort((a, b) => b.updatedAt - a.updatedAt);
46
+ }
47
+ catch {
48
+ return [];
49
+ }
50
+ }
51
+ function getSessionMessagesPath(workspaceDir, agentId, sessionKey) {
52
+ return join(workspaceDir, "memory", agentId, "sessions", `${sessionKey}.jsonl`);
53
+ }
54
+ function loadSessionMessages(workspaceDir, agentId, sessionKey) {
55
+ const messagesPath = getSessionMessagesPath(workspaceDir, agentId, sessionKey);
56
+ if (!existsSync(messagesPath)) {
57
+ return [];
58
+ }
59
+ const messages = [];
60
+ const content = readFileSync(messagesPath, "utf-8");
61
+ const lines = content.trim().split("\n").filter(Boolean);
62
+ for (const line of lines) {
63
+ try {
64
+ const entry = JSON.parse(line);
65
+ if (entry.role === "user" || entry.role === "assistant" || entry.role === "system") {
66
+ messages.push({
67
+ role: entry.role,
68
+ content: entry.content || entry.message || "",
69
+ timestamp: entry.timestamp || entry.createdAt || 0,
70
+ toolCalls: entry.toolCalls || entry.tools,
71
+ });
72
+ }
73
+ }
74
+ catch {
75
+ continue;
76
+ }
77
+ }
78
+ return messages;
79
+ }
80
+ function searchSessions(workspaceDir, agentId, query, limit = 10) {
81
+ const sessions = loadSessions(workspaceDir, agentId);
82
+ const results = [];
83
+ const queryLower = query.toLowerCase();
84
+ for (const session of sessions.slice(0, 50)) {
85
+ // Limit search to recent 50 sessions
86
+ const messages = loadSessionMessages(workspaceDir, agentId, session.sessionKey);
87
+ const matches = [];
88
+ for (const msg of messages) {
89
+ if (msg.content.toLowerCase().includes(queryLower)) {
90
+ // Extract context around the match
91
+ const idx = msg.content.toLowerCase().indexOf(queryLower);
92
+ const start = Math.max(0, idx - 50);
93
+ const end = Math.min(msg.content.length, idx + query.length + 50);
94
+ const excerpt = msg.content.substring(start, end).replace(/\n/g, " ").trim();
95
+ matches.push(`[${msg.role}] ${excerpt}...`);
96
+ if (matches.length >= 3)
97
+ break; // Max 3 matches per session
98
+ }
99
+ }
100
+ if (matches.length > 0) {
101
+ results.push({ session, matches });
102
+ }
103
+ if (results.length >= limit)
104
+ break;
105
+ }
106
+ return results;
107
+ }
108
+ // ── MCP Server Implementation ────────────────────────────────────────
109
+ export class PoolBotMCPServer {
110
+ server;
111
+ options;
112
+ workspaceDir;
113
+ agentId;
114
+ constructor(options = {}) {
115
+ this.options = {
116
+ httpPort: 3000,
117
+ readOnly: false,
118
+ ...options,
119
+ };
120
+ const config = loadConfig();
121
+ this.agentId = options.agentId || resolveDefaultAgentId(config);
122
+ this.workspaceDir = options.workspaceDir || resolveAgentWorkspaceDir(config, this.agentId);
123
+ this.server = new Server({
124
+ name: "poolbot-mcp",
125
+ version: "1.0.0",
126
+ }, {
127
+ capabilities: {
128
+ tools: {},
129
+ resources: {},
130
+ },
131
+ });
132
+ this.setupHandlers();
133
+ }
134
+ setupHandlers() {
135
+ // List available tools
136
+ this.server.setRequestHandler(ListToolsRequestSchema, () => {
137
+ const tools = [
138
+ {
139
+ name: "list_sessions",
140
+ description: "List all PoolBot sessions with metadata",
141
+ inputSchema: {
142
+ type: "object",
143
+ properties: {
144
+ limit: {
145
+ type: "number",
146
+ description: "Maximum number of sessions to return (default: 20)",
147
+ default: 20,
148
+ },
149
+ },
150
+ },
151
+ },
152
+ {
153
+ name: "get_session",
154
+ description: "Get details of a specific PoolBot session",
155
+ inputSchema: {
156
+ type: "object",
157
+ properties: {
158
+ sessionKey: {
159
+ type: "string",
160
+ description: "Session key or ID",
161
+ },
162
+ },
163
+ required: ["sessionKey"],
164
+ },
165
+ },
166
+ {
167
+ name: "get_messages",
168
+ description: "Get messages from a PoolBot session",
169
+ inputSchema: {
170
+ type: "object",
171
+ properties: {
172
+ sessionKey: {
173
+ type: "string",
174
+ description: "Session key or ID",
175
+ },
176
+ limit: {
177
+ type: "number",
178
+ description: "Maximum messages to return (default: 50)",
179
+ default: 50,
180
+ },
181
+ role: {
182
+ type: "string",
183
+ description: "Filter by role: user, assistant, or system",
184
+ enum: ["user", "assistant", "system"],
185
+ },
186
+ },
187
+ required: ["sessionKey"],
188
+ },
189
+ },
190
+ {
191
+ name: "search_sessions",
192
+ description: "Search across all PoolBot sessions for text",
193
+ inputSchema: {
194
+ type: "object",
195
+ properties: {
196
+ query: {
197
+ type: "string",
198
+ description: "Search query",
199
+ },
200
+ limit: {
201
+ type: "number",
202
+ description: "Maximum results to return (default: 10)",
203
+ default: 10,
204
+ },
205
+ },
206
+ required: ["query"],
207
+ },
208
+ },
209
+ {
210
+ name: "get_session_stats",
211
+ description: "Get statistics about PoolBot sessions",
212
+ inputSchema: {
213
+ type: "object",
214
+ properties: {},
215
+ },
216
+ },
217
+ ];
218
+ if (!this.options.readOnly) {
219
+ tools.push({
220
+ name: "send_message",
221
+ description: "Send a message to a PoolBot session",
222
+ inputSchema: {
223
+ type: "object",
224
+ properties: {
225
+ sessionKey: { type: "string", description: "Session key or ID" },
226
+ message: { type: "string", description: "Message to send" },
227
+ },
228
+ required: ["sessionKey", "message"],
229
+ },
230
+ });
231
+ }
232
+ return { tools };
233
+ });
234
+ // List available resources
235
+ this.server.setRequestHandler(ListResourcesRequestSchema, () => {
236
+ const sessions = loadSessions(this.workspaceDir, this.agentId);
237
+ const resources = sessions.slice(0, 100).map((session) => ({
238
+ uri: `poolbot://session/${session.sessionKey}`,
239
+ name: `Session ${session.sessionKey.substring(0, 8)}...`,
240
+ description: `${session.messageCount} messages, last updated ${new Date(session.updatedAt).toISOString()}`,
241
+ mimeType: "application/json",
242
+ }));
243
+ return { resources };
244
+ });
245
+ // Read resource content
246
+ this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
247
+ const uri = request.params.uri;
248
+ const parsed = parseUrl(uri);
249
+ if (parsed.protocol === "poolbot:" && parsed.pathname?.startsWith("/session/")) {
250
+ const sessionKey = parsed.pathname.replace("/session/", "");
251
+ const messages = loadSessionMessages(this.workspaceDir, this.agentId, sessionKey);
252
+ return {
253
+ contents: [
254
+ {
255
+ uri,
256
+ mimeType: "application/json",
257
+ text: JSON.stringify(messages, null, 2),
258
+ },
259
+ ],
260
+ };
261
+ }
262
+ throw new Error(`Unknown resource: ${uri}`);
263
+ });
264
+ // Handle tool calls
265
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
266
+ const { name, arguments: args } = request.params;
267
+ try {
268
+ switch (name) {
269
+ case "list_sessions": {
270
+ const limit = args?.limit || 20;
271
+ const sessions = loadSessions(this.workspaceDir, this.agentId).slice(0, limit);
272
+ return {
273
+ content: [
274
+ {
275
+ type: "text",
276
+ text: JSON.stringify(sessions, null, 2),
277
+ },
278
+ ],
279
+ };
280
+ }
281
+ case "get_session": {
282
+ const sessionKey = args?.sessionKey;
283
+ if (!sessionKey) {
284
+ throw new Error("sessionKey is required");
285
+ }
286
+ const sessions = loadSessions(this.workspaceDir, this.agentId);
287
+ const session = sessions.find((s) => s.sessionKey === sessionKey || s.sessionId === sessionKey);
288
+ if (!session) {
289
+ throw new Error(`Session not found: ${sessionKey}`);
290
+ }
291
+ return {
292
+ content: [
293
+ {
294
+ type: "text",
295
+ text: JSON.stringify(session, null, 2),
296
+ },
297
+ ],
298
+ };
299
+ }
300
+ case "get_messages": {
301
+ const sessionKey = args?.sessionKey;
302
+ const limit = args?.limit || 50;
303
+ const role = args?.role;
304
+ if (!sessionKey) {
305
+ throw new Error("sessionKey is required");
306
+ }
307
+ let messages = loadSessionMessages(this.workspaceDir, this.agentId, sessionKey);
308
+ if (role) {
309
+ messages = messages.filter((m) => m.role === role);
310
+ }
311
+ messages = messages.slice(-limit);
312
+ return {
313
+ content: [
314
+ {
315
+ type: "text",
316
+ text: JSON.stringify(messages, null, 2),
317
+ },
318
+ ],
319
+ };
320
+ }
321
+ case "search_sessions": {
322
+ const query = args?.query;
323
+ const limit = args?.limit || 10;
324
+ if (!query) {
325
+ throw new Error("query is required");
326
+ }
327
+ const results = searchSessions(this.workspaceDir, this.agentId, query, limit);
328
+ return {
329
+ content: [
330
+ {
331
+ type: "text",
332
+ text: JSON.stringify(results, null, 2),
333
+ },
334
+ ],
335
+ };
336
+ }
337
+ case "get_session_stats": {
338
+ const sessions = loadSessions(this.workspaceDir, this.agentId);
339
+ const totalMessages = sessions.reduce((sum, s) => sum + s.messageCount, 0);
340
+ const now = Date.now();
341
+ const last24h = sessions.filter((s) => now - s.updatedAt < 24 * 60 * 60 * 1000).length;
342
+ const last7d = sessions.filter((s) => now - s.updatedAt < 7 * 24 * 60 * 60 * 1000).length;
343
+ const stats = {
344
+ totalSessions: sessions.length,
345
+ totalMessages,
346
+ sessionsLast24h: last24h,
347
+ sessionsLast7d: last7d,
348
+ oldestSession: sessions.length > 0
349
+ ? new Date(sessions[sessions.length - 1].createdAt).toISOString()
350
+ : null,
351
+ newestSession: sessions.length > 0 ? new Date(sessions[0].updatedAt).toISOString() : null,
352
+ };
353
+ return {
354
+ content: [
355
+ {
356
+ type: "text",
357
+ text: JSON.stringify(stats, null, 2),
358
+ },
359
+ ],
360
+ };
361
+ }
362
+ case "send_message": {
363
+ if (this.options.readOnly) {
364
+ throw new Error("Server is in read-only mode");
365
+ }
366
+ const sessionKey = args?.sessionKey;
367
+ const message = args?.message;
368
+ if (!sessionKey || !message) {
369
+ throw new Error("sessionKey and message are required");
370
+ }
371
+ // Note: This would need integration with the actual gateway to send messages
372
+ // For now, return a placeholder response
373
+ return {
374
+ content: [
375
+ {
376
+ type: "text",
377
+ text: `Message sent to session ${sessionKey}. Note: Full integration requires gateway connection.`,
378
+ },
379
+ ],
380
+ };
381
+ }
382
+ default:
383
+ throw new Error(`Unknown tool: ${name}`);
384
+ }
385
+ }
386
+ catch (error) {
387
+ return {
388
+ content: [
389
+ {
390
+ type: "text",
391
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
392
+ },
393
+ ],
394
+ isError: true,
395
+ };
396
+ }
397
+ });
398
+ }
399
+ /**
400
+ * Start MCP server with stdio transport (for Claude Desktop, etc.)
401
+ */
402
+ async startStdio() {
403
+ const transport = new StdioServerTransport();
404
+ await this.server.connect(transport);
405
+ console.error("PoolBot MCP Server running on stdio");
406
+ }
407
+ /**
408
+ * Start MCP server with HTTP transport (for web clients)
409
+ */
410
+ async startHTTP(port) {
411
+ const httpPort = port || this.options.httpPort || 3000;
412
+ const transport = new StreamableHTTPServerTransport({
413
+ sessionIdGenerator: () => crypto.randomUUID(),
414
+ });
415
+ await this.server.connect(transport);
416
+ const server = createServer(async (req, res) => {
417
+ if (req.url === "/mcp") {
418
+ await transport.handleRequest(req, res);
419
+ }
420
+ else {
421
+ res.writeHead(404);
422
+ res.end("Not found");
423
+ }
424
+ });
425
+ await new Promise((resolve) => {
426
+ server.listen(httpPort, () => {
427
+ console.error(`PoolBot MCP Server running on http://localhost:${httpPort}/mcp`);
428
+ resolve();
429
+ });
430
+ });
431
+ }
432
+ /**
433
+ * Stop the MCP server
434
+ */
435
+ async stop() {
436
+ await this.server.close();
437
+ }
438
+ }
439
+ // ── CLI Entry Point ──────────────────────────────────────────────────
440
+ export async function runMCPServer(options = {}) {
441
+ const server = new PoolBotMCPServer(options);
442
+ const args = process.argv.slice(2);
443
+ const httpPort = args.find((arg) => arg.startsWith("--port="))?.split("=")[1];
444
+ const isHTTP = args.includes("--http") || !!httpPort;
445
+ try {
446
+ if (isHTTP) {
447
+ await server.startHTTP(httpPort ? parseInt(httpPort, 10) : undefined);
448
+ }
449
+ else {
450
+ await server.startStdio();
451
+ }
452
+ // Handle shutdown gracefully
453
+ process.on("SIGINT", async () => {
454
+ await server.stop();
455
+ process.exit(0);
456
+ });
457
+ process.on("SIGTERM", async () => {
458
+ await server.stop();
459
+ process.exit(0);
460
+ });
461
+ }
462
+ catch (error) {
463
+ console.error("Failed to start MCP server:", error);
464
+ process.exit(1);
465
+ }
466
+ }
@@ -0,0 +1,190 @@
1
+ # PoolBot MCP Server
2
+
3
+ ## Overview
4
+
5
+ The PoolBot MCP (Model Context Protocol) Server exposes your PoolBot sessions and conversations to MCP-compatible clients like:
6
+
7
+ - **Claude Desktop** - Chat directly with your PoolBot session history
8
+ - **Cursor** - Search and reference PoolBot conversations in your IDE
9
+ - **VS Code** - Access PoolBot context from your editor
10
+ - **Any MCP Client** - Standard protocol support
11
+
12
+ ## Features
13
+
14
+ ### Tools
15
+
16
+ | Tool | Description |
17
+ |------|-------------|
18
+ | `list_sessions` | List all PoolBot sessions with metadata |
19
+ | `get_session` | Get details of a specific session |
20
+ | `get_messages` | Get messages from a session (with optional role filter) |
21
+ | `search_sessions` | Search across all sessions for text |
22
+ | `get_session_stats` | Get statistics about your sessions |
23
+ | `send_message` | Send a message to a session (optional, read-only mode disables this) |
24
+
25
+ ### Resources
26
+
27
+ Sessions are exposed as MCP resources at URIs like:
28
+ ```
29
+ poolbot://session/<sessionKey>
30
+ ```
31
+
32
+ ## Installation
33
+
34
+ ### Claude Desktop
35
+
36
+ Add to your `claude_desktop_config.json`:
37
+
38
+ ```json
39
+ {
40
+ "mcpServers": {
41
+ "poolbot": {
42
+ "command": "poolbot",
43
+ "args": ["mcp", "serve"]
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ### Cursor / VS Code
50
+
51
+ Add to your MCP settings:
52
+
53
+ ```json
54
+ {
55
+ "mcp": {
56
+ "servers": {
57
+ "poolbot": {
58
+ "command": "poolbot",
59
+ "args": ["mcp", "serve"]
60
+ }
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ ## Usage
67
+
68
+ ### Start MCP Server (stdio mode)
69
+
70
+ ```bash
71
+ poolbot mcp serve
72
+ ```
73
+
74
+ This is the default mode for Claude Desktop and most MCP clients.
75
+
76
+ ### Start MCP Server (HTTP mode)
77
+
78
+ ```bash
79
+ poolbot mcp serve --http
80
+ poolbot mcp serve --http --port 3001
81
+ ```
82
+
83
+ HTTP mode is useful for web-based MCP clients or remote access.
84
+
85
+ ### Read-Only Mode
86
+
87
+ ```bash
88
+ poolbot mcp serve --read-only
89
+ ```
90
+
91
+ Disables the `send_message` tool for security.
92
+
93
+ ## Examples
94
+
95
+ ### List Sessions
96
+
97
+ ```json
98
+ {
99
+ "name": "list_sessions",
100
+ "arguments": {
101
+ "limit": 20
102
+ }
103
+ }
104
+ ```
105
+
106
+ ### Search Sessions
107
+
108
+ ```json
109
+ {
110
+ "name": "search_sessions",
111
+ "arguments": {
112
+ "query": "deployment configuration",
113
+ "limit": 10
114
+ }
115
+ }
116
+ ```
117
+
118
+ ### Get Messages
119
+
120
+ ```json
121
+ {
122
+ "name": "get_messages",
123
+ "arguments": {
124
+ "sessionKey": "abc123...",
125
+ "limit": 50,
126
+ "role": "assistant"
127
+ }
128
+ }
129
+ ```
130
+
131
+ ## Security
132
+
133
+ - MCP server runs locally by default
134
+ - No external network access required for stdio mode
135
+ - HTTP mode binds to localhost by default
136
+ - Use `--read-only` to disable message sending
137
+ - Sessions are read from your local PoolBot workspace
138
+
139
+ ## Troubleshooting
140
+
141
+ ### "Command not found: poolbot"
142
+
143
+ Ensure PoolBot is installed globally:
144
+ ```bash
145
+ npm install -g @poolzin/pool-bot
146
+ ```
147
+
148
+ ### "No sessions found"
149
+
150
+ Sessions are loaded from your PoolBot workspace. Ensure you have existing sessions in `~/.poolbot/memory/<agentId>/sessions/`.
151
+
152
+ ### HTTP mode not connecting
153
+
154
+ Ensure the port is not in use:
155
+ ```bash
156
+ poolbot mcp serve --http --port 3001
157
+ ```
158
+
159
+ ## Architecture
160
+
161
+ The MCP server:
162
+ 1. Reads sessions from PoolBot's session store (`sessions.json`)
163
+ 2. Loads messages from JSONL files (`sessions/<sessionKey>.jsonl`)
164
+ 3. Exposes tools and resources via the MCP protocol
165
+ 4. Supports both stdio (for local clients) and HTTP transports
166
+
167
+ ## See Also
168
+
169
+ - [Model Context Protocol Documentation](https://modelcontextprotocol.io/)
170
+ - [Claude Desktop MCP Setup](https://claude.ai/mcp)
171
+ - [PoolBot Documentation](/docs/)
172
+
173
+ ## Direct Usage (without CLI integration)
174
+
175
+ If the `poolbot mcp serve` command is not available in your version, you can run the MCP server directly with Node.js:
176
+
177
+ ```bash
178
+ # stdio mode
179
+ node -e "import('@poolzin/pool-bot/dist/mcp/server.js').then(m => m.runMCPServer())"
180
+
181
+ # HTTP mode
182
+ node -e "import('@poolzin/pool-bot/dist/mcp/server.js').then(m => m.runMCPServer(['--http', '--port=3000']))"
183
+ ```
184
+
185
+ Or create a wrapper script:
186
+
187
+ ```bash
188
+ #!/bin/bash
189
+ node -e "import('@poolzin/pool-bot/dist/mcp/server.js').then(m => m.runMCPServer(process.argv.slice(2)))" "$@"
190
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poolzin/pool-bot",
3
- "version": "2026.4.33",
3
+ "version": "2026.4.35",
4
4
  "description": "🎱 Pool Bot - AI assistant with PLCODE integrations",
5
5
  "keywords": [],
6
6
  "license": "MIT",