@diegoaltoworks/chatter 0.1.8 → 0.8.0

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.
Files changed (57) hide show
  1. package/README.md +164 -106
  2. package/dist/auth/apikeys.test.d.ts +2 -0
  3. package/dist/auth/apikeys.test.d.ts.map +1 -0
  4. package/dist/core/guardrails.test.d.ts +2 -0
  5. package/dist/core/guardrails.test.d.ts.map +1 -0
  6. package/dist/core/llm.d.ts +1 -0
  7. package/dist/core/llm.d.ts.map +1 -1
  8. package/dist/core/loaders.test.d.ts +2 -0
  9. package/dist/core/loaders.test.d.ts.map +1 -0
  10. package/dist/core/prompts.test.d.ts +2 -0
  11. package/dist/core/prompts.test.d.ts.map +1 -0
  12. package/dist/core/session.test.d.ts +2 -0
  13. package/dist/core/session.test.d.ts.map +1 -0
  14. package/dist/core/widgets.test.d.ts +2 -0
  15. package/dist/core/widgets.test.d.ts.map +1 -0
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +22638 -1395
  19. package/dist/index.mjs +22619 -1373
  20. package/dist/mcp-server/conversation-id.d.ts +27 -0
  21. package/dist/mcp-server/conversation-id.d.ts.map +1 -0
  22. package/dist/mcp-server/conversation-id.test.d.ts +2 -0
  23. package/dist/mcp-server/conversation-id.test.d.ts.map +1 -0
  24. package/dist/mcp-server/cost-tracker.d.ts +36 -0
  25. package/dist/mcp-server/cost-tracker.d.ts.map +1 -0
  26. package/dist/mcp-server/cost-tracker.test.d.ts +2 -0
  27. package/dist/mcp-server/cost-tracker.test.d.ts.map +1 -0
  28. package/dist/mcp-server/index.d.ts +12 -0
  29. package/dist/mcp-server/index.d.ts.map +1 -0
  30. package/dist/mcp-server/logger.d.ts +25 -0
  31. package/dist/mcp-server/logger.d.ts.map +1 -0
  32. package/dist/mcp-server/logger.test.d.ts +2 -0
  33. package/dist/mcp-server/logger.test.d.ts.map +1 -0
  34. package/dist/mcp-server/rate-limiter.d.ts +59 -0
  35. package/dist/mcp-server/rate-limiter.d.ts.map +1 -0
  36. package/dist/mcp-server/rate-limiter.test.d.ts +2 -0
  37. package/dist/mcp-server/rate-limiter.test.d.ts.map +1 -0
  38. package/dist/mcp-server/types.d.ts +82 -0
  39. package/dist/mcp-server/types.d.ts.map +1 -0
  40. package/dist/mcp-server.d.ts +36 -0
  41. package/dist/mcp-server.d.ts.map +1 -0
  42. package/dist/mcp-server.js +671 -0
  43. package/dist/mcp-server.mjs +636 -0
  44. package/dist/middleware/auth.test.d.ts +2 -0
  45. package/dist/middleware/auth.test.d.ts.map +1 -0
  46. package/dist/middleware/cors.test.d.ts +2 -0
  47. package/dist/middleware/cors.test.d.ts.map +1 -0
  48. package/dist/middleware/jwt.test.d.ts +2 -0
  49. package/dist/middleware/jwt.test.d.ts.map +1 -0
  50. package/dist/middleware/ratelimit.test.d.ts +2 -0
  51. package/dist/middleware/ratelimit.test.d.ts.map +1 -0
  52. package/dist/middleware/referrer.d.ts.map +1 -1
  53. package/dist/middleware/referrer.test.d.ts +2 -0
  54. package/dist/middleware/referrer.test.d.ts.map +1 -0
  55. package/dist/middleware/session.test.d.ts +2 -0
  56. package/dist/middleware/session.test.d.ts.map +1 -0
  57. package/package.json +26 -10
@@ -0,0 +1,636 @@
1
+ // src/mcp-server.ts
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import OpenAI from "openai";
4
+ import { z } from "zod";
5
+
6
+ // src/core/guardrails.ts
7
+ function detectLeakage(text) {
8
+ const flags = [
9
+ /system prompt/i,
10
+ /hidden instruction/i,
11
+ /here (are|is) (my|the) rules/i,
12
+ /BEGIN SYSTEM PROMPT/i,
13
+ /tool instructions/i
14
+ ];
15
+ return flags.some((rx) => rx.test(text));
16
+ }
17
+ function scrubOutput(text) {
18
+ return text.replace(/sk-[A-Za-z0-9]{15,}/g, "[REDACTED]").replace(/AIza[0-9A-Za-z_-]{30,}/g, "[REDACTED]");
19
+ }
20
+
21
+ // src/core/llm.ts
22
+ async function completeOnce({
23
+ client,
24
+ system,
25
+ messages,
26
+ temperature = 0.2
27
+ }) {
28
+ const res = await client.chat.completions.create({
29
+ model: "gpt-4o",
30
+ temperature,
31
+ messages: [{ role: "system", content: system }, ...messages]
32
+ });
33
+ let text = res.choices[0]?.message?.content ?? "";
34
+ if (detectLeakage(text))
35
+ text = "Sorry, I can't share internal instructions. How else can I help?";
36
+ return {
37
+ content: scrubOutput(text),
38
+ usage: res.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
39
+ };
40
+ }
41
+
42
+ // src/core/prompts.ts
43
+ import { readFileSync } from "node:fs";
44
+ import { join } from "node:path";
45
+ var PromptLoader = class {
46
+ constructor(promptsDir, bot) {
47
+ this.promptsDir = promptsDir;
48
+ this.bot = bot;
49
+ }
50
+ /**
51
+ * Load a prompt template and interpolate variables
52
+ */
53
+ load(filename) {
54
+ const path = join(this.promptsDir, filename);
55
+ const template = readFileSync(path, "utf-8");
56
+ const personFirstName = this.bot.personName.split(" ")[0];
57
+ return template.replace(/\{\{botName\}\}/g, this.bot.name).replace(/\{\{personName\}\}/g, this.bot.personName).replace(/\{\{personFirstName\}\}/g, personFirstName);
58
+ }
59
+ /** Base system rules prompt */
60
+ get baseSystemRules() {
61
+ return this.load("base.txt");
62
+ }
63
+ /** Public persona prompt */
64
+ get publicPersona() {
65
+ return this.load("public.txt");
66
+ }
67
+ /** Private persona prompt */
68
+ get privatePersona() {
69
+ return this.load("private.txt");
70
+ }
71
+ };
72
+
73
+ // src/core/retrieval.ts
74
+ import { createClient } from "@libsql/client";
75
+
76
+ // src/core/loaders.ts
77
+ import { readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
78
+ import { join as join2 } from "node:path";
79
+ function walk(dir) {
80
+ const items = [];
81
+ try {
82
+ for (const e of readdirSync(dir)) {
83
+ const p = join2(dir, e);
84
+ const st = statSync(p);
85
+ if (st.isDirectory()) items.push(...walk(p));
86
+ else if (p.endsWith(".md")) items.push(p);
87
+ }
88
+ } catch {
89
+ }
90
+ return items;
91
+ }
92
+ function loadKnowledge(root = "config/knowledge") {
93
+ const docs = [];
94
+ for (const bucket of ["base", "public", "private"]) {
95
+ const dir = join2(root, bucket);
96
+ for (const f of walk(dir)) {
97
+ docs.push({ id: f, text: readFileSync2(f, "utf8"), source: f, bucket });
98
+ }
99
+ }
100
+ return docs;
101
+ }
102
+
103
+ // src/core/retrieval.ts
104
+ var EMB_MODEL = "text-embedding-3-large";
105
+ function chunk(text, max = 900) {
106
+ const out = [];
107
+ let buf = "";
108
+ for (const line of text.replace(/\r/g, "").split("\n")) {
109
+ if (`${buf}
110
+ ${line}`.length > max) {
111
+ out.push(buf.trim());
112
+ buf = line;
113
+ } else buf += `
114
+ ${line}`;
115
+ }
116
+ if (buf.trim()) out.push(buf.trim());
117
+ return out;
118
+ }
119
+ async function sha256(input) {
120
+ const data = new TextEncoder().encode(input);
121
+ const hash = await crypto.subtle.digest("SHA-256", data);
122
+ return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("");
123
+ }
124
+ var VectorStore = class _VectorStore {
125
+ constructor(client, options) {
126
+ this.client = client;
127
+ this.db = createClient({
128
+ url: options.databaseUrl,
129
+ authToken: options.databaseAuthToken
130
+ });
131
+ this.knowledgeDir = options.knowledgeDir || "./config/knowledge";
132
+ }
133
+ db;
134
+ knowledgeDir;
135
+ // On boot: ingest new chunks and embed only missing ones.
136
+ async build() {
137
+ console.log("\u{1F504} Building knowledge base...");
138
+ await this.db.execute(`
139
+ CREATE TABLE IF NOT EXISTS chunks (id TEXT PRIMARY KEY, bucket TEXT NOT NULL, source TEXT NOT NULL, text TEXT NOT NULL);
140
+ `);
141
+ await this.db.execute(`
142
+ CREATE TABLE IF NOT EXISTS embeddings (id TEXT PRIMARY KEY, model TEXT NOT NULL, embedding BLOB NOT NULL);
143
+ `);
144
+ await this.db.execute("CREATE INDEX IF NOT EXISTS idx_chunks_bucket ON chunks(bucket);");
145
+ const docs = loadKnowledge(this.knowledgeDir);
146
+ console.log(`\u{1F4DA} Loaded ${docs.length} knowledge documents`);
147
+ const rows = [];
148
+ for (const d of docs) {
149
+ for (const part of chunk(d.text)) {
150
+ const id = await sha256(`${d.bucket}|${d.source}|${part}`);
151
+ rows.push({ id, bucket: d.bucket, source: d.source, text: part });
152
+ }
153
+ }
154
+ console.log(`\u{1F4E6} Created ${rows.length} chunks from knowledge documents`);
155
+ await this.cleanupStaleChunks(rows.map((r) => r.id));
156
+ const tx = await this.db.transaction("write");
157
+ try {
158
+ for (const r of rows) {
159
+ await tx.execute({
160
+ sql: `INSERT INTO chunks(id,bucket,source,text) VALUES(?,?,?,?)
161
+ ON CONFLICT(id) DO NOTHING`,
162
+ args: [r.id, r.bucket, r.source, r.text]
163
+ });
164
+ }
165
+ await tx.commit();
166
+ } catch (e) {
167
+ await tx.rollback();
168
+ throw e;
169
+ }
170
+ const ids = rows.map((r) => r.id);
171
+ const missing = [];
172
+ for (let i = 0; i < ids.length; i += 500) {
173
+ const batch = ids.slice(i, i + 500);
174
+ const placeholders = batch.map(() => "?").join(",");
175
+ const res = await this.db.execute({
176
+ sql: `SELECT id FROM chunks WHERE id IN (${placeholders})
177
+ EXCEPT SELECT id FROM embeddings`,
178
+ args: batch
179
+ });
180
+ for (const row of res.rows) missing.push(String(row.id));
181
+ }
182
+ if (missing.length === 0) {
183
+ console.log("\u2705 No new chunks to embed - knowledge base is up to date");
184
+ return;
185
+ }
186
+ console.log(`\u{1F52E} Embedding ${missing.length} new/updated chunks with ${EMB_MODEL}...`);
187
+ const textById = new Map(rows.map((r) => [r.id, r.text]));
188
+ const BATCH = 96;
189
+ for (let i = 0; i < missing.length; i += BATCH) {
190
+ const batchIds = missing.slice(i, i + BATCH);
191
+ const inputs = batchIds.map((id) => textById.get(id) || "");
192
+ const emb = await this.client.embeddings.create({ model: EMB_MODEL, input: inputs });
193
+ const tx2 = await this.db.transaction("write");
194
+ try {
195
+ emb.data.forEach((d, idx) => {
196
+ const id = batchIds[idx];
197
+ tx2.execute({
198
+ sql: "INSERT INTO embeddings(id,model,embedding) VALUES(?,?,?)",
199
+ args: [id, EMB_MODEL, JSON.stringify(d.embedding)]
200
+ });
201
+ });
202
+ await tx2.commit();
203
+ } catch (e) {
204
+ await tx2.rollback();
205
+ throw e;
206
+ }
207
+ }
208
+ console.log(`\u2705 Successfully embedded ${missing.length} new chunks`);
209
+ }
210
+ // Remove chunks from database that no longer exist in markdown files
211
+ async cleanupStaleChunks(currentIds) {
212
+ const result = await this.db.execute("SELECT id FROM chunks");
213
+ const dbIds = result.rows.map((row) => String(row.id));
214
+ const currentIdSet = new Set(currentIds);
215
+ const staleIds = dbIds.filter((id) => !currentIdSet.has(id));
216
+ if (staleIds.length === 0) {
217
+ return;
218
+ }
219
+ console.log(`\u{1F9F9} Cleaning up ${staleIds.length} stale chunks...`);
220
+ const BATCH_SIZE = 500;
221
+ for (let i = 0; i < staleIds.length; i += BATCH_SIZE) {
222
+ const batch = staleIds.slice(i, i + BATCH_SIZE);
223
+ const placeholders = batch.map(() => "?").join(",");
224
+ await this.db.execute({
225
+ sql: `DELETE FROM chunks WHERE id IN (${placeholders})`,
226
+ args: batch
227
+ });
228
+ }
229
+ console.log(`\u2713 Cleaned up ${staleIds.length} stale chunks and their embeddings`);
230
+ }
231
+ static cosine(a, b) {
232
+ let dot = 0;
233
+ let na = 0;
234
+ let nb = 0;
235
+ for (let i = 0; i < a.length; i++) {
236
+ dot += a[i] * b[i];
237
+ na += a[i] * a[i];
238
+ nb += b[i] * b[i];
239
+ }
240
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
241
+ }
242
+ // Retrieve top-k across allowed buckets; compute similarity in app (simple & portable).
243
+ async query(q, k = 6, allowed = ["base"]) {
244
+ const qv = (await this.client.embeddings.create({ model: EMB_MODEL, input: [q] })).data[0].embedding;
245
+ const placeholders = allowed.map(() => "?").join(",");
246
+ const res = await this.db.execute({
247
+ sql: `SELECT c.id, c.text, e.embedding
248
+ FROM chunks c
249
+ JOIN embeddings e ON e.id = c.id
250
+ WHERE c.bucket IN (${placeholders})`,
251
+ args: allowed
252
+ });
253
+ const scored = [];
254
+ for (const row of res.rows) {
255
+ const emb = JSON.parse(String(row.embedding));
256
+ const s = _VectorStore.cosine(qv, emb);
257
+ scored.push({ s, text: String(row.text) });
258
+ }
259
+ scored.sort((a, b) => b.s - a.s);
260
+ return scored.slice(0, k).map((r) => r.text);
261
+ }
262
+ };
263
+
264
+ // src/mcp-server/conversation-id.ts
265
+ function generateConversationId() {
266
+ const timestamp = Date.now();
267
+ const random = Math.random().toString(36).slice(2, 11);
268
+ return `conv_${timestamp}_${random}`;
269
+ }
270
+ function getOrGenerateConversationId(providedId) {
271
+ return providedId || generateConversationId();
272
+ }
273
+
274
+ // src/mcp-server/cost-tracker.ts
275
+ function calculateCost(usage) {
276
+ const promptTokens = usage.prompt_tokens || 0;
277
+ const completionTokens = usage.completion_tokens || 0;
278
+ const totalTokens = promptTokens + completionTokens;
279
+ const promptCost = promptTokens / 1e6 * 2.5;
280
+ const completionCost = completionTokens / 1e6 * 10;
281
+ const estimatedCost = promptCost + completionCost;
282
+ return {
283
+ promptTokens,
284
+ completionTokens,
285
+ totalTokens,
286
+ estimatedCost
287
+ };
288
+ }
289
+
290
+ // src/mcp-server/logger.ts
291
+ var MCPLogger = class {
292
+ enableConsole;
293
+ callback;
294
+ constructor(enableConsole = true, callback) {
295
+ this.enableConsole = enableConsole;
296
+ this.callback = callback;
297
+ }
298
+ /**
299
+ * Log a chat interaction
300
+ */
301
+ async logChatInteraction(toolName, conversationId, conversationMessages, ragContext, response, duration, cost) {
302
+ const lastUserMsg = [...conversationMessages].reverse().find((m) => m.role === "user");
303
+ const logEvent = {
304
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
305
+ toolName,
306
+ conversationId,
307
+ userMessage: lastUserMsg?.content || "",
308
+ conversationHistory: conversationMessages,
309
+ ragContext,
310
+ response,
311
+ duration,
312
+ cost
313
+ };
314
+ if (this.enableConsole) {
315
+ console.log(
316
+ JSON.stringify({
317
+ event: "mcp_chat",
318
+ ...logEvent
319
+ })
320
+ );
321
+ }
322
+ if (this.callback) {
323
+ try {
324
+ await this.callback(logEvent);
325
+ } catch (err) {
326
+ console.error("Logging callback error:", err);
327
+ }
328
+ }
329
+ }
330
+ };
331
+ function createLogger(enableConsole = true, callback) {
332
+ return new MCPLogger(enableConsole, callback);
333
+ }
334
+
335
+ // src/mcp-server/rate-limiter.ts
336
+ var RateLimiter = class {
337
+ requestMap;
338
+ limit;
339
+ windowMs;
340
+ /**
341
+ * Create a new rate limiter
342
+ *
343
+ * @param limit - Maximum requests allowed in the time window
344
+ * @param windowMs - Time window in milliseconds (default: 60000 = 1 minute)
345
+ */
346
+ constructor(limit, windowMs = 6e4) {
347
+ this.requestMap = /* @__PURE__ */ new Map();
348
+ this.limit = limit;
349
+ this.windowMs = windowMs;
350
+ }
351
+ /**
352
+ * Check if a request should be allowed
353
+ *
354
+ * @param key - Unique identifier for the rate limit (e.g., tool name)
355
+ * @returns true if request is allowed, false if rate limit exceeded
356
+ */
357
+ check(key) {
358
+ const now = Date.now();
359
+ const windowStart = now - this.windowMs;
360
+ const timestamps = this.requestMap.get(key) || [];
361
+ const recentTimestamps = timestamps.filter((ts) => ts > windowStart);
362
+ if (recentTimestamps.length >= this.limit) {
363
+ return false;
364
+ }
365
+ recentTimestamps.push(now);
366
+ this.requestMap.set(key, recentTimestamps);
367
+ return true;
368
+ }
369
+ /**
370
+ * Get current request count for a key
371
+ *
372
+ * @param key - Unique identifier
373
+ * @returns Current number of requests in the window
374
+ */
375
+ getCount(key) {
376
+ const now = Date.now();
377
+ const windowStart = now - this.windowMs;
378
+ const timestamps = this.requestMap.get(key) || [];
379
+ return timestamps.filter((ts) => ts > windowStart).length;
380
+ }
381
+ /**
382
+ * Reset rate limit for a specific key
383
+ *
384
+ * @param key - Unique identifier to reset
385
+ */
386
+ reset(key) {
387
+ this.requestMap.delete(key);
388
+ }
389
+ /**
390
+ * Reset all rate limits
391
+ */
392
+ resetAll() {
393
+ this.requestMap.clear();
394
+ }
395
+ /**
396
+ * Get time until rate limit resets (in ms)
397
+ *
398
+ * @param key - Unique identifier
399
+ * @returns Milliseconds until the oldest request falls out of the window
400
+ */
401
+ getTimeUntilReset(key) {
402
+ const timestamps = this.requestMap.get(key) || [];
403
+ if (timestamps.length === 0) return 0;
404
+ const now = Date.now();
405
+ const oldestTimestamp = Math.min(...timestamps);
406
+ const timeUntilReset = this.windowMs - (now - oldestTimestamp);
407
+ return Math.max(0, timeUntilReset);
408
+ }
409
+ };
410
+ function createRateLimiter(limit, windowMs = 6e4) {
411
+ return limit ? new RateLimiter(limit, windowMs) : null;
412
+ }
413
+
414
+ // src/mcp-server.ts
415
+ async function createMCPServer(config) {
416
+ console.log(`\u{1F680} Starting ${config.bot.name} MCP Server...`);
417
+ const knowledgeDir = config.knowledgeDir || "./config/knowledge";
418
+ const promptsDir = config.promptsDir || "./config/prompts";
419
+ const transportMode = config.transport || "stdio";
420
+ const rateLimiter = createRateLimiter(config.toolRateLimit);
421
+ const logger = createLogger(config.logging?.console !== false, config.logging?.onChat);
422
+ const publicToolConfig = {
423
+ enabled: config.tools?.public?.enabled !== false,
424
+ name: config.tools?.public?.name || "chat_public",
425
+ description: config.tools?.public?.description || `Chat with ${config.bot.name} using public knowledge base. Supports both single messages and conversation history.`
426
+ };
427
+ const privateToolConfig = {
428
+ enabled: config.tools?.private?.enabled !== false,
429
+ name: config.tools?.private?.name || "chat_private",
430
+ description: config.tools?.private?.description || `Chat with ${config.bot.name} using private/internal knowledge base. Supports both single messages and conversation history.`
431
+ };
432
+ const client = new OpenAI({
433
+ apiKey: config.openai.apiKey
434
+ });
435
+ const store = new VectorStore(client, {
436
+ databaseUrl: config.database.url,
437
+ databaseAuthToken: config.database.authToken,
438
+ knowledgeDir
439
+ });
440
+ await store.build();
441
+ console.log("\u2705 Knowledge base ready");
442
+ const prompts = new PromptLoader(promptsDir, config.bot);
443
+ const server = new McpServer({
444
+ name: config.bot.name,
445
+ version: "1.0.0"
446
+ });
447
+ console.log("\u2705 MCP server initialized");
448
+ if (publicToolConfig.enabled) {
449
+ const publicSchema = z.object({
450
+ message: z.string().optional().describe("A single message to send (for simple queries)"),
451
+ messages: z.array(
452
+ z.object({
453
+ role: z.enum(["user", "assistant"]),
454
+ content: z.string()
455
+ })
456
+ ).optional().describe("Conversation history with alternating user/assistant messages"),
457
+ conversationId: z.string().optional().describe(
458
+ "Optional conversation ID to track sessions. If not provided, a new ID will be generated."
459
+ )
460
+ }).refine(
461
+ (data) => data.message !== void 0 || data.messages !== void 0,
462
+ "Either 'message' or 'messages' must be provided"
463
+ );
464
+ server.registerTool(
465
+ publicToolConfig.name,
466
+ {
467
+ description: publicToolConfig.description,
468
+ inputSchema: publicSchema
469
+ },
470
+ async (args) => {
471
+ const { message, messages, conversationId } = args;
472
+ const startTime = Date.now();
473
+ const convId = getOrGenerateConversationId(conversationId);
474
+ if (rateLimiter && !rateLimiter.check(publicToolConfig.name)) {
475
+ throw new Error(
476
+ `Rate limit exceeded for ${publicToolConfig.name}. Maximum ${config.toolRateLimit} requests per minute.`
477
+ );
478
+ }
479
+ let conversationMessages;
480
+ if (messages && Array.isArray(messages)) {
481
+ conversationMessages = messages;
482
+ } else if (message) {
483
+ conversationMessages = [{ role: "user", content: String(message) }];
484
+ } else {
485
+ throw new Error("Either 'message' or 'messages' is required");
486
+ }
487
+ const lastUserMsg = [...conversationMessages].reverse().find((m) => m.role === "user");
488
+ if (!lastUserMsg) {
489
+ throw new Error("No user message found in conversation");
490
+ }
491
+ const ctx = await store.query(lastUserMsg.content, 6, ["base", "public"]);
492
+ const system = [
493
+ prompts.baseSystemRules,
494
+ prompts.publicPersona,
495
+ `Context:
496
+ ${ctx.join("\n\n")}`
497
+ ].join("\n\n");
498
+ const result = await completeOnce({
499
+ client,
500
+ system,
501
+ messages: conversationMessages
502
+ });
503
+ const cost = calculateCost(result.usage);
504
+ const duration = Date.now() - startTime;
505
+ await logger.logChatInteraction(
506
+ publicToolConfig.name,
507
+ convId,
508
+ conversationMessages,
509
+ ctx,
510
+ result.content,
511
+ duration,
512
+ cost
513
+ ).catch((err) => console.error("Logging error:", err));
514
+ return {
515
+ content: [
516
+ {
517
+ type: "text",
518
+ text: result.content
519
+ }
520
+ ],
521
+ _meta: {
522
+ conversationId: convId,
523
+ cost: {
524
+ promptTokens: cost.promptTokens,
525
+ completionTokens: cost.completionTokens,
526
+ totalTokens: cost.totalTokens,
527
+ estimatedCostUSD: cost.estimatedCost
528
+ }
529
+ }
530
+ };
531
+ }
532
+ );
533
+ console.log(`\u2705 Registered tool: ${publicToolConfig.name}`);
534
+ }
535
+ if (privateToolConfig.enabled) {
536
+ const privateSchema = z.object({
537
+ message: z.string().optional().describe("A single message to send (for simple queries)"),
538
+ messages: z.array(
539
+ z.object({
540
+ role: z.enum(["user", "assistant"]),
541
+ content: z.string()
542
+ })
543
+ ).optional().describe("Conversation history with alternating user/assistant messages"),
544
+ conversationId: z.string().optional().describe(
545
+ "Optional conversation ID to track sessions. If not provided, a new ID will be generated."
546
+ )
547
+ }).refine(
548
+ (data) => data.message !== void 0 || data.messages !== void 0,
549
+ "Either 'message' or 'messages' must be provided"
550
+ );
551
+ server.registerTool(
552
+ privateToolConfig.name,
553
+ {
554
+ description: privateToolConfig.description,
555
+ inputSchema: privateSchema
556
+ },
557
+ async (args) => {
558
+ const { message, messages, conversationId } = args;
559
+ const startTime = Date.now();
560
+ const convId = getOrGenerateConversationId(conversationId);
561
+ if (rateLimiter && !rateLimiter.check(privateToolConfig.name)) {
562
+ throw new Error(
563
+ `Rate limit exceeded for ${privateToolConfig.name}. Maximum ${config.toolRateLimit} requests per minute.`
564
+ );
565
+ }
566
+ let conversationMessages;
567
+ if (messages && Array.isArray(messages)) {
568
+ conversationMessages = messages;
569
+ } else if (message) {
570
+ conversationMessages = [{ role: "user", content: String(message) }];
571
+ } else {
572
+ throw new Error("Either 'message' or 'messages' is required");
573
+ }
574
+ const lastUserMsg = [...conversationMessages].reverse().find((m) => m.role === "user");
575
+ if (!lastUserMsg) {
576
+ throw new Error("No user message found in conversation");
577
+ }
578
+ const ctx = await store.query(lastUserMsg.content, 8, ["base", "private"]);
579
+ const system = [
580
+ prompts.baseSystemRules,
581
+ prompts.privatePersona,
582
+ `Internal Context:
583
+ ${ctx.join("\n\n")}`
584
+ ].join("\n\n");
585
+ const result = await completeOnce({
586
+ client,
587
+ system,
588
+ messages: conversationMessages
589
+ });
590
+ const cost = calculateCost(result.usage);
591
+ const duration = Date.now() - startTime;
592
+ await logger.logChatInteraction(
593
+ privateToolConfig.name,
594
+ convId,
595
+ conversationMessages,
596
+ ctx,
597
+ result.content,
598
+ duration,
599
+ cost
600
+ ).catch((err) => console.error("Logging error:", err));
601
+ return {
602
+ content: [
603
+ {
604
+ type: "text",
605
+ text: result.content
606
+ }
607
+ ],
608
+ _meta: {
609
+ conversationId: convId,
610
+ cost: {
611
+ promptTokens: cost.promptTokens,
612
+ completionTokens: cost.completionTokens,
613
+ totalTokens: cost.totalTokens,
614
+ estimatedCostUSD: cost.estimatedCost
615
+ }
616
+ }
617
+ };
618
+ }
619
+ );
620
+ console.log(`\u2705 Registered tool: ${privateToolConfig.name}`);
621
+ }
622
+ const enabledTools = [
623
+ publicToolConfig.enabled ? publicToolConfig.name : null,
624
+ privateToolConfig.enabled ? privateToolConfig.name : null
625
+ ].filter(Boolean);
626
+ if (enabledTools.length === 0) {
627
+ console.warn("\u26A0\uFE0F Warning: No MCP tools enabled");
628
+ }
629
+ console.log(
630
+ `\u2705 ${config.bot.name} MCP Server ready (transport: ${transportMode}, tools: ${enabledTools.join(", ") || "none"})`
631
+ );
632
+ return server;
633
+ }
634
+ export {
635
+ createMCPServer
636
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.test.d.ts","sourceRoot":"","sources":["../../src/middleware/auth.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cors.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cors.test.d.ts","sourceRoot":"","sources":["../../src/middleware/cors.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=jwt.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt.test.d.ts","sourceRoot":"","sources":["../../src/middleware/jwt.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ratelimit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ratelimit.test.d.ts","sourceRoot":"","sources":["../../src/middleware/ratelimit.test.ts"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"referrer.d.ts","sourceRoot":"","sources":["../../src/middleware/referrer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI1C;;;GAGG;AACH,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM,EAAE,IACxC,GAAG,OAAO,EAAE,MAAM,IAAI;;8BAqCrC;AAGD,eAAO,MAAM,mBAAmB,wBAAkB,CAAC"}
1
+ {"version":3,"file":"referrer.d.ts","sourceRoot":"","sources":["../../src/middleware/referrer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI1C;;;GAGG;AACH,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM,EAAE,IACxC,GAAG,OAAO,EAAE,MAAM,IAAI;;8BA8CrC;AAGD,eAAO,MAAM,mBAAmB,wBAAkB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=referrer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"referrer.test.d.ts","sourceRoot":"","sources":["../../src/middleware/referrer.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=session.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.test.d.ts","sourceRoot":"","sources":["../../src/middleware/session.test.ts"],"names":[],"mappings":""}