@band-ai/sdk 0.1.2
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/CodexAdapter-A1k_LMdZ.d.ts +1278 -0
- package/dist/CodexAdapter-BbZD-nBd.d.cts +1278 -0
- package/dist/adapters.cjs +10643 -0
- package/dist/adapters.d.cts +217 -0
- package/dist/adapters.d.ts +217 -0
- package/dist/adapters.js +92 -0
- package/dist/backends-BnOLsG7w.d.cts +20 -0
- package/dist/backends-C4n6cKOf.d.ts +20 -0
- package/dist/chunk-3BKAC4OZ.js +44 -0
- package/dist/chunk-5CXIMAH2.js +52 -0
- package/dist/chunk-6AJA2OPC.js +3682 -0
- package/dist/chunk-A3TDK6GP.js +6674 -0
- package/dist/chunk-AVVDPXP4.js +39 -0
- package/dist/chunk-EDPKUUJL.js +177 -0
- package/dist/chunk-FUODYQRE.js +2562 -0
- package/dist/chunk-GVEQ3RAH.js +373 -0
- package/dist/chunk-INA6YZTZ.js +88 -0
- package/dist/chunk-IZPV6QS6.js +115 -0
- package/dist/chunk-LY55KYVI.js +0 -0
- package/dist/chunk-NIAQBANR.js +577 -0
- package/dist/chunk-OD2G5LFQ.js +78 -0
- package/dist/chunk-SFVKOEQH.js +103 -0
- package/dist/chunk-SG4WLD2H.js +1093 -0
- package/dist/chunk-YR3BOQNW.js +39 -0
- package/dist/chunk-ZZJRRBPQ.js +82 -0
- package/dist/claude-CcnQ5OUC.d.ts +53 -0
- package/dist/claude-CwTAxhwI.d.cts +53 -0
- package/dist/config.cjs +164 -0
- package/dist/config.d.cts +19 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.js +9 -0
- package/dist/converters.cjs +1086 -0
- package/dist/converters.d.cts +143 -0
- package/dist/converters.d.ts +143 -0
- package/dist/converters.js +468 -0
- package/dist/core.cjs +185 -0
- package/dist/core.d.cts +22 -0
- package/dist/core.d.ts +22 -0
- package/dist/core.js +24 -0
- package/dist/customTools-BzF0IISO.d.cts +10 -0
- package/dist/customTools-BzF0IISO.d.ts +10 -0
- package/dist/dtos-DUpbIu8k.d.cts +163 -0
- package/dist/dtos-DUpbIu8k.d.ts +163 -0
- package/dist/history-ByessXNq.d.ts +24 -0
- package/dist/history-i6yN7neC.d.cts +24 -0
- package/dist/index.cjs +13914 -0
- package/dist/index.d.cts +63 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +198 -0
- package/dist/linear.cjs +2207 -0
- package/dist/linear.d.cts +167 -0
- package/dist/linear.d.ts +167 -0
- package/dist/linear.js +2139 -0
- package/dist/logger-CABQOnlR.d.cts +21 -0
- package/dist/logger-CABQOnlR.d.ts +21 -0
- package/dist/mcp-claude.cjs +584 -0
- package/dist/mcp-claude.d.cts +4 -0
- package/dist/mcp-claude.d.ts +4 -0
- package/dist/mcp-claude.js +11 -0
- package/dist/mcp.cjs +1247 -0
- package/dist/mcp.d.cts +97 -0
- package/dist/mcp.d.ts +97 -0
- package/dist/mcp.js +27 -0
- package/dist/opencode-BBcDchcN.d.cts +159 -0
- package/dist/opencode-DthQQUsZ.d.ts +159 -0
- package/dist/pagination-BPiys10t.d.cts +25 -0
- package/dist/pagination-CcDkVFxK.d.ts +25 -0
- package/dist/protocols-CInhtFpA.d.cts +116 -0
- package/dist/protocols-DrhzOH6K.d.ts +116 -0
- package/dist/rest.cjs +1159 -0
- package/dist/rest.d.cts +144 -0
- package/dist/rest.d.ts +144 -0
- package/dist/rest.js +18 -0
- package/dist/runtime.cjs +5353 -0
- package/dist/runtime.d.cts +276 -0
- package/dist/runtime.d.ts +276 -0
- package/dist/runtime.js +274 -0
- package/dist/schemas-BpEBkq5V.d.cts +592 -0
- package/dist/schemas-vos1AaBc.d.ts +592 -0
- package/dist/sdk-HJUVSSWA.js +10 -0
- package/dist/simpleAdapter-9OE_p6QW.d.ts +29 -0
- package/dist/simpleAdapter-DqhvjsGr.d.cts +29 -0
- package/dist/testing.cjs +217 -0
- package/dist/testing.d.cts +126 -0
- package/dist/testing.d.ts +126 -0
- package/dist/testing.js +186 -0
- package/dist/types-7jVzOeOP.d.ts +446 -0
- package/dist/types-CSHFipIc.d.cts +331 -0
- package/dist/types-CovcHpYf.d.cts +446 -0
- package/dist/types-CxII3XkA.d.ts +331 -0
- package/package.json +189 -0
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildRoomScopedRegistrations,
|
|
3
|
+
buildSingleContextRegistrations,
|
|
4
|
+
buildZodShape
|
|
5
|
+
} from "./chunk-EDPKUUJL.js";
|
|
6
|
+
import {
|
|
7
|
+
mcpToolNames
|
|
8
|
+
} from "./chunk-GVEQ3RAH.js";
|
|
9
|
+
|
|
10
|
+
// src/mcp/stdio.ts
|
|
11
|
+
var ThenvoiMcpStdioServer = class {
|
|
12
|
+
options;
|
|
13
|
+
registrations;
|
|
14
|
+
mcpServer = null;
|
|
15
|
+
transport = null;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.options = options;
|
|
18
|
+
const regOptions = {
|
|
19
|
+
enableMemoryTools: options.enableMemoryTools,
|
|
20
|
+
enableContactTools: options.enableContactTools,
|
|
21
|
+
additionalTools: options.additionalTools
|
|
22
|
+
};
|
|
23
|
+
if (typeof options.tools === "function") {
|
|
24
|
+
this.registrations = buildRoomScopedRegistrations(options.tools, regOptions);
|
|
25
|
+
} else {
|
|
26
|
+
this.registrations = buildSingleContextRegistrations(options.tools, regOptions);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
get toolNames() {
|
|
30
|
+
return this.registrations.map((r) => r.name);
|
|
31
|
+
}
|
|
32
|
+
async start() {
|
|
33
|
+
if (this.transport) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const { McpServer } = await import("@modelcontextprotocol/sdk/server/mcp.js");
|
|
37
|
+
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
38
|
+
const { z } = await import("zod");
|
|
39
|
+
const mcpServer = new McpServer({
|
|
40
|
+
name: this.options.name ?? "thenvoi",
|
|
41
|
+
version: "1.0.0"
|
|
42
|
+
});
|
|
43
|
+
registerTools(mcpServer, z, this.registrations);
|
|
44
|
+
const transport = new StdioServerTransport(this.options.stdin, this.options.stdout);
|
|
45
|
+
await mcpServer.connect(transport);
|
|
46
|
+
this.mcpServer = mcpServer;
|
|
47
|
+
this.transport = transport;
|
|
48
|
+
}
|
|
49
|
+
async stop() {
|
|
50
|
+
await this.transport?.close();
|
|
51
|
+
this.transport = null;
|
|
52
|
+
this.mcpServer = null;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
function registerTools(mcpServer, z, registrations) {
|
|
56
|
+
for (const reg of registrations) {
|
|
57
|
+
const zodShape = buildZodShape(z, reg.inputSchema.properties, new Set(reg.inputSchema.required));
|
|
58
|
+
mcpServer.registerTool(
|
|
59
|
+
reg.name,
|
|
60
|
+
{
|
|
61
|
+
description: reg.description,
|
|
62
|
+
inputSchema: z.object(zodShape)
|
|
63
|
+
},
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- MCP SDK handler signature is complex; our McpToolResult is compatible
|
|
65
|
+
async (args) => reg.execute(args)
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/mcp/server.ts
|
|
71
|
+
var PORT_RANGE_START = 5e4;
|
|
72
|
+
var PORT_RANGE_END = 6e4;
|
|
73
|
+
var MCP_SERVER_VERSION = "1.0.0";
|
|
74
|
+
var SESSION_IDLE_TTL_MS = 15 * 60 * 1e3;
|
|
75
|
+
var SESSION_SWEEP_INTERVAL_MS = 60 * 1e3;
|
|
76
|
+
var ThenvoiMcpServer = class {
|
|
77
|
+
options;
|
|
78
|
+
registrations;
|
|
79
|
+
httpServer = null;
|
|
80
|
+
actualPort = null;
|
|
81
|
+
sessions = /* @__PURE__ */ new Map();
|
|
82
|
+
sessionSweepTimer = null;
|
|
83
|
+
constructor(options) {
|
|
84
|
+
this.options = options;
|
|
85
|
+
const regOptions = {
|
|
86
|
+
enableMemoryTools: options.enableMemoryTools,
|
|
87
|
+
enableContactTools: options.enableContactTools,
|
|
88
|
+
additionalTools: options.additionalTools
|
|
89
|
+
};
|
|
90
|
+
if (typeof options.tools === "function") {
|
|
91
|
+
this.registrations = buildRoomScopedRegistrations(options.tools, regOptions);
|
|
92
|
+
} else {
|
|
93
|
+
this.registrations = buildSingleContextRegistrations(options.tools, regOptions);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** Port the server is listening on. Null if not started. */
|
|
97
|
+
get port() {
|
|
98
|
+
return this.actualPort;
|
|
99
|
+
}
|
|
100
|
+
/** MCP endpoint URL. Null if not started. */
|
|
101
|
+
get url() {
|
|
102
|
+
if (this.actualPort === null) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return `http://127.0.0.1:${this.actualPort}/mcp`;
|
|
106
|
+
}
|
|
107
|
+
/** Tool names registered on this server. */
|
|
108
|
+
get toolNames() {
|
|
109
|
+
return this.registrations.map((r) => r.name);
|
|
110
|
+
}
|
|
111
|
+
async start() {
|
|
112
|
+
const { McpServer } = await import("@modelcontextprotocol/sdk/server/mcp.js");
|
|
113
|
+
const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
114
|
+
const { isInitializeRequest } = await import("@modelcontextprotocol/sdk/types.js");
|
|
115
|
+
const expressModule = await import("express");
|
|
116
|
+
const express = expressModule.default ?? expressModule;
|
|
117
|
+
const http = await import("http");
|
|
118
|
+
const { randomUUID } = await import("crypto");
|
|
119
|
+
const { z } = await import("zod");
|
|
120
|
+
const serverName = this.options.name ?? "thenvoi";
|
|
121
|
+
const app = express();
|
|
122
|
+
app.use(express.json());
|
|
123
|
+
app.get("/healthz", (_req, res) => {
|
|
124
|
+
res.json({ status: "ok" });
|
|
125
|
+
});
|
|
126
|
+
const handleMcpRequest = async (req, res) => {
|
|
127
|
+
try {
|
|
128
|
+
const sessionId = getSessionIdHeader(req.headers["mcp-session-id"]);
|
|
129
|
+
let session = sessionId ? this.sessions.get(sessionId) : void 0;
|
|
130
|
+
if (!session) {
|
|
131
|
+
if (req.method !== "POST" || sessionId || !isInitializeRequest(req.body)) {
|
|
132
|
+
sendMcpError(res, sessionId ? 404 : 400, sessionId ? "Session not found" : "Bad Request: No valid session ID provided");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
session = await this.createSession({
|
|
136
|
+
McpServer,
|
|
137
|
+
StreamableHTTPServerTransport,
|
|
138
|
+
serverName,
|
|
139
|
+
sessionIdGenerator: randomUUID,
|
|
140
|
+
z
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
session.lastSeenAt = Date.now();
|
|
144
|
+
await session.transport.handleRequest(req, res, req.body);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
if (!res.headersSent) {
|
|
147
|
+
sendMcpError(res, 500, "Internal server error");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
app.post("/mcp", async (req, res) => {
|
|
152
|
+
await handleMcpRequest(req, res);
|
|
153
|
+
});
|
|
154
|
+
app.get("/mcp", async (req, res) => {
|
|
155
|
+
await handleMcpRequest(req, res);
|
|
156
|
+
});
|
|
157
|
+
app.delete("/mcp", async (req, res) => {
|
|
158
|
+
await handleMcpRequest(req, res);
|
|
159
|
+
});
|
|
160
|
+
const port = this.options.port ?? await findAvailablePort(http);
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
const server = http.createServer(app);
|
|
163
|
+
server.on("error", reject);
|
|
164
|
+
server.listen(port, "127.0.0.1", () => {
|
|
165
|
+
this.httpServer = server;
|
|
166
|
+
this.actualPort = port;
|
|
167
|
+
this.startSessionSweep();
|
|
168
|
+
resolve();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
async stop() {
|
|
173
|
+
this.stopSessionSweep();
|
|
174
|
+
const activeSessions = [...this.sessions.values()];
|
|
175
|
+
this.sessions.clear();
|
|
176
|
+
await Promise.all(activeSessions.map(async (session) => {
|
|
177
|
+
await session.transport.close();
|
|
178
|
+
}));
|
|
179
|
+
if (!this.httpServer) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
return new Promise((resolve, reject) => {
|
|
183
|
+
this.httpServer.close((error) => {
|
|
184
|
+
this.httpServer = null;
|
|
185
|
+
this.actualPort = null;
|
|
186
|
+
if (error) {
|
|
187
|
+
reject(error);
|
|
188
|
+
} else {
|
|
189
|
+
resolve();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
async createSession(input) {
|
|
195
|
+
const createdAt = Date.now();
|
|
196
|
+
const mcpServer = new input.McpServer({ name: input.serverName, version: MCP_SERVER_VERSION });
|
|
197
|
+
registerTools2(mcpServer, input.z, this.registrations);
|
|
198
|
+
let record = null;
|
|
199
|
+
const transport = new input.StreamableHTTPServerTransport({
|
|
200
|
+
sessionIdGenerator: input.sessionIdGenerator,
|
|
201
|
+
onsessioninitialized: (sessionId) => {
|
|
202
|
+
if (!record) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
this.sessions.set(sessionId, record);
|
|
206
|
+
},
|
|
207
|
+
onsessionclosed: (sessionId) => {
|
|
208
|
+
this.sessions.delete(sessionId);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
transport.onclose = () => {
|
|
212
|
+
const sessionId = transport.sessionId;
|
|
213
|
+
if (sessionId) {
|
|
214
|
+
this.sessions.delete(sessionId);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
await mcpServer.connect(transport);
|
|
218
|
+
record = {
|
|
219
|
+
mcpServer,
|
|
220
|
+
transport,
|
|
221
|
+
createdAt,
|
|
222
|
+
lastSeenAt: createdAt
|
|
223
|
+
};
|
|
224
|
+
return record;
|
|
225
|
+
}
|
|
226
|
+
startSessionSweep() {
|
|
227
|
+
if (this.sessionSweepTimer) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
this.sessionSweepTimer = setInterval(() => {
|
|
231
|
+
void this.closeIdleSessions();
|
|
232
|
+
}, SESSION_SWEEP_INTERVAL_MS);
|
|
233
|
+
this.sessionSweepTimer.unref?.();
|
|
234
|
+
}
|
|
235
|
+
stopSessionSweep() {
|
|
236
|
+
if (!this.sessionSweepTimer) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
clearInterval(this.sessionSweepTimer);
|
|
240
|
+
this.sessionSweepTimer = null;
|
|
241
|
+
}
|
|
242
|
+
async closeIdleSessions() {
|
|
243
|
+
const cutoff = Date.now() - SESSION_IDLE_TTL_MS;
|
|
244
|
+
const idleSessions = [...this.sessions.values()].filter((session) => session.lastSeenAt < cutoff);
|
|
245
|
+
await Promise.all(idleSessions.map(async (session) => {
|
|
246
|
+
await session.transport.close();
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
function getSessionIdHeader(headerValue) {
|
|
251
|
+
if (typeof headerValue === "string" && headerValue.length > 0) {
|
|
252
|
+
return headerValue;
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
function sendMcpError(res, status, message) {
|
|
257
|
+
res.status(status).json({
|
|
258
|
+
jsonrpc: "2.0",
|
|
259
|
+
error: {
|
|
260
|
+
code: -32e3,
|
|
261
|
+
message
|
|
262
|
+
},
|
|
263
|
+
id: null
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
function registerTools2(mcpServer, z, registrations) {
|
|
267
|
+
for (const reg of registrations) {
|
|
268
|
+
const zodShape = buildZodShape(z, reg.inputSchema.properties, new Set(reg.inputSchema.required));
|
|
269
|
+
mcpServer.registerTool(
|
|
270
|
+
reg.name,
|
|
271
|
+
{
|
|
272
|
+
description: reg.description,
|
|
273
|
+
inputSchema: z.object(zodShape)
|
|
274
|
+
},
|
|
275
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- MCP SDK handler signature is complex; our McpToolResult is compatible
|
|
276
|
+
async (args) => reg.execute(args)
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async function findAvailablePort(http) {
|
|
281
|
+
for (let attempt = 0; attempt < 20; attempt++) {
|
|
282
|
+
const port = PORT_RANGE_START + Math.floor(Math.random() * (PORT_RANGE_END - PORT_RANGE_START));
|
|
283
|
+
const available = await checkPort(http, port);
|
|
284
|
+
if (available) {
|
|
285
|
+
return port;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return 0;
|
|
289
|
+
}
|
|
290
|
+
function checkPort(http, port) {
|
|
291
|
+
return new Promise((resolve) => {
|
|
292
|
+
const server = http.createServer();
|
|
293
|
+
server.once("error", () => resolve(false));
|
|
294
|
+
server.listen(port, "127.0.0.1", () => {
|
|
295
|
+
server.close(() => resolve(true));
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/mcp/sse.ts
|
|
301
|
+
var PORT_RANGE_START2 = 5e4;
|
|
302
|
+
var PORT_RANGE_END2 = 6e4;
|
|
303
|
+
var SESSION_IDLE_TTL_MS2 = 15 * 60 * 1e3;
|
|
304
|
+
var SESSION_SWEEP_INTERVAL_MS2 = 60 * 1e3;
|
|
305
|
+
var ThenvoiMcpSseServer = class {
|
|
306
|
+
options;
|
|
307
|
+
registrations;
|
|
308
|
+
httpServer = null;
|
|
309
|
+
actualPort = null;
|
|
310
|
+
sessions = /* @__PURE__ */ new Map();
|
|
311
|
+
sweepTimer = null;
|
|
312
|
+
constructor(options) {
|
|
313
|
+
this.options = options;
|
|
314
|
+
const regOptions = {
|
|
315
|
+
enableMemoryTools: options.enableMemoryTools,
|
|
316
|
+
enableContactTools: options.enableContactTools,
|
|
317
|
+
additionalTools: options.additionalTools
|
|
318
|
+
};
|
|
319
|
+
if (typeof options.tools === "function") {
|
|
320
|
+
this.registrations = buildRoomScopedRegistrations(options.tools, regOptions);
|
|
321
|
+
} else {
|
|
322
|
+
this.registrations = buildSingleContextRegistrations(options.tools, regOptions);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
get port() {
|
|
326
|
+
return this.actualPort;
|
|
327
|
+
}
|
|
328
|
+
get sseUrl() {
|
|
329
|
+
if (this.actualPort === null) {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
return `http://127.0.0.1:${this.actualPort}/sse`;
|
|
333
|
+
}
|
|
334
|
+
get toolNames() {
|
|
335
|
+
return this.registrations.map((registration) => registration.name);
|
|
336
|
+
}
|
|
337
|
+
async start() {
|
|
338
|
+
if (this.httpServer) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const { McpServer } = await import("@modelcontextprotocol/sdk/server/mcp.js");
|
|
342
|
+
const { SSEServerTransport } = await import("@modelcontextprotocol/sdk/server/sse.js");
|
|
343
|
+
const expressModule = await import("express");
|
|
344
|
+
const express = expressModule.default ?? expressModule;
|
|
345
|
+
const http = await import("http");
|
|
346
|
+
const { z } = await import("zod");
|
|
347
|
+
const app = express();
|
|
348
|
+
app.use(express.json());
|
|
349
|
+
app.get("/healthz", (_req, res) => {
|
|
350
|
+
res.json({ status: "ok" });
|
|
351
|
+
});
|
|
352
|
+
app.get("/sse", async (_req, res) => {
|
|
353
|
+
const transport = new SSEServerTransport("/messages", res);
|
|
354
|
+
const mcpServer = new McpServer({
|
|
355
|
+
name: this.options.name ?? "thenvoi",
|
|
356
|
+
version: "1.0.0"
|
|
357
|
+
});
|
|
358
|
+
registerTools3(mcpServer, z, this.registrations);
|
|
359
|
+
await mcpServer.connect(transport);
|
|
360
|
+
const sessionId = transport.sessionId;
|
|
361
|
+
this.sessions.set(sessionId, {
|
|
362
|
+
mcpServer,
|
|
363
|
+
transport,
|
|
364
|
+
lastSeenAt: Date.now()
|
|
365
|
+
});
|
|
366
|
+
transport.onclose = () => {
|
|
367
|
+
this.sessions.delete(sessionId);
|
|
368
|
+
};
|
|
369
|
+
transport.onerror = () => {
|
|
370
|
+
this.sessions.delete(sessionId);
|
|
371
|
+
};
|
|
372
|
+
res.on("close", () => {
|
|
373
|
+
this.sessions.delete(sessionId);
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
app.post("/messages", async (req, res) => {
|
|
377
|
+
const sessionId = typeof req.query.sessionId === "string" ? req.query.sessionId : null;
|
|
378
|
+
if (!sessionId) {
|
|
379
|
+
res.status(400).send("Missing sessionId");
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const session = this.sessions.get(sessionId);
|
|
383
|
+
if (!session) {
|
|
384
|
+
res.status(404).send("Session not found");
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
session.lastSeenAt = Date.now();
|
|
388
|
+
await session.transport.handlePostMessage(req, res, req.body);
|
|
389
|
+
});
|
|
390
|
+
const port = this.options.port ?? await findAvailablePort2(http);
|
|
391
|
+
await new Promise((resolve, reject) => {
|
|
392
|
+
const server = http.createServer(app);
|
|
393
|
+
server.on("error", reject);
|
|
394
|
+
server.listen(port, "127.0.0.1", () => {
|
|
395
|
+
this.httpServer = server;
|
|
396
|
+
this.actualPort = port;
|
|
397
|
+
this.startSessionSweep();
|
|
398
|
+
resolve();
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
async stop() {
|
|
403
|
+
this.stopSessionSweep();
|
|
404
|
+
const sessions = [...this.sessions.values()];
|
|
405
|
+
this.sessions.clear();
|
|
406
|
+
await Promise.all(sessions.map(async (session) => {
|
|
407
|
+
await session.transport.close();
|
|
408
|
+
}));
|
|
409
|
+
if (!this.httpServer) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const server = this.httpServer;
|
|
413
|
+
this.httpServer = null;
|
|
414
|
+
this.actualPort = null;
|
|
415
|
+
await new Promise((resolve, reject) => {
|
|
416
|
+
server.close((error) => {
|
|
417
|
+
if (error) {
|
|
418
|
+
reject(error);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
resolve();
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
startSessionSweep() {
|
|
426
|
+
if (this.sweepTimer) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
this.sweepTimer = setInterval(() => {
|
|
430
|
+
this.closeIdleSessions();
|
|
431
|
+
}, SESSION_SWEEP_INTERVAL_MS2);
|
|
432
|
+
this.sweepTimer.unref?.();
|
|
433
|
+
}
|
|
434
|
+
stopSessionSweep() {
|
|
435
|
+
if (this.sweepTimer) {
|
|
436
|
+
clearInterval(this.sweepTimer);
|
|
437
|
+
this.sweepTimer = null;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
closeIdleSessions() {
|
|
441
|
+
const cutoff = Date.now() - SESSION_IDLE_TTL_MS2;
|
|
442
|
+
for (const [sessionId, session] of this.sessions) {
|
|
443
|
+
if (session.lastSeenAt < cutoff) {
|
|
444
|
+
this.sessions.delete(sessionId);
|
|
445
|
+
void session.transport.close();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
function registerTools3(mcpServer, z, registrations) {
|
|
451
|
+
for (const registration of registrations) {
|
|
452
|
+
const zodShape = buildZodShape(
|
|
453
|
+
z,
|
|
454
|
+
registration.inputSchema.properties,
|
|
455
|
+
new Set(registration.inputSchema.required)
|
|
456
|
+
);
|
|
457
|
+
mcpServer.registerTool(
|
|
458
|
+
registration.name,
|
|
459
|
+
{
|
|
460
|
+
description: registration.description,
|
|
461
|
+
inputSchema: z.object(zodShape)
|
|
462
|
+
},
|
|
463
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- MCP SDK handler signature is complex; our McpToolResult is compatible
|
|
464
|
+
async (args) => registration.execute(args)
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
async function findAvailablePort2(http) {
|
|
469
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
470
|
+
const port = PORT_RANGE_START2 + Math.floor(Math.random() * (PORT_RANGE_END2 - PORT_RANGE_START2));
|
|
471
|
+
if (await checkPort2(http, port)) {
|
|
472
|
+
return port;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return 0;
|
|
476
|
+
}
|
|
477
|
+
function checkPort2(http, port) {
|
|
478
|
+
return new Promise((resolve) => {
|
|
479
|
+
const server = http.createServer();
|
|
480
|
+
server.once("error", () => {
|
|
481
|
+
resolve(false);
|
|
482
|
+
});
|
|
483
|
+
server.listen(port, "127.0.0.1", () => {
|
|
484
|
+
server.close(() => resolve(true));
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/mcp/backends.ts
|
|
490
|
+
async function createThenvoiMcpBackend(options) {
|
|
491
|
+
const registrations = buildRoomScopedRegistrations(
|
|
492
|
+
options.getToolsForRoom,
|
|
493
|
+
{
|
|
494
|
+
enableMemoryTools: options.enableMemoryTools,
|
|
495
|
+
enableContactTools: true,
|
|
496
|
+
additionalTools: options.additionalTools
|
|
497
|
+
}
|
|
498
|
+
);
|
|
499
|
+
const allowedTools = mcpToolNames(new Set(registrations.map((registration) => registration.name)));
|
|
500
|
+
if (options.kind === "sdk") {
|
|
501
|
+
const { createThenvoiSdkMcpServer } = await import("./sdk-HJUVSSWA.js");
|
|
502
|
+
const server2 = createThenvoiSdkMcpServer({
|
|
503
|
+
enableMemoryTools: options.enableMemoryTools,
|
|
504
|
+
getToolsForRoom: options.getToolsForRoom,
|
|
505
|
+
additionalTools: options.additionalTools
|
|
506
|
+
});
|
|
507
|
+
return {
|
|
508
|
+
kind: "sdk",
|
|
509
|
+
server: server2,
|
|
510
|
+
allowedTools: server2.allowedTools,
|
|
511
|
+
stop: async () => void 0
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
if (options.kind === "stdio") {
|
|
515
|
+
const server2 = new ThenvoiMcpStdioServer({
|
|
516
|
+
tools: options.getToolsForRoom,
|
|
517
|
+
enableMemoryTools: options.enableMemoryTools,
|
|
518
|
+
enableContactTools: true,
|
|
519
|
+
additionalTools: options.additionalTools
|
|
520
|
+
});
|
|
521
|
+
await server2.start();
|
|
522
|
+
return {
|
|
523
|
+
kind: "stdio",
|
|
524
|
+
server: server2,
|
|
525
|
+
allowedTools,
|
|
526
|
+
stop: async () => {
|
|
527
|
+
await server2.stop();
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
if (options.kind === "sse") {
|
|
532
|
+
const server2 = new ThenvoiMcpSseServer({
|
|
533
|
+
tools: options.getToolsForRoom,
|
|
534
|
+
enableMemoryTools: options.enableMemoryTools,
|
|
535
|
+
enableContactTools: true,
|
|
536
|
+
additionalTools: options.additionalTools
|
|
537
|
+
});
|
|
538
|
+
await server2.start();
|
|
539
|
+
return {
|
|
540
|
+
kind: "sse",
|
|
541
|
+
server: server2,
|
|
542
|
+
allowedTools,
|
|
543
|
+
stop: async () => {
|
|
544
|
+
await server2.stop();
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
const server = new ThenvoiMcpServer({
|
|
549
|
+
tools: options.getToolsForRoom,
|
|
550
|
+
enableMemoryTools: options.enableMemoryTools,
|
|
551
|
+
enableContactTools: true,
|
|
552
|
+
additionalTools: options.additionalTools
|
|
553
|
+
});
|
|
554
|
+
await server.start();
|
|
555
|
+
return {
|
|
556
|
+
kind: "http",
|
|
557
|
+
server,
|
|
558
|
+
allowedTools,
|
|
559
|
+
stop: async () => {
|
|
560
|
+
await server.stop();
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function getThenvoiSdkMcpServerConfig(backend) {
|
|
565
|
+
if (backend.kind !== "sdk") {
|
|
566
|
+
throw new Error(`Expected sdk MCP backend, received ${backend.kind}`);
|
|
567
|
+
}
|
|
568
|
+
return backend.server.serverConfig;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
export {
|
|
572
|
+
ThenvoiMcpStdioServer,
|
|
573
|
+
ThenvoiMcpServer,
|
|
574
|
+
ThenvoiMcpSseServer,
|
|
575
|
+
createThenvoiMcpBackend,
|
|
576
|
+
getThenvoiSdkMcpServerConfig
|
|
577
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/core/logger.ts
|
|
2
|
+
var noop = () => void 0;
|
|
3
|
+
var REDACTED_VALUE = "[REDACTED]";
|
|
4
|
+
var CIRCULAR_VALUE = "[Circular]";
|
|
5
|
+
var SENSITIVE_KEY_PATTERN = /(authorization|api[-_]?key|token|secret|password|cookie)/i;
|
|
6
|
+
var NoopLogger = class {
|
|
7
|
+
debug = noop;
|
|
8
|
+
info = noop;
|
|
9
|
+
warn = noop;
|
|
10
|
+
error = noop;
|
|
11
|
+
};
|
|
12
|
+
var ConsoleLogger = class {
|
|
13
|
+
debug(message, context) {
|
|
14
|
+
this.emit("debug", message, context);
|
|
15
|
+
}
|
|
16
|
+
info(message, context) {
|
|
17
|
+
this.emit("info", message, context);
|
|
18
|
+
}
|
|
19
|
+
warn(message, context) {
|
|
20
|
+
this.emit("warn", message, context);
|
|
21
|
+
}
|
|
22
|
+
error(message, context) {
|
|
23
|
+
const payload = context ? ` ${safeSerializeContext(context)}` : "";
|
|
24
|
+
process.stderr.write(`${message}${payload}
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
emit(level, message, context) {
|
|
28
|
+
const fn = console[level];
|
|
29
|
+
if (context === void 0) {
|
|
30
|
+
fn(message);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
fn(message, sanitizeValue(context));
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
function safeSerializeContext(context) {
|
|
37
|
+
try {
|
|
38
|
+
return JSON.stringify(sanitizeValue(context));
|
|
39
|
+
} catch {
|
|
40
|
+
return JSON.stringify({ context: "[Unserializable]" });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function sanitizeValue(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
44
|
+
if (value === null || value === void 0) {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
if (typeof value !== "object") {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
if (value instanceof Error) {
|
|
51
|
+
return sanitizeValue(
|
|
52
|
+
{
|
|
53
|
+
name: value.name,
|
|
54
|
+
message: value.message,
|
|
55
|
+
stack: value.stack,
|
|
56
|
+
...value.cause !== void 0 ? { cause: value.cause } : {}
|
|
57
|
+
},
|
|
58
|
+
seen
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (seen.has(value)) {
|
|
62
|
+
return CIRCULAR_VALUE;
|
|
63
|
+
}
|
|
64
|
+
seen.add(value);
|
|
65
|
+
if (Array.isArray(value)) {
|
|
66
|
+
return value.map((entry) => sanitizeValue(entry, seen));
|
|
67
|
+
}
|
|
68
|
+
const sanitized = {};
|
|
69
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
70
|
+
sanitized[key] = SENSITIVE_KEY_PATTERN.test(key) ? REDACTED_VALUE : sanitizeValue(entry, seen);
|
|
71
|
+
}
|
|
72
|
+
return sanitized;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
NoopLogger,
|
|
77
|
+
ConsoleLogger
|
|
78
|
+
};
|