mem-brain-mcp 1.0.0__py3-none-any.whl → 1.0.2__py3-none-any.whl
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.
- mem_brain_mcp/__init__.py +1 -1
- mem_brain_mcp/config.py +7 -2
- mem_brain_mcp/server.py +741 -192
- {mem_brain_mcp-1.0.0.dist-info → mem_brain_mcp-1.0.2.dist-info}/METADATA +50 -18
- mem_brain_mcp-1.0.2.dist-info/RECORD +9 -0
- mem_brain_mcp-1.0.0.dist-info/RECORD +0 -9
- {mem_brain_mcp-1.0.0.dist-info → mem_brain_mcp-1.0.2.dist-info}/WHEEL +0 -0
- {mem_brain_mcp-1.0.0.dist-info → mem_brain_mcp-1.0.2.dist-info}/entry_points.txt +0 -0
mem_brain_mcp/__init__.py
CHANGED
mem_brain_mcp/config.py
CHANGED
|
@@ -16,11 +16,16 @@ class Settings(BaseSettings):
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
# API Configuration
|
|
19
|
-
api_base_url: str = "http://
|
|
20
|
-
|
|
19
|
+
api_base_url: str = "http://membrain-api-alb-1094729422.ap-south-1.elb.amazonaws.com"
|
|
20
|
+
membrain_api_key: Optional[str] = None
|
|
21
21
|
# NOTE: default_user_id is deprecated and unused.
|
|
22
22
|
# Per-user API keys are extracted from request headers for proper isolation.
|
|
23
23
|
# Each MCP client should configure their own API key via headers.
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def api_key(self) -> Optional[str]:
|
|
27
|
+
"""Backward compatibility property for api_key."""
|
|
28
|
+
return self.membrain_api_key
|
|
24
29
|
|
|
25
30
|
# MCP Server Configuration
|
|
26
31
|
mcp_server_host: str = "0.0.0.0"
|
mem_brain_mcp/server.py
CHANGED
|
@@ -380,112 +380,6 @@ async def refresh_context() -> PromptMessage:
|
|
|
380
380
|
# TOOLS (Operations)
|
|
381
381
|
# ============================================================================
|
|
382
382
|
|
|
383
|
-
@mcp.tool()
|
|
384
|
-
async def login(email: str, password: str) -> str:
|
|
385
|
-
"""Login to mem-brain API and get JWT token. Store the token for subsequent requests.
|
|
386
|
-
|
|
387
|
-
Args:
|
|
388
|
-
email: User email address
|
|
389
|
-
password: User password
|
|
390
|
-
|
|
391
|
-
Returns:
|
|
392
|
-
Success message with instructions for using the token
|
|
393
|
-
"""
|
|
394
|
-
try:
|
|
395
|
-
import httpx
|
|
396
|
-
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
397
|
-
response = await client.post(
|
|
398
|
-
f"{settings.api_base_url}/api/v1/auth/login",
|
|
399
|
-
json={"email": email, "password": password},
|
|
400
|
-
headers={"Content-Type": "application/json"}
|
|
401
|
-
)
|
|
402
|
-
response.raise_for_status()
|
|
403
|
-
data = response.json()
|
|
404
|
-
|
|
405
|
-
access_token = data.get("access_token")
|
|
406
|
-
refresh_token = data.get("refresh_token")
|
|
407
|
-
|
|
408
|
-
if access_token:
|
|
409
|
-
# Store tokens in context (in a real implementation, you'd use FastMCP context)
|
|
410
|
-
logger.info(f"Login successful for {email}")
|
|
411
|
-
return f"""Login successful!
|
|
412
|
-
|
|
413
|
-
Access Token: {access_token[:50]}...
|
|
414
|
-
|
|
415
|
-
Use this token in the Authorization header for subsequent requests:
|
|
416
|
-
Authorization: Bearer {access_token}
|
|
417
|
-
|
|
418
|
-
The token will be automatically used for all memory operations.
|
|
419
|
-
Refresh token saved for automatic token renewal."""
|
|
420
|
-
else:
|
|
421
|
-
raise ToolError("Login failed: No token received")
|
|
422
|
-
except httpx.HTTPStatusError as e:
|
|
423
|
-
if e.response.status_code == 401:
|
|
424
|
-
raise ToolError("Login failed: Invalid email or password")
|
|
425
|
-
logger.error(f"Login error: {e.response.status_code} - {e.response.text}")
|
|
426
|
-
raise ToolError(f"Login failed: {e.response.status_code}")
|
|
427
|
-
except Exception as e:
|
|
428
|
-
logger.error(f"Unexpected login error: {e}", exc_info=True)
|
|
429
|
-
raise ToolError(f"Login failed: {str(e)}")
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
@mcp.tool()
|
|
433
|
-
async def signup(email: str, password: str, full_name: str, organization_name: str) -> str:
|
|
434
|
-
"""Sign up for a new mem-brain account and organization.
|
|
435
|
-
|
|
436
|
-
Args:
|
|
437
|
-
email: User email address
|
|
438
|
-
password: User password (min 8 chars, must contain letters and numbers)
|
|
439
|
-
full_name: User's full name
|
|
440
|
-
organization_name: Name for your organization
|
|
441
|
-
|
|
442
|
-
Returns:
|
|
443
|
-
Success message with access token
|
|
444
|
-
"""
|
|
445
|
-
try:
|
|
446
|
-
import httpx
|
|
447
|
-
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
448
|
-
response = await client.post(
|
|
449
|
-
f"{settings.api_base_url}/api/v1/auth/signup",
|
|
450
|
-
json={
|
|
451
|
-
"email": email,
|
|
452
|
-
"password": password,
|
|
453
|
-
"full_name": full_name,
|
|
454
|
-
"organization_name": organization_name
|
|
455
|
-
},
|
|
456
|
-
headers={"Content-Type": "application/json"}
|
|
457
|
-
)
|
|
458
|
-
response.raise_for_status()
|
|
459
|
-
data = response.json()
|
|
460
|
-
|
|
461
|
-
access_token = data.get("access_token")
|
|
462
|
-
|
|
463
|
-
if access_token:
|
|
464
|
-
logger.info(f"Signup successful for {email}")
|
|
465
|
-
return f"""Account created successfully!
|
|
466
|
-
|
|
467
|
-
Organization: {organization_name}
|
|
468
|
-
Email: {email}
|
|
469
|
-
|
|
470
|
-
Access Token: {access_token[:50]}...
|
|
471
|
-
|
|
472
|
-
Use this token in the Authorization header for subsequent requests:
|
|
473
|
-
Authorization: Bearer {access_token}
|
|
474
|
-
|
|
475
|
-
The token will be automatically used for all memory operations."""
|
|
476
|
-
else:
|
|
477
|
-
raise ToolError("Signup failed: No token received")
|
|
478
|
-
except httpx.HTTPStatusError as e:
|
|
479
|
-
error_detail = e.response.text if e.response else "Unknown error"
|
|
480
|
-
logger.error(f"Signup error: {e.response.status_code} - {error_detail}")
|
|
481
|
-
if e.response.status_code == 400:
|
|
482
|
-
raise ToolError(f"Signup failed: {error_detail}")
|
|
483
|
-
raise ToolError(f"Signup failed: {e.response.status_code}")
|
|
484
|
-
except Exception as e:
|
|
485
|
-
logger.error(f"Unexpected signup error: {e}", exc_info=True)
|
|
486
|
-
raise ToolError(f"Signup failed: {str(e)}")
|
|
487
|
-
|
|
488
|
-
|
|
489
383
|
@mcp.tool()
|
|
490
384
|
async def get_agent_instructions(include_dynamic_context: bool = True) -> str:
|
|
491
385
|
"""Get comprehensive system prompt and best practices for using the memory system effectively. This contains the intelligence for smart memory management, search strategies, and agent workflows."""
|
|
@@ -508,34 +402,79 @@ async def add_memory(
|
|
|
508
402
|
IMPORTANT: Before creating a new memory, you MUST first search existing memories using search_memories() to check if a similar memory already exists. This prevents duplicates and helps maintain memory quality. Only create a new memory if no similar memory is found.
|
|
509
403
|
|
|
510
404
|
Parameters:
|
|
511
|
-
content (str,
|
|
512
|
-
|
|
513
|
-
|
|
405
|
+
content (str, REQUIRED): The memory content to store. Must be a non-empty string.
|
|
406
|
+
- Cannot be None, empty string, or whitespace-only
|
|
407
|
+
- Example: "User prefers Python over JavaScript"
|
|
408
|
+
- Example: "User prefers dark mode interfaces"
|
|
409
|
+
|
|
410
|
+
tags (list[str], optional): List of tags to categorize the memory.
|
|
411
|
+
- Can be None (default) or a list of strings
|
|
412
|
+
- Example: ["coding", "preferences"]
|
|
413
|
+
- Example: ["personal", "pets", "animals"]
|
|
414
|
+
- If you pass a single string, it will be converted to a list
|
|
415
|
+
|
|
416
|
+
category (str, optional): Category name for the memory.
|
|
417
|
+
- Can be None (default) or a non-empty string
|
|
418
|
+
- Example: "interests"
|
|
419
|
+
- Example: "preferences"
|
|
514
420
|
|
|
515
421
|
Returns:
|
|
516
422
|
str: A formatted string with the memory ID and details of the created memory.
|
|
517
423
|
|
|
424
|
+
Common Errors and Solutions:
|
|
425
|
+
- Error: "Tool call arguments for mcp were invalid"
|
|
426
|
+
Solution: Ensure 'content' parameter is provided as a string. Example: add_memory(content="User prefers dark mode")
|
|
427
|
+
|
|
428
|
+
- Error: "The 'content' parameter cannot be empty"
|
|
429
|
+
Solution: Provide non-empty content. Example: add_memory(content="User loves Python programming")
|
|
430
|
+
|
|
431
|
+
- Error: "tags must be a list"
|
|
432
|
+
Solution: Pass tags as a list. Example: add_memory(content="...", tags=["coding"]) not tags="coding"
|
|
433
|
+
|
|
518
434
|
Example workflow:
|
|
519
435
|
1. search_memories(query="User prefers Python") # Check for existing memories
|
|
520
436
|
2. If no similar memory found, then: add_memory(content="User prefers Python over JavaScript", tags=["coding", "preferences"])
|
|
521
437
|
|
|
522
|
-
|
|
438
|
+
Examples:
|
|
439
|
+
# Basic usage (required parameter only)
|
|
440
|
+
add_memory(content="User prefers dark mode")
|
|
441
|
+
|
|
442
|
+
# With tags
|
|
443
|
+
add_memory(content="User loves Python programming", tags=["coding", "preferences"])
|
|
444
|
+
|
|
445
|
+
# With tags and category
|
|
523
446
|
add_memory(
|
|
524
447
|
content="User loves working with TypeScript",
|
|
525
448
|
tags=["coding", "typescript"],
|
|
526
449
|
category="interests"
|
|
527
450
|
)
|
|
451
|
+
|
|
452
|
+
# Tags as empty list (treated as None)
|
|
453
|
+
add_memory(content="User prefers coffee", tags=[])
|
|
528
454
|
"""
|
|
529
455
|
# Validate parameters with detailed error messages
|
|
530
456
|
if content is None:
|
|
531
|
-
raise ToolError(
|
|
457
|
+
raise ToolError(
|
|
458
|
+
"The 'content' parameter is required but was not provided.\n"
|
|
459
|
+
"Example: add_memory(content=\"User prefers dark mode\")\n"
|
|
460
|
+
"Example: add_memory(content=\"User loves Python programming\", tags=[\"coding\"])"
|
|
461
|
+
)
|
|
532
462
|
|
|
533
463
|
if not isinstance(content, str):
|
|
534
|
-
raise ToolError(
|
|
464
|
+
raise ToolError(
|
|
465
|
+
f"The 'content' parameter must be a string, but got {type(content).__name__}.\n"
|
|
466
|
+
f"Received: {repr(content)}\n"
|
|
467
|
+
"Example: add_memory(content=\"User prefers dark mode\")"
|
|
468
|
+
)
|
|
535
469
|
|
|
536
470
|
content_str = str(content).strip()
|
|
537
471
|
if not content_str:
|
|
538
|
-
raise ToolError(
|
|
472
|
+
raise ToolError(
|
|
473
|
+
"The 'content' parameter cannot be empty or whitespace-only.\n"
|
|
474
|
+
"Please provide a non-empty string with actual content.\n"
|
|
475
|
+
"Example: add_memory(content=\"User prefers dark mode\")\n"
|
|
476
|
+
"Example: add_memory(content=\"User loves Python programming\")"
|
|
477
|
+
)
|
|
539
478
|
|
|
540
479
|
try:
|
|
541
480
|
logger.info(f"add_memory called - content length: {len(content_str)}, tags: {tags}, category: {category}")
|
|
@@ -545,16 +484,26 @@ async def add_memory(
|
|
|
545
484
|
normalized_tags = None
|
|
546
485
|
if tags is not None:
|
|
547
486
|
if isinstance(tags, list):
|
|
487
|
+
# Validate list contents are strings
|
|
488
|
+
if tags:
|
|
489
|
+
invalid_items = [item for item in tags if not isinstance(item, str)]
|
|
490
|
+
if invalid_items:
|
|
491
|
+
raise ToolError(
|
|
492
|
+
f"The 'tags' parameter must be a list of strings, but found non-string items: {invalid_items}\n"
|
|
493
|
+
f"Example: add_memory(content=\"...\", tags=[\"coding\", \"preferences\"])\n"
|
|
494
|
+
f"Example: add_memory(content=\"...\", tags=[\"personal\", \"pets\"])"
|
|
495
|
+
)
|
|
548
496
|
normalized_tags = tags if tags else None # Empty list becomes None
|
|
549
497
|
elif isinstance(tags, str):
|
|
550
498
|
# Handle case where tags might be passed as a single string
|
|
551
499
|
normalized_tags = [tags]
|
|
552
500
|
else:
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
501
|
+
raise ToolError(
|
|
502
|
+
f"The 'tags' parameter must be a list of strings or None, but got {type(tags).__name__}.\n"
|
|
503
|
+
f"Received: {repr(tags)}\n"
|
|
504
|
+
"Example: add_memory(content=\"...\", tags=[\"coding\", \"preferences\"])\n"
|
|
505
|
+
"Example: add_memory(content=\"...\", tags=None) # or omit tags parameter"
|
|
506
|
+
)
|
|
558
507
|
|
|
559
508
|
# Normalize category: convert empty string to None
|
|
560
509
|
normalized_category = category.strip() if category and isinstance(category, str) and category.strip() else None
|
|
@@ -586,49 +535,188 @@ async def add_memory(
|
|
|
586
535
|
|
|
587
536
|
@mcp.tool()
|
|
588
537
|
async def search_memories(query: str, k: int = 5) -> str:
|
|
589
|
-
"""Search memories using semantic similarity. CRITICAL: Formulate specific, natural language queries, NOT simple keywords. Examples: ✅ 'Who is Maga and what is their relationship to me?' vs ❌ 'maga'. Check related_memories field for graph connections and synthesize across results. See mem-brain://docs/workflow-guide for search strategies. DO NOT use vague keywords - always use full questions.
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
538
|
+
"""Search memories using semantic similarity. CRITICAL: Formulate specific, natural language queries, NOT simple keywords. Examples: ✅ 'Who is Maga and what is their relationship to me?' vs ❌ 'maga'. Check related_memories field for graph connections and synthesize across results. See mem-brain://docs/workflow-guide for search strategies. DO NOT use vague keywords - always use full questions.
|
|
539
|
+
|
|
540
|
+
Parameters:
|
|
541
|
+
query (str, REQUIRED): Search query string. Use natural language questions, not keywords.
|
|
542
|
+
- Example: "Who is Rakshith and what did he build?"
|
|
543
|
+
- Example: "What are the user's preferences for programming languages?"
|
|
544
|
+
- Example: "Tell me about memories related to the Dubai presentation"
|
|
545
|
+
|
|
546
|
+
k (int, optional): Number of results to return. Default is 5.
|
|
547
|
+
- Must be between 1 and 100
|
|
548
|
+
- Example: 5 (default)
|
|
549
|
+
- Example: 10
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
str: Formatted search results with memory nodes and relationship edges.
|
|
553
|
+
|
|
554
|
+
Common Errors and Solutions:
|
|
555
|
+
- Error: "Query cannot be empty"
|
|
556
|
+
Solution: Provide a non-empty search query. Example: search_memories(query="What is the user's name?")
|
|
557
|
+
|
|
558
|
+
- Error: "k must be between 1 and 100"
|
|
559
|
+
Solution: Provide k between 1 and 100. Example: search_memories(query="...", k=10)
|
|
560
|
+
|
|
561
|
+
Examples:
|
|
562
|
+
# Basic search
|
|
563
|
+
search_memories(query="Who is Rakshith?")
|
|
564
|
+
|
|
565
|
+
# Search with more results
|
|
566
|
+
search_memories(query="What are the user's programming preferences?", k=10)
|
|
567
|
+
|
|
568
|
+
# Complex query
|
|
569
|
+
search_memories(query="Tell me about memories related to mem-brain and its features")
|
|
570
|
+
"""
|
|
571
|
+
# Validate parameters with detailed error messages
|
|
572
|
+
if query is None:
|
|
573
|
+
raise ToolError(
|
|
574
|
+
"The 'query' parameter is required but was not provided.\n"
|
|
575
|
+
"Example: search_memories(query=\"Who is Rakshith?\")\n"
|
|
576
|
+
"Example: search_memories(query=\"What are the user's preferences?\")"
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
if not isinstance(query, str):
|
|
580
|
+
raise ToolError(
|
|
581
|
+
f"The 'query' parameter must be a string, but got {type(query).__name__}.\n"
|
|
582
|
+
f"Received: {repr(query)}\n"
|
|
583
|
+
"Example: search_memories(query=\"Who is Rakshith?\")"
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
query_str = query.strip()
|
|
587
|
+
if not query_str:
|
|
588
|
+
raise ToolError(
|
|
589
|
+
"The 'query' parameter cannot be empty or whitespace-only.\n"
|
|
590
|
+
"Provide a natural language question or search query.\n"
|
|
591
|
+
"Example: search_memories(query=\"Who is Rakshith?\")\n"
|
|
592
|
+
"Example: search_memories(query=\"What are the user's preferences?\")"
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
if not isinstance(k, int):
|
|
596
|
+
raise ToolError(
|
|
597
|
+
f"The 'k' parameter must be an integer, but got {type(k).__name__}.\n"
|
|
598
|
+
f"Received: {repr(k)}\n"
|
|
599
|
+
"Example: search_memories(query=\"...\", k=10)"
|
|
600
|
+
)
|
|
601
|
+
|
|
593
602
|
if not (1 <= k <= 100):
|
|
594
|
-
raise ToolError(
|
|
603
|
+
raise ToolError(
|
|
604
|
+
f"The 'k' parameter must be between 1 and 100, but got {k}.\n"
|
|
605
|
+
"Example: search_memories(query=\"...\", k=5)\n"
|
|
606
|
+
"Example: search_memories(query=\"...\", k=10)"
|
|
607
|
+
)
|
|
595
608
|
|
|
596
609
|
try:
|
|
610
|
+
logger.info(f"search_memories called - query length: {len(query_str)}, k: {k}")
|
|
597
611
|
client = await _get_api_client()
|
|
598
|
-
result = await client.search_memories(
|
|
612
|
+
result = await client.search_memories(query_str, k)
|
|
599
613
|
return f"Found {result.get('count', 0)} results:\n{_format_search_results(result.get('results', []))}"
|
|
600
614
|
except httpx.HTTPStatusError as e:
|
|
615
|
+
error_detail = e.response.text if e.response else "Unknown error"
|
|
616
|
+
logger.error(f"API error: {e.response.status_code} - {error_detail}")
|
|
601
617
|
if e.response.status_code == 401:
|
|
602
|
-
raise ToolError("Authentication failed. Please
|
|
603
|
-
|
|
604
|
-
|
|
618
|
+
raise ToolError("Authentication failed. Please login using the 'login' tool or configure your JWT token in the MCP client headers.")
|
|
619
|
+
raise ToolError(f"Failed to search memories: HTTP {e.response.status_code} - {error_detail}")
|
|
620
|
+
except ToolError:
|
|
621
|
+
raise
|
|
605
622
|
except Exception as e:
|
|
606
|
-
logger.error(f"Unexpected error: {e}", exc_info=True)
|
|
623
|
+
logger.error(f"Unexpected error in search_memories: {e}", exc_info=True)
|
|
607
624
|
raise ToolError(f"Error searching memories: {str(e)}")
|
|
608
625
|
|
|
609
626
|
|
|
610
627
|
@mcp.tool()
|
|
611
628
|
async def get_memories(memory_ids: List[str]) -> str:
|
|
612
|
-
"""Retrieve one or more memories by ID. Use this when you need full details for specific memories identified from search results.
|
|
613
|
-
|
|
629
|
+
"""Retrieve one or more memories by ID. Use this when you need full details for specific memories identified from search results.
|
|
630
|
+
|
|
631
|
+
Parameters:
|
|
632
|
+
memory_ids (list[str], REQUIRED): List of memory IDs to retrieve. Must be a non-empty list.
|
|
633
|
+
- Example: ["480c1f76-bcdf-4491-8781-24510db992e3"]
|
|
634
|
+
- Example: ["480c1f76-...", "300d9716-...", "6fb6b23f-..."]
|
|
635
|
+
- Get memory IDs from search_memories() results
|
|
636
|
+
|
|
637
|
+
Returns:
|
|
638
|
+
str: Formatted details of the retrieved memories.
|
|
639
|
+
|
|
640
|
+
Common Errors and Solutions:
|
|
641
|
+
- Error: "memory_ids cannot be empty"
|
|
642
|
+
Solution: Provide a list with at least one memory ID. Example: get_memories(memory_ids=["480c1f76-..."])
|
|
643
|
+
|
|
644
|
+
- Error: "Memory IDs cannot be empty"
|
|
645
|
+
Solution: Ensure all IDs in the list are non-empty strings. Example: get_memories(memory_ids=["480c1f76-..."])
|
|
646
|
+
|
|
647
|
+
- Error: "memory_ids must be a list"
|
|
648
|
+
Solution: Pass memory_ids as a list. Example: get_memories(memory_ids=["..."]) not memory_ids="..."
|
|
649
|
+
|
|
650
|
+
Examples:
|
|
651
|
+
# Get single memory
|
|
652
|
+
get_memories(memory_ids=["480c1f76-bcdf-4491-8781-24510db992e3"])
|
|
653
|
+
|
|
654
|
+
# Get multiple memories
|
|
655
|
+
get_memories(memory_ids=["480c1f76-...", "300d9716-...", "6fb6b23f-..."])
|
|
656
|
+
"""
|
|
657
|
+
# Validate parameters with detailed error messages
|
|
658
|
+
if memory_ids is None:
|
|
659
|
+
raise ToolError(
|
|
660
|
+
"The 'memory_ids' parameter is required but was not provided.\n"
|
|
661
|
+
"Example: get_memories(memory_ids=[\"480c1f76-bcdf-4491-8781-24510db992e3\"])\n"
|
|
662
|
+
"Example: get_memories(memory_ids=[\"480c1f76-...\", \"300d9716-...\"])"
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
if not isinstance(memory_ids, list):
|
|
666
|
+
raise ToolError(
|
|
667
|
+
f"The 'memory_ids' parameter must be a list of strings, but got {type(memory_ids).__name__}.\n"
|
|
668
|
+
f"Received: {repr(memory_ids)}\n"
|
|
669
|
+
"Example: get_memories(memory_ids=[\"480c1f76-...\"])"
|
|
670
|
+
)
|
|
671
|
+
|
|
614
672
|
if not memory_ids:
|
|
615
|
-
raise ToolError(
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
673
|
+
raise ToolError(
|
|
674
|
+
"The 'memory_ids' parameter cannot be an empty list.\n"
|
|
675
|
+
"Provide at least one memory ID.\n"
|
|
676
|
+
"Example: get_memories(memory_ids=[\"480c1f76-bcdf-4491-8781-24510db992e3\"])"
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
# Validate each memory ID in the list
|
|
680
|
+
validated_ids = []
|
|
681
|
+
for i, memory_id in enumerate(memory_ids):
|
|
682
|
+
if memory_id is None:
|
|
683
|
+
raise ToolError(
|
|
684
|
+
f"Memory ID at index {i} is None. All memory IDs must be non-empty strings.\n"
|
|
685
|
+
"Example: get_memories(memory_ids=[\"480c1f76-...\"])"
|
|
686
|
+
)
|
|
687
|
+
if not isinstance(memory_id, str):
|
|
688
|
+
raise ToolError(
|
|
689
|
+
f"Memory ID at index {i} must be a string, but got {type(memory_id).__name__}.\n"
|
|
690
|
+
f"Received: {repr(memory_id)}\n"
|
|
691
|
+
"Example: get_memories(memory_ids=[\"480c1f76-...\"])"
|
|
692
|
+
)
|
|
693
|
+
memory_id_str = memory_id.strip()
|
|
694
|
+
if not memory_id_str:
|
|
695
|
+
raise ToolError(
|
|
696
|
+
f"Memory ID at index {i} cannot be empty or whitespace-only.\n"
|
|
697
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
698
|
+
"Example: get_memories(memory_ids=[\"480c1f76-bcdf-4491-8781-24510db992e3\"])"
|
|
699
|
+
)
|
|
700
|
+
validated_ids.append(memory_id_str)
|
|
619
701
|
|
|
620
702
|
try:
|
|
703
|
+
logger.info(f"get_memories called - count: {len(validated_ids)}")
|
|
621
704
|
client = await _get_api_client()
|
|
622
|
-
result = await client.get_memories(
|
|
705
|
+
result = await client.get_memories(validated_ids)
|
|
623
706
|
memories = result.get("memories", [])
|
|
624
707
|
return f"Retrieved {len(memories)} memories:\n{_format_memories_list(memories)}"
|
|
625
708
|
except httpx.HTTPStatusError as e:
|
|
709
|
+
error_detail = e.response.text if e.response else "Unknown error"
|
|
710
|
+
logger.error(f"API error: {e.response.status_code} - {error_detail}")
|
|
626
711
|
if e.response.status_code == 401:
|
|
627
|
-
raise ToolError("Authentication failed. Please
|
|
628
|
-
|
|
629
|
-
|
|
712
|
+
raise ToolError("Authentication failed. Please login using the 'login' tool or configure your JWT token in the MCP client headers.")
|
|
713
|
+
elif e.response.status_code == 404:
|
|
714
|
+
raise ToolError(f"One or more memories not found.\nVerify the memory IDs are correct by searching for them first.")
|
|
715
|
+
raise ToolError(f"Failed to get memories: HTTP {e.response.status_code} - {error_detail}")
|
|
716
|
+
except ToolError:
|
|
717
|
+
raise
|
|
630
718
|
except Exception as e:
|
|
631
|
-
logger.error(f"Unexpected error: {e}", exc_info=True)
|
|
719
|
+
logger.error(f"Unexpected error in get_memories: {e}", exc_info=True)
|
|
632
720
|
raise ToolError(f"Error getting memories: {str(e)}")
|
|
633
721
|
|
|
634
722
|
|
|
@@ -638,25 +726,147 @@ async def update_memory(
|
|
|
638
726
|
content: Optional[str] = None,
|
|
639
727
|
tags: Optional[List[str]] = None
|
|
640
728
|
) -> str:
|
|
641
|
-
"""Update an existing memory when information evolves or changes. Use this when user contradicts a past memory ('I no longer like X') or when details need updating.
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
729
|
+
"""Update an existing memory when information evolves or changes. Use this when user contradicts a past memory ('I no longer like X') or when details need updating.
|
|
730
|
+
|
|
731
|
+
Parameters:
|
|
732
|
+
memory_id (str, REQUIRED): The ID of the memory to update. Must be a non-empty string.
|
|
733
|
+
- Example: "480c1f76-bcdf-4491-8781-24510db992e3"
|
|
734
|
+
- Get memory IDs from search_memories() or get_memories() results
|
|
735
|
+
|
|
736
|
+
content (str, optional): New content for the memory.
|
|
737
|
+
- Can be None (to keep existing content) or a non-empty string
|
|
738
|
+
- If provided, must not be empty or whitespace-only
|
|
739
|
+
- Example: "User no longer likes TypeScript, prefers Python"
|
|
740
|
+
|
|
741
|
+
tags (list[str], optional): New tags for the memory.
|
|
742
|
+
- Can be None (to keep existing tags) or a list of strings
|
|
743
|
+
- Example: ["coding", "python"]
|
|
744
|
+
- Example: ["preferences", "updated"]
|
|
745
|
+
|
|
746
|
+
Returns:
|
|
747
|
+
str: A formatted string with the updated memory details.
|
|
748
|
+
|
|
749
|
+
Common Errors and Solutions:
|
|
750
|
+
- Error: "Tool call arguments for mcp were invalid"
|
|
751
|
+
Solution: Ensure 'memory_id' parameter is provided as a string. Example: update_memory(memory_id="...")
|
|
752
|
+
|
|
753
|
+
- Error: "memory_id cannot be empty"
|
|
754
|
+
Solution: Provide a valid memory ID from search results. Example: update_memory(memory_id="480c1f76-...")
|
|
755
|
+
|
|
756
|
+
- Error: "At least one of 'content' or 'tags' must be provided"
|
|
757
|
+
Solution: Provide content or tags to update. Example: update_memory(memory_id="...", content="New content")
|
|
758
|
+
|
|
759
|
+
Examples:
|
|
760
|
+
# Update content only
|
|
761
|
+
update_memory(memory_id="480c1f76-...", content="User prefers Python over JavaScript")
|
|
762
|
+
|
|
763
|
+
# Update tags only
|
|
764
|
+
update_memory(memory_id="480c1f76-...", tags=["coding", "preferences"])
|
|
765
|
+
|
|
766
|
+
# Update both content and tags
|
|
767
|
+
update_memory(
|
|
768
|
+
memory_id="480c1f76-...",
|
|
769
|
+
content="User no longer likes TypeScript",
|
|
770
|
+
tags=["coding", "python"]
|
|
771
|
+
)
|
|
772
|
+
"""
|
|
773
|
+
# Validate parameters with detailed error messages
|
|
774
|
+
if memory_id is None:
|
|
775
|
+
raise ToolError(
|
|
776
|
+
"The 'memory_id' parameter is required but was not provided.\n"
|
|
777
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
778
|
+
"Example: update_memory(memory_id=\"480c1f76-bcdf-4491-8781-24510db992e3\", content=\"New content\")"
|
|
779
|
+
)
|
|
780
|
+
|
|
781
|
+
if not isinstance(memory_id, str):
|
|
782
|
+
raise ToolError(
|
|
783
|
+
f"The 'memory_id' parameter must be a string, but got {type(memory_id).__name__}.\n"
|
|
784
|
+
f"Received: {repr(memory_id)}\n"
|
|
785
|
+
"Example: update_memory(memory_id=\"480c1f76-...\", content=\"New content\")"
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
memory_id_str = memory_id.strip()
|
|
789
|
+
if not memory_id_str:
|
|
790
|
+
raise ToolError(
|
|
791
|
+
"The 'memory_id' parameter cannot be empty or whitespace-only.\n"
|
|
792
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
793
|
+
"Example: update_memory(memory_id=\"480c1f76-bcdf-4491-8781-24510db992e3\", content=\"New content\")"
|
|
794
|
+
)
|
|
795
|
+
|
|
796
|
+
# Validate that at least one update parameter is provided
|
|
797
|
+
if content is None and tags is None:
|
|
798
|
+
raise ToolError(
|
|
799
|
+
"At least one of 'content' or 'tags' must be provided to update the memory.\n"
|
|
800
|
+
"Example: update_memory(memory_id=\"...\", content=\"New content\")\n"
|
|
801
|
+
"Example: update_memory(memory_id=\"...\", tags=[\"new\", \"tags\"])"
|
|
802
|
+
)
|
|
803
|
+
|
|
804
|
+
# Validate content if provided
|
|
805
|
+
if content is not None:
|
|
806
|
+
if not isinstance(content, str):
|
|
807
|
+
raise ToolError(
|
|
808
|
+
f"The 'content' parameter must be a string or None, but got {type(content).__name__}.\n"
|
|
809
|
+
f"Received: {repr(content)}\n"
|
|
810
|
+
"Example: update_memory(memory_id=\"...\", content=\"New content\")"
|
|
811
|
+
)
|
|
812
|
+
content_str = str(content).strip()
|
|
813
|
+
if not content_str:
|
|
814
|
+
raise ToolError(
|
|
815
|
+
"The 'content' parameter cannot be empty or whitespace-only.\n"
|
|
816
|
+
"Provide a non-empty string or omit the parameter to keep existing content.\n"
|
|
817
|
+
"Example: update_memory(memory_id=\"...\", content=\"New content\")"
|
|
818
|
+
)
|
|
819
|
+
else:
|
|
820
|
+
content_str = None
|
|
821
|
+
|
|
822
|
+
# Validate tags if provided
|
|
823
|
+
normalized_tags = None
|
|
824
|
+
if tags is not None:
|
|
825
|
+
if isinstance(tags, list):
|
|
826
|
+
# Validate list contents are strings
|
|
827
|
+
if tags:
|
|
828
|
+
invalid_items = [item for item in tags if not isinstance(item, str)]
|
|
829
|
+
if invalid_items:
|
|
830
|
+
raise ToolError(
|
|
831
|
+
f"The 'tags' parameter must be a list of strings, but found non-string items: {invalid_items}\n"
|
|
832
|
+
"Example: update_memory(memory_id=\"...\", tags=[\"coding\", \"preferences\"])\n"
|
|
833
|
+
"Example: update_memory(memory_id=\"...\", tags=None) # or omit tags parameter"
|
|
834
|
+
)
|
|
835
|
+
normalized_tags = tags if tags else None # Empty list becomes None
|
|
836
|
+
elif isinstance(tags, str):
|
|
837
|
+
# Handle case where tags might be passed as a single string
|
|
838
|
+
normalized_tags = [tags]
|
|
839
|
+
else:
|
|
840
|
+
raise ToolError(
|
|
841
|
+
f"The 'tags' parameter must be a list of strings or None, but got {type(tags).__name__}.\n"
|
|
842
|
+
f"Received: {repr(tags)}\n"
|
|
843
|
+
"Example: update_memory(memory_id=\"...\", tags=[\"coding\", \"preferences\"])\n"
|
|
844
|
+
"Example: update_memory(memory_id=\"...\", tags=None) # or omit tags parameter"
|
|
845
|
+
)
|
|
645
846
|
|
|
646
847
|
try:
|
|
848
|
+
logger.info(f"update_memory called - memory_id: {memory_id_str}, content length: {len(content_str) if content_str else 0}, tags: {normalized_tags}")
|
|
849
|
+
|
|
647
850
|
client = await _get_api_client()
|
|
648
|
-
result = await client.update_memory(
|
|
851
|
+
result = await client.update_memory(memory_id_str, content_str, normalized_tags)
|
|
649
852
|
memory = result.get('memory')
|
|
650
853
|
if memory:
|
|
651
854
|
return f"Memory updated:\n{_format_memory(memory)}"
|
|
652
|
-
return f"Memory {
|
|
855
|
+
return f"Memory {memory_id_str} updated"
|
|
653
856
|
except httpx.HTTPStatusError as e:
|
|
857
|
+
error_detail = e.response.text if e.response else "Unknown error"
|
|
858
|
+
logger.error(f"API error: {e.response.status_code} - {error_detail}")
|
|
654
859
|
if e.response.status_code == 401:
|
|
655
|
-
raise ToolError("Authentication failed. Please
|
|
656
|
-
|
|
657
|
-
|
|
860
|
+
raise ToolError("Authentication failed. Please login using the 'login' tool or configure your JWT token in the MCP client headers.")
|
|
861
|
+
elif e.response.status_code == 400:
|
|
862
|
+
raise ToolError(f"Invalid request: {error_detail}\nExample: update_memory(memory_id=\"...\", content=\"New content\")")
|
|
863
|
+
elif e.response.status_code == 404:
|
|
864
|
+
raise ToolError(f"Memory not found: {memory_id_str}\nVerify the memory_id is correct by searching for it first.")
|
|
865
|
+
raise ToolError(f"Failed to update memory: HTTP {e.response.status_code} - {error_detail}")
|
|
866
|
+
except ToolError:
|
|
867
|
+
raise
|
|
658
868
|
except Exception as e:
|
|
659
|
-
logger.error(f"Unexpected error: {e}", exc_info=True)
|
|
869
|
+
logger.error(f"Unexpected error in update_memory: {e}", exc_info=True)
|
|
660
870
|
raise ToolError(f"Error updating memory: {str(e)}")
|
|
661
871
|
|
|
662
872
|
|
|
@@ -666,49 +876,243 @@ async def delete_memories(
|
|
|
666
876
|
tags: Optional[str] = None,
|
|
667
877
|
category: Optional[str] = None
|
|
668
878
|
) -> str:
|
|
669
|
-
"""Delete memories by ID or by filter (tags/category). If memory_id is provided, it takes precedence over filters. Use for removing wrong memories or when user explicitly requests deletion.
|
|
879
|
+
"""Delete memories by ID or by filter (tags/category). If memory_id is provided, it takes precedence over filters. Use for removing wrong memories or when user explicitly requests deletion.
|
|
880
|
+
|
|
881
|
+
Parameters:
|
|
882
|
+
memory_id (str, optional): Specific memory ID to delete. Takes precedence over filters.
|
|
883
|
+
- Example: "480c1f76-bcdf-4491-8781-24510db992e3"
|
|
884
|
+
- Get memory IDs from search_memories() or get_memories() results
|
|
885
|
+
|
|
886
|
+
tags (str, optional): Comma-separated tags for filter-based deletion.
|
|
887
|
+
- Example: "coding,preferences"
|
|
888
|
+
- Example: "personal,pets"
|
|
889
|
+
- Only used if memory_id is not provided
|
|
890
|
+
|
|
891
|
+
category (str, optional): Category name for filter-based deletion.
|
|
892
|
+
- Example: "interests"
|
|
893
|
+
- Example: "preferences"
|
|
894
|
+
- Only used if memory_id is not provided
|
|
895
|
+
|
|
896
|
+
Returns:
|
|
897
|
+
str: A message indicating how many memories were deleted and their IDs.
|
|
898
|
+
|
|
899
|
+
Common Errors and Solutions:
|
|
900
|
+
- Error: "At least one parameter must be provided"
|
|
901
|
+
Solution: Provide memory_id, tags, or category. Example: delete_memories(memory_id="...")
|
|
902
|
+
|
|
903
|
+
- Error: "memory_id cannot be empty"
|
|
904
|
+
Solution: Provide a valid memory ID or omit the parameter. Example: delete_memories(memory_id="480c1f76-...")
|
|
905
|
+
|
|
906
|
+
Examples:
|
|
907
|
+
# Delete by memory ID
|
|
908
|
+
delete_memories(memory_id="480c1f76-bcdf-4491-8781-24510db992e3")
|
|
909
|
+
|
|
910
|
+
# Delete by tags
|
|
911
|
+
delete_memories(tags="coding,preferences")
|
|
912
|
+
|
|
913
|
+
# Delete by category
|
|
914
|
+
delete_memories(category="interests")
|
|
915
|
+
"""
|
|
916
|
+
# Validate that at least one parameter is provided
|
|
917
|
+
if memory_id is None and tags is None and category is None:
|
|
918
|
+
raise ToolError(
|
|
919
|
+
"At least one parameter (memory_id, tags, or category) must be provided to delete memories.\n"
|
|
920
|
+
"Example: delete_memories(memory_id=\"480c1f76-bcdf-4491-8781-24510db992e3\")\n"
|
|
921
|
+
"Example: delete_memories(tags=\"coding,preferences\")\n"
|
|
922
|
+
"Example: delete_memories(category=\"interests\")"
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
# Validate memory_id if provided
|
|
926
|
+
if memory_id is not None:
|
|
927
|
+
if not isinstance(memory_id, str):
|
|
928
|
+
raise ToolError(
|
|
929
|
+
f"The 'memory_id' parameter must be a string or None, but got {type(memory_id).__name__}.\n"
|
|
930
|
+
f"Received: {repr(memory_id)}\n"
|
|
931
|
+
"Example: delete_memories(memory_id=\"480c1f76-bcdf-4491-8781-24510db992e3\")"
|
|
932
|
+
)
|
|
933
|
+
memory_id_str = memory_id.strip()
|
|
934
|
+
if not memory_id_str:
|
|
935
|
+
raise ToolError(
|
|
936
|
+
"The 'memory_id' parameter cannot be empty or whitespace-only.\n"
|
|
937
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
938
|
+
"Example: delete_memories(memory_id=\"480c1f76-bcdf-4491-8781-24510db992e3\")"
|
|
939
|
+
)
|
|
940
|
+
else:
|
|
941
|
+
memory_id_str = None
|
|
942
|
+
|
|
943
|
+
# Validate tags if provided
|
|
944
|
+
if tags is not None:
|
|
945
|
+
if not isinstance(tags, str):
|
|
946
|
+
raise ToolError(
|
|
947
|
+
f"The 'tags' parameter must be a string or None, but got {type(tags).__name__}.\n"
|
|
948
|
+
f"Received: {repr(tags)}\n"
|
|
949
|
+
"Example: delete_memories(tags=\"coding,preferences\")"
|
|
950
|
+
)
|
|
951
|
+
tags_str = tags.strip()
|
|
952
|
+
if not tags_str:
|
|
953
|
+
raise ToolError(
|
|
954
|
+
"The 'tags' parameter cannot be empty or whitespace-only.\n"
|
|
955
|
+
"Provide comma-separated tags or omit the parameter.\n"
|
|
956
|
+
"Example: delete_memories(tags=\"coding,preferences\")"
|
|
957
|
+
)
|
|
958
|
+
else:
|
|
959
|
+
tags_str = None
|
|
960
|
+
|
|
961
|
+
# Validate category if provided
|
|
962
|
+
if category is not None:
|
|
963
|
+
if not isinstance(category, str):
|
|
964
|
+
raise ToolError(
|
|
965
|
+
f"The 'category' parameter must be a string or None, but got {type(category).__name__}.\n"
|
|
966
|
+
f"Received: {repr(category)}\n"
|
|
967
|
+
"Example: delete_memories(category=\"interests\")"
|
|
968
|
+
)
|
|
969
|
+
category_str = category.strip()
|
|
970
|
+
if not category_str:
|
|
971
|
+
raise ToolError(
|
|
972
|
+
"The 'category' parameter cannot be empty or whitespace-only.\n"
|
|
973
|
+
"Provide a category name or omit the parameter.\n"
|
|
974
|
+
"Example: delete_memories(category=\"interests\")"
|
|
975
|
+
)
|
|
976
|
+
else:
|
|
977
|
+
category_str = None
|
|
978
|
+
|
|
670
979
|
try:
|
|
980
|
+
logger.info(f"delete_memories called - memory_id: {memory_id_str}, tags: {tags_str}, category: {category_str}")
|
|
671
981
|
client = await _get_api_client()
|
|
672
|
-
result = await client.delete_memories(
|
|
982
|
+
result = await client.delete_memories(memory_id_str, tags_str, category_str)
|
|
673
983
|
deleted_ids = result.get('memory_ids', [])[:10]
|
|
674
984
|
return f"Deleted {result.get('deleted_count', 0)} memories. IDs: {', '.join(deleted_ids)}"
|
|
675
985
|
except httpx.HTTPStatusError as e:
|
|
986
|
+
error_detail = e.response.text if e.response else "Unknown error"
|
|
987
|
+
logger.error(f"API error: {e.response.status_code} - {error_detail}")
|
|
676
988
|
if e.response.status_code == 401:
|
|
677
|
-
raise ToolError("Authentication failed. Please
|
|
678
|
-
|
|
679
|
-
|
|
989
|
+
raise ToolError("Authentication failed. Please login using the 'login' tool or configure your JWT token in the MCP client headers.")
|
|
990
|
+
elif e.response.status_code == 404:
|
|
991
|
+
raise ToolError(f"Memory not found: {memory_id_str}\nVerify the memory_id is correct by searching for it first.")
|
|
992
|
+
raise ToolError(f"Failed to delete memories: HTTP {e.response.status_code} - {error_detail}")
|
|
993
|
+
except ToolError:
|
|
994
|
+
raise
|
|
680
995
|
except Exception as e:
|
|
681
|
-
logger.error(f"Unexpected error: {e}", exc_info=True)
|
|
996
|
+
logger.error(f"Unexpected error in delete_memories: {e}", exc_info=True)
|
|
682
997
|
raise ToolError(f"Error deleting memories: {str(e)}")
|
|
683
998
|
|
|
684
999
|
|
|
685
1000
|
@mcp.tool()
|
|
686
1001
|
async def unlink_memories(memory_id_1: str, memory_id_2: str) -> str:
|
|
687
|
-
"""Remove link between two memories when the connection is no longer relevant or accurate.
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
1002
|
+
"""Remove link between two memories when the connection is no longer relevant or accurate.
|
|
1003
|
+
|
|
1004
|
+
Parameters:
|
|
1005
|
+
memory_id_1 (str, REQUIRED): First memory ID in the link to remove.
|
|
1006
|
+
- Example: "480c1f76-bcdf-4491-8781-24510db992e3"
|
|
1007
|
+
- Get memory IDs from search_memories() or get_memories() results
|
|
1008
|
+
|
|
1009
|
+
memory_id_2 (str, REQUIRED): Second memory ID in the link to remove.
|
|
1010
|
+
- Example: "300d9716-a3a6-44d3-b0f4-b28002a65da8"
|
|
1011
|
+
- Get memory IDs from search_memories() or get_memories() results
|
|
1012
|
+
|
|
1013
|
+
Returns:
|
|
1014
|
+
str: Confirmation message that the memories were unlinked.
|
|
1015
|
+
|
|
1016
|
+
Common Errors and Solutions:
|
|
1017
|
+
- Error: "memory_id_1 cannot be empty"
|
|
1018
|
+
Solution: Provide a valid memory ID. Example: unlink_memories(memory_id_1="480c1f76-...", memory_id_2="300d9716-...")
|
|
1019
|
+
|
|
1020
|
+
- Error: "memory_id_2 cannot be empty"
|
|
1021
|
+
Solution: Provide a valid memory ID. Example: unlink_memories(memory_id_1="480c1f76-...", memory_id_2="300d9716-...")
|
|
1022
|
+
|
|
1023
|
+
Examples:
|
|
1024
|
+
# Unlink two memories
|
|
1025
|
+
unlink_memories(
|
|
1026
|
+
memory_id_1="480c1f76-bcdf-4491-8781-24510db992e3",
|
|
1027
|
+
memory_id_2="300d9716-a3a6-44d3-b0f4-b28002a65da8"
|
|
1028
|
+
)
|
|
1029
|
+
"""
|
|
1030
|
+
# Validate parameters with detailed error messages
|
|
1031
|
+
if memory_id_1 is None:
|
|
1032
|
+
raise ToolError(
|
|
1033
|
+
"The 'memory_id_1' parameter is required but was not provided.\n"
|
|
1034
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1035
|
+
"Example: unlink_memories(memory_id_1=\"480c1f76-...\", memory_id_2=\"300d9716-...\")"
|
|
1036
|
+
)
|
|
1037
|
+
|
|
1038
|
+
if not isinstance(memory_id_1, str):
|
|
1039
|
+
raise ToolError(
|
|
1040
|
+
f"The 'memory_id_1' parameter must be a string, but got {type(memory_id_1).__name__}.\n"
|
|
1041
|
+
f"Received: {repr(memory_id_1)}\n"
|
|
1042
|
+
"Example: unlink_memories(memory_id_1=\"480c1f76-...\", memory_id_2=\"300d9716-...\")"
|
|
1043
|
+
)
|
|
1044
|
+
|
|
1045
|
+
memory_id_1_str = memory_id_1.strip()
|
|
1046
|
+
if not memory_id_1_str:
|
|
1047
|
+
raise ToolError(
|
|
1048
|
+
"The 'memory_id_1' parameter cannot be empty or whitespace-only.\n"
|
|
1049
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1050
|
+
"Example: unlink_memories(memory_id_1=\"480c1f76-bcdf-4491-8781-24510db992e3\", memory_id_2=\"300d9716-...\")"
|
|
1051
|
+
)
|
|
1052
|
+
|
|
1053
|
+
if memory_id_2 is None:
|
|
1054
|
+
raise ToolError(
|
|
1055
|
+
"The 'memory_id_2' parameter is required but was not provided.\n"
|
|
1056
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1057
|
+
"Example: unlink_memories(memory_id_1=\"480c1f76-...\", memory_id_2=\"300d9716-...\")"
|
|
1058
|
+
)
|
|
1059
|
+
|
|
1060
|
+
if not isinstance(memory_id_2, str):
|
|
1061
|
+
raise ToolError(
|
|
1062
|
+
f"The 'memory_id_2' parameter must be a string, but got {type(memory_id_2).__name__}.\n"
|
|
1063
|
+
f"Received: {repr(memory_id_2)}\n"
|
|
1064
|
+
"Example: unlink_memories(memory_id_1=\"480c1f76-...\", memory_id_2=\"300d9716-...\")"
|
|
1065
|
+
)
|
|
1066
|
+
|
|
1067
|
+
memory_id_2_str = memory_id_2.strip()
|
|
1068
|
+
if not memory_id_2_str:
|
|
1069
|
+
raise ToolError(
|
|
1070
|
+
"The 'memory_id_2' parameter cannot be empty or whitespace-only.\n"
|
|
1071
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1072
|
+
"Example: unlink_memories(memory_id_1=\"480c1f76-...\", memory_id_2=\"300d9716-a3a6-44d3-b0f4-b28002a65da8\")"
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
# Ensure the IDs are different
|
|
1076
|
+
if memory_id_1_str == memory_id_2_str:
|
|
1077
|
+
raise ToolError(
|
|
1078
|
+
"memory_id_1 and memory_id_2 must be different.\n"
|
|
1079
|
+
"You cannot unlink a memory from itself.\n"
|
|
1080
|
+
"Example: unlink_memories(memory_id_1=\"480c1f76-...\", memory_id_2=\"300d9716-...\")"
|
|
1081
|
+
)
|
|
693
1082
|
|
|
694
1083
|
try:
|
|
1084
|
+
logger.info(f"unlink_memories called - memory_id_1: {memory_id_1_str}, memory_id_2: {memory_id_2_str}")
|
|
695
1085
|
client = await _get_api_client()
|
|
696
|
-
result = await client.unlink_memories(
|
|
1086
|
+
result = await client.unlink_memories(memory_id_1_str, memory_id_2_str)
|
|
697
1087
|
return result.get("message", "Memories unlinked")
|
|
698
1088
|
except httpx.HTTPStatusError as e:
|
|
1089
|
+
error_detail = e.response.text if e.response else "Unknown error"
|
|
1090
|
+
logger.error(f"API error: {e.response.status_code} - {error_detail}")
|
|
699
1091
|
if e.response.status_code == 401:
|
|
700
|
-
raise ToolError("Authentication failed. Please
|
|
701
|
-
|
|
702
|
-
|
|
1092
|
+
raise ToolError("Authentication failed. Please login using the 'login' tool or configure your JWT token in the MCP client headers.")
|
|
1093
|
+
elif e.response.status_code == 404:
|
|
1094
|
+
raise ToolError(f"One or both memories not found.\nVerify the memory IDs are correct by searching for them first.")
|
|
1095
|
+
raise ToolError(f"Failed to unlink memories: HTTP {e.response.status_code} - {error_detail}")
|
|
1096
|
+
except ToolError:
|
|
1097
|
+
raise
|
|
703
1098
|
except Exception as e:
|
|
704
|
-
logger.error(f"Unexpected error: {e}", exc_info=True)
|
|
1099
|
+
logger.error(f"Unexpected error in unlink_memories: {e}", exc_info=True)
|
|
705
1100
|
raise ToolError(f"Error unlinking memories: {str(e)}")
|
|
706
1101
|
|
|
707
1102
|
|
|
708
1103
|
@mcp.tool()
|
|
709
1104
|
async def get_stats() -> str:
|
|
710
|
-
"""Get memory system statistics including total memories, links, and top tags. Use this when user asks 'how much do you remember?' or wants an overview of their memory system.
|
|
1105
|
+
"""Get memory system statistics including total memories, links, and top tags. Use this when user asks 'how much do you remember?' or wants an overview of their memory system.
|
|
1106
|
+
|
|
1107
|
+
Returns:
|
|
1108
|
+
str: Formatted statistics including total memories, links, and top tags.
|
|
1109
|
+
|
|
1110
|
+
Examples:
|
|
1111
|
+
# Get statistics
|
|
1112
|
+
get_stats()
|
|
1113
|
+
"""
|
|
711
1114
|
try:
|
|
1115
|
+
logger.info("get_stats called")
|
|
712
1116
|
client = await _get_api_client()
|
|
713
1117
|
result = await client.get_stats()
|
|
714
1118
|
top_tags = ', '.join([f"{tag}({count})" for tag, count in result.get('top_tags', [])[:10]])
|
|
@@ -718,27 +1122,97 @@ Total Links: {result.get('total_links', 0)}
|
|
|
718
1122
|
Average Links per Memory: {result.get('avg_links_per_memory', 0):.2f}
|
|
719
1123
|
Top Tags: {top_tags}"""
|
|
720
1124
|
except httpx.HTTPStatusError as e:
|
|
1125
|
+
error_detail = e.response.text if e.response else "Unknown error"
|
|
1126
|
+
logger.error(f"API error: {e.response.status_code} - {error_detail}")
|
|
721
1127
|
if e.response.status_code == 401:
|
|
722
|
-
raise ToolError("Authentication failed. Please
|
|
723
|
-
|
|
724
|
-
|
|
1128
|
+
raise ToolError("Authentication failed. Please login using the 'login' tool or configure your JWT token in the MCP client headers.")
|
|
1129
|
+
raise ToolError(f"Failed to get stats: HTTP {e.response.status_code} - {error_detail}")
|
|
1130
|
+
except ToolError:
|
|
1131
|
+
raise
|
|
725
1132
|
except Exception as e:
|
|
726
|
-
logger.error(f"Unexpected error: {e}", exc_info=True)
|
|
1133
|
+
logger.error(f"Unexpected error in get_stats: {e}", exc_info=True)
|
|
727
1134
|
raise ToolError(f"Error getting stats: {str(e)}")
|
|
728
1135
|
|
|
729
1136
|
|
|
730
1137
|
@mcp.tool()
|
|
731
1138
|
async def find_path(from_id: str, to_id: str) -> str:
|
|
732
|
-
"""Find shortest path between two memories in the memory graph. Use this to explain connections between seemingly unrelated memories.
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
1139
|
+
"""Find shortest path between two memories in the memory graph. Use this to explain connections between seemingly unrelated memories.
|
|
1140
|
+
|
|
1141
|
+
Parameters:
|
|
1142
|
+
from_id (str, REQUIRED): Source memory ID to start the path from.
|
|
1143
|
+
- Example: "480c1f76-bcdf-4491-8781-24510db992e3"
|
|
1144
|
+
- Get memory IDs from search_memories() or get_memories() results
|
|
1145
|
+
|
|
1146
|
+
to_id (str, REQUIRED): Target memory ID to find path to.
|
|
1147
|
+
- Example: "300d9716-a3a6-44d3-b0f4-b28002a65da8"
|
|
1148
|
+
- Get memory IDs from search_memories() or get_memories() results
|
|
1149
|
+
|
|
1150
|
+
Returns:
|
|
1151
|
+
str: The shortest path between the two memories, or a message if no path exists.
|
|
1152
|
+
|
|
1153
|
+
Common Errors and Solutions:
|
|
1154
|
+
- Error: "from_id cannot be empty"
|
|
1155
|
+
Solution: Provide a valid memory ID. Example: find_path(from_id="480c1f76-...", to_id="300d9716-...")
|
|
1156
|
+
|
|
1157
|
+
- Error: "to_id cannot be empty"
|
|
1158
|
+
Solution: Provide a valid memory ID. Example: find_path(from_id="480c1f76-...", to_id="300d9716-...")
|
|
1159
|
+
|
|
1160
|
+
Examples:
|
|
1161
|
+
# Find path between two memories
|
|
1162
|
+
find_path(
|
|
1163
|
+
from_id="480c1f76-bcdf-4491-8781-24510db992e3",
|
|
1164
|
+
to_id="300d9716-a3a6-44d3-b0f4-b28002a65da8"
|
|
1165
|
+
)
|
|
1166
|
+
"""
|
|
1167
|
+
# Validate parameters with detailed error messages
|
|
1168
|
+
if from_id is None:
|
|
1169
|
+
raise ToolError(
|
|
1170
|
+
"The 'from_id' parameter is required but was not provided.\n"
|
|
1171
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1172
|
+
"Example: find_path(from_id=\"480c1f76-...\", to_id=\"300d9716-...\")"
|
|
1173
|
+
)
|
|
1174
|
+
|
|
1175
|
+
if not isinstance(from_id, str):
|
|
1176
|
+
raise ToolError(
|
|
1177
|
+
f"The 'from_id' parameter must be a string, but got {type(from_id).__name__}.\n"
|
|
1178
|
+
f"Received: {repr(from_id)}\n"
|
|
1179
|
+
"Example: find_path(from_id=\"480c1f76-...\", to_id=\"300d9716-...\")"
|
|
1180
|
+
)
|
|
1181
|
+
|
|
1182
|
+
from_id_str = from_id.strip()
|
|
1183
|
+
if not from_id_str:
|
|
1184
|
+
raise ToolError(
|
|
1185
|
+
"The 'from_id' parameter cannot be empty or whitespace-only.\n"
|
|
1186
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1187
|
+
"Example: find_path(from_id=\"480c1f76-bcdf-4491-8781-24510db992e3\", to_id=\"300d9716-...\")"
|
|
1188
|
+
)
|
|
1189
|
+
|
|
1190
|
+
if to_id is None:
|
|
1191
|
+
raise ToolError(
|
|
1192
|
+
"The 'to_id' parameter is required but was not provided.\n"
|
|
1193
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1194
|
+
"Example: find_path(from_id=\"480c1f76-...\", to_id=\"300d9716-...\")"
|
|
1195
|
+
)
|
|
1196
|
+
|
|
1197
|
+
if not isinstance(to_id, str):
|
|
1198
|
+
raise ToolError(
|
|
1199
|
+
f"The 'to_id' parameter must be a string, but got {type(to_id).__name__}.\n"
|
|
1200
|
+
f"Received: {repr(to_id)}\n"
|
|
1201
|
+
"Example: find_path(from_id=\"480c1f76-...\", to_id=\"300d9716-...\")"
|
|
1202
|
+
)
|
|
1203
|
+
|
|
1204
|
+
to_id_str = to_id.strip()
|
|
1205
|
+
if not to_id_str:
|
|
1206
|
+
raise ToolError(
|
|
1207
|
+
"The 'to_id' parameter cannot be empty or whitespace-only.\n"
|
|
1208
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1209
|
+
"Example: find_path(from_id=\"480c1f76-...\", to_id=\"300d9716-a3a6-44d3-b0f4-b28002a65da8\")"
|
|
1210
|
+
)
|
|
738
1211
|
|
|
739
1212
|
try:
|
|
1213
|
+
logger.info(f"find_path called - from_id: {from_id_str}, to_id: {to_id_str}")
|
|
740
1214
|
client = await _get_api_client()
|
|
741
|
-
result = await client.find_path(
|
|
1215
|
+
result = await client.find_path(from_id_str, to_id_str)
|
|
742
1216
|
if result.get("status") == "success":
|
|
743
1217
|
path_text = f"Path found (length: {result.get('length', 0)}):\n"
|
|
744
1218
|
for mem in result.get("memories", []):
|
|
@@ -746,27 +1220,97 @@ async def find_path(from_id: str, to_id: str) -> str:
|
|
|
746
1220
|
return path_text
|
|
747
1221
|
return result.get("message", "No path found")
|
|
748
1222
|
except httpx.HTTPStatusError as e:
|
|
1223
|
+
error_detail = e.response.text if e.response else "Unknown error"
|
|
1224
|
+
logger.error(f"API error: {e.response.status_code} - {error_detail}")
|
|
749
1225
|
if e.response.status_code == 401:
|
|
750
|
-
raise ToolError("Authentication failed. Please
|
|
751
|
-
|
|
752
|
-
|
|
1226
|
+
raise ToolError("Authentication failed. Please login using the 'login' tool or configure your JWT token in the MCP client headers.")
|
|
1227
|
+
elif e.response.status_code == 404:
|
|
1228
|
+
raise ToolError(f"One or both memories not found.\nVerify the memory IDs are correct by searching for them first.")
|
|
1229
|
+
raise ToolError(f"Failed to find path: HTTP {e.response.status_code} - {error_detail}")
|
|
1230
|
+
except ToolError:
|
|
1231
|
+
raise
|
|
753
1232
|
except Exception as e:
|
|
754
|
-
logger.error(f"Unexpected error: {e}", exc_info=True)
|
|
1233
|
+
logger.error(f"Unexpected error in find_path: {e}", exc_info=True)
|
|
755
1234
|
raise ToolError(f"Error finding path: {str(e)}")
|
|
756
1235
|
|
|
757
1236
|
|
|
758
1237
|
@mcp.tool()
|
|
759
1238
|
async def get_neighborhood(memory_id: str, hops: int = 2) -> str:
|
|
760
|
-
"""Get all memories within N hops of a given memory. Use this for deep context and understanding relationships around important memories.
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
1239
|
+
"""Get all memories within N hops of a given memory. Use this for deep context and understanding relationships around important memories.
|
|
1240
|
+
|
|
1241
|
+
Parameters:
|
|
1242
|
+
memory_id (str, REQUIRED): Center memory ID to get neighborhood around.
|
|
1243
|
+
- Example: "480c1f76-bcdf-4491-8781-24510db992e3"
|
|
1244
|
+
- Get memory IDs from search_memories() or get_memories() results
|
|
1245
|
+
|
|
1246
|
+
hops (int, optional): Number of hops to traverse. Default is 2.
|
|
1247
|
+
- Must be between 1 and 5
|
|
1248
|
+
- 1 hop = direct connections only
|
|
1249
|
+
- 2 hops = direct connections + their connections
|
|
1250
|
+
- Example: 2 (default)
|
|
1251
|
+
- Example: 3
|
|
1252
|
+
|
|
1253
|
+
Returns:
|
|
1254
|
+
str: Formatted list of memories in the neighborhood with their hop distances.
|
|
1255
|
+
|
|
1256
|
+
Common Errors and Solutions:
|
|
1257
|
+
- Error: "memory_id cannot be empty"
|
|
1258
|
+
Solution: Provide a valid memory ID. Example: get_neighborhood(memory_id="480c1f76-...")
|
|
1259
|
+
|
|
1260
|
+
- Error: "hops must be between 1 and 5"
|
|
1261
|
+
Solution: Provide hops between 1 and 5. Example: get_neighborhood(memory_id="...", hops=3)
|
|
1262
|
+
|
|
1263
|
+
Examples:
|
|
1264
|
+
# Get neighborhood with default 2 hops
|
|
1265
|
+
get_neighborhood(memory_id="480c1f76-bcdf-4491-8781-24510db992e3")
|
|
1266
|
+
|
|
1267
|
+
# Get neighborhood with 3 hops
|
|
1268
|
+
get_neighborhood(memory_id="480c1f76-bcdf-4491-8781-24510db992e3", hops=3)
|
|
1269
|
+
|
|
1270
|
+
# Get direct connections only (1 hop)
|
|
1271
|
+
get_neighborhood(memory_id="480c1f76-bcdf-4491-8781-24510db992e3", hops=1)
|
|
1272
|
+
"""
|
|
1273
|
+
# Validate parameters with detailed error messages
|
|
1274
|
+
if memory_id is None:
|
|
1275
|
+
raise ToolError(
|
|
1276
|
+
"The 'memory_id' parameter is required but was not provided.\n"
|
|
1277
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1278
|
+
"Example: get_neighborhood(memory_id=\"480c1f76-bcdf-4491-8781-24510db992e3\")"
|
|
1279
|
+
)
|
|
1280
|
+
|
|
1281
|
+
if not isinstance(memory_id, str):
|
|
1282
|
+
raise ToolError(
|
|
1283
|
+
f"The 'memory_id' parameter must be a string, but got {type(memory_id).__name__}.\n"
|
|
1284
|
+
f"Received: {repr(memory_id)}\n"
|
|
1285
|
+
"Example: get_neighborhood(memory_id=\"480c1f76-bcdf-4491-8781-24510db992e3\")"
|
|
1286
|
+
)
|
|
1287
|
+
|
|
1288
|
+
memory_id_str = memory_id.strip()
|
|
1289
|
+
if not memory_id_str:
|
|
1290
|
+
raise ToolError(
|
|
1291
|
+
"The 'memory_id' parameter cannot be empty or whitespace-only.\n"
|
|
1292
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1293
|
+
"Example: get_neighborhood(memory_id=\"480c1f76-bcdf-4491-8781-24510db992e3\")"
|
|
1294
|
+
)
|
|
1295
|
+
|
|
1296
|
+
if not isinstance(hops, int):
|
|
1297
|
+
raise ToolError(
|
|
1298
|
+
f"The 'hops' parameter must be an integer, but got {type(hops).__name__}.\n"
|
|
1299
|
+
f"Received: {repr(hops)}\n"
|
|
1300
|
+
"Example: get_neighborhood(memory_id=\"...\", hops=2)"
|
|
1301
|
+
)
|
|
1302
|
+
|
|
764
1303
|
if not (1 <= hops <= 5):
|
|
765
|
-
raise ToolError(
|
|
1304
|
+
raise ToolError(
|
|
1305
|
+
f"The 'hops' parameter must be between 1 and 5, but got {hops}.\n"
|
|
1306
|
+
"Example: get_neighborhood(memory_id=\"...\", hops=2)\n"
|
|
1307
|
+
"Example: get_neighborhood(memory_id=\"...\", hops=3)"
|
|
1308
|
+
)
|
|
766
1309
|
|
|
767
1310
|
try:
|
|
1311
|
+
logger.info(f"get_neighborhood called - memory_id: {memory_id_str}, hops: {hops}")
|
|
768
1312
|
client = await _get_api_client()
|
|
769
|
-
result = await client.get_neighborhood(
|
|
1313
|
+
result = await client.get_neighborhood(memory_id_str, hops)
|
|
770
1314
|
neighborhood_text = f"Neighborhood (hops={result.get('hops', 2)}, total={result.get('total_in_neighborhood', 0)}):\n"
|
|
771
1315
|
for mem in result.get("neighborhood", []):
|
|
772
1316
|
hop_dist = mem.get("hop_distance", 0)
|
|
@@ -774,12 +1318,17 @@ async def get_neighborhood(memory_id: str, hops: int = 2) -> str:
|
|
|
774
1318
|
neighborhood_text += f" [{hop_dist}]{is_center} {mem.get('id', 'unknown')}: {mem.get('content', '')[:100]}\n"
|
|
775
1319
|
return neighborhood_text
|
|
776
1320
|
except httpx.HTTPStatusError as e:
|
|
1321
|
+
error_detail = e.response.text if e.response else "Unknown error"
|
|
1322
|
+
logger.error(f"API error: {e.response.status_code} - {error_detail}")
|
|
777
1323
|
if e.response.status_code == 401:
|
|
778
|
-
raise ToolError("Authentication failed. Please
|
|
779
|
-
|
|
780
|
-
|
|
1324
|
+
raise ToolError("Authentication failed. Please login using the 'login' tool or configure your JWT token in the MCP client headers.")
|
|
1325
|
+
elif e.response.status_code == 404:
|
|
1326
|
+
raise ToolError(f"Memory not found: {memory_id_str}\nVerify the memory_id is correct by searching for it first.")
|
|
1327
|
+
raise ToolError(f"Failed to get neighborhood: HTTP {e.response.status_code} - {error_detail}")
|
|
1328
|
+
except ToolError:
|
|
1329
|
+
raise
|
|
781
1330
|
except Exception as e:
|
|
782
|
-
logger.error(f"Unexpected error: {e}", exc_info=True)
|
|
1331
|
+
logger.error(f"Unexpected error in get_neighborhood: {e}", exc_info=True)
|
|
783
1332
|
raise ToolError(f"Error getting neighborhood: {str(e)}")
|
|
784
1333
|
|
|
785
1334
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mem-brain-mcp
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: MCP Server for Mem-Brain API - Exposes memory operations as MCP tools
|
|
5
5
|
Keywords: ai,claude,cursor,llm,mcp,memory,model-context-protocol
|
|
6
6
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -38,21 +38,40 @@ MCP (Model Context Protocol) server that exposes Mem-Brain API functionality as
|
|
|
38
38
|
- **HTTP/SSE Transport**: Run independently, accessible remotely
|
|
39
39
|
- **CLI Interface**: Packaged for easy global execution
|
|
40
40
|
|
|
41
|
-
##
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
### From PyPI (Recommended)
|
|
44
|
+
|
|
45
|
+
Install from PyPI using `pip` or `uv`:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Using pip
|
|
49
|
+
pip install mem-brain-mcp
|
|
50
|
+
|
|
51
|
+
# Using uv
|
|
52
|
+
uv pip install mem-brain-mcp
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Then run globally:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
mem-brain-mcp
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Instant Execution with uvx
|
|
42
62
|
|
|
43
|
-
You can run the MCP server instantly without manual installation using `
|
|
63
|
+
You can run the MCP server instantly without manual installation using `uvx`:
|
|
44
64
|
|
|
45
65
|
```bash
|
|
46
|
-
# Run using uvx (
|
|
66
|
+
# Run using uvx (uses default API URL)
|
|
47
67
|
uvx mem-brain-mcp
|
|
48
68
|
|
|
49
|
-
#
|
|
50
|
-
export API_BASE_URL=http://your-api-
|
|
69
|
+
# Override API URL or set JWT token if needed
|
|
70
|
+
export API_BASE_URL=http://your-custom-api-url.com
|
|
71
|
+
export MEMBRAIN_API_KEY=your-jwt-token-here
|
|
51
72
|
uvx mem-brain-mcp
|
|
52
73
|
```
|
|
53
74
|
|
|
54
|
-
## Installation
|
|
55
|
-
|
|
56
75
|
### From Source
|
|
57
76
|
|
|
58
77
|
1. Install using `uv` (recommended) or `pip`:
|
|
@@ -70,13 +89,14 @@ mem-brain-mcp
|
|
|
70
89
|
|
|
71
90
|
## Configuration
|
|
72
91
|
|
|
73
|
-
The server reads configuration from environment variables or a `.env` file in the current working directory:
|
|
92
|
+
The server reads configuration from environment variables or a `.env` file in the current working directory. Most settings have sensible defaults:
|
|
74
93
|
|
|
75
94
|
```env
|
|
76
|
-
# API Configuration
|
|
77
|
-
API_BASE_URL=http://
|
|
78
|
-
# NOTE:
|
|
79
|
-
|
|
95
|
+
# API Configuration (optional - defaults to production API)
|
|
96
|
+
API_BASE_URL=http://membrain-api-alb-1094729422.ap-south-1.elb.amazonaws.com
|
|
97
|
+
# NOTE: MEMBRAIN_API_KEY is actually a JWT access token (from login/signup)
|
|
98
|
+
# Per-user JWT tokens are typically configured in MCP clients via headers
|
|
99
|
+
MEMBRAIN_API_KEY=your_jwt_token_here # Optional: fallback for single-user scenarios
|
|
80
100
|
|
|
81
101
|
# MCP Server Configuration
|
|
82
102
|
MCP_SERVER_HOST=0.0.0.0
|
|
@@ -86,9 +106,11 @@ MCP_SERVER_PORT=8100
|
|
|
86
106
|
LOG_LEVEL=INFO
|
|
87
107
|
```
|
|
88
108
|
|
|
89
|
-
|
|
109
|
+
**Note**: The `API_BASE_URL` defaults to the production Mem-Brain API endpoint, so you typically don't need to set it unless you're using a custom API instance.
|
|
110
|
+
|
|
111
|
+
## Per-User JWT Token Configuration
|
|
90
112
|
|
|
91
|
-
Each user must configure their own
|
|
113
|
+
Each user must configure their own JWT access token in their MCP client for proper user isolation. The server extracts tokens from request headers. Get your JWT token by logging in or signing up to the Mem-Brain API.
|
|
92
114
|
|
|
93
115
|
### Cursor IDE (`~/.cursor/mcp.json`)
|
|
94
116
|
|
|
@@ -129,10 +151,20 @@ Add to your Claude Desktop configuration (`~/Library/Application Support/Claude/
|
|
|
129
151
|
"mcpServers": {
|
|
130
152
|
"mem-brain": {
|
|
131
153
|
"command": "uvx",
|
|
132
|
-
"args": ["mem-brain-mcp"]
|
|
154
|
+
"args": ["mem-brain-mcp"]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Note**: After installing from PyPI, you can also use `mem-brain-mcp` directly. The API URL is set by default, but you can override it if needed:
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"mcpServers": {
|
|
164
|
+
"mem-brain": {
|
|
165
|
+
"command": "mem-brain-mcp",
|
|
133
166
|
"env": {
|
|
134
|
-
"API_BASE_URL": "http://your-
|
|
135
|
-
"JWT_SECRET_KEY": "your-secret"
|
|
167
|
+
"API_BASE_URL": "http://your-custom-api-url"
|
|
136
168
|
}
|
|
137
169
|
}
|
|
138
170
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
mem_brain_mcp/__init__.py,sha256=NMoDpXlRhGO858M6lrLqPN0fz7hKpqler4e96WniWxg,89
|
|
2
|
+
mem_brain_mcp/__main__.py,sha256=H_mwoKm1FBmu4KzAcQcq-TXZqeNvlrAekAxB1s4F4hA,712
|
|
3
|
+
mem_brain_mcp/client.py,sha256=7KFGcLoPDaOOLiuG2lygQK7xH5Kio-YifDjuSpDoDJ8,6993
|
|
4
|
+
mem_brain_mcp/config.py,sha256=xx2lBkCIeT85t0HxtORwZHSU3hZT_EdsThpfjwPJhbQ,1261
|
|
5
|
+
mem_brain_mcp/server.py,sha256=qnevDzJkt1rH-slV2r0pP_SeFkknZVqWRnZEKH_IeVo,66308
|
|
6
|
+
mem_brain_mcp-1.0.2.dist-info/METADATA,sha256=zLQMVD-sIRSH03dYAP6RovZH_49XqREG061Yf87ROW4,5228
|
|
7
|
+
mem_brain_mcp-1.0.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
8
|
+
mem_brain_mcp-1.0.2.dist-info/entry_points.txt,sha256=NH6QYQ-Sd8eJn5crpe_DL1PvGeUlL3y65968xPhmwG8,62
|
|
9
|
+
mem_brain_mcp-1.0.2.dist-info/RECORD,,
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
mem_brain_mcp/__init__.py,sha256=DifX5h1zBo_ResWlU7kT_HFCeptgPaJouZ03wxBUSqc,89
|
|
2
|
-
mem_brain_mcp/__main__.py,sha256=H_mwoKm1FBmu4KzAcQcq-TXZqeNvlrAekAxB1s4F4hA,712
|
|
3
|
-
mem_brain_mcp/client.py,sha256=7KFGcLoPDaOOLiuG2lygQK7xH5Kio-YifDjuSpDoDJ8,6993
|
|
4
|
-
mem_brain_mcp/config.py,sha256=mu2HQPKkEjV_2QnlQ2gq7u0niXIvDCwpOgo7dRJjEcI,1055
|
|
5
|
-
mem_brain_mcp/server.py,sha256=mlbl3-D3OFb4HHP54daqiP3nMWEdiTv3upEdUFnlhAA,39604
|
|
6
|
-
mem_brain_mcp-1.0.0.dist-info/METADATA,sha256=xeGMlWratQ-1jPrI3A8E3hc_AdrxI9VLOBPZStgWmWk,4327
|
|
7
|
-
mem_brain_mcp-1.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
8
|
-
mem_brain_mcp-1.0.0.dist-info/entry_points.txt,sha256=NH6QYQ-Sd8eJn5crpe_DL1PvGeUlL3y65968xPhmwG8,62
|
|
9
|
-
mem_brain_mcp-1.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|