@poolzin/pool-bot 2026.4.33 → 2026.4.34
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.
- package/dist/agents/tools/vps-security-tool.d.ts.map +1 -1
- package/dist/agents/tools/vps-security-tool.js +13 -2
- package/dist/build-info.json +3 -3
- package/dist/cli/mcp-cli.d.ts +10 -0
- package/dist/cli/mcp-cli.d.ts.map +1 -0
- package/dist/cli/mcp-cli.js +44 -0
- package/dist/mcp/server.d.ts +39 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +463 -0
- package/docs/mcp-server.md +171 -0
- package/package.json +1 -1
|
@@ -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,
|
|
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: [
|
|
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 = [
|
|
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";
|
package/dist/build-info.json
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
export declare function runMCPCLI(args: string[]): Promise<void>;
|
|
10
|
+
//# 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;AAMH,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmC7D"}
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
export async function runMCPCLI(args) {
|
|
13
|
+
const config = loadConfig();
|
|
14
|
+
const agentId = resolveDefaultAgentId(config);
|
|
15
|
+
const isHTTP = args.includes("--http");
|
|
16
|
+
const portArg = args.find((arg) => arg.startsWith("--port="));
|
|
17
|
+
const port = portArg ? parseInt(portArg.split("=")[1], 10) : undefined;
|
|
18
|
+
const readOnly = args.includes("--read-only");
|
|
19
|
+
console.error("Starting PoolBot MCP Server...");
|
|
20
|
+
console.error(` Agent: ${agentId}`);
|
|
21
|
+
console.error(` Mode: ${isHTTP ? "HTTP" : "stdio"}`);
|
|
22
|
+
if (isHTTP) {
|
|
23
|
+
console.error(` Port: ${port || 3000}`);
|
|
24
|
+
}
|
|
25
|
+
if (readOnly) {
|
|
26
|
+
console.error(" Read-only mode: enabled");
|
|
27
|
+
}
|
|
28
|
+
console.error("");
|
|
29
|
+
console.error("For Claude Desktop, add to claude_desktop_config.json:");
|
|
30
|
+
console.error(JSON.stringify({
|
|
31
|
+
mcpServers: {
|
|
32
|
+
poolbot: {
|
|
33
|
+
command: "poolbot",
|
|
34
|
+
args: ["mcp", "serve"],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
}, null, 2));
|
|
38
|
+
console.error("");
|
|
39
|
+
await runMCPServer({
|
|
40
|
+
agentId,
|
|
41
|
+
httpPort: port,
|
|
42
|
+
readOnly,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -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;AA0HD,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;IAiSrB;;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,463 @@
|
|
|
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)) { // Limit search to recent 50 sessions
|
|
85
|
+
const messages = loadSessionMessages(workspaceDir, agentId, session.sessionKey);
|
|
86
|
+
const matches = [];
|
|
87
|
+
for (const msg of messages) {
|
|
88
|
+
if (msg.content.toLowerCase().includes(queryLower)) {
|
|
89
|
+
// Extract context around the match
|
|
90
|
+
const idx = msg.content.toLowerCase().indexOf(queryLower);
|
|
91
|
+
const start = Math.max(0, idx - 50);
|
|
92
|
+
const end = Math.min(msg.content.length, idx + query.length + 50);
|
|
93
|
+
const excerpt = msg.content.substring(start, end).replace(/\n/g, " ").trim();
|
|
94
|
+
matches.push(`[${msg.role}] ${excerpt}...`);
|
|
95
|
+
if (matches.length >= 3)
|
|
96
|
+
break; // Max 3 matches per session
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (matches.length > 0) {
|
|
100
|
+
results.push({ session, matches });
|
|
101
|
+
}
|
|
102
|
+
if (results.length >= limit)
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
return results;
|
|
106
|
+
}
|
|
107
|
+
// ── MCP Server Implementation ────────────────────────────────────────
|
|
108
|
+
export class PoolBotMCPServer {
|
|
109
|
+
server;
|
|
110
|
+
options;
|
|
111
|
+
workspaceDir;
|
|
112
|
+
agentId;
|
|
113
|
+
constructor(options = {}) {
|
|
114
|
+
this.options = {
|
|
115
|
+
httpPort: 3000,
|
|
116
|
+
readOnly: false,
|
|
117
|
+
...options,
|
|
118
|
+
};
|
|
119
|
+
const config = loadConfig();
|
|
120
|
+
this.agentId = options.agentId || resolveDefaultAgentId(config);
|
|
121
|
+
this.workspaceDir = options.workspaceDir || resolveAgentWorkspaceDir(config, this.agentId);
|
|
122
|
+
this.server = new Server({
|
|
123
|
+
name: "poolbot-mcp",
|
|
124
|
+
version: "1.0.0",
|
|
125
|
+
}, {
|
|
126
|
+
capabilities: {
|
|
127
|
+
tools: {},
|
|
128
|
+
resources: {},
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
this.setupHandlers();
|
|
132
|
+
}
|
|
133
|
+
setupHandlers() {
|
|
134
|
+
// List available tools
|
|
135
|
+
this.server.setRequestHandler(ListToolsRequestSchema, () => {
|
|
136
|
+
const tools = [
|
|
137
|
+
{
|
|
138
|
+
name: "list_sessions",
|
|
139
|
+
description: "List all PoolBot sessions with metadata",
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {
|
|
143
|
+
limit: {
|
|
144
|
+
type: "number",
|
|
145
|
+
description: "Maximum number of sessions to return (default: 20)",
|
|
146
|
+
default: 20,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: "get_session",
|
|
153
|
+
description: "Get details of a specific PoolBot session",
|
|
154
|
+
inputSchema: {
|
|
155
|
+
type: "object",
|
|
156
|
+
properties: {
|
|
157
|
+
sessionKey: {
|
|
158
|
+
type: "string",
|
|
159
|
+
description: "Session key or ID",
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
required: ["sessionKey"],
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "get_messages",
|
|
167
|
+
description: "Get messages from a PoolBot session",
|
|
168
|
+
inputSchema: {
|
|
169
|
+
type: "object",
|
|
170
|
+
properties: {
|
|
171
|
+
sessionKey: {
|
|
172
|
+
type: "string",
|
|
173
|
+
description: "Session key or ID",
|
|
174
|
+
},
|
|
175
|
+
limit: {
|
|
176
|
+
type: "number",
|
|
177
|
+
description: "Maximum messages to return (default: 50)",
|
|
178
|
+
default: 50,
|
|
179
|
+
},
|
|
180
|
+
role: {
|
|
181
|
+
type: "string",
|
|
182
|
+
description: "Filter by role: user, assistant, or system",
|
|
183
|
+
enum: ["user", "assistant", "system"],
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
required: ["sessionKey"],
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "search_sessions",
|
|
191
|
+
description: "Search across all PoolBot sessions for text",
|
|
192
|
+
inputSchema: {
|
|
193
|
+
type: "object",
|
|
194
|
+
properties: {
|
|
195
|
+
query: {
|
|
196
|
+
type: "string",
|
|
197
|
+
description: "Search query",
|
|
198
|
+
},
|
|
199
|
+
limit: {
|
|
200
|
+
type: "number",
|
|
201
|
+
description: "Maximum results to return (default: 10)",
|
|
202
|
+
default: 10,
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
required: ["query"],
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: "get_session_stats",
|
|
210
|
+
description: "Get statistics about PoolBot sessions",
|
|
211
|
+
inputSchema: {
|
|
212
|
+
type: "object",
|
|
213
|
+
properties: {},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
];
|
|
217
|
+
if (!this.options.readOnly) {
|
|
218
|
+
tools.push({
|
|
219
|
+
name: "send_message",
|
|
220
|
+
description: "Send a message to a PoolBot session",
|
|
221
|
+
inputSchema: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {
|
|
224
|
+
sessionKey: { type: "string", description: "Session key or ID" },
|
|
225
|
+
message: { type: "string", description: "Message to send" },
|
|
226
|
+
},
|
|
227
|
+
required: ["sessionKey", "message"],
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
return { tools };
|
|
232
|
+
});
|
|
233
|
+
// List available resources
|
|
234
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, () => {
|
|
235
|
+
const sessions = loadSessions(this.workspaceDir, this.agentId);
|
|
236
|
+
const resources = sessions.slice(0, 100).map((session) => ({
|
|
237
|
+
uri: `poolbot://session/${session.sessionKey}`,
|
|
238
|
+
name: `Session ${session.sessionKey.substring(0, 8)}...`,
|
|
239
|
+
description: `${session.messageCount} messages, last updated ${new Date(session.updatedAt).toISOString()}`,
|
|
240
|
+
mimeType: "application/json",
|
|
241
|
+
}));
|
|
242
|
+
return { resources };
|
|
243
|
+
});
|
|
244
|
+
// Read resource content
|
|
245
|
+
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
246
|
+
const uri = request.params.uri;
|
|
247
|
+
const parsed = parseUrl(uri);
|
|
248
|
+
if (parsed.protocol === "poolbot:" && parsed.pathname?.startsWith("/session/")) {
|
|
249
|
+
const sessionKey = parsed.pathname.replace("/session/", "");
|
|
250
|
+
const messages = loadSessionMessages(this.workspaceDir, this.agentId, sessionKey);
|
|
251
|
+
return {
|
|
252
|
+
contents: [
|
|
253
|
+
{
|
|
254
|
+
uri,
|
|
255
|
+
mimeType: "application/json",
|
|
256
|
+
text: JSON.stringify(messages, null, 2),
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
262
|
+
});
|
|
263
|
+
// Handle tool calls
|
|
264
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
265
|
+
const { name, arguments: args } = request.params;
|
|
266
|
+
try {
|
|
267
|
+
switch (name) {
|
|
268
|
+
case "list_sessions": {
|
|
269
|
+
const limit = args?.limit || 20;
|
|
270
|
+
const sessions = loadSessions(this.workspaceDir, this.agentId).slice(0, limit);
|
|
271
|
+
return {
|
|
272
|
+
content: [
|
|
273
|
+
{
|
|
274
|
+
type: "text",
|
|
275
|
+
text: JSON.stringify(sessions, null, 2),
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
case "get_session": {
|
|
281
|
+
const sessionKey = args?.sessionKey;
|
|
282
|
+
if (!sessionKey) {
|
|
283
|
+
throw new Error("sessionKey is required");
|
|
284
|
+
}
|
|
285
|
+
const sessions = loadSessions(this.workspaceDir, this.agentId);
|
|
286
|
+
const session = sessions.find((s) => s.sessionKey === sessionKey || s.sessionId === sessionKey);
|
|
287
|
+
if (!session) {
|
|
288
|
+
throw new Error(`Session not found: ${sessionKey}`);
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
content: [
|
|
292
|
+
{
|
|
293
|
+
type: "text",
|
|
294
|
+
text: JSON.stringify(session, null, 2),
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
case "get_messages": {
|
|
300
|
+
const sessionKey = args?.sessionKey;
|
|
301
|
+
const limit = args?.limit || 50;
|
|
302
|
+
const role = args?.role;
|
|
303
|
+
if (!sessionKey) {
|
|
304
|
+
throw new Error("sessionKey is required");
|
|
305
|
+
}
|
|
306
|
+
let messages = loadSessionMessages(this.workspaceDir, this.agentId, sessionKey);
|
|
307
|
+
if (role) {
|
|
308
|
+
messages = messages.filter((m) => m.role === role);
|
|
309
|
+
}
|
|
310
|
+
messages = messages.slice(-limit);
|
|
311
|
+
return {
|
|
312
|
+
content: [
|
|
313
|
+
{
|
|
314
|
+
type: "text",
|
|
315
|
+
text: JSON.stringify(messages, null, 2),
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
case "search_sessions": {
|
|
321
|
+
const query = args?.query;
|
|
322
|
+
const limit = args?.limit || 10;
|
|
323
|
+
if (!query) {
|
|
324
|
+
throw new Error("query is required");
|
|
325
|
+
}
|
|
326
|
+
const results = searchSessions(this.workspaceDir, this.agentId, query, limit);
|
|
327
|
+
return {
|
|
328
|
+
content: [
|
|
329
|
+
{
|
|
330
|
+
type: "text",
|
|
331
|
+
text: JSON.stringify(results, null, 2),
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
case "get_session_stats": {
|
|
337
|
+
const sessions = loadSessions(this.workspaceDir, this.agentId);
|
|
338
|
+
const totalMessages = sessions.reduce((sum, s) => sum + s.messageCount, 0);
|
|
339
|
+
const now = Date.now();
|
|
340
|
+
const last24h = sessions.filter((s) => now - s.updatedAt < 24 * 60 * 60 * 1000).length;
|
|
341
|
+
const last7d = sessions.filter((s) => now - s.updatedAt < 7 * 24 * 60 * 60 * 1000).length;
|
|
342
|
+
const stats = {
|
|
343
|
+
totalSessions: sessions.length,
|
|
344
|
+
totalMessages,
|
|
345
|
+
sessionsLast24h: last24h,
|
|
346
|
+
sessionsLast7d: last7d,
|
|
347
|
+
oldestSession: sessions.length > 0 ? new Date(sessions[sessions.length - 1].createdAt).toISOString() : null,
|
|
348
|
+
newestSession: sessions.length > 0 ? new Date(sessions[0].updatedAt).toISOString() : null,
|
|
349
|
+
};
|
|
350
|
+
return {
|
|
351
|
+
content: [
|
|
352
|
+
{
|
|
353
|
+
type: "text",
|
|
354
|
+
text: JSON.stringify(stats, null, 2),
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
case "send_message": {
|
|
360
|
+
if (this.options.readOnly) {
|
|
361
|
+
throw new Error("Server is in read-only mode");
|
|
362
|
+
}
|
|
363
|
+
const sessionKey = args?.sessionKey;
|
|
364
|
+
const message = args?.message;
|
|
365
|
+
if (!sessionKey || !message) {
|
|
366
|
+
throw new Error("sessionKey and message are required");
|
|
367
|
+
}
|
|
368
|
+
// Note: This would need integration with the actual gateway to send messages
|
|
369
|
+
// For now, return a placeholder response
|
|
370
|
+
return {
|
|
371
|
+
content: [
|
|
372
|
+
{
|
|
373
|
+
type: "text",
|
|
374
|
+
text: `Message sent to session ${sessionKey}. Note: Full integration requires gateway connection.`,
|
|
375
|
+
},
|
|
376
|
+
],
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
default:
|
|
380
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
return {
|
|
385
|
+
content: [
|
|
386
|
+
{
|
|
387
|
+
type: "text",
|
|
388
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
389
|
+
},
|
|
390
|
+
],
|
|
391
|
+
isError: true,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Start MCP server with stdio transport (for Claude Desktop, etc.)
|
|
398
|
+
*/
|
|
399
|
+
async startStdio() {
|
|
400
|
+
const transport = new StdioServerTransport();
|
|
401
|
+
await this.server.connect(transport);
|
|
402
|
+
console.error("PoolBot MCP Server running on stdio");
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Start MCP server with HTTP transport (for web clients)
|
|
406
|
+
*/
|
|
407
|
+
async startHTTP(port) {
|
|
408
|
+
const httpPort = port || this.options.httpPort || 3000;
|
|
409
|
+
const transport = new StreamableHTTPServerTransport({
|
|
410
|
+
sessionIdGenerator: () => crypto.randomUUID(),
|
|
411
|
+
});
|
|
412
|
+
await this.server.connect(transport);
|
|
413
|
+
const server = createServer(async (req, res) => {
|
|
414
|
+
if (req.url === "/mcp") {
|
|
415
|
+
await transport.handleRequest(req, res);
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
res.writeHead(404);
|
|
419
|
+
res.end("Not found");
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
await new Promise((resolve) => {
|
|
423
|
+
server.listen(httpPort, () => {
|
|
424
|
+
console.error(`PoolBot MCP Server running on http://localhost:${httpPort}/mcp`);
|
|
425
|
+
resolve();
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Stop the MCP server
|
|
431
|
+
*/
|
|
432
|
+
async stop() {
|
|
433
|
+
await this.server.close();
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// ── CLI Entry Point ──────────────────────────────────────────────────
|
|
437
|
+
export async function runMCPServer(options = {}) {
|
|
438
|
+
const server = new PoolBotMCPServer(options);
|
|
439
|
+
const args = process.argv.slice(2);
|
|
440
|
+
const httpPort = args.find((arg) => arg.startsWith("--port="))?.split("=")[1];
|
|
441
|
+
const isHTTP = args.includes("--http") || !!httpPort;
|
|
442
|
+
try {
|
|
443
|
+
if (isHTTP) {
|
|
444
|
+
await server.startHTTP(httpPort ? parseInt(httpPort, 10) : undefined);
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
await server.startStdio();
|
|
448
|
+
}
|
|
449
|
+
// Handle shutdown gracefully
|
|
450
|
+
process.on("SIGINT", async () => {
|
|
451
|
+
await server.stop();
|
|
452
|
+
process.exit(0);
|
|
453
|
+
});
|
|
454
|
+
process.on("SIGTERM", async () => {
|
|
455
|
+
await server.stop();
|
|
456
|
+
process.exit(0);
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
catch (error) {
|
|
460
|
+
console.error("Failed to start MCP server:", error);
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
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/)
|