@marsnme/mcp-gateway 0.1.8 → 0.2.1
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/package.json +4 -3
- package/server.mjs +386 -81
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marsnme/mcp-gateway",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"mcpName": "io.github.Marsmanleo/marsnme",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Agent-agnostic, LLM-agnostic memory backend for MCP-compatible tools",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"session-memory",
|
|
27
27
|
"cross-session",
|
|
28
28
|
"llm",
|
|
29
|
-
"symbiosis"
|
|
29
|
+
"symbiosis",
|
|
30
|
+
"perplexity"
|
|
30
31
|
],
|
|
31
32
|
"bin": {
|
|
32
33
|
"marsnme": "server.mjs"
|
|
@@ -48,4 +49,4 @@
|
|
|
48
49
|
"engines": {
|
|
49
50
|
"node": ">=20"
|
|
50
51
|
}
|
|
51
|
-
}
|
|
52
|
+
}
|
package/server.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import http from 'node:http';
|
|
3
3
|
import crypto from 'node:crypto';
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
5
6
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
7
|
import { dirname } from 'node:path';
|
|
7
8
|
|
|
@@ -171,6 +172,28 @@ if (!Number.isInteger(PORT) || PORT < 1 || PORT > 65535) {
|
|
|
171
172
|
throw new Error(`Invalid PORT: ${PORT_RAW || String(PORT)}. Must be an integer between 1 and 65535.`);
|
|
172
173
|
}
|
|
173
174
|
const SUPABASE_BASE_URL = process.env.SUPABASE_BASE_URL || 'http://127.0.0.1:8100';
|
|
175
|
+
const SUPABASE_AUTH_MODE_VALUES = new Set(['service', 'anon', 'hybrid']);
|
|
176
|
+
const SUPABASE_AUTH_MODE_RAW = String(process.env.MCP_SUPABASE_AUTH_MODE || 'service')
|
|
177
|
+
.trim()
|
|
178
|
+
.toLowerCase();
|
|
179
|
+
if (SUPABASE_AUTH_MODE_RAW && !SUPABASE_AUTH_MODE_VALUES.has(SUPABASE_AUTH_MODE_RAW)) {
|
|
180
|
+
console.warn(
|
|
181
|
+
`[config] invalid MCP_SUPABASE_AUTH_MODE=${SUPABASE_AUTH_MODE_RAW}; fallback to service`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const SUPABASE_AUTH_MODE = SUPABASE_AUTH_MODE_VALUES.has(SUPABASE_AUTH_MODE_RAW)
|
|
185
|
+
? SUPABASE_AUTH_MODE_RAW
|
|
186
|
+
: 'service';
|
|
187
|
+
const SUPABASE_ANON_KEY = String(process.env.SUPABASE_ANON_KEY || '').trim();
|
|
188
|
+
const ANON_ALLOWED_TOOL_NAMES = new Set([
|
|
189
|
+
'list_memories',
|
|
190
|
+
'search_memories',
|
|
191
|
+
'reload_source_registry',
|
|
192
|
+
'recall',
|
|
193
|
+
'health_check',
|
|
194
|
+
'explain_memory'
|
|
195
|
+
]);
|
|
196
|
+
const SUPABASE_REQUEST_CONTEXT = new AsyncLocalStorage();
|
|
174
197
|
const OAUTH_ENABLED = process.env.MCP_OAUTH_ENABLED !== 'false';
|
|
175
198
|
const REQUIRE_BEARER = process.env.MCP_REQUIRE_BEARER === 'true';
|
|
176
199
|
const BYPASS_BEARER_FOR_PRIVATE = process.env.MCP_BYPASS_BEARER_FOR_PRIVATE !== 'false';
|
|
@@ -284,29 +307,29 @@ function buildTools() {
|
|
|
284
307
|
return [
|
|
285
308
|
{
|
|
286
309
|
name: 'insert_memory',
|
|
287
|
-
description: `
|
|
310
|
+
description: `Store a short-term ${PROFILE.displayName} memory into ${DB_PROFILE}.memories (ephemeral context that auto-expires). Use this to capture observations, decisions, session notes, or any context worth remembering temporarily. Memories last 7 days by default unless expires_at is set. For permanent knowledge, use memory_ingest instead.`,
|
|
288
311
|
inputSchema: {
|
|
289
312
|
type: 'object',
|
|
290
313
|
properties: {
|
|
291
|
-
body: { type: 'string', description: '
|
|
314
|
+
body: { type: 'string', description: 'The memory content to store. Be specific and include relevant context — this text is used for semantic search later.' },
|
|
292
315
|
source: buildMemorySourceSchema(),
|
|
293
|
-
session_id: { type: 'string', description: '
|
|
316
|
+
session_id: { type: 'string', description: 'Unique identifier for the current session/conversation. Used to group related memories together.' },
|
|
294
317
|
tags: {
|
|
295
318
|
type: 'array',
|
|
296
319
|
items: { type: 'string' },
|
|
297
|
-
description: '
|
|
320
|
+
description: 'Categorization labels for filtering (e.g. ["decision", "architecture", "bugfix"])'
|
|
298
321
|
},
|
|
299
322
|
expires_at: {
|
|
300
323
|
type: 'string',
|
|
301
|
-
description: '
|
|
324
|
+
description: 'Custom expiration time in ISO 8601 format (e.g. "2026-06-17T00:00:00Z"). Defaults to 7 days from now if omitted.'
|
|
302
325
|
},
|
|
303
326
|
agent_body: {
|
|
304
327
|
type: 'string',
|
|
305
|
-
description: '
|
|
328
|
+
description: 'Which persona/agent body this memory belongs to (e.g. "coco", "toto"). Defaults to the profile of this server.'
|
|
306
329
|
},
|
|
307
330
|
environment: {
|
|
308
331
|
type: 'string',
|
|
309
|
-
description: '
|
|
332
|
+
description: 'Deployment environment label (e.g. "production", "staging"). Defaults to MCP_ENVIRONMENT if set.'
|
|
310
333
|
}
|
|
311
334
|
},
|
|
312
335
|
required: ['body', 'source', 'session_id'],
|
|
@@ -315,36 +338,36 @@ function buildTools() {
|
|
|
315
338
|
},
|
|
316
339
|
{
|
|
317
340
|
name: 'list_memories',
|
|
318
|
-
description: `List recent memories from ${DB_PROFILE}.memories
|
|
341
|
+
description: `List recent short-term memories from ${DB_PROFILE}.memories in reverse chronological order — like a daily log or activity feed. Use this to review what was recently captured, check for duplicate memories before inserting, or browse recent activity. This is a time-ordered listing tool, not a search tool. For semantic search by meaning, use search_memories instead. For long-term knowledge retrieval, use recall. Returns memory body, source, creation time, and expiration status.`,
|
|
319
342
|
inputSchema: {
|
|
320
343
|
type: 'object',
|
|
321
344
|
properties: {
|
|
322
|
-
limit: { type: 'number', minimum: 1, maximum: 100, default: 20 },
|
|
345
|
+
limit: { type: 'number', minimum: 1, maximum: 100, default: 20, description: 'Maximum number of memories to return (default 20)' },
|
|
323
346
|
source: buildMemorySourceSchema(),
|
|
324
|
-
unexpired_only: { type: 'boolean', default: true }
|
|
347
|
+
unexpired_only: { type: 'boolean', default: true, description: 'When true (default), only return memories that have not yet expired. Set false to include expired entries.' }
|
|
325
348
|
},
|
|
326
349
|
additionalProperties: false
|
|
327
350
|
}
|
|
328
351
|
},
|
|
329
352
|
{
|
|
330
353
|
name: 'search_memories',
|
|
331
|
-
description: `Semantic search memories
|
|
354
|
+
description: `Semantic search across short-term memories in ${DB_PROFILE}.memories using Jina embeddings. Finds memories by meaning, not just keywords — ask a natural language question and get the most relevant matches. Use this to recall past decisions, find related context, or check if something was already discussed recently.`,
|
|
332
355
|
inputSchema: {
|
|
333
356
|
type: 'object',
|
|
334
357
|
properties: {
|
|
335
|
-
query: { type: 'string', description: '
|
|
336
|
-
limit: { type: 'number', minimum: 1, maximum: 100, default: 20 },
|
|
358
|
+
query: { type: 'string', description: 'Natural language search query. Describe what you are looking for — semantic matching finds relevant results even without exact keywords.' },
|
|
359
|
+
limit: { type: 'number', minimum: 1, maximum: 100, default: 20, description: 'Maximum number of results to return' },
|
|
337
360
|
source: buildMemorySourceSchema(),
|
|
338
|
-
unexpired_only: { type: 'boolean', default: true },
|
|
339
|
-
min_similarity: { type: 'number', minimum: -1, maximum: 1 },
|
|
340
|
-
scope: { type: 'string', enum: ['this_body', 'all_bodies'], default: 'this_body' },
|
|
361
|
+
unexpired_only: { type: 'boolean', default: true, description: 'When true (default), exclude expired memories from results' },
|
|
362
|
+
min_similarity: { type: 'number', minimum: -1, maximum: 1, description: 'Minimum cosine similarity threshold for results (range -1 to 1). Higher values return fewer but more relevant matches.' },
|
|
363
|
+
scope: { type: 'string', enum: ['this_body', 'all_bodies'], default: 'this_body', description: 'Search scope: "this_body" searches only the current profile, "all_bodies" searches across all personas.' },
|
|
341
364
|
agent_body: {
|
|
342
365
|
type: 'string',
|
|
343
|
-
description: '
|
|
366
|
+
description: 'Filter results to a specific persona/body (e.g. "coco", "toto")'
|
|
344
367
|
},
|
|
345
368
|
environment: {
|
|
346
369
|
type: 'string',
|
|
347
|
-
description: '
|
|
370
|
+
description: 'Filter results to a specific environment (e.g. "production", "staging")'
|
|
348
371
|
}
|
|
349
372
|
},
|
|
350
373
|
required: ['query'],
|
|
@@ -354,7 +377,7 @@ function buildTools() {
|
|
|
354
377
|
{
|
|
355
378
|
name: 'reload_source_registry',
|
|
356
379
|
description:
|
|
357
|
-
`Reload
|
|
380
|
+
`Reload the source registry cache from ${DB_PROFILE}.${SOURCE_REGISTRY_TABLE}. Use this after adding or removing entries to refresh which sources are enabled for insert/search filtering. Only effective when running in registry mode (MCP_SOURCE_MODE=registry).`,
|
|
358
381
|
inputSchema: {
|
|
359
382
|
type: 'object',
|
|
360
383
|
properties: {},
|
|
@@ -363,31 +386,31 @@ function buildTools() {
|
|
|
363
386
|
},
|
|
364
387
|
{
|
|
365
388
|
name: 'recall',
|
|
366
|
-
description: `Semantic recall from ${DB_PROFILE}.marsvault_chunks using Jina embeddings
|
|
389
|
+
description: `Semantic recall from long-term memory (${DB_PROFILE}.marsvault_chunks) using Jina embeddings. Searches across promoted insights, digests, and archived knowledge using vector similarity. Use this to retrieve established patterns, past decisions, documented workflows, or any knowledge that was previously promoted to long-term storage. This is the primary tool for accessing institutional memory.`,
|
|
367
390
|
inputSchema: {
|
|
368
391
|
type: 'object',
|
|
369
392
|
properties: {
|
|
370
|
-
query: { type: 'string', description: '
|
|
371
|
-
limit: { type: 'number', minimum: 1, maximum: 50, default: 5 },
|
|
372
|
-
body: { type: 'string', enum: PROFILE.recallBodyEnum, default: DB_PROFILE },
|
|
373
|
-
include_global: { type: 'boolean', default: true },
|
|
374
|
-
include_shared: { type: 'boolean', default: true },
|
|
375
|
-
include_private: { type: 'boolean', default: true },
|
|
376
|
-
type: { type: 'string', description: '
|
|
377
|
-
min_similarity: { type: 'number', minimum: -1, maximum: 1 },
|
|
378
|
-
scope: { type: 'string', enum: ['this_body', 'all_bodies'], default: 'this_body' },
|
|
393
|
+
query: { type: 'string', description: 'Natural language query describing what knowledge you need. The system finds semantically similar chunks — describe the concept, not just keywords.' },
|
|
394
|
+
limit: { type: 'number', minimum: 1, maximum: 50, default: 5, description: 'Maximum number of chunks to return (default 5)' },
|
|
395
|
+
body: { type: 'string', enum: PROFILE.recallBodyEnum, default: DB_PROFILE, description: 'Which persona profile to search in (e.g. "coco", "toto", "system")' },
|
|
396
|
+
include_global: { type: 'boolean', default: true, description: 'Include globally visible chunks in results' },
|
|
397
|
+
include_shared: { type: 'boolean', default: true, description: 'Include shared-visibility chunks in results' },
|
|
398
|
+
include_private: { type: 'boolean', default: true, description: 'Include private chunks in results' },
|
|
399
|
+
type: { type: 'string', description: 'Filter by chunk type (e.g. "insight", "digest", "observation")' },
|
|
400
|
+
min_similarity: { type: 'number', minimum: -1, maximum: 1, description: 'Minimum cosine similarity threshold. Raise for higher precision, lower for broader recall.' },
|
|
401
|
+
scope: { type: 'string', enum: ['this_body', 'all_bodies'], default: 'this_body', description: 'Search scope: "this_body" for current profile only, "all_bodies" for cross-persona search' },
|
|
379
402
|
agent_body: {
|
|
380
403
|
type: 'string',
|
|
381
|
-
description: '
|
|
404
|
+
description: 'Filter to a specific persona/body scope'
|
|
382
405
|
},
|
|
383
406
|
environment: {
|
|
384
407
|
type: 'string',
|
|
385
|
-
description: '
|
|
408
|
+
description: 'Filter to a specific environment label'
|
|
386
409
|
},
|
|
387
410
|
debug_explain: {
|
|
388
411
|
type: 'boolean',
|
|
389
412
|
default: false,
|
|
390
|
-
description: '
|
|
413
|
+
description: 'When true, include token overlap details in each result for debugging relevance'
|
|
391
414
|
}
|
|
392
415
|
},
|
|
393
416
|
required: ['query'],
|
|
@@ -396,7 +419,7 @@ function buildTools() {
|
|
|
396
419
|
},
|
|
397
420
|
{
|
|
398
421
|
name: 'health_check',
|
|
399
|
-
description: `Run ${PROFILE.displayName} memory
|
|
422
|
+
description: `Run comprehensive ${PROFILE.displayName} memory system diagnostics. Returns chunk counts by type and visibility, identifies expiring short-term memories that need promotion, detects timeline coverage gaps, and finds conflicting or redundant long-term chunks. Use this regularly to maintain memory hygiene and before batch promotion.`,
|
|
400
423
|
inputSchema: {
|
|
401
424
|
type: 'object',
|
|
402
425
|
properties: {
|
|
@@ -491,11 +514,11 @@ function buildTools() {
|
|
|
491
514
|
{
|
|
492
515
|
name: 'session_boot',
|
|
493
516
|
description:
|
|
494
|
-
|
|
517
|
+
`Initialize a new ${PROFILE.displayName} session by loading identity, workflow context, and recent status from long-term memory. Also runs an expiry-focused health snapshot and records a heartbeat sign-in. Call this once at the start of every new conversation to restore continuity and awareness of pending work.`,
|
|
495
518
|
inputSchema: {
|
|
496
519
|
type: 'object',
|
|
497
520
|
properties: {
|
|
498
|
-
source: { type: 'string', enum: PROFILE.sourceWhitelist },
|
|
521
|
+
source: { type: 'string', enum: PROFILE.sourceWhitelist, description: 'Source tool identifier (e.g. "warp", "cursor", "perplexity") for provenance tracking' },
|
|
499
522
|
body_name: {
|
|
500
523
|
type: 'string',
|
|
501
524
|
description: 'Optional body/persona name (例如:大家姐、三哥、五妹、Toto)'
|
|
@@ -528,7 +551,8 @@ function buildTools() {
|
|
|
528
551
|
type: 'number',
|
|
529
552
|
minimum: 1,
|
|
530
553
|
maximum: 10,
|
|
531
|
-
default: 5
|
|
554
|
+
default: 5,
|
|
555
|
+
description: 'Maximum recall results per category (identity/workflow/status, default 5)'
|
|
532
556
|
},
|
|
533
557
|
alert_window_hours: {
|
|
534
558
|
type: 'number',
|
|
@@ -556,7 +580,7 @@ function buildTools() {
|
|
|
556
580
|
{
|
|
557
581
|
name: 'session_close',
|
|
558
582
|
description:
|
|
559
|
-
|
|
583
|
+
`Save a ${PROFILE.displayName} session close summary before ending a conversation. The summary is stored as a short-term memory with 7-day retention, providing context for the next session boot. Include what was accomplished, what is still pending, and any important context for continuity.`,
|
|
560
584
|
inputSchema: {
|
|
561
585
|
type: 'object',
|
|
562
586
|
properties: {
|
|
@@ -581,24 +605,24 @@ function buildTools() {
|
|
|
581
605
|
},
|
|
582
606
|
{
|
|
583
607
|
name: 'dream_ingest',
|
|
584
|
-
description: `
|
|
608
|
+
description: `Ingest a Hermes digest (periodic summary report) into ${DB_PROFILE}.marsvault_chunks long-term memory. The content is automatically chunked into semantically meaningful segments and stored with vector embeddings for future recall. Use this for scheduled reports, daily digests, or any structured summary content that should be permanently available.`,
|
|
585
609
|
inputSchema: {
|
|
586
610
|
type: 'object',
|
|
587
611
|
properties: {
|
|
588
|
-
content: { type: 'string', description: '
|
|
589
|
-
source_file: { type: 'string', description: 'Logical
|
|
590
|
-
section: { type: 'string', description: '
|
|
612
|
+
content: { type: 'string', description: 'The full text content of the digest to ingest. Will be automatically split into semantically coherent chunks.' },
|
|
613
|
+
source_file: { type: 'string', description: 'Logical file path or identifier for the source of this digest (e.g. "hermes/daily/2026-06-10")' },
|
|
614
|
+
section: { type: 'string', description: 'Optional section label prefix to tag all resulting chunks' },
|
|
591
615
|
tags: {
|
|
592
616
|
type: 'array',
|
|
593
617
|
items: { type: 'string' },
|
|
594
|
-
description: '
|
|
618
|
+
description: 'Tags for categorization (e.g. ["digest", "daily", "report"])'
|
|
595
619
|
},
|
|
596
|
-
type: { type: 'string', description: 'Chunk type', default: 'digest' },
|
|
597
|
-
date: { type: 'string', description: '
|
|
598
|
-
body: { type: 'string', enum: PROFILE.recallBodyEnum, default: DB_PROFILE },
|
|
599
|
-
visibility: { type: 'string', enum: ['private', 'shared', 'global'], default: 'private' },
|
|
600
|
-
origin: { type: 'string', description: 'Origin marker', default: PROFILE.digestDefaultOrigin },
|
|
601
|
-
max_chunk_chars: { type: 'number', minimum: 300, maximum: 3000, default: 1200 }
|
|
620
|
+
type: { type: 'string', description: 'Chunk type label', default: 'digest' },
|
|
621
|
+
date: { type: 'string', description: 'Date for this digest in YYYY-MM-DD format. Defaults to today if omitted.' },
|
|
622
|
+
body: { type: 'string', enum: PROFILE.recallBodyEnum, default: DB_PROFILE, description: 'Target persona profile for storage' },
|
|
623
|
+
visibility: { type: 'string', enum: ['private', 'shared', 'global'], default: 'private', description: 'Access level: "private" = owner only, "shared" = cross-profile, "global" = system-wide' },
|
|
624
|
+
origin: { type: 'string', description: 'Origin marker identifying where this content came from', default: PROFILE.digestDefaultOrigin },
|
|
625
|
+
max_chunk_chars: { type: 'number', minimum: 300, maximum: 3000, default: 1200, description: 'Maximum character length per chunk (default 1200). Larger values = fewer, longer chunks.' }
|
|
602
626
|
},
|
|
603
627
|
required: ['content'],
|
|
604
628
|
additionalProperties: false
|
|
@@ -606,48 +630,48 @@ function buildTools() {
|
|
|
606
630
|
},
|
|
607
631
|
{
|
|
608
632
|
name: PROFILE.memoryIngestToolName,
|
|
609
|
-
description: `
|
|
633
|
+
description: `Promote content into permanent long-term memory (${DB_PROFILE}.marsvault_chunks). Automatically chunks the input text, generates vector embeddings, and stores each segment for semantic recall. Use this to preserve important insights, decisions, patterns, or knowledge that should survive beyond the current session. This is the primary path from ephemeral to permanent memory.`,
|
|
610
634
|
inputSchema: {
|
|
611
635
|
type: 'object',
|
|
612
636
|
properties: {
|
|
613
|
-
content: { type: 'string', description: '
|
|
614
|
-
source_file: { type: 'string', description: 'Logical
|
|
615
|
-
section: { type: 'string', description: '
|
|
637
|
+
content: { type: 'string', description: 'The insight content to promote to long-term memory. Be specific and self-contained — future recall depends on the quality of this text.' },
|
|
638
|
+
source_file: { type: 'string', description: 'Logical file path or identifier for the source (e.g. "sessions/2026-06-10-session-notes")' },
|
|
639
|
+
section: { type: 'string', description: 'Optional section label prefix to organize chunks within the source' },
|
|
616
640
|
tags: {
|
|
617
641
|
type: 'array',
|
|
618
642
|
items: { type: 'string' },
|
|
619
|
-
description: '
|
|
643
|
+
description: 'Categorization tags (e.g. ["decision", "architecture"])'
|
|
620
644
|
},
|
|
621
|
-
type: { type: 'string', description: '
|
|
622
|
-
date: { type: 'string', description: '
|
|
623
|
-
visibility: { type: 'string', enum: ['private', 'shared', 'global'], default: 'private' },
|
|
645
|
+
type: { type: 'string', description: 'Content type label (e.g. "insight", "observation", "decision")', default: 'insight' },
|
|
646
|
+
date: { type: 'string', description: 'Date for this content in YYYY-MM-DD format. Defaults to today if omitted.' },
|
|
647
|
+
visibility: { type: 'string', enum: ['private', 'shared', 'global'], default: 'private', description: 'Access level: "private" = this profile only, "shared" = cross-profile readable, "global" = system-wide' },
|
|
624
648
|
origin: buildMemoryIngestOriginSchema(),
|
|
625
649
|
source_memory_id: {
|
|
626
650
|
type: 'string',
|
|
627
|
-
description: '
|
|
651
|
+
description: 'Link this promotion to a specific short-term memory ID (for traceability)'
|
|
628
652
|
},
|
|
629
653
|
source_session_id: {
|
|
630
654
|
type: 'string',
|
|
631
|
-
description: '
|
|
655
|
+
description: 'Session ID where this insight originated (for provenance tracking)'
|
|
632
656
|
},
|
|
633
657
|
source_tool: {
|
|
634
658
|
type: 'string',
|
|
635
659
|
enum: PROFILE.sourceWhitelist,
|
|
636
|
-
description: '
|
|
660
|
+
description: 'The tool/platform where this insight was originally captured'
|
|
637
661
|
},
|
|
638
662
|
source_user_note: {
|
|
639
663
|
type: 'string',
|
|
640
|
-
description: '
|
|
664
|
+
description: 'Brief note explaining why this memory was selected for promotion'
|
|
641
665
|
},
|
|
642
666
|
agent_body: {
|
|
643
667
|
type: 'string',
|
|
644
|
-
description: '
|
|
668
|
+
description: 'The persona/body this memory belongs to (e.g. "coco", "toto")'
|
|
645
669
|
},
|
|
646
670
|
environment: {
|
|
647
671
|
type: 'string',
|
|
648
|
-
description: '
|
|
672
|
+
description: 'Environment label (e.g. "production", "staging")'
|
|
649
673
|
},
|
|
650
|
-
max_chunk_chars: { type: 'number', minimum: 300, maximum: 3000, default: 1200 }
|
|
674
|
+
max_chunk_chars: { type: 'number', minimum: 300, maximum: 3000, default: 1200, description: 'Maximum characters per chunk (default 1200). Adjust for finer or coarser granularity.' }
|
|
651
675
|
},
|
|
652
676
|
required: ['content'],
|
|
653
677
|
additionalProperties: false
|
|
@@ -655,19 +679,19 @@ function buildTools() {
|
|
|
655
679
|
},
|
|
656
680
|
{
|
|
657
681
|
name: 'demote_memory',
|
|
658
|
-
description: `
|
|
682
|
+
description: `Deprecate a long-term memory chunk in ${DB_PROFILE}.marsvault_chunks, marking it as outdated or superseded. The chunk is not deleted — it is flagged so it no longer appears in recall results. Use this when information becomes incorrect, irrelevant, or is replaced by newer knowledge. Optionally link to the replacement chunk for traceability.`,
|
|
659
683
|
inputSchema: {
|
|
660
684
|
type: 'object',
|
|
661
685
|
properties: {
|
|
662
|
-
id: { type: 'string', description: '
|
|
663
|
-
deprecated_reason: { type: 'string', description: '
|
|
686
|
+
id: { type: 'string', description: 'UUID of the long-term memory chunk to deprecate' },
|
|
687
|
+
deprecated_reason: { type: 'string', description: 'Why this memory is being deprecated (e.g. "superseded by newer architecture decision", "information confirmed incorrect")' },
|
|
664
688
|
superseded_by: {
|
|
665
689
|
type: 'string',
|
|
666
|
-
description: '
|
|
690
|
+
description: 'UUID of the replacement chunk, if this memory is being superseded by updated content'
|
|
667
691
|
},
|
|
668
692
|
deprecated_at: {
|
|
669
693
|
type: 'string',
|
|
670
|
-
description: '
|
|
694
|
+
description: 'Custom deprecation timestamp in ISO 8601 format. Defaults to now if omitted.'
|
|
671
695
|
}
|
|
672
696
|
},
|
|
673
697
|
required: ['id', 'deprecated_reason'],
|
|
@@ -676,7 +700,7 @@ function buildTools() {
|
|
|
676
700
|
},
|
|
677
701
|
{
|
|
678
702
|
name: 'soft_forget',
|
|
679
|
-
description: `Expire short-
|
|
703
|
+
description: `Expire short-term memories in ${DB_PROFILE}.memories early without permanently deleting them. Marked memories become invisible to search and list operations but remain in the database for audit purposes. Use this to clean up irrelevant or incorrect short-term memories before their natural expiration.`,
|
|
680
704
|
inputSchema: {
|
|
681
705
|
type: 'object',
|
|
682
706
|
properties: {
|
|
@@ -685,15 +709,15 @@ function buildTools() {
|
|
|
685
709
|
items: { type: 'string' },
|
|
686
710
|
minItems: 1,
|
|
687
711
|
maxItems: 50,
|
|
688
|
-
description: '
|
|
712
|
+
description: 'Array of short-term memory UUIDs to expire. Up to 50 at a time.'
|
|
689
713
|
},
|
|
690
714
|
reason: {
|
|
691
715
|
type: 'string',
|
|
692
|
-
description: '
|
|
716
|
+
description: 'Brief explanation of why these memories are being forgotten (for audit trail)'
|
|
693
717
|
},
|
|
694
718
|
forgotten_at: {
|
|
695
719
|
type: 'string',
|
|
696
|
-
description: '
|
|
720
|
+
description: 'Custom expiration timestamp in ISO 8601 format. Defaults to now if omitted.'
|
|
697
721
|
}
|
|
698
722
|
},
|
|
699
723
|
required: ['ids'],
|
|
@@ -702,20 +726,61 @@ function buildTools() {
|
|
|
702
726
|
},
|
|
703
727
|
{
|
|
704
728
|
name: 'explain_memory',
|
|
705
|
-
description: `
|
|
729
|
+
description: `Trace the full provenance of a long-term memory chunk in ${DB_PROFILE}.marsvault_chunks — where it came from, how it was created, and what source material it was derived from. Returns the original source memory, session, tool, and timeline information. Use this to understand why a piece of knowledge exists or to verify its reliability.`,
|
|
706
730
|
inputSchema: {
|
|
707
731
|
type: 'object',
|
|
708
732
|
properties: {
|
|
709
|
-
id: { type: 'string', description: '
|
|
733
|
+
id: { type: 'string', description: 'UUID of the long-term memory chunk to explain' }
|
|
710
734
|
},
|
|
711
735
|
required: ['id'],
|
|
712
736
|
additionalProperties: false
|
|
713
737
|
}
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
name: 'batch_promote',
|
|
741
|
+
description: `Automatically promote expiring short-term memories from ${DB_PROFILE}.memories to permanent long-term storage (${DB_PROFILE}.marsvault_chunks). Scans for memories that will expire within the alert window, then chunks and ingests each one with vector embeddings. Use this to prevent valuable context from being lost when short-term memories expire. Supports dry-run mode to preview candidates before committing.`,
|
|
742
|
+
inputSchema: {
|
|
743
|
+
type: 'object',
|
|
744
|
+
properties: {
|
|
745
|
+
alert_window_hours: {
|
|
746
|
+
type: 'number',
|
|
747
|
+
minimum: 1,
|
|
748
|
+
maximum: 720,
|
|
749
|
+
default: 48,
|
|
750
|
+
description: 'Look-ahead window in hours. Memories expiring within this window are candidates for promotion (default 48).'
|
|
751
|
+
},
|
|
752
|
+
max_promote: {
|
|
753
|
+
type: 'number',
|
|
754
|
+
minimum: 1,
|
|
755
|
+
maximum: 50,
|
|
756
|
+
default: 10,
|
|
757
|
+
description: 'Maximum number of memories to promote in a single batch (default 10).'
|
|
758
|
+
},
|
|
759
|
+
dry_run: {
|
|
760
|
+
type: 'boolean',
|
|
761
|
+
default: false,
|
|
762
|
+
description: 'When true, list candidate memories without actually promoting them. Use to preview before committing.'
|
|
763
|
+
},
|
|
764
|
+
memory_ids: {
|
|
765
|
+
type: 'array',
|
|
766
|
+
items: { type: 'string' },
|
|
767
|
+
maxItems: 50,
|
|
768
|
+
description: 'Explicit list of short-term memory UUIDs to promote. When provided, auto-detection is skipped and only these IDs are processed.'
|
|
769
|
+
},
|
|
770
|
+
origin: {
|
|
771
|
+
type: 'string',
|
|
772
|
+
description: 'Origin marker tagged on all promoted chunks for traceability',
|
|
773
|
+
default: 'batch-promote'
|
|
774
|
+
}
|
|
775
|
+
},
|
|
776
|
+
additionalProperties: false
|
|
777
|
+
}
|
|
714
778
|
}
|
|
715
779
|
];
|
|
716
780
|
}
|
|
717
781
|
|
|
718
782
|
const TOOLS = buildTools();
|
|
783
|
+
const TOOL_BY_NAME = new Map(TOOLS.map((tool) => [tool.name, tool]));
|
|
719
784
|
|
|
720
785
|
const OAUTH_CLIENTS = new Map();
|
|
721
786
|
const OAUTH_CODES = new Map();
|
|
@@ -876,7 +941,15 @@ function getServiceKey() {
|
|
|
876
941
|
);
|
|
877
942
|
}
|
|
878
943
|
|
|
879
|
-
const SERVICE_KEY = getServiceKey();
|
|
944
|
+
const SERVICE_KEY = SUPABASE_AUTH_MODE === 'anon' ? '' : getServiceKey();
|
|
945
|
+
if (
|
|
946
|
+
(SUPABASE_AUTH_MODE === 'anon' || SUPABASE_AUTH_MODE === 'hybrid') &&
|
|
947
|
+
!SUPABASE_ANON_KEY
|
|
948
|
+
) {
|
|
949
|
+
throw new Error(
|
|
950
|
+
'SUPABASE_ANON_KEY is required when MCP_SUPABASE_AUTH_MODE is anon or hybrid'
|
|
951
|
+
);
|
|
952
|
+
}
|
|
880
953
|
loadPersistedOauthClients();
|
|
881
954
|
|
|
882
955
|
if (STATIC_CLIENT_ID && STATIC_CLIENT_SECRET && OAUTH_ENABLED) {
|
|
@@ -935,10 +1008,67 @@ async function readRequestBody(req) {
|
|
|
935
1008
|
}
|
|
936
1009
|
}
|
|
937
1010
|
|
|
1011
|
+
function resolveSupabaseRequestAuthMode() {
|
|
1012
|
+
const storeMode = String(SUPABASE_REQUEST_CONTEXT.getStore()?.db_auth_mode || '')
|
|
1013
|
+
.trim()
|
|
1014
|
+
.toLowerCase();
|
|
1015
|
+
if (SUPABASE_AUTH_MODE_VALUES.has(storeMode)) {
|
|
1016
|
+
return storeMode;
|
|
1017
|
+
}
|
|
1018
|
+
if (SUPABASE_AUTH_MODE === 'anon') {
|
|
1019
|
+
return 'anon';
|
|
1020
|
+
}
|
|
1021
|
+
return 'service';
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
function isToolAllowedInAnonMode(toolName) {
|
|
1025
|
+
return ANON_ALLOWED_TOOL_NAMES.has(String(toolName || '').trim());
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
function assertToolCapabilityForAuthMode(toolName) {
|
|
1029
|
+
if (SUPABASE_AUTH_MODE !== 'anon') return;
|
|
1030
|
+
if (isToolAllowedInAnonMode(toolName)) return;
|
|
1031
|
+
throw new Error(
|
|
1032
|
+
`capability_not_available_in_anon_mode: ${toolName} is disabled; use MCP_SUPABASE_AUTH_MODE=service or hybrid`
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function resolveDbAuthModeForTool(toolName) {
|
|
1037
|
+
if (SUPABASE_AUTH_MODE === 'anon') return 'anon';
|
|
1038
|
+
if (SUPABASE_AUTH_MODE === 'service') return 'service';
|
|
1039
|
+
return isToolAllowedInAnonMode(toolName) ? 'anon' : 'service';
|
|
1040
|
+
}
|
|
1041
|
+
function listAvailableToolNamesForAuthMode(authMode) {
|
|
1042
|
+
if (authMode === 'anon') {
|
|
1043
|
+
return TOOLS
|
|
1044
|
+
.map((tool) => String(tool?.name || '').trim())
|
|
1045
|
+
.filter((name) => name && isToolAllowedInAnonMode(name));
|
|
1046
|
+
}
|
|
1047
|
+
return TOOLS.map((tool) => String(tool?.name || '').trim()).filter(Boolean);
|
|
1048
|
+
}
|
|
1049
|
+
function listAvailableToolsForAuthMode(authMode) {
|
|
1050
|
+
const names = listAvailableToolNamesForAuthMode(authMode);
|
|
1051
|
+
return names
|
|
1052
|
+
.map((name) => TOOL_BY_NAME.get(name))
|
|
1053
|
+
.filter((tool) => Boolean(tool));
|
|
1054
|
+
}
|
|
1055
|
+
function buildSupabaseCapabilityPayload() {
|
|
1056
|
+
return {
|
|
1057
|
+
mode: SUPABASE_AUTH_MODE,
|
|
1058
|
+
anon_allowed_tools: listAvailableToolNamesForAuthMode('anon'),
|
|
1059
|
+
effective_tools: listAvailableToolNamesForAuthMode(SUPABASE_AUTH_MODE)
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
|
|
938
1063
|
async function supabaseRequest(path, options = {}) {
|
|
1064
|
+
const requestAuthMode = resolveSupabaseRequestAuthMode();
|
|
1065
|
+
const requestApiKey = requestAuthMode === 'anon' ? SUPABASE_ANON_KEY : SERVICE_KEY;
|
|
1066
|
+
if (!requestApiKey) {
|
|
1067
|
+
throw new Error(`Supabase API key missing for auth mode: ${requestAuthMode}`);
|
|
1068
|
+
}
|
|
939
1069
|
const headers = {
|
|
940
|
-
apikey:
|
|
941
|
-
Authorization: `Bearer ${
|
|
1070
|
+
apikey: requestApiKey,
|
|
1071
|
+
Authorization: `Bearer ${requestApiKey}`,
|
|
942
1072
|
'content-type': 'application/json'
|
|
943
1073
|
};
|
|
944
1074
|
if (options.profile) {
|
|
@@ -1455,7 +1585,10 @@ async function writeToolUsageTelemetry(payload = {}) {
|
|
|
1455
1585
|
new Date().toISOString();
|
|
1456
1586
|
const agentBody =
|
|
1457
1587
|
normalizeOptionalAgentBody(payload.agent_body) || DB_PROFILE;
|
|
1458
|
-
|
|
1588
|
+
if (SUPABASE_AUTH_MODE === 'anon') {
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
const writeTelemetry = async () => {
|
|
1459
1592
|
await supabaseRequest(
|
|
1460
1593
|
`/rest/v1/memory_tool_usage?select=${TOOL_USAGE_INSERT_SELECT_COLUMNS}`,
|
|
1461
1594
|
{
|
|
@@ -1473,6 +1606,13 @@ async function writeToolUsageTelemetry(payload = {}) {
|
|
|
1473
1606
|
]
|
|
1474
1607
|
}
|
|
1475
1608
|
);
|
|
1609
|
+
};
|
|
1610
|
+
try {
|
|
1611
|
+
if (SUPABASE_AUTH_MODE === 'hybrid') {
|
|
1612
|
+
await SUPABASE_REQUEST_CONTEXT.run({ db_auth_mode: 'service' }, writeTelemetry);
|
|
1613
|
+
} else {
|
|
1614
|
+
await writeTelemetry();
|
|
1615
|
+
}
|
|
1476
1616
|
} catch (error) {
|
|
1477
1617
|
console.warn(
|
|
1478
1618
|
`[telemetry] failed to persist memory_tool_usage: ${String(error?.message || error)}`
|
|
@@ -2717,6 +2857,156 @@ async function runHealthExpiryCheck(args = {}) {
|
|
|
2717
2857
|
return payload;
|
|
2718
2858
|
}
|
|
2719
2859
|
|
|
2860
|
+
async function runBatchPromote(args = {}) {
|
|
2861
|
+
const alertWindowHours = clampInteger(args.alert_window_hours, 1, 720, 48);
|
|
2862
|
+
const maxPromote = clampInteger(args.max_promote, 1, 50, 10);
|
|
2863
|
+
const dryRun = Boolean(args.dry_run);
|
|
2864
|
+
const origin = String(args.origin || 'batch-promote').trim() || 'batch-promote';
|
|
2865
|
+
const explicitIds = Array.isArray(args.memory_ids)
|
|
2866
|
+
? args.memory_ids
|
|
2867
|
+
.map((id) => normalizeOptionalUuid(id, 'memory_ids'))
|
|
2868
|
+
.filter(Boolean)
|
|
2869
|
+
: [];
|
|
2870
|
+
|
|
2871
|
+
const candidates = [];
|
|
2872
|
+
|
|
2873
|
+
if (explicitIds.length > 0) {
|
|
2874
|
+
for (const memoryId of explicitIds.slice(0, maxPromote)) {
|
|
2875
|
+
const memory = await fetchMemoryById(memoryId);
|
|
2876
|
+
if (!memory) {
|
|
2877
|
+
candidates.push({ id: memoryId, status: 'not_found', skipped: true });
|
|
2878
|
+
continue;
|
|
2879
|
+
}
|
|
2880
|
+
if (memory.promoted || memory.promoted_at) {
|
|
2881
|
+
candidates.push({
|
|
2882
|
+
id: memoryId,
|
|
2883
|
+
status: 'already_promoted',
|
|
2884
|
+
skipped: true,
|
|
2885
|
+
excerpt: String(memory.body || '').slice(0, 140)
|
|
2886
|
+
});
|
|
2887
|
+
continue;
|
|
2888
|
+
}
|
|
2889
|
+
candidates.push({
|
|
2890
|
+
id: memoryId,
|
|
2891
|
+
status: 'eligible',
|
|
2892
|
+
skipped: false,
|
|
2893
|
+
excerpt: String(memory.body || '').slice(0, 140),
|
|
2894
|
+
tags: normalizeTags(memory.tags),
|
|
2895
|
+
expires_at: memory.expires_at || null,
|
|
2896
|
+
memory
|
|
2897
|
+
});
|
|
2898
|
+
}
|
|
2899
|
+
} else {
|
|
2900
|
+
const healthResult = await runHealthExpiryCheck({
|
|
2901
|
+
alert_window_hours: alertWindowHours
|
|
2902
|
+
});
|
|
2903
|
+
const soonExpiring = healthResult?.expiry_alert?.soon_expiring || [];
|
|
2904
|
+
const recommended = soonExpiring
|
|
2905
|
+
.filter((item) => item.recommend_promote === 'Y')
|
|
2906
|
+
.slice(0, maxPromote);
|
|
2907
|
+
|
|
2908
|
+
for (const item of recommended) {
|
|
2909
|
+
const memory = await fetchMemoryById(item.id);
|
|
2910
|
+
if (!memory) {
|
|
2911
|
+
candidates.push({ id: item.id, status: 'not_found', skipped: true });
|
|
2912
|
+
continue;
|
|
2913
|
+
}
|
|
2914
|
+
candidates.push({
|
|
2915
|
+
id: item.id,
|
|
2916
|
+
status: 'eligible',
|
|
2917
|
+
skipped: false,
|
|
2918
|
+
excerpt: item.excerpt || String(memory.body || '').slice(0, 140),
|
|
2919
|
+
tags: item.tags || normalizeTags(memory.tags),
|
|
2920
|
+
expires_at: item.expires_at || memory.expires_at || null,
|
|
2921
|
+
recommendation_reason: item.recommendation_reason || null,
|
|
2922
|
+
memory
|
|
2923
|
+
});
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2927
|
+
const eligible = candidates.filter((c) => !c.skipped);
|
|
2928
|
+
|
|
2929
|
+
if (dryRun) {
|
|
2930
|
+
return {
|
|
2931
|
+
ok: true,
|
|
2932
|
+
dry_run: true,
|
|
2933
|
+
profile: DB_PROFILE,
|
|
2934
|
+
origin,
|
|
2935
|
+
alert_window_hours: alertWindowHours,
|
|
2936
|
+
max_promote: maxPromote,
|
|
2937
|
+
total_candidates: candidates.length,
|
|
2938
|
+
eligible_count: eligible.length,
|
|
2939
|
+
skipped_count: candidates.length - eligible.length,
|
|
2940
|
+
candidates: candidates.map(({ memory, ...rest }) => rest)
|
|
2941
|
+
};
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
const promoted = [];
|
|
2945
|
+
const errors = [];
|
|
2946
|
+
|
|
2947
|
+
for (const candidate of eligible) {
|
|
2948
|
+
try {
|
|
2949
|
+
const memory = candidate.memory;
|
|
2950
|
+
const content = String(memory.body || '').trim();
|
|
2951
|
+
if (!content) {
|
|
2952
|
+
errors.push({ id: candidate.id, error: 'empty memory body' });
|
|
2953
|
+
continue;
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
const ingestResult = await ingestMarsvaultChunks(
|
|
2957
|
+
{
|
|
2958
|
+
content,
|
|
2959
|
+
tags: normalizeTags(memory.tags),
|
|
2960
|
+
source_memory_id: memory.id,
|
|
2961
|
+
source_session_id: memory.session_id || null,
|
|
2962
|
+
source_tool: memory.source || null,
|
|
2963
|
+
origin
|
|
2964
|
+
},
|
|
2965
|
+
{
|
|
2966
|
+
defaultType: 'insight',
|
|
2967
|
+
defaultSourceFile: PROFILE.memoryIngestDefaultSourceFile,
|
|
2968
|
+
defaultSectionPrefix: 'batch-promote',
|
|
2969
|
+
defaultOrigin: origin,
|
|
2970
|
+
body: DB_PROFILE,
|
|
2971
|
+
fixedTags: [...PROFILE.memoryIngestFixedTags, 'batch-promote']
|
|
2972
|
+
}
|
|
2973
|
+
);
|
|
2974
|
+
|
|
2975
|
+
const promotedMemory = await markMemoryAsPromoted(memory.id);
|
|
2976
|
+
|
|
2977
|
+
promoted.push({
|
|
2978
|
+
id: memory.id,
|
|
2979
|
+
chunk_count: ingestResult.chunk_count || 0,
|
|
2980
|
+
promoted_at: promotedMemory?.promoted_at || null,
|
|
2981
|
+
excerpt: String(content).slice(0, 140)
|
|
2982
|
+
});
|
|
2983
|
+
} catch (error) {
|
|
2984
|
+
errors.push({
|
|
2985
|
+
id: candidate.id,
|
|
2986
|
+
error: normalizeOptionalText(error?.message || String(error), 280)
|
|
2987
|
+
});
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
return {
|
|
2992
|
+
ok: true,
|
|
2993
|
+
dry_run: false,
|
|
2994
|
+
profile: DB_PROFILE,
|
|
2995
|
+
origin,
|
|
2996
|
+
alert_window_hours: alertWindowHours,
|
|
2997
|
+
max_promote: maxPromote,
|
|
2998
|
+
total_candidates: candidates.length,
|
|
2999
|
+
promoted_count: promoted.length,
|
|
3000
|
+
error_count: errors.length,
|
|
3001
|
+
skipped_count: candidates.length - eligible.length,
|
|
3002
|
+
promoted,
|
|
3003
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
3004
|
+
skipped: candidates
|
|
3005
|
+
.filter((c) => c.skipped)
|
|
3006
|
+
.map(({ memory, ...rest }) => rest)
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
|
|
2720
3010
|
async function runHealthCheck(args = {}) {
|
|
2721
3011
|
const alertWindowHours = clampInteger(args.alert_window_hours, 1, 720, 48);
|
|
2722
3012
|
const gapDays = clampInteger(args.gap_days, 7, 365, 30);
|
|
@@ -3620,6 +3910,9 @@ async function callTool(name, args = {}) {
|
|
|
3620
3910
|
};
|
|
3621
3911
|
}
|
|
3622
3912
|
|
|
3913
|
+
if (toolName === 'batch_promote') {
|
|
3914
|
+
return await runBatchPromote(args);
|
|
3915
|
+
}
|
|
3623
3916
|
if (toolName === 'health_check') {
|
|
3624
3917
|
return await runHealthCheck(args);
|
|
3625
3918
|
}
|
|
@@ -4237,6 +4530,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
4237
4530
|
embedding_provider: 'jina',
|
|
4238
4531
|
embedding_model: JINA_EMBEDDING_MODEL,
|
|
4239
4532
|
embedding_enabled: Boolean(JINA_API_KEY),
|
|
4533
|
+
supabase_auth_mode: SUPABASE_AUTH_MODE,
|
|
4534
|
+
supabase_capabilities: buildSupabaseCapabilityPayload(),
|
|
4240
4535
|
source_mode: SOURCE_MODE,
|
|
4241
4536
|
memory_sources: MEMORY_SOURCE_LIST.slice(),
|
|
4242
4537
|
extra_sources: EXTRA_SOURCE_LIST.slice(),
|
|
@@ -4318,14 +4613,24 @@ const server = http.createServer(async (req, res) => {
|
|
|
4318
4613
|
}
|
|
4319
4614
|
|
|
4320
4615
|
if (method === 'tools/list') {
|
|
4321
|
-
json(
|
|
4616
|
+
json(
|
|
4617
|
+
res,
|
|
4618
|
+
200,
|
|
4619
|
+
mcpResult(id, { tools: listAvailableToolsForAuthMode(SUPABASE_AUTH_MODE) })
|
|
4620
|
+
);
|
|
4322
4621
|
return;
|
|
4323
4622
|
}
|
|
4324
4623
|
|
|
4325
4624
|
if (method === 'tools/call') {
|
|
4326
|
-
const
|
|
4625
|
+
const requestedToolName = rpc?.params?.name;
|
|
4626
|
+
const toolName = resolveToolName(requestedToolName);
|
|
4627
|
+
assertToolCapabilityForAuthMode(toolName);
|
|
4628
|
+
const dbAuthMode = resolveDbAuthModeForTool(toolName);
|
|
4327
4629
|
const args = rpc?.params?.arguments ?? {};
|
|
4328
|
-
const result = await
|
|
4630
|
+
const result = await SUPABASE_REQUEST_CONTEXT.run(
|
|
4631
|
+
{ db_auth_mode: dbAuthMode },
|
|
4632
|
+
async () => await callTool(toolName, args)
|
|
4633
|
+
);
|
|
4329
4634
|
json(
|
|
4330
4635
|
res,
|
|
4331
4636
|
200,
|