mem-brain-mcp 1.0.1__py3-none-any.whl → 1.0.3__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/server.py +786 -195
- {mem_brain_mcp-1.0.1.dist-info → mem_brain_mcp-1.0.3.dist-info}/METADATA +1 -1
- mem_brain_mcp-1.0.3.dist-info/RECORD +9 -0
- mem_brain_mcp-1.0.1.dist-info/RECORD +0 -9
- {mem_brain_mcp-1.0.1.dist-info → mem_brain_mcp-1.0.3.dist-info}/WHEEL +0 -0
- {mem_brain_mcp-1.0.1.dist-info → mem_brain_mcp-1.0.3.dist-info}/entry_points.txt +0 -0
mem_brain_mcp/__init__.py
CHANGED
mem_brain_mcp/server.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""MCP Server for Mem-Brain API using FastMCP."""
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
import logging
|
|
4
5
|
from typing import Any, Dict, List, Optional, Union
|
|
5
6
|
import httpx
|
|
@@ -380,112 +381,6 @@ async def refresh_context() -> PromptMessage:
|
|
|
380
381
|
# TOOLS (Operations)
|
|
381
382
|
# ============================================================================
|
|
382
383
|
|
|
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
384
|
@mcp.tool()
|
|
490
385
|
async def get_agent_instructions(include_dynamic_context: bool = True) -> str:
|
|
491
386
|
"""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,53 +403,128 @@ async def add_memory(
|
|
|
508
403
|
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
404
|
|
|
510
405
|
Parameters:
|
|
511
|
-
content (str,
|
|
512
|
-
|
|
513
|
-
|
|
406
|
+
content (str, REQUIRED): The memory content to store. Must be a non-empty string.
|
|
407
|
+
- Cannot be None, empty string, or whitespace-only
|
|
408
|
+
- Example: "User prefers Python over JavaScript"
|
|
409
|
+
- Example: "User prefers dark mode interfaces"
|
|
410
|
+
|
|
411
|
+
tags (list[str] or str, optional): Tags to categorize the memory.
|
|
412
|
+
- Can be None (default), a list of strings, a comma-separated string, or a JSON array string
|
|
413
|
+
- If omitted, the system will auto-generate tags based on content
|
|
414
|
+
- Example: ["coding", "preferences"]
|
|
415
|
+
- Example: "coding,preferences" (comma-separated)
|
|
416
|
+
- Example: '["coding", "preferences"]' (JSON string)
|
|
417
|
+
- Note: The system auto-generates relevant tags, so providing tags is optional
|
|
418
|
+
|
|
419
|
+
category (str, optional): Category name for the memory.
|
|
420
|
+
- Can be None (default) or a non-empty string
|
|
421
|
+
- Example: "interests"
|
|
422
|
+
- Example: "preferences"
|
|
514
423
|
|
|
515
424
|
Returns:
|
|
516
425
|
str: A formatted string with the memory ID and details of the created memory.
|
|
517
426
|
|
|
427
|
+
Common Errors and Solutions:
|
|
428
|
+
- Error: "Tool call arguments for mcp were invalid"
|
|
429
|
+
Solution: Ensure 'content' parameter is provided as a string. Example: add_memory(content="User prefers dark mode")
|
|
430
|
+
|
|
431
|
+
- Error: "The 'content' parameter cannot be empty"
|
|
432
|
+
Solution: Provide non-empty content. Example: add_memory(content="User loves Python programming")
|
|
433
|
+
|
|
434
|
+
- Error: "tags must be a list"
|
|
435
|
+
Solution: Pass tags as a list. Example: add_memory(content="...", tags=["coding"]) not tags="coding"
|
|
436
|
+
|
|
518
437
|
Example workflow:
|
|
519
438
|
1. search_memories(query="User prefers Python") # Check for existing memories
|
|
520
439
|
2. If no similar memory found, then: add_memory(content="User prefers Python over JavaScript", tags=["coding", "preferences"])
|
|
521
440
|
|
|
522
|
-
|
|
441
|
+
Examples:
|
|
442
|
+
# Basic usage (required parameter only)
|
|
443
|
+
add_memory(content="User prefers dark mode")
|
|
444
|
+
|
|
445
|
+
# With tags
|
|
446
|
+
add_memory(content="User loves Python programming", tags=["coding", "preferences"])
|
|
447
|
+
|
|
448
|
+
# With tags and category
|
|
523
449
|
add_memory(
|
|
524
450
|
content="User loves working with TypeScript",
|
|
525
451
|
tags=["coding", "typescript"],
|
|
526
452
|
category="interests"
|
|
527
453
|
)
|
|
454
|
+
|
|
455
|
+
# Tags as empty list (treated as None)
|
|
456
|
+
add_memory(content="User prefers coffee", tags=[])
|
|
528
457
|
"""
|
|
529
458
|
# Validate parameters with detailed error messages
|
|
530
459
|
if content is None:
|
|
531
|
-
raise ToolError(
|
|
460
|
+
raise ToolError(
|
|
461
|
+
"The 'content' parameter is required but was not provided.\n"
|
|
462
|
+
"Example: add_memory(content=\"User prefers dark mode\")\n"
|
|
463
|
+
"Example: add_memory(content=\"User loves Python programming\", tags=[\"coding\"])"
|
|
464
|
+
)
|
|
532
465
|
|
|
533
466
|
if not isinstance(content, str):
|
|
534
|
-
raise ToolError(
|
|
467
|
+
raise ToolError(
|
|
468
|
+
f"The 'content' parameter must be a string, but got {type(content).__name__}.\n"
|
|
469
|
+
f"Received: {repr(content)}\n"
|
|
470
|
+
"Example: add_memory(content=\"User prefers dark mode\")"
|
|
471
|
+
)
|
|
535
472
|
|
|
536
473
|
content_str = str(content).strip()
|
|
537
474
|
if not content_str:
|
|
538
|
-
raise ToolError(
|
|
475
|
+
raise ToolError(
|
|
476
|
+
"The 'content' parameter cannot be empty or whitespace-only.\n"
|
|
477
|
+
"Please provide a non-empty string with actual content.\n"
|
|
478
|
+
"Example: add_memory(content=\"User prefers dark mode\")\n"
|
|
479
|
+
"Example: add_memory(content=\"User loves Python programming\")"
|
|
480
|
+
)
|
|
539
481
|
|
|
540
482
|
try:
|
|
541
483
|
logger.info(f"add_memory called - content length: {len(content_str)}, tags: {tags}, category: {category}")
|
|
542
484
|
logger.debug(f"add_memory full content: {content_str[:100]}...")
|
|
543
485
|
|
|
544
|
-
# Normalize tags:
|
|
486
|
+
# Normalize tags: handle various input formats and convert to list of strings
|
|
545
487
|
normalized_tags = None
|
|
546
488
|
if tags is not None:
|
|
547
489
|
if isinstance(tags, list):
|
|
490
|
+
# Validate list contents are strings
|
|
491
|
+
if tags:
|
|
492
|
+
invalid_items = [item for item in tags if not isinstance(item, str)]
|
|
493
|
+
if invalid_items:
|
|
494
|
+
raise ToolError(
|
|
495
|
+
f"The 'tags' parameter must be a list of strings, but found non-string items: {invalid_items}\n"
|
|
496
|
+
f"Example: add_memory(content=\"...\", tags=[\"coding\", \"preferences\"])\n"
|
|
497
|
+
f"Example: add_memory(content=\"...\", tags=[\"personal\", \"pets\"])"
|
|
498
|
+
)
|
|
548
499
|
normalized_tags = tags if tags else None # Empty list becomes None
|
|
549
500
|
elif isinstance(tags, str):
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
else:
|
|
553
|
-
try:
|
|
554
|
-
normalized_tags = list(tags) if tags else None
|
|
555
|
-
except (TypeError, ValueError):
|
|
556
|
-
logger.warning(f"Could not convert tags to list: {tags}")
|
|
501
|
+
tags_str = tags.strip()
|
|
502
|
+
if not tags_str:
|
|
557
503
|
normalized_tags = None
|
|
504
|
+
else:
|
|
505
|
+
# Try to parse as JSON array first (e.g., '["tag1", "tag2"]')
|
|
506
|
+
try:
|
|
507
|
+
parsed = json.loads(tags_str)
|
|
508
|
+
if isinstance(parsed, list):
|
|
509
|
+
normalized_tags = [str(item).strip() for item in parsed if str(item).strip()]
|
|
510
|
+
else:
|
|
511
|
+
# If JSON but not a list, treat as single tag
|
|
512
|
+
normalized_tags = [tags_str]
|
|
513
|
+
except (json.JSONDecodeError, ValueError):
|
|
514
|
+
# Not JSON, try comma-separated string
|
|
515
|
+
if ',' in tags_str:
|
|
516
|
+
normalized_tags = [tag.strip() for tag in tags_str.split(',') if tag.strip()]
|
|
517
|
+
else:
|
|
518
|
+
# Single tag string
|
|
519
|
+
normalized_tags = [tags_str]
|
|
520
|
+
else:
|
|
521
|
+
raise ToolError(
|
|
522
|
+
f"The 'tags' parameter must be a list of strings, a comma-separated string, or None, but got {type(tags).__name__}.\n"
|
|
523
|
+
f"Received: {repr(tags)}\n"
|
|
524
|
+
"Example: add_memory(content=\"...\", tags=[\"coding\", \"preferences\"])\n"
|
|
525
|
+
"Example: add_memory(content=\"...\", tags=\"coding,preferences\")\n"
|
|
526
|
+
"Example: add_memory(content=\"...\", tags=None) # or omit tags parameter"
|
|
527
|
+
)
|
|
558
528
|
|
|
559
529
|
# Normalize category: convert empty string to None
|
|
560
530
|
normalized_category = category.strip() if category and isinstance(category, str) and category.strip() else None
|
|
@@ -586,49 +556,188 @@ async def add_memory(
|
|
|
586
556
|
|
|
587
557
|
@mcp.tool()
|
|
588
558
|
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
|
-
|
|
559
|
+
"""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.
|
|
560
|
+
|
|
561
|
+
Parameters:
|
|
562
|
+
query (str, REQUIRED): Search query string. Use natural language questions, not keywords.
|
|
563
|
+
- Example: "Who is Rakshith and what did he build?"
|
|
564
|
+
- Example: "What are the user's preferences for programming languages?"
|
|
565
|
+
- Example: "Tell me about memories related to the Dubai presentation"
|
|
566
|
+
|
|
567
|
+
k (int, optional): Number of results to return. Default is 5.
|
|
568
|
+
- Must be between 1 and 100
|
|
569
|
+
- Example: 5 (default)
|
|
570
|
+
- Example: 10
|
|
571
|
+
|
|
572
|
+
Returns:
|
|
573
|
+
str: Formatted search results with memory nodes and relationship edges.
|
|
574
|
+
|
|
575
|
+
Common Errors and Solutions:
|
|
576
|
+
- Error: "Query cannot be empty"
|
|
577
|
+
Solution: Provide a non-empty search query. Example: search_memories(query="What is the user's name?")
|
|
578
|
+
|
|
579
|
+
- Error: "k must be between 1 and 100"
|
|
580
|
+
Solution: Provide k between 1 and 100. Example: search_memories(query="...", k=10)
|
|
581
|
+
|
|
582
|
+
Examples:
|
|
583
|
+
# Basic search
|
|
584
|
+
search_memories(query="Who is Rakshith?")
|
|
585
|
+
|
|
586
|
+
# Search with more results
|
|
587
|
+
search_memories(query="What are the user's programming preferences?", k=10)
|
|
588
|
+
|
|
589
|
+
# Complex query
|
|
590
|
+
search_memories(query="Tell me about memories related to mem-brain and its features")
|
|
591
|
+
"""
|
|
592
|
+
# Validate parameters with detailed error messages
|
|
593
|
+
if query is None:
|
|
594
|
+
raise ToolError(
|
|
595
|
+
"The 'query' parameter is required but was not provided.\n"
|
|
596
|
+
"Example: search_memories(query=\"Who is Rakshith?\")\n"
|
|
597
|
+
"Example: search_memories(query=\"What are the user's preferences?\")"
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
if not isinstance(query, str):
|
|
601
|
+
raise ToolError(
|
|
602
|
+
f"The 'query' parameter must be a string, but got {type(query).__name__}.\n"
|
|
603
|
+
f"Received: {repr(query)}\n"
|
|
604
|
+
"Example: search_memories(query=\"Who is Rakshith?\")"
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
query_str = query.strip()
|
|
608
|
+
if not query_str:
|
|
609
|
+
raise ToolError(
|
|
610
|
+
"The 'query' parameter cannot be empty or whitespace-only.\n"
|
|
611
|
+
"Provide a natural language question or search query.\n"
|
|
612
|
+
"Example: search_memories(query=\"Who is Rakshith?\")\n"
|
|
613
|
+
"Example: search_memories(query=\"What are the user's preferences?\")"
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
if not isinstance(k, int):
|
|
617
|
+
raise ToolError(
|
|
618
|
+
f"The 'k' parameter must be an integer, but got {type(k).__name__}.\n"
|
|
619
|
+
f"Received: {repr(k)}\n"
|
|
620
|
+
"Example: search_memories(query=\"...\", k=10)"
|
|
621
|
+
)
|
|
622
|
+
|
|
593
623
|
if not (1 <= k <= 100):
|
|
594
|
-
raise ToolError(
|
|
624
|
+
raise ToolError(
|
|
625
|
+
f"The 'k' parameter must be between 1 and 100, but got {k}.\n"
|
|
626
|
+
"Example: search_memories(query=\"...\", k=5)\n"
|
|
627
|
+
"Example: search_memories(query=\"...\", k=10)"
|
|
628
|
+
)
|
|
595
629
|
|
|
596
630
|
try:
|
|
631
|
+
logger.info(f"search_memories called - query length: {len(query_str)}, k: {k}")
|
|
597
632
|
client = await _get_api_client()
|
|
598
|
-
result = await client.search_memories(
|
|
633
|
+
result = await client.search_memories(query_str, k)
|
|
599
634
|
return f"Found {result.get('count', 0)} results:\n{_format_search_results(result.get('results', []))}"
|
|
600
635
|
except httpx.HTTPStatusError as e:
|
|
636
|
+
error_detail = e.response.text if e.response else "Unknown error"
|
|
637
|
+
logger.error(f"API error: {e.response.status_code} - {error_detail}")
|
|
601
638
|
if e.response.status_code == 401:
|
|
602
|
-
raise ToolError("Authentication failed. Please
|
|
603
|
-
|
|
604
|
-
|
|
639
|
+
raise ToolError("Authentication failed. Please login using the 'login' tool or configure your JWT token in the MCP client headers.")
|
|
640
|
+
raise ToolError(f"Failed to search memories: HTTP {e.response.status_code} - {error_detail}")
|
|
641
|
+
except ToolError:
|
|
642
|
+
raise
|
|
605
643
|
except Exception as e:
|
|
606
|
-
logger.error(f"Unexpected error: {e}", exc_info=True)
|
|
644
|
+
logger.error(f"Unexpected error in search_memories: {e}", exc_info=True)
|
|
607
645
|
raise ToolError(f"Error searching memories: {str(e)}")
|
|
608
646
|
|
|
609
647
|
|
|
610
648
|
@mcp.tool()
|
|
611
649
|
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
|
-
|
|
650
|
+
"""Retrieve one or more memories by ID. Use this when you need full details for specific memories identified from search results.
|
|
651
|
+
|
|
652
|
+
Parameters:
|
|
653
|
+
memory_ids (list[str], REQUIRED): List of memory IDs to retrieve. Must be a non-empty list.
|
|
654
|
+
- Example: ["480c1f76-bcdf-4491-8781-24510db992e3"]
|
|
655
|
+
- Example: ["480c1f76-...", "300d9716-...", "6fb6b23f-..."]
|
|
656
|
+
- Get memory IDs from search_memories() results
|
|
657
|
+
|
|
658
|
+
Returns:
|
|
659
|
+
str: Formatted details of the retrieved memories.
|
|
660
|
+
|
|
661
|
+
Common Errors and Solutions:
|
|
662
|
+
- Error: "memory_ids cannot be empty"
|
|
663
|
+
Solution: Provide a list with at least one memory ID. Example: get_memories(memory_ids=["480c1f76-..."])
|
|
664
|
+
|
|
665
|
+
- Error: "Memory IDs cannot be empty"
|
|
666
|
+
Solution: Ensure all IDs in the list are non-empty strings. Example: get_memories(memory_ids=["480c1f76-..."])
|
|
667
|
+
|
|
668
|
+
- Error: "memory_ids must be a list"
|
|
669
|
+
Solution: Pass memory_ids as a list. Example: get_memories(memory_ids=["..."]) not memory_ids="..."
|
|
670
|
+
|
|
671
|
+
Examples:
|
|
672
|
+
# Get single memory
|
|
673
|
+
get_memories(memory_ids=["480c1f76-bcdf-4491-8781-24510db992e3"])
|
|
674
|
+
|
|
675
|
+
# Get multiple memories
|
|
676
|
+
get_memories(memory_ids=["480c1f76-...", "300d9716-...", "6fb6b23f-..."])
|
|
677
|
+
"""
|
|
678
|
+
# Validate parameters with detailed error messages
|
|
679
|
+
if memory_ids is None:
|
|
680
|
+
raise ToolError(
|
|
681
|
+
"The 'memory_ids' parameter is required but was not provided.\n"
|
|
682
|
+
"Example: get_memories(memory_ids=[\"480c1f76-bcdf-4491-8781-24510db992e3\"])\n"
|
|
683
|
+
"Example: get_memories(memory_ids=[\"480c1f76-...\", \"300d9716-...\"])"
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
if not isinstance(memory_ids, list):
|
|
687
|
+
raise ToolError(
|
|
688
|
+
f"The 'memory_ids' parameter must be a list of strings, but got {type(memory_ids).__name__}.\n"
|
|
689
|
+
f"Received: {repr(memory_ids)}\n"
|
|
690
|
+
"Example: get_memories(memory_ids=[\"480c1f76-...\"])"
|
|
691
|
+
)
|
|
692
|
+
|
|
614
693
|
if not memory_ids:
|
|
615
|
-
raise ToolError(
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
694
|
+
raise ToolError(
|
|
695
|
+
"The 'memory_ids' parameter cannot be an empty list.\n"
|
|
696
|
+
"Provide at least one memory ID.\n"
|
|
697
|
+
"Example: get_memories(memory_ids=[\"480c1f76-bcdf-4491-8781-24510db992e3\"])"
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
# Validate each memory ID in the list
|
|
701
|
+
validated_ids = []
|
|
702
|
+
for i, memory_id in enumerate(memory_ids):
|
|
703
|
+
if memory_id is None:
|
|
704
|
+
raise ToolError(
|
|
705
|
+
f"Memory ID at index {i} is None. All memory IDs must be non-empty strings.\n"
|
|
706
|
+
"Example: get_memories(memory_ids=[\"480c1f76-...\"])"
|
|
707
|
+
)
|
|
708
|
+
if not isinstance(memory_id, str):
|
|
709
|
+
raise ToolError(
|
|
710
|
+
f"Memory ID at index {i} must be a string, but got {type(memory_id).__name__}.\n"
|
|
711
|
+
f"Received: {repr(memory_id)}\n"
|
|
712
|
+
"Example: get_memories(memory_ids=[\"480c1f76-...\"])"
|
|
713
|
+
)
|
|
714
|
+
memory_id_str = memory_id.strip()
|
|
715
|
+
if not memory_id_str:
|
|
716
|
+
raise ToolError(
|
|
717
|
+
f"Memory ID at index {i} cannot be empty or whitespace-only.\n"
|
|
718
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
719
|
+
"Example: get_memories(memory_ids=[\"480c1f76-bcdf-4491-8781-24510db992e3\"])"
|
|
720
|
+
)
|
|
721
|
+
validated_ids.append(memory_id_str)
|
|
619
722
|
|
|
620
723
|
try:
|
|
724
|
+
logger.info(f"get_memories called - count: {len(validated_ids)}")
|
|
621
725
|
client = await _get_api_client()
|
|
622
|
-
result = await client.get_memories(
|
|
726
|
+
result = await client.get_memories(validated_ids)
|
|
623
727
|
memories = result.get("memories", [])
|
|
624
728
|
return f"Retrieved {len(memories)} memories:\n{_format_memories_list(memories)}"
|
|
625
729
|
except httpx.HTTPStatusError as e:
|
|
730
|
+
error_detail = e.response.text if e.response else "Unknown error"
|
|
731
|
+
logger.error(f"API error: {e.response.status_code} - {error_detail}")
|
|
626
732
|
if e.response.status_code == 401:
|
|
627
|
-
raise ToolError("Authentication failed. Please
|
|
628
|
-
|
|
629
|
-
|
|
733
|
+
raise ToolError("Authentication failed. Please login using the 'login' tool or configure your JWT token in the MCP client headers.")
|
|
734
|
+
elif e.response.status_code == 404:
|
|
735
|
+
raise ToolError(f"One or more memories not found.\nVerify the memory IDs are correct by searching for them first.")
|
|
736
|
+
raise ToolError(f"Failed to get memories: HTTP {e.response.status_code} - {error_detail}")
|
|
737
|
+
except ToolError:
|
|
738
|
+
raise
|
|
630
739
|
except Exception as e:
|
|
631
|
-
logger.error(f"Unexpected error: {e}", exc_info=True)
|
|
740
|
+
logger.error(f"Unexpected error in get_memories: {e}", exc_info=True)
|
|
632
741
|
raise ToolError(f"Error getting memories: {str(e)}")
|
|
633
742
|
|
|
634
743
|
|
|
@@ -638,25 +747,168 @@ async def update_memory(
|
|
|
638
747
|
content: Optional[str] = None,
|
|
639
748
|
tags: Optional[List[str]] = None
|
|
640
749
|
) -> 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
|
-
|
|
750
|
+
"""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.
|
|
751
|
+
|
|
752
|
+
Parameters:
|
|
753
|
+
memory_id (str, REQUIRED): The ID of the memory to update. Must be a non-empty string.
|
|
754
|
+
- Example: "480c1f76-bcdf-4491-8781-24510db992e3"
|
|
755
|
+
- Get memory IDs from search_memories() or get_memories() results
|
|
756
|
+
|
|
757
|
+
content (str, optional): New content for the memory.
|
|
758
|
+
- Can be None (to keep existing content) or a non-empty string
|
|
759
|
+
- If provided, must not be empty or whitespace-only
|
|
760
|
+
- Example: "User no longer likes TypeScript, prefers Python"
|
|
761
|
+
|
|
762
|
+
tags (list[str] or str, optional): New tags for the memory.
|
|
763
|
+
- Can be None (to keep existing tags), a list of strings, a comma-separated string, or a JSON array string
|
|
764
|
+
- If provided, replaces existing tags
|
|
765
|
+
- Example: ["coding", "python"]
|
|
766
|
+
- Example: "coding,python" (comma-separated)
|
|
767
|
+
- Example: '["coding", "python"]' (JSON string)
|
|
768
|
+
- Note: The system can auto-generate tags if you omit this parameter
|
|
769
|
+
|
|
770
|
+
Returns:
|
|
771
|
+
str: A formatted string with the updated memory details.
|
|
772
|
+
|
|
773
|
+
Common Errors and Solutions:
|
|
774
|
+
- Error: "Tool call arguments for mcp were invalid"
|
|
775
|
+
Solution: Ensure 'memory_id' parameter is provided as a string. Example: update_memory(memory_id="...")
|
|
776
|
+
|
|
777
|
+
- Error: "memory_id cannot be empty"
|
|
778
|
+
Solution: Provide a valid memory ID from search results. Example: update_memory(memory_id="480c1f76-...")
|
|
779
|
+
|
|
780
|
+
- Error: "At least one of 'content' or 'tags' must be provided"
|
|
781
|
+
Solution: Provide content or tags to update. Example: update_memory(memory_id="...", content="New content")
|
|
782
|
+
|
|
783
|
+
Examples:
|
|
784
|
+
# Update content only
|
|
785
|
+
update_memory(memory_id="480c1f76-...", content="User prefers Python over JavaScript")
|
|
786
|
+
|
|
787
|
+
# Update tags only
|
|
788
|
+
update_memory(memory_id="480c1f76-...", tags=["coding", "preferences"])
|
|
789
|
+
|
|
790
|
+
# Update both content and tags
|
|
791
|
+
update_memory(
|
|
792
|
+
memory_id="480c1f76-...",
|
|
793
|
+
content="User no longer likes TypeScript",
|
|
794
|
+
tags=["coding", "python"]
|
|
795
|
+
)
|
|
796
|
+
"""
|
|
797
|
+
# Validate parameters with detailed error messages
|
|
798
|
+
if memory_id is None:
|
|
799
|
+
raise ToolError(
|
|
800
|
+
"The 'memory_id' parameter is required but was not provided.\n"
|
|
801
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
802
|
+
"Example: update_memory(memory_id=\"480c1f76-bcdf-4491-8781-24510db992e3\", content=\"New content\")"
|
|
803
|
+
)
|
|
804
|
+
|
|
805
|
+
if not isinstance(memory_id, str):
|
|
806
|
+
raise ToolError(
|
|
807
|
+
f"The 'memory_id' parameter must be a string, but got {type(memory_id).__name__}.\n"
|
|
808
|
+
f"Received: {repr(memory_id)}\n"
|
|
809
|
+
"Example: update_memory(memory_id=\"480c1f76-...\", content=\"New content\")"
|
|
810
|
+
)
|
|
811
|
+
|
|
812
|
+
memory_id_str = memory_id.strip()
|
|
813
|
+
if not memory_id_str:
|
|
814
|
+
raise ToolError(
|
|
815
|
+
"The 'memory_id' parameter cannot be empty or whitespace-only.\n"
|
|
816
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
817
|
+
"Example: update_memory(memory_id=\"480c1f76-bcdf-4491-8781-24510db992e3\", content=\"New content\")"
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
# Validate that at least one update parameter is provided
|
|
821
|
+
if content is None and tags is None:
|
|
822
|
+
raise ToolError(
|
|
823
|
+
"At least one of 'content' or 'tags' must be provided to update the memory.\n"
|
|
824
|
+
"Example: update_memory(memory_id=\"...\", content=\"New content\")\n"
|
|
825
|
+
"Example: update_memory(memory_id=\"...\", tags=[\"new\", \"tags\"])"
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
# Validate content if provided
|
|
829
|
+
if content is not None:
|
|
830
|
+
if not isinstance(content, str):
|
|
831
|
+
raise ToolError(
|
|
832
|
+
f"The 'content' parameter must be a string or None, but got {type(content).__name__}.\n"
|
|
833
|
+
f"Received: {repr(content)}\n"
|
|
834
|
+
"Example: update_memory(memory_id=\"...\", content=\"New content\")"
|
|
835
|
+
)
|
|
836
|
+
content_str = str(content).strip()
|
|
837
|
+
if not content_str:
|
|
838
|
+
raise ToolError(
|
|
839
|
+
"The 'content' parameter cannot be empty or whitespace-only.\n"
|
|
840
|
+
"Provide a non-empty string or omit the parameter to keep existing content.\n"
|
|
841
|
+
"Example: update_memory(memory_id=\"...\", content=\"New content\")"
|
|
842
|
+
)
|
|
843
|
+
else:
|
|
844
|
+
content_str = None
|
|
845
|
+
|
|
846
|
+
# Validate tags if provided - handle various input formats
|
|
847
|
+
normalized_tags = None
|
|
848
|
+
if tags is not None:
|
|
849
|
+
if isinstance(tags, list):
|
|
850
|
+
# Validate list contents are strings
|
|
851
|
+
if tags:
|
|
852
|
+
invalid_items = [item for item in tags if not isinstance(item, str)]
|
|
853
|
+
if invalid_items:
|
|
854
|
+
raise ToolError(
|
|
855
|
+
f"The 'tags' parameter must be a list of strings, but found non-string items: {invalid_items}\n"
|
|
856
|
+
"Example: update_memory(memory_id=\"...\", tags=[\"coding\", \"preferences\"])\n"
|
|
857
|
+
"Example: update_memory(memory_id=\"...\", tags=None) # or omit tags parameter"
|
|
858
|
+
)
|
|
859
|
+
normalized_tags = tags if tags else None # Empty list becomes None
|
|
860
|
+
elif isinstance(tags, str):
|
|
861
|
+
tags_str = tags.strip()
|
|
862
|
+
if not tags_str:
|
|
863
|
+
normalized_tags = None
|
|
864
|
+
else:
|
|
865
|
+
# Try to parse as JSON array first (e.g., '["tag1", "tag2"]')
|
|
866
|
+
try:
|
|
867
|
+
parsed = json.loads(tags_str)
|
|
868
|
+
if isinstance(parsed, list):
|
|
869
|
+
normalized_tags = [str(item).strip() for item in parsed if str(item).strip()]
|
|
870
|
+
else:
|
|
871
|
+
# If JSON but not a list, treat as single tag
|
|
872
|
+
normalized_tags = [tags_str]
|
|
873
|
+
except (json.JSONDecodeError, ValueError):
|
|
874
|
+
# Not JSON, try comma-separated string
|
|
875
|
+
if ',' in tags_str:
|
|
876
|
+
normalized_tags = [tag.strip() for tag in tags_str.split(',') if tag.strip()]
|
|
877
|
+
else:
|
|
878
|
+
# Single tag string
|
|
879
|
+
normalized_tags = [tags_str]
|
|
880
|
+
else:
|
|
881
|
+
raise ToolError(
|
|
882
|
+
f"The 'tags' parameter must be a list of strings, a comma-separated string, or None, but got {type(tags).__name__}.\n"
|
|
883
|
+
f"Received: {repr(tags)}\n"
|
|
884
|
+
"Example: update_memory(memory_id=\"...\", tags=[\"coding\", \"preferences\"])\n"
|
|
885
|
+
"Example: update_memory(memory_id=\"...\", tags=\"coding,preferences\")\n"
|
|
886
|
+
"Example: update_memory(memory_id=\"...\", tags=None) # or omit tags parameter"
|
|
887
|
+
)
|
|
645
888
|
|
|
646
889
|
try:
|
|
890
|
+
logger.info(f"update_memory called - memory_id: {memory_id_str}, content length: {len(content_str) if content_str else 0}, tags: {normalized_tags}")
|
|
891
|
+
|
|
647
892
|
client = await _get_api_client()
|
|
648
|
-
result = await client.update_memory(
|
|
893
|
+
result = await client.update_memory(memory_id_str, content_str, normalized_tags)
|
|
649
894
|
memory = result.get('memory')
|
|
650
895
|
if memory:
|
|
651
896
|
return f"Memory updated:\n{_format_memory(memory)}"
|
|
652
|
-
return f"Memory {
|
|
897
|
+
return f"Memory {memory_id_str} updated"
|
|
653
898
|
except httpx.HTTPStatusError as e:
|
|
899
|
+
error_detail = e.response.text if e.response else "Unknown error"
|
|
900
|
+
logger.error(f"API error: {e.response.status_code} - {error_detail}")
|
|
654
901
|
if e.response.status_code == 401:
|
|
655
|
-
raise ToolError("Authentication failed. Please
|
|
656
|
-
|
|
657
|
-
|
|
902
|
+
raise ToolError("Authentication failed. Please login using the 'login' tool or configure your JWT token in the MCP client headers.")
|
|
903
|
+
elif e.response.status_code == 400:
|
|
904
|
+
raise ToolError(f"Invalid request: {error_detail}\nExample: update_memory(memory_id=\"...\", content=\"New content\")")
|
|
905
|
+
elif e.response.status_code == 404:
|
|
906
|
+
raise ToolError(f"Memory not found: {memory_id_str}\nVerify the memory_id is correct by searching for it first.")
|
|
907
|
+
raise ToolError(f"Failed to update memory: HTTP {e.response.status_code} - {error_detail}")
|
|
908
|
+
except ToolError:
|
|
909
|
+
raise
|
|
658
910
|
except Exception as e:
|
|
659
|
-
logger.error(f"Unexpected error: {e}", exc_info=True)
|
|
911
|
+
logger.error(f"Unexpected error in update_memory: {e}", exc_info=True)
|
|
660
912
|
raise ToolError(f"Error updating memory: {str(e)}")
|
|
661
913
|
|
|
662
914
|
|
|
@@ -666,49 +918,243 @@ async def delete_memories(
|
|
|
666
918
|
tags: Optional[str] = None,
|
|
667
919
|
category: Optional[str] = None
|
|
668
920
|
) -> 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.
|
|
921
|
+
"""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.
|
|
922
|
+
|
|
923
|
+
Parameters:
|
|
924
|
+
memory_id (str, optional): Specific memory ID to delete. Takes precedence over filters.
|
|
925
|
+
- Example: "480c1f76-bcdf-4491-8781-24510db992e3"
|
|
926
|
+
- Get memory IDs from search_memories() or get_memories() results
|
|
927
|
+
|
|
928
|
+
tags (str, optional): Comma-separated tags for filter-based deletion.
|
|
929
|
+
- Example: "coding,preferences"
|
|
930
|
+
- Example: "personal,pets"
|
|
931
|
+
- Only used if memory_id is not provided
|
|
932
|
+
|
|
933
|
+
category (str, optional): Category name for filter-based deletion.
|
|
934
|
+
- Example: "interests"
|
|
935
|
+
- Example: "preferences"
|
|
936
|
+
- Only used if memory_id is not provided
|
|
937
|
+
|
|
938
|
+
Returns:
|
|
939
|
+
str: A message indicating how many memories were deleted and their IDs.
|
|
940
|
+
|
|
941
|
+
Common Errors and Solutions:
|
|
942
|
+
- Error: "At least one parameter must be provided"
|
|
943
|
+
Solution: Provide memory_id, tags, or category. Example: delete_memories(memory_id="...")
|
|
944
|
+
|
|
945
|
+
- Error: "memory_id cannot be empty"
|
|
946
|
+
Solution: Provide a valid memory ID or omit the parameter. Example: delete_memories(memory_id="480c1f76-...")
|
|
947
|
+
|
|
948
|
+
Examples:
|
|
949
|
+
# Delete by memory ID
|
|
950
|
+
delete_memories(memory_id="480c1f76-bcdf-4491-8781-24510db992e3")
|
|
951
|
+
|
|
952
|
+
# Delete by tags
|
|
953
|
+
delete_memories(tags="coding,preferences")
|
|
954
|
+
|
|
955
|
+
# Delete by category
|
|
956
|
+
delete_memories(category="interests")
|
|
957
|
+
"""
|
|
958
|
+
# Validate that at least one parameter is provided
|
|
959
|
+
if memory_id is None and tags is None and category is None:
|
|
960
|
+
raise ToolError(
|
|
961
|
+
"At least one parameter (memory_id, tags, or category) must be provided to delete memories.\n"
|
|
962
|
+
"Example: delete_memories(memory_id=\"480c1f76-bcdf-4491-8781-24510db992e3\")\n"
|
|
963
|
+
"Example: delete_memories(tags=\"coding,preferences\")\n"
|
|
964
|
+
"Example: delete_memories(category=\"interests\")"
|
|
965
|
+
)
|
|
966
|
+
|
|
967
|
+
# Validate memory_id if provided
|
|
968
|
+
if memory_id is not None:
|
|
969
|
+
if not isinstance(memory_id, str):
|
|
970
|
+
raise ToolError(
|
|
971
|
+
f"The 'memory_id' parameter must be a string or None, but got {type(memory_id).__name__}.\n"
|
|
972
|
+
f"Received: {repr(memory_id)}\n"
|
|
973
|
+
"Example: delete_memories(memory_id=\"480c1f76-bcdf-4491-8781-24510db992e3\")"
|
|
974
|
+
)
|
|
975
|
+
memory_id_str = memory_id.strip()
|
|
976
|
+
if not memory_id_str:
|
|
977
|
+
raise ToolError(
|
|
978
|
+
"The 'memory_id' parameter cannot be empty or whitespace-only.\n"
|
|
979
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
980
|
+
"Example: delete_memories(memory_id=\"480c1f76-bcdf-4491-8781-24510db992e3\")"
|
|
981
|
+
)
|
|
982
|
+
else:
|
|
983
|
+
memory_id_str = None
|
|
984
|
+
|
|
985
|
+
# Validate tags if provided
|
|
986
|
+
if tags is not None:
|
|
987
|
+
if not isinstance(tags, str):
|
|
988
|
+
raise ToolError(
|
|
989
|
+
f"The 'tags' parameter must be a string or None, but got {type(tags).__name__}.\n"
|
|
990
|
+
f"Received: {repr(tags)}\n"
|
|
991
|
+
"Example: delete_memories(tags=\"coding,preferences\")"
|
|
992
|
+
)
|
|
993
|
+
tags_str = tags.strip()
|
|
994
|
+
if not tags_str:
|
|
995
|
+
raise ToolError(
|
|
996
|
+
"The 'tags' parameter cannot be empty or whitespace-only.\n"
|
|
997
|
+
"Provide comma-separated tags or omit the parameter.\n"
|
|
998
|
+
"Example: delete_memories(tags=\"coding,preferences\")"
|
|
999
|
+
)
|
|
1000
|
+
else:
|
|
1001
|
+
tags_str = None
|
|
1002
|
+
|
|
1003
|
+
# Validate category if provided
|
|
1004
|
+
if category is not None:
|
|
1005
|
+
if not isinstance(category, str):
|
|
1006
|
+
raise ToolError(
|
|
1007
|
+
f"The 'category' parameter must be a string or None, but got {type(category).__name__}.\n"
|
|
1008
|
+
f"Received: {repr(category)}\n"
|
|
1009
|
+
"Example: delete_memories(category=\"interests\")"
|
|
1010
|
+
)
|
|
1011
|
+
category_str = category.strip()
|
|
1012
|
+
if not category_str:
|
|
1013
|
+
raise ToolError(
|
|
1014
|
+
"The 'category' parameter cannot be empty or whitespace-only.\n"
|
|
1015
|
+
"Provide a category name or omit the parameter.\n"
|
|
1016
|
+
"Example: delete_memories(category=\"interests\")"
|
|
1017
|
+
)
|
|
1018
|
+
else:
|
|
1019
|
+
category_str = None
|
|
1020
|
+
|
|
670
1021
|
try:
|
|
1022
|
+
logger.info(f"delete_memories called - memory_id: {memory_id_str}, tags: {tags_str}, category: {category_str}")
|
|
671
1023
|
client = await _get_api_client()
|
|
672
|
-
result = await client.delete_memories(
|
|
1024
|
+
result = await client.delete_memories(memory_id_str, tags_str, category_str)
|
|
673
1025
|
deleted_ids = result.get('memory_ids', [])[:10]
|
|
674
1026
|
return f"Deleted {result.get('deleted_count', 0)} memories. IDs: {', '.join(deleted_ids)}"
|
|
675
1027
|
except httpx.HTTPStatusError as e:
|
|
1028
|
+
error_detail = e.response.text if e.response else "Unknown error"
|
|
1029
|
+
logger.error(f"API error: {e.response.status_code} - {error_detail}")
|
|
676
1030
|
if e.response.status_code == 401:
|
|
677
|
-
raise ToolError("Authentication failed. Please
|
|
678
|
-
|
|
679
|
-
|
|
1031
|
+
raise ToolError("Authentication failed. Please login using the 'login' tool or configure your JWT token in the MCP client headers.")
|
|
1032
|
+
elif e.response.status_code == 404:
|
|
1033
|
+
raise ToolError(f"Memory not found: {memory_id_str}\nVerify the memory_id is correct by searching for it first.")
|
|
1034
|
+
raise ToolError(f"Failed to delete memories: HTTP {e.response.status_code} - {error_detail}")
|
|
1035
|
+
except ToolError:
|
|
1036
|
+
raise
|
|
680
1037
|
except Exception as e:
|
|
681
|
-
logger.error(f"Unexpected error: {e}", exc_info=True)
|
|
1038
|
+
logger.error(f"Unexpected error in delete_memories: {e}", exc_info=True)
|
|
682
1039
|
raise ToolError(f"Error deleting memories: {str(e)}")
|
|
683
1040
|
|
|
684
1041
|
|
|
685
1042
|
@mcp.tool()
|
|
686
1043
|
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
|
-
|
|
1044
|
+
"""Remove link between two memories when the connection is no longer relevant or accurate.
|
|
1045
|
+
|
|
1046
|
+
Parameters:
|
|
1047
|
+
memory_id_1 (str, REQUIRED): First memory ID in the link to remove.
|
|
1048
|
+
- Example: "480c1f76-bcdf-4491-8781-24510db992e3"
|
|
1049
|
+
- Get memory IDs from search_memories() or get_memories() results
|
|
1050
|
+
|
|
1051
|
+
memory_id_2 (str, REQUIRED): Second memory ID in the link to remove.
|
|
1052
|
+
- Example: "300d9716-a3a6-44d3-b0f4-b28002a65da8"
|
|
1053
|
+
- Get memory IDs from search_memories() or get_memories() results
|
|
1054
|
+
|
|
1055
|
+
Returns:
|
|
1056
|
+
str: Confirmation message that the memories were unlinked.
|
|
1057
|
+
|
|
1058
|
+
Common Errors and Solutions:
|
|
1059
|
+
- Error: "memory_id_1 cannot be empty"
|
|
1060
|
+
Solution: Provide a valid memory ID. Example: unlink_memories(memory_id_1="480c1f76-...", memory_id_2="300d9716-...")
|
|
1061
|
+
|
|
1062
|
+
- Error: "memory_id_2 cannot be empty"
|
|
1063
|
+
Solution: Provide a valid memory ID. Example: unlink_memories(memory_id_1="480c1f76-...", memory_id_2="300d9716-...")
|
|
1064
|
+
|
|
1065
|
+
Examples:
|
|
1066
|
+
# Unlink two memories
|
|
1067
|
+
unlink_memories(
|
|
1068
|
+
memory_id_1="480c1f76-bcdf-4491-8781-24510db992e3",
|
|
1069
|
+
memory_id_2="300d9716-a3a6-44d3-b0f4-b28002a65da8"
|
|
1070
|
+
)
|
|
1071
|
+
"""
|
|
1072
|
+
# Validate parameters with detailed error messages
|
|
1073
|
+
if memory_id_1 is None:
|
|
1074
|
+
raise ToolError(
|
|
1075
|
+
"The 'memory_id_1' parameter is required but was not provided.\n"
|
|
1076
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1077
|
+
"Example: unlink_memories(memory_id_1=\"480c1f76-...\", memory_id_2=\"300d9716-...\")"
|
|
1078
|
+
)
|
|
1079
|
+
|
|
1080
|
+
if not isinstance(memory_id_1, str):
|
|
1081
|
+
raise ToolError(
|
|
1082
|
+
f"The 'memory_id_1' parameter must be a string, but got {type(memory_id_1).__name__}.\n"
|
|
1083
|
+
f"Received: {repr(memory_id_1)}\n"
|
|
1084
|
+
"Example: unlink_memories(memory_id_1=\"480c1f76-...\", memory_id_2=\"300d9716-...\")"
|
|
1085
|
+
)
|
|
1086
|
+
|
|
1087
|
+
memory_id_1_str = memory_id_1.strip()
|
|
1088
|
+
if not memory_id_1_str:
|
|
1089
|
+
raise ToolError(
|
|
1090
|
+
"The 'memory_id_1' parameter cannot be empty or whitespace-only.\n"
|
|
1091
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1092
|
+
"Example: unlink_memories(memory_id_1=\"480c1f76-bcdf-4491-8781-24510db992e3\", memory_id_2=\"300d9716-...\")"
|
|
1093
|
+
)
|
|
1094
|
+
|
|
1095
|
+
if memory_id_2 is None:
|
|
1096
|
+
raise ToolError(
|
|
1097
|
+
"The 'memory_id_2' parameter is required but was not provided.\n"
|
|
1098
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1099
|
+
"Example: unlink_memories(memory_id_1=\"480c1f76-...\", memory_id_2=\"300d9716-...\")"
|
|
1100
|
+
)
|
|
1101
|
+
|
|
1102
|
+
if not isinstance(memory_id_2, str):
|
|
1103
|
+
raise ToolError(
|
|
1104
|
+
f"The 'memory_id_2' parameter must be a string, but got {type(memory_id_2).__name__}.\n"
|
|
1105
|
+
f"Received: {repr(memory_id_2)}\n"
|
|
1106
|
+
"Example: unlink_memories(memory_id_1=\"480c1f76-...\", memory_id_2=\"300d9716-...\")"
|
|
1107
|
+
)
|
|
1108
|
+
|
|
1109
|
+
memory_id_2_str = memory_id_2.strip()
|
|
1110
|
+
if not memory_id_2_str:
|
|
1111
|
+
raise ToolError(
|
|
1112
|
+
"The 'memory_id_2' parameter cannot be empty or whitespace-only.\n"
|
|
1113
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1114
|
+
"Example: unlink_memories(memory_id_1=\"480c1f76-...\", memory_id_2=\"300d9716-a3a6-44d3-b0f4-b28002a65da8\")"
|
|
1115
|
+
)
|
|
1116
|
+
|
|
1117
|
+
# Ensure the IDs are different
|
|
1118
|
+
if memory_id_1_str == memory_id_2_str:
|
|
1119
|
+
raise ToolError(
|
|
1120
|
+
"memory_id_1 and memory_id_2 must be different.\n"
|
|
1121
|
+
"You cannot unlink a memory from itself.\n"
|
|
1122
|
+
"Example: unlink_memories(memory_id_1=\"480c1f76-...\", memory_id_2=\"300d9716-...\")"
|
|
1123
|
+
)
|
|
693
1124
|
|
|
694
1125
|
try:
|
|
1126
|
+
logger.info(f"unlink_memories called - memory_id_1: {memory_id_1_str}, memory_id_2: {memory_id_2_str}")
|
|
695
1127
|
client = await _get_api_client()
|
|
696
|
-
result = await client.unlink_memories(
|
|
1128
|
+
result = await client.unlink_memories(memory_id_1_str, memory_id_2_str)
|
|
697
1129
|
return result.get("message", "Memories unlinked")
|
|
698
1130
|
except httpx.HTTPStatusError as e:
|
|
1131
|
+
error_detail = e.response.text if e.response else "Unknown error"
|
|
1132
|
+
logger.error(f"API error: {e.response.status_code} - {error_detail}")
|
|
699
1133
|
if e.response.status_code == 401:
|
|
700
|
-
raise ToolError("Authentication failed. Please
|
|
701
|
-
|
|
702
|
-
|
|
1134
|
+
raise ToolError("Authentication failed. Please login using the 'login' tool or configure your JWT token in the MCP client headers.")
|
|
1135
|
+
elif e.response.status_code == 404:
|
|
1136
|
+
raise ToolError(f"One or both memories not found.\nVerify the memory IDs are correct by searching for them first.")
|
|
1137
|
+
raise ToolError(f"Failed to unlink memories: HTTP {e.response.status_code} - {error_detail}")
|
|
1138
|
+
except ToolError:
|
|
1139
|
+
raise
|
|
703
1140
|
except Exception as e:
|
|
704
|
-
logger.error(f"Unexpected error: {e}", exc_info=True)
|
|
1141
|
+
logger.error(f"Unexpected error in unlink_memories: {e}", exc_info=True)
|
|
705
1142
|
raise ToolError(f"Error unlinking memories: {str(e)}")
|
|
706
1143
|
|
|
707
1144
|
|
|
708
1145
|
@mcp.tool()
|
|
709
1146
|
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.
|
|
1147
|
+
"""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.
|
|
1148
|
+
|
|
1149
|
+
Returns:
|
|
1150
|
+
str: Formatted statistics including total memories, links, and top tags.
|
|
1151
|
+
|
|
1152
|
+
Examples:
|
|
1153
|
+
# Get statistics
|
|
1154
|
+
get_stats()
|
|
1155
|
+
"""
|
|
711
1156
|
try:
|
|
1157
|
+
logger.info("get_stats called")
|
|
712
1158
|
client = await _get_api_client()
|
|
713
1159
|
result = await client.get_stats()
|
|
714
1160
|
top_tags = ', '.join([f"{tag}({count})" for tag, count in result.get('top_tags', [])[:10]])
|
|
@@ -718,27 +1164,97 @@ Total Links: {result.get('total_links', 0)}
|
|
|
718
1164
|
Average Links per Memory: {result.get('avg_links_per_memory', 0):.2f}
|
|
719
1165
|
Top Tags: {top_tags}"""
|
|
720
1166
|
except httpx.HTTPStatusError as e:
|
|
1167
|
+
error_detail = e.response.text if e.response else "Unknown error"
|
|
1168
|
+
logger.error(f"API error: {e.response.status_code} - {error_detail}")
|
|
721
1169
|
if e.response.status_code == 401:
|
|
722
|
-
raise ToolError("Authentication failed. Please
|
|
723
|
-
|
|
724
|
-
|
|
1170
|
+
raise ToolError("Authentication failed. Please login using the 'login' tool or configure your JWT token in the MCP client headers.")
|
|
1171
|
+
raise ToolError(f"Failed to get stats: HTTP {e.response.status_code} - {error_detail}")
|
|
1172
|
+
except ToolError:
|
|
1173
|
+
raise
|
|
725
1174
|
except Exception as e:
|
|
726
|
-
logger.error(f"Unexpected error: {e}", exc_info=True)
|
|
1175
|
+
logger.error(f"Unexpected error in get_stats: {e}", exc_info=True)
|
|
727
1176
|
raise ToolError(f"Error getting stats: {str(e)}")
|
|
728
1177
|
|
|
729
1178
|
|
|
730
1179
|
@mcp.tool()
|
|
731
1180
|
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
|
-
|
|
1181
|
+
"""Find shortest path between two memories in the memory graph. Use this to explain connections between seemingly unrelated memories.
|
|
1182
|
+
|
|
1183
|
+
Parameters:
|
|
1184
|
+
from_id (str, REQUIRED): Source memory ID to start the path from.
|
|
1185
|
+
- Example: "480c1f76-bcdf-4491-8781-24510db992e3"
|
|
1186
|
+
- Get memory IDs from search_memories() or get_memories() results
|
|
1187
|
+
|
|
1188
|
+
to_id (str, REQUIRED): Target memory ID to find path to.
|
|
1189
|
+
- Example: "300d9716-a3a6-44d3-b0f4-b28002a65da8"
|
|
1190
|
+
- Get memory IDs from search_memories() or get_memories() results
|
|
1191
|
+
|
|
1192
|
+
Returns:
|
|
1193
|
+
str: The shortest path between the two memories, or a message if no path exists.
|
|
1194
|
+
|
|
1195
|
+
Common Errors and Solutions:
|
|
1196
|
+
- Error: "from_id cannot be empty"
|
|
1197
|
+
Solution: Provide a valid memory ID. Example: find_path(from_id="480c1f76-...", to_id="300d9716-...")
|
|
1198
|
+
|
|
1199
|
+
- Error: "to_id cannot be empty"
|
|
1200
|
+
Solution: Provide a valid memory ID. Example: find_path(from_id="480c1f76-...", to_id="300d9716-...")
|
|
1201
|
+
|
|
1202
|
+
Examples:
|
|
1203
|
+
# Find path between two memories
|
|
1204
|
+
find_path(
|
|
1205
|
+
from_id="480c1f76-bcdf-4491-8781-24510db992e3",
|
|
1206
|
+
to_id="300d9716-a3a6-44d3-b0f4-b28002a65da8"
|
|
1207
|
+
)
|
|
1208
|
+
"""
|
|
1209
|
+
# Validate parameters with detailed error messages
|
|
1210
|
+
if from_id is None:
|
|
1211
|
+
raise ToolError(
|
|
1212
|
+
"The 'from_id' parameter is required but was not provided.\n"
|
|
1213
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1214
|
+
"Example: find_path(from_id=\"480c1f76-...\", to_id=\"300d9716-...\")"
|
|
1215
|
+
)
|
|
1216
|
+
|
|
1217
|
+
if not isinstance(from_id, str):
|
|
1218
|
+
raise ToolError(
|
|
1219
|
+
f"The 'from_id' parameter must be a string, but got {type(from_id).__name__}.\n"
|
|
1220
|
+
f"Received: {repr(from_id)}\n"
|
|
1221
|
+
"Example: find_path(from_id=\"480c1f76-...\", to_id=\"300d9716-...\")"
|
|
1222
|
+
)
|
|
1223
|
+
|
|
1224
|
+
from_id_str = from_id.strip()
|
|
1225
|
+
if not from_id_str:
|
|
1226
|
+
raise ToolError(
|
|
1227
|
+
"The 'from_id' parameter cannot be empty or whitespace-only.\n"
|
|
1228
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1229
|
+
"Example: find_path(from_id=\"480c1f76-bcdf-4491-8781-24510db992e3\", to_id=\"300d9716-...\")"
|
|
1230
|
+
)
|
|
1231
|
+
|
|
1232
|
+
if to_id is None:
|
|
1233
|
+
raise ToolError(
|
|
1234
|
+
"The 'to_id' parameter is required but was not provided.\n"
|
|
1235
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1236
|
+
"Example: find_path(from_id=\"480c1f76-...\", to_id=\"300d9716-...\")"
|
|
1237
|
+
)
|
|
1238
|
+
|
|
1239
|
+
if not isinstance(to_id, str):
|
|
1240
|
+
raise ToolError(
|
|
1241
|
+
f"The 'to_id' parameter must be a string, but got {type(to_id).__name__}.\n"
|
|
1242
|
+
f"Received: {repr(to_id)}\n"
|
|
1243
|
+
"Example: find_path(from_id=\"480c1f76-...\", to_id=\"300d9716-...\")"
|
|
1244
|
+
)
|
|
1245
|
+
|
|
1246
|
+
to_id_str = to_id.strip()
|
|
1247
|
+
if not to_id_str:
|
|
1248
|
+
raise ToolError(
|
|
1249
|
+
"The 'to_id' parameter cannot be empty or whitespace-only.\n"
|
|
1250
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1251
|
+
"Example: find_path(from_id=\"480c1f76-...\", to_id=\"300d9716-a3a6-44d3-b0f4-b28002a65da8\")"
|
|
1252
|
+
)
|
|
738
1253
|
|
|
739
1254
|
try:
|
|
1255
|
+
logger.info(f"find_path called - from_id: {from_id_str}, to_id: {to_id_str}")
|
|
740
1256
|
client = await _get_api_client()
|
|
741
|
-
result = await client.find_path(
|
|
1257
|
+
result = await client.find_path(from_id_str, to_id_str)
|
|
742
1258
|
if result.get("status") == "success":
|
|
743
1259
|
path_text = f"Path found (length: {result.get('length', 0)}):\n"
|
|
744
1260
|
for mem in result.get("memories", []):
|
|
@@ -746,27 +1262,97 @@ async def find_path(from_id: str, to_id: str) -> str:
|
|
|
746
1262
|
return path_text
|
|
747
1263
|
return result.get("message", "No path found")
|
|
748
1264
|
except httpx.HTTPStatusError as e:
|
|
1265
|
+
error_detail = e.response.text if e.response else "Unknown error"
|
|
1266
|
+
logger.error(f"API error: {e.response.status_code} - {error_detail}")
|
|
749
1267
|
if e.response.status_code == 401:
|
|
750
|
-
raise ToolError("Authentication failed. Please
|
|
751
|
-
|
|
752
|
-
|
|
1268
|
+
raise ToolError("Authentication failed. Please login using the 'login' tool or configure your JWT token in the MCP client headers.")
|
|
1269
|
+
elif e.response.status_code == 404:
|
|
1270
|
+
raise ToolError(f"One or both memories not found.\nVerify the memory IDs are correct by searching for them first.")
|
|
1271
|
+
raise ToolError(f"Failed to find path: HTTP {e.response.status_code} - {error_detail}")
|
|
1272
|
+
except ToolError:
|
|
1273
|
+
raise
|
|
753
1274
|
except Exception as e:
|
|
754
|
-
logger.error(f"Unexpected error: {e}", exc_info=True)
|
|
1275
|
+
logger.error(f"Unexpected error in find_path: {e}", exc_info=True)
|
|
755
1276
|
raise ToolError(f"Error finding path: {str(e)}")
|
|
756
1277
|
|
|
757
1278
|
|
|
758
1279
|
@mcp.tool()
|
|
759
1280
|
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
|
-
|
|
1281
|
+
"""Get all memories within N hops of a given memory. Use this for deep context and understanding relationships around important memories.
|
|
1282
|
+
|
|
1283
|
+
Parameters:
|
|
1284
|
+
memory_id (str, REQUIRED): Center memory ID to get neighborhood around.
|
|
1285
|
+
- Example: "480c1f76-bcdf-4491-8781-24510db992e3"
|
|
1286
|
+
- Get memory IDs from search_memories() or get_memories() results
|
|
1287
|
+
|
|
1288
|
+
hops (int, optional): Number of hops to traverse. Default is 2.
|
|
1289
|
+
- Must be between 1 and 5
|
|
1290
|
+
- 1 hop = direct connections only
|
|
1291
|
+
- 2 hops = direct connections + their connections
|
|
1292
|
+
- Example: 2 (default)
|
|
1293
|
+
- Example: 3
|
|
1294
|
+
|
|
1295
|
+
Returns:
|
|
1296
|
+
str: Formatted list of memories in the neighborhood with their hop distances.
|
|
1297
|
+
|
|
1298
|
+
Common Errors and Solutions:
|
|
1299
|
+
- Error: "memory_id cannot be empty"
|
|
1300
|
+
Solution: Provide a valid memory ID. Example: get_neighborhood(memory_id="480c1f76-...")
|
|
1301
|
+
|
|
1302
|
+
- Error: "hops must be between 1 and 5"
|
|
1303
|
+
Solution: Provide hops between 1 and 5. Example: get_neighborhood(memory_id="...", hops=3)
|
|
1304
|
+
|
|
1305
|
+
Examples:
|
|
1306
|
+
# Get neighborhood with default 2 hops
|
|
1307
|
+
get_neighborhood(memory_id="480c1f76-bcdf-4491-8781-24510db992e3")
|
|
1308
|
+
|
|
1309
|
+
# Get neighborhood with 3 hops
|
|
1310
|
+
get_neighborhood(memory_id="480c1f76-bcdf-4491-8781-24510db992e3", hops=3)
|
|
1311
|
+
|
|
1312
|
+
# Get direct connections only (1 hop)
|
|
1313
|
+
get_neighborhood(memory_id="480c1f76-bcdf-4491-8781-24510db992e3", hops=1)
|
|
1314
|
+
"""
|
|
1315
|
+
# Validate parameters with detailed error messages
|
|
1316
|
+
if memory_id is None:
|
|
1317
|
+
raise ToolError(
|
|
1318
|
+
"The 'memory_id' parameter is required but was not provided.\n"
|
|
1319
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1320
|
+
"Example: get_neighborhood(memory_id=\"480c1f76-bcdf-4491-8781-24510db992e3\")"
|
|
1321
|
+
)
|
|
1322
|
+
|
|
1323
|
+
if not isinstance(memory_id, str):
|
|
1324
|
+
raise ToolError(
|
|
1325
|
+
f"The 'memory_id' parameter must be a string, but got {type(memory_id).__name__}.\n"
|
|
1326
|
+
f"Received: {repr(memory_id)}\n"
|
|
1327
|
+
"Example: get_neighborhood(memory_id=\"480c1f76-bcdf-4491-8781-24510db992e3\")"
|
|
1328
|
+
)
|
|
1329
|
+
|
|
1330
|
+
memory_id_str = memory_id.strip()
|
|
1331
|
+
if not memory_id_str:
|
|
1332
|
+
raise ToolError(
|
|
1333
|
+
"The 'memory_id' parameter cannot be empty or whitespace-only.\n"
|
|
1334
|
+
"Get memory IDs from search_memories() or get_memories() results.\n"
|
|
1335
|
+
"Example: get_neighborhood(memory_id=\"480c1f76-bcdf-4491-8781-24510db992e3\")"
|
|
1336
|
+
)
|
|
1337
|
+
|
|
1338
|
+
if not isinstance(hops, int):
|
|
1339
|
+
raise ToolError(
|
|
1340
|
+
f"The 'hops' parameter must be an integer, but got {type(hops).__name__}.\n"
|
|
1341
|
+
f"Received: {repr(hops)}\n"
|
|
1342
|
+
"Example: get_neighborhood(memory_id=\"...\", hops=2)"
|
|
1343
|
+
)
|
|
1344
|
+
|
|
764
1345
|
if not (1 <= hops <= 5):
|
|
765
|
-
raise ToolError(
|
|
1346
|
+
raise ToolError(
|
|
1347
|
+
f"The 'hops' parameter must be between 1 and 5, but got {hops}.\n"
|
|
1348
|
+
"Example: get_neighborhood(memory_id=\"...\", hops=2)\n"
|
|
1349
|
+
"Example: get_neighborhood(memory_id=\"...\", hops=3)"
|
|
1350
|
+
)
|
|
766
1351
|
|
|
767
1352
|
try:
|
|
1353
|
+
logger.info(f"get_neighborhood called - memory_id: {memory_id_str}, hops: {hops}")
|
|
768
1354
|
client = await _get_api_client()
|
|
769
|
-
result = await client.get_neighborhood(
|
|
1355
|
+
result = await client.get_neighborhood(memory_id_str, hops)
|
|
770
1356
|
neighborhood_text = f"Neighborhood (hops={result.get('hops', 2)}, total={result.get('total_in_neighborhood', 0)}):\n"
|
|
771
1357
|
for mem in result.get("neighborhood", []):
|
|
772
1358
|
hop_dist = mem.get("hop_distance", 0)
|
|
@@ -774,12 +1360,17 @@ async def get_neighborhood(memory_id: str, hops: int = 2) -> str:
|
|
|
774
1360
|
neighborhood_text += f" [{hop_dist}]{is_center} {mem.get('id', 'unknown')}: {mem.get('content', '')[:100]}\n"
|
|
775
1361
|
return neighborhood_text
|
|
776
1362
|
except httpx.HTTPStatusError as e:
|
|
1363
|
+
error_detail = e.response.text if e.response else "Unknown error"
|
|
1364
|
+
logger.error(f"API error: {e.response.status_code} - {error_detail}")
|
|
777
1365
|
if e.response.status_code == 401:
|
|
778
|
-
raise ToolError("Authentication failed. Please
|
|
779
|
-
|
|
780
|
-
|
|
1366
|
+
raise ToolError("Authentication failed. Please login using the 'login' tool or configure your JWT token in the MCP client headers.")
|
|
1367
|
+
elif e.response.status_code == 404:
|
|
1368
|
+
raise ToolError(f"Memory not found: {memory_id_str}\nVerify the memory_id is correct by searching for it first.")
|
|
1369
|
+
raise ToolError(f"Failed to get neighborhood: HTTP {e.response.status_code} - {error_detail}")
|
|
1370
|
+
except ToolError:
|
|
1371
|
+
raise
|
|
781
1372
|
except Exception as e:
|
|
782
|
-
logger.error(f"Unexpected error: {e}", exc_info=True)
|
|
1373
|
+
logger.error(f"Unexpected error in get_neighborhood: {e}", exc_info=True)
|
|
783
1374
|
raise ToolError(f"Error getting neighborhood: {str(e)}")
|
|
784
1375
|
|
|
785
1376
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mem-brain-mcp
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
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
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
mem_brain_mcp/__init__.py,sha256=4mabvpmReQ0ZVZ-xw8FrwHAdGDhjWQDpHhWg_11lxmY,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=ieSIkwWp3777ETa1M3J_SBMRUu571e3YsJj_C70UXRM,68812
|
|
6
|
+
mem_brain_mcp-1.0.3.dist-info/METADATA,sha256=dR7sgte11k7CQyL38FTrsRDVBjx4hhil8HCXtbl9abQ,5228
|
|
7
|
+
mem_brain_mcp-1.0.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
8
|
+
mem_brain_mcp-1.0.3.dist-info/entry_points.txt,sha256=NH6QYQ-Sd8eJn5crpe_DL1PvGeUlL3y65968xPhmwG8,62
|
|
9
|
+
mem_brain_mcp-1.0.3.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=xx2lBkCIeT85t0HxtORwZHSU3hZT_EdsThpfjwPJhbQ,1261
|
|
5
|
-
mem_brain_mcp/server.py,sha256=mlbl3-D3OFb4HHP54daqiP3nMWEdiTv3upEdUFnlhAA,39604
|
|
6
|
-
mem_brain_mcp-1.0.1.dist-info/METADATA,sha256=AaOmWLOT6mGIrw7CysLZMacG3PbrzuOHD-K9XbuNsUs,5228
|
|
7
|
-
mem_brain_mcp-1.0.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
8
|
-
mem_brain_mcp-1.0.1.dist-info/entry_points.txt,sha256=NH6QYQ-Sd8eJn5crpe_DL1PvGeUlL3y65968xPhmwG8,62
|
|
9
|
-
mem_brain_mcp-1.0.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|