repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources15d ago| #1 | """ |
| #2 | Mnemosyne MCP Server — Model Context Protocol for cross-agent sharing. |
| #3 | |
| #4 | This module provides MCP tool definitions and handlers for Mnemosyne, |
| #5 | enabling any MCP-compatible client (Claude Desktop, etc.) to interact |
| #6 | with the memory system. |
| #7 | |
| #8 | Usage: |
| #9 | from mnemosyne.mcp_tools import TOOLS, handle_tool_call |
| #10 | |
| #11 | All imports are guarded — this module loads safely even if mcp is not installed. |
| #12 | """ |
| #13 | |
| #14 | from typing import Dict, Any, List, Optional |
| #15 | import json |
| #16 | import os |
| #17 | |
| #18 | # Guarded import — MCP is optional |
| #19 | try: |
| #20 | from mcp.types import Tool, TextContent, CallToolResult, ErrorData |
| #21 | _MCP_AVAILABLE = True |
| #22 | except ImportError: |
| #23 | _MCP_AVAILABLE = False |
| #24 | Tool = None |
| #25 | TextContent = None |
| #26 | CallToolResult = None |
| #27 | ErrorData = None |
| #28 | |
| #29 | from mnemosyne.core.memory import Mnemosyne |
| #30 | from mnemosyne.clawd_brain import BrainConfig, ClawdBrain |
| #31 | |
| #32 | # --------------------------------------------------------------------------- |
| #33 | # Tool Schemas |
| #34 | # --------------------------------------------------------------------------- |
| #35 | |
| #36 | _REMEMBER_SCHEMA = { |
| #37 | "type": "object", |
| #38 | "properties": { |
| #39 | "content": { |
| #40 | "type": "string", |
| #41 | "description": "The memory content to store." |
| #42 | }, |
| #43 | "source": { |
| #44 | "type": "string", |
| #45 | "description": "Source label (e.g., 'conversation', 'file', 'web').", |
| #46 | "default": "conversation" |
| #47 | }, |
| #48 | "importance": { |
| #49 | "type": "number", |
| #50 | "description": "Importance score from 0.0 to 1.0.", |
| #51 | "default": 0.5 |
| #52 | }, |
| #53 | "metadata": { |
| #54 | "type": "object", |
| #55 | "description": "Optional key-value metadata.", |
| #56 | "default": {} |
| #57 | }, |
| #58 | "extract_entities": { |
| #59 | "type": "boolean", |
| #60 | "description": "Extract and store entities from content (Phase 1 feature).", |
| #61 | "default": False |
| #62 | }, |
| #63 | "extract": { |
| #64 | "type": "boolean", |
| #65 | "description": "Extract structured facts from content (Phase 2 feature).", |
| #66 | "default": False |
| #67 | }, |
| #68 | "author_id": { |
| #69 | "type": "string", |
| #70 | "description": "Who stored this memory (e.g., 'abdias', 'codex-agent'). Auto-set from env MNEMOSYNE_AUTHOR_ID if not provided." |
| #71 | }, |
| #72 | "author_type": { |
| #73 | "type": "string", |
| #74 | "description": "Type of author: 'human', 'agent', or 'system'. Auto-set from env MNEMOSYNE_AUTHOR_TYPE." |
| #75 | }, |
| #76 | "channel_id": { |
| #77 | "type": "string", |
| #78 | "description": "Channel or group this memory belongs to (e.g., 'fluxspeak-team')." |
| #79 | }, |
| #80 | "bank": { |
| #81 | "type": "string", |
| #82 | "description": "Memory bank to store in (Phase 5 feature).", |
| #83 | "default": "default" |
| #84 | } |
| #85 | }, |
| #86 | "required": ["content"] |
| #87 | } |
| #88 | |
| #89 | _RECALL_SCHEMA = { |
| #90 | "type": "object", |
| #91 | "properties": { |
| #92 | "query": { |
| #93 | "type": "string", |
| #94 | "description": "Search query." |
| #95 | }, |
| #96 | "top_k": { |
| #97 | "type": "integer", |
| #98 | "description": "Maximum results to return.", |
| #99 | "default": 5 |
| #100 | }, |
| #101 | "bank": { |
| #102 | "type": "string", |
| #103 | "description": "Memory bank to search (Phase 5 feature).", |
| #104 | "default": "default" |
| #105 | }, |
| #106 | "temporal_weight": { |
| #107 | "type": "number", |
| #108 | "description": "Temporal boost weight (Phase 3 feature). 0.0 = disabled.", |
| #109 | "default": 0.0 |
| #110 | }, |
| #111 | "query_time": { |
| #112 | "type": "string", |
| #113 | "description": "ISO timestamp for temporal reference. Null = now.", |
| #114 | "default": None |
| #115 | }, |
| #116 | "vec_weight": { |
| #117 | "type": "number", |
| #118 | "description": "Vector similarity weight (Phase 4 feature).", |
| #119 | "default": 0.5 |
| #120 | }, |
| #121 | "fts_weight": { |
| #122 | "type": "number", |
| #123 | "description": "Full-text search weight (Phase 4 feature).", |
| #124 | "default": 0.3 |
| #125 | }, |
| #126 | "importance_weight": { |
| #127 | "type": "number", |
| #128 | "description": "Importance score weight (Phase 4 feature).", |
| #129 | "default": 0.2 |
| #130 | }, |
| #131 | "author_id": { |
| #132 | "type": "string", |
| #133 | "description": "Filter by author (e.g., 'abdias', 'codex-agent'). Only recalls memories by this author." |
| #134 | }, |
| #135 | "author_type": { |
| #136 | "type": "string", |
| #137 | "description": "Filter by author type: 'human', 'agent', or 'system'." |
| #138 | }, |
| #139 | "channel_id": { |
| #140 | "type": "string", |
| #141 | "description": "Filter by channel/group (e.g., 'fluxspeak-team')." |
| #142 | } |
| #143 | }, |
| #144 | "required": ["query"] |
| #145 | } |
| #146 | |
| #147 | _SLEEP_SCHEMA = { |
| #148 | "type": "object", |
| #149 | "properties": { |
| #150 | "dry_run": { |
| #151 | "type": "boolean", |
| #152 | "description": "If true, preview consolidation without executing.", |
| #153 | "default": False |
| #154 | }, |
| #155 | "bank": { |
| #156 | "type": "string", |
| #157 | "description": "Memory bank to consolidate.", |
| #158 | "default": "default" |
| #159 | } |
| #160 | } |
| #161 | } |
| #162 | |
| #163 | _SCRATCHPAD_READ_SCHEMA = { |
| #164 | "type": "object", |
| #165 | "properties": { |
| #166 | "bank": { |
| #167 | "type": "string", |
| #168 | "description": "Memory bank.", |
| #169 | "default": "default" |
| #170 | } |
| #171 | } |
| #172 | } |
| #173 | |
| #174 | _SCRATCHPAD_WRITE_SCHEMA = { |
| #175 | "type": "object", |
| #176 | "properties": { |
| #177 | "content": { |
| #178 | "type": "string", |
| #179 | "description": "Content to write to scratchpad." |
| #180 | }, |
| #181 | "bank": { |
| #182 | "type": "string", |
| #183 | "description": "Memory bank.", |
| #184 | "default": "default" |
| #185 | } |
| #186 | }, |
| #187 | "required": ["content"] |
| #188 | } |
| #189 | |
| #190 | _GET_STATS_SCHEMA = { |
| #191 | "type": "object", |
| #192 | "properties": { |
| #193 | "bank": { |
| #194 | "type": "string", |
| #195 | "description": "Memory bank.", |
| #196 | "default": "default" |
| #197 | } |
| #198 | } |
| #199 | } |
| #200 | |
| #201 | _CLAWD_REMEMBER_SCHEMA = { |
| #202 | "type": "object", |
| #203 | "properties": { |
| #204 | "title": {"type": "string", "description": "Obsidian note title."}, |
| #205 | "content": {"type": "string", "description": "Memory/wiki note body."}, |
| #206 | "kind": { |
| #207 | "type": "string", |
| #208 | "description": "note, research, signal, trade, agent, protocol, wallet, or perp.", |
| #209 | "default": "note", |
| #210 | }, |
| #211 | "source": {"type": "string", "description": "Source label or URL.", "default": "mcp"}, |
| #212 | "tags": {"type": "array", "items": {"type": "string"}, "default": []}, |
| #213 | "importance": {"type": "number", "default": 0.65}, |
| #214 | "bank": {"type": "string", "default": "clawd"}, |
| #215 | }, |
| #216 | "required": ["title", "content"], |
| #217 | } |
| #218 | |
| #219 | _CLAWD_RECALL_SCHEMA = { |
| #220 | "type": "object", |
| #221 | "properties": { |
| #222 | "query": {"type": "string", "description": "Search query."}, |
| #223 | "top_k": {"type": "integer", "default": 8}, |
| #224 | "bank": {"type": "string", "default": "clawd"}, |
| #225 | }, |
| #226 | "required": ["query"], |
| #227 | } |
| #228 | |
| #229 | _CLAWD_RESEARCH_SCHEMA = { |
| #230 | "type": "object", |
| #231 | "properties": { |
| #232 | "target": {"type": "string", "description": "URL to archive or topic to queue."}, |
| #233 | "tags": {"type": "array", "items": {"type": "string"}, "default": []}, |
| #234 | "bank": {"type": "string", "default": "clawd"}, |
| #235 | }, |
| #236 | "required": ["target"], |
| #237 | } |
| #238 | |
| #239 | # --------------------------------------------------------------------------- |
| #240 | # Tool Definitions |
| #241 | # --------------------------------------------------------------------------- |
| #242 | |
| #243 | TOOLS: List[Dict[str, Any]] = [ |
| #244 | { |
| #245 | "name": "mnemosyne_remember", |
| #246 | "description": "Store a memory in Mnemosyne. Supports entity extraction, fact extraction, and bank selection.", |
| #247 | "inputSchema": _REMEMBER_SCHEMA |
| #248 | }, |
| #249 | { |
| #250 | "name": "mnemosyne_recall", |
| #251 | "description": "Search memories with hybrid scoring (vector + full-text + importance + temporal). Supports bank selection and configurable weights.", |
| #252 | "inputSchema": _RECALL_SCHEMA |
| #253 | }, |
| #254 | { |
| #255 | "name": "mnemosyne_sleep", |
| #256 | "description": "Run consolidation sleep cycle to merge old working memories into episodic memory.", |
| #257 | "inputSchema": _SLEEP_SCHEMA |
| #258 | }, |
| #259 | { |
| #260 | "name": "mnemosyne_scratchpad_read", |
| #261 | "description": "Read the agent scratchpad (temporary reasoning workspace).", |
| #262 | "inputSchema": _SCRATCHPAD_READ_SCHEMA |
| #263 | }, |
| #264 | { |
| #265 | "name": "mnemosyne_scratchpad_write", |
| #266 | "description": "Write to the agent scratchpad.", |
| #267 | "inputSchema": _SCRATCHPAD_WRITE_SCHEMA |
| #268 | }, |
| #269 | { |
| #270 | "name": "mnemosyne_get_stats", |
| #271 | "description": "Get memory system statistics (counts, banks, last memory).", |
| #272 | "inputSchema": _GET_STATS_SCHEMA |
| #273 | }, |
| #274 | { |
| #275 | "name": "clawd_brain_remember", |
| #276 | "description": "Store a Solana-Clawd memory in the persistent Mnemosyne bank and Obsidian-style wiki vault.", |
| #277 | "inputSchema": _CLAWD_REMEMBER_SCHEMA |
| #278 | }, |
| #279 | { |
| #280 | "name": "clawd_brain_recall", |
| #281 | "description": "Recall Clawd memories and matching wiki notes from the persistent brain.", |
| #282 | "inputSchema": _CLAWD_RECALL_SCHEMA |
| #283 | }, |
| #284 | { |
| #285 | "name": "clawd_brain_research", |
| #286 | "description": "Archive a research URL or queue a Solana/perp/agent research topic in the Clawd brain.", |
| #287 | "inputSchema": _CLAWD_RESEARCH_SCHEMA |
| #288 | } |
| #289 | ] |
| #290 | |
| #291 | # --------------------------------------------------------------------------- |
| #292 | # Mnemosyne Instance Per Connection (no module-level cache) |
| #293 | # --------------------------------------------------------------------------- |
| #294 | def _create_instance(session_id: str = None, author_id: str = None, |
| #295 | author_type: str = None, channel_id: str = None, |
| #296 | bank: str = "default") -> Mnemosyne: |
| #297 | """Create a fresh Mnemosyne instance for each MCP connection. |
| #298 | |
| #299 | Identity is resolved from: |
| #300 | 1. Explicit args (from tool call or constructor) |
| #301 | 2. Environment variables (MNEMOSYNE_AUTHOR_ID, etc.) |
| #302 | 3. None (backward compatible, no identity tracking) |
| #303 | """ |
| #304 | auth = author_id or os.environ.get("MNEMOSYNE_AUTHOR_ID") |
| #305 | auth_type = author_type or os.environ.get("MNEMOSYNE_AUTHOR_TYPE") |
| #306 | chan = channel_id or os.environ.get("MNEMOSYNE_CHANNEL_ID") or session_id or "default" |
| #307 | sess = session_id or f"mcp_{bank}" |
| #308 | |
| #309 | return Mnemosyne( |
| #310 | session_id=sess, |
| #311 | author_id=auth, |
| #312 | author_type=auth_type, |
| #313 | channel_id=chan, |
| #314 | bank=bank |
| #315 | ) |
| #316 | |
| #317 | |
| #318 | def _resolve_bank(arguments: Dict[str, Any]) -> str: |
| #319 | """Resolve per-call bank, falling back to MCP server default bank.""" |
| #320 | return arguments.get("bank") or os.environ.get("MNEMOSYNE_MCP_BANK") or "default" |
| #321 | |
| #322 | |
| #323 | # --------------------------------------------------------------------------- |
| #324 | # Tool Handlers |
| #325 | # --------------------------------------------------------------------------- |
| #326 | |
| #327 | def _handle_remember(arguments: Dict[str, Any]) -> Dict[str, Any]: |
| #328 | """Handle mnemosyne_remember tool call.""" |
| #329 | content = arguments["content"] |
| #330 | source = arguments.get("source", "conversation") |
| #331 | importance = arguments.get("importance", 0.5) |
| #332 | metadata = arguments.get("metadata", {}) |
| #333 | extract_entities = arguments.get("extract_entities", False) |
| #334 | extract = arguments.get("extract", False) |
| #335 | bank = _resolve_bank(arguments) |
| #336 | |
| #337 | mem = _create_instance(author_id=arguments.get("author_id"), author_type=arguments.get("author_type"), channel_id=arguments.get("channel_id"), bank=bank) |
| #338 | memory_id = mem.remember( |
| #339 | content=content, |
| #340 | source=source, |
| #341 | importance=importance, |
| #342 | metadata=metadata, |
| #343 | extract_entities=extract_entities, |
| #344 | extract=extract |
| #345 | ) |
| #346 | |
| #347 | return { |
| #348 | "status": "stored", |
| #349 | "memory_id": memory_id, |
| #350 | "bank": bank |
| #351 | } |
| #352 | |
| #353 | |
| #354 | def _handle_recall(arguments: Dict[str, Any]) -> Dict[str, Any]: |
| #355 | """Handle mnemosyne_recall tool call.""" |
| #356 | query = arguments["query"] |
| #357 | top_k = arguments.get("top_k", 5) |
| #358 | bank = _resolve_bank(arguments) |
| #359 | temporal_weight = arguments.get("temporal_weight", 0.0) |
| #360 | query_time = arguments.get("query_time") |
| #361 | vec_weight = arguments.get("vec_weight") |
| #362 | fts_weight = arguments.get("fts_weight") |
| #363 | importance_weight = arguments.get("importance_weight") |
| #364 | |
| #365 | mem = _create_instance(author_id=arguments.get("author_id"), author_type=arguments.get("author_type"), channel_id=arguments.get("channel_id"), bank=bank) |
| #366 | results = mem.recall( |
| #367 | query=query, |
| #368 | top_k=top_k, |
| #369 | temporal_weight=temporal_weight, |
| #370 | query_time=query_time, |
| #371 | vec_weight=vec_weight, |
| #372 | fts_weight=fts_weight, |
| #373 | importance_weight=importance_weight, |
| #374 | ) |
| #375 | |
| #376 | # Serialize for JSON — datetime objects aren't JSON serializable |
| #377 | serializable = [] |
| #378 | for r in results: |
| #379 | item = dict(r) if hasattr(r, "keys") else r |
| #380 | # Convert any datetime to ISO string |
| #381 | for key in ["timestamp", "created_at", "valid_until", "last_recalled"]: |
| #382 | if key in item and item[key] is not None: |
| #383 | if hasattr(item[key], "isoformat"): |
| #384 | item[key] = item[key].isoformat() |
| #385 | serializable.append(item) |
| #386 | |
| #387 | return { |
| #388 | "status": "ok", |
| #389 | "count": len(serializable), |
| #390 | "results": serializable, |
| #391 | "bank": bank |
| #392 | } |
| #393 | |
| #394 | |
| #395 | def _handle_sleep(arguments: Dict[str, Any]) -> Dict[str, Any]: |
| #396 | """Handle mnemosyne_sleep tool call.""" |
| #397 | dry_run = arguments.get("dry_run", False) |
| #398 | all_sessions = arguments.get("all_sessions", False) |
| #399 | bank = _resolve_bank(arguments) |
| #400 | |
| #401 | mem = _create_instance(author_id=arguments.get("author_id"), author_type=arguments.get("author_type"), channel_id=arguments.get("channel_id"), bank=bank) |
| #402 | if all_sessions and hasattr(mem, "sleep_all_sessions"): |
| #403 | result = mem.sleep_all_sessions(dry_run=dry_run) |
| #404 | else: |
| #405 | result = mem.sleep(dry_run=dry_run) |
| #406 | |
| #407 | return { |
| #408 | "status": "ok", |
| #409 | "dry_run": dry_run, |
| #410 | "all_sessions": all_sessions, |
| #411 | "result": result, |
| #412 | "bank": bank |
| #413 | } |
| #414 | |
| #415 | |
| #416 | def _handle_scratchpad_read(arguments: Dict[str, Any]) -> Dict[str, Any]: |
| #417 | """Handle mnemosyne_scratchpad_read tool call.""" |
| #418 | bank = _resolve_bank(arguments) |
| #419 | |
| #420 | mem = _create_instance(author_id=arguments.get("author_id"), author_type=arguments.get("author_type"), channel_id=arguments.get("channel_id"), bank=bank) |
| #421 | entries = mem.scratchpad_read() |
| #422 | |
| #423 | return { |
| #424 | "status": "ok", |
| #425 | "count": len(entries), |
| #426 | "entries": entries, |
| #427 | "bank": bank |
| #428 | } |
| #429 | |
| #430 | |
| #431 | def _handle_scratchpad_write(arguments: Dict[str, Any]) -> Dict[str, Any]: |
| #432 | """Handle mnemosyne_scratchpad_write tool call.""" |
| #433 | content = arguments["content"] |
| #434 | bank = _resolve_bank(arguments) |
| #435 | |
| #436 | mem = _create_instance(author_id=arguments.get("author_id"), author_type=arguments.get("author_type"), channel_id=arguments.get("channel_id"), bank=bank) |
| #437 | entry_id = mem.scratchpad_write(content) |
| #438 | |
| #439 | return { |
| #440 | "status": "stored", |
| #441 | "entry_id": entry_id, |
| #442 | "bank": bank |
| #443 | } |
| #444 | |
| #445 | |
| #446 | def _handle_get_stats(arguments: Dict[str, Any]) -> Dict[str, Any]: |
| #447 | """Handle mnemosyne_get_stats tool call.""" |
| #448 | bank = _resolve_bank(arguments) |
| #449 | |
| #450 | mem = _create_instance(author_id=arguments.get("author_id"), author_type=arguments.get("author_type"), channel_id=arguments.get("channel_id"), bank=bank) |
| #451 | stats = mem.get_stats() |
| #452 | |
| #453 | # Serialize for JSON |
| #454 | def _serialize(obj): |
| #455 | if hasattr(obj, "isoformat"): |
| #456 | return obj.isoformat() |
| #457 | if isinstance(obj, dict): |
| #458 | return {k: _serialize(v) for k, v in obj.items()} |
| #459 | if isinstance(obj, list): |
| #460 | return [_serialize(i) for i in obj] |
| #461 | return obj |
| #462 | |
| #463 | return { |
| #464 | "status": "ok", |
| #465 | "stats": _serialize(stats), |
| #466 | "bank": bank |
| #467 | } |
| #468 | |
| #469 | |
| #470 | def _create_clawd_brain(arguments: Dict[str, Any]) -> ClawdBrain: |
| #471 | bank = arguments.get("bank") or "clawd" |
| #472 | return ClawdBrain(BrainConfig(bank=bank)) |
| #473 | |
| #474 | |
| #475 | def _handle_clawd_remember(arguments: Dict[str, Any]) -> Dict[str, Any]: |
| #476 | brain = _create_clawd_brain(arguments) |
| #477 | result = brain.remember( |
| #478 | arguments["title"], |
| #479 | arguments["content"], |
| #480 | kind=arguments.get("kind", "note"), |
| #481 | source=arguments.get("source", "mcp"), |
| #482 | tags=arguments.get("tags", []), |
| #483 | importance=arguments.get("importance", 0.65), |
| #484 | ) |
| #485 | return {"status": "stored", "result": result, "bank": brain.config.bank} |
| #486 | |
| #487 | |
| #488 | def _handle_clawd_recall(arguments: Dict[str, Any]) -> Dict[str, Any]: |
| #489 | brain = _create_clawd_brain(arguments) |
| #490 | return {"status": "ok", "result": brain.recall(arguments["query"], arguments.get("top_k", 8)), "bank": brain.config.bank} |
| #491 | |
| #492 | |
| #493 | def _handle_clawd_research(arguments: Dict[str, Any]) -> Dict[str, Any]: |
| #494 | brain = _create_clawd_brain(arguments) |
| #495 | result = brain.auto_research(arguments["target"], tags=arguments.get("tags", [])) |
| #496 | return {"status": "queued" if result else "ok", "result": result, "bank": brain.config.bank} |
| #497 | |
| #498 | |
| #499 | # --------------------------------------------------------------------------- |
| #500 | # Dispatch |
| #501 | # --------------------------------------------------------------------------- |
| #502 | |
| #503 | _TOOL_HANDLERS = { |
| #504 | "mnemosyne_remember": _handle_remember, |
| #505 | "mnemosyne_recall": _handle_recall, |
| #506 | "mnemosyne_sleep": _handle_sleep, |
| #507 | "mnemosyne_scratchpad_read": _handle_scratchpad_read, |
| #508 | "mnemosyne_scratchpad_write": _handle_scratchpad_write, |
| #509 | "mnemosyne_get_stats": _handle_get_stats, |
| #510 | "clawd_brain_remember": _handle_clawd_remember, |
| #511 | "clawd_brain_recall": _handle_clawd_recall, |
| #512 | "clawd_brain_research": _handle_clawd_research, |
| #513 | } |
| #514 | |
| #515 | |
| #516 | def handle_tool_call(name: str, arguments: Dict[str, Any]) -> Dict[str, Any]: |
| #517 | """ |
| #518 | Dispatch an MCP tool call to the correct handler. |
| #519 | |
| #520 | Args: |
| #521 | name: Tool name (e.g., "mnemosyne_remember") |
| #522 | arguments: Parsed JSON arguments |
| #523 | |
| #524 | Returns: |
| #525 | JSON-serializable result dict |
| #526 | |
| #527 | Raises: |
| #528 | ValueError: If tool name is unknown |
| #529 | """ |
| #530 | handler = _TOOL_HANDLERS.get(name) |
| #531 | if handler is None: |
| #532 | raise ValueError(f"Unknown tool: {name}. Available: {list(_TOOL_HANDLERS.keys())}") |
| #533 | |
| #534 | return handler(arguments) |
| #535 | |
| #536 | |
| #537 | def get_tool_definitions() -> List[Dict[str, Any]]: |
| #538 | """Return all tool definitions for MCP server registration.""" |
| #539 | return TOOLS |
| #540 |