diff --git a/.gitignore b/.gitignore index 3f0aa6cc..938dc56b 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ ENV/ projects/*.db projects/*.db-journal /.coverage + + +*.log \ No newline at end of file diff --git a/README.md b/README.md index acec186d..fdf37ca5 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ See: https://modelcontextprotocol.io/docs/first-server/python#connect-to-claude- ```json { "mcpServers": { - "weather": { + "basic-memory": { "command": "uv", "args": [ "--directory", @@ -56,4 +56,68 @@ See: https://modelcontextprotocol.io/docs/first-server/python#connect-to-claude- } } } -``` \ No newline at end of file +``` + +### logs + +/Users/phernandez/Library/Logs/Claude/mcp-server-basic-memory.log + + + + +# MCP quickstart +https://modelcontextprotocol.io/quickstart + +Claude Desktop config json +/Users/phernandez/Library/Application Support/Claude/claude_desktop_config.json + +## stdout logging + +logs-dir: +/Users/phernandez/Library/Logs/Claude + +- mcp-server-sqlite.log +- mcp.log + +To test changes efficiently: + +- Configuration changes: Restart Claude Desktop +- Server code changes: Use Command-R to reload +- Quick iteration: Use Inspector during development + - https://modelcontextprotocol.io/docs/tools/inspector + +eg. +```bash +npx @modelcontextprotocol/inspector uvx mcp-server-sqlite --db-path /Users/phernandez/dev/basicmachines/mcp-quickstart/test.db +``` + +## tools +Inspector: https://modelcontextprotocol.io/docs/tools/inspector +Debugger: https://modelcontextprotocol.io/docs/tools/debugging + +## dev tools + +```bash +jq '.allowDevTools = true' ~/Library/Application\ Support/Claude/developer_settings.json > tmp.json \ + && mv tmp.json ~/Library/Application\ Support/Claude/developer_settings.json + +``` + +Open DevTools: Command-Option-Shift-i + + +## TODO + +logs + +/Users/phernandez/Library/Logs/Claude/mcp-server-basic-memory.log + +```text +2024-12-08 20:32:38.802 | INFO | __main__:run_server:264 - Starting MCP server basic-memory +The garbage collector is trying to clean up non-checked-in connection >, which will be dropped, as it cannot be safely terminated. Please ensure that SQLAlchemy pooled connections are returned to the pool explicitly, either by calling ``close()`` or by using appropriate context managers to manage their lifecycle. +The garbage collector is trying to clean up non-checked-in connection >, which will be dropped, as it cannot be safely terminated. Please ensure that SQLAlchemy pooled connections are returned to the pool explicitly, either by calling ``close()`` or by using appropriate context managers to manage their lifecycle. +~ +``` + +more info about setting log level: https://modelcontextprotocol.io/docs/first-server/python +- scroll down to logging diff --git a/basic-memory-mcp.log b/basic-memory-mcp.log new file mode 100644 index 00000000..bcafbcb1 --- /dev/null +++ b/basic-memory-mcp.log @@ -0,0 +1,186 @@ +2024-12-08 20:58:32.697 | INFO | __main__:run_server:299 - Starting MCP server basic-memory +2024-12-08 20:58:32.710 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 20:58:32.715 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 20:58:32.810 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 20:58:32.814 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 20:58:32.838 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 20:58:32.844 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 20:58:33.114 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 20:58:33.118 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 20:58:33.129 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 20:58:33.133 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 20:58:33.144 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 20:58:33.146 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 20:59:03.173 | DEBUG | __main__:handle_call_tool:257 - Handling tool call: search_nodes with args: {'query': 'basic-memory server'} +2024-12-08 20:59:03.196 | DEBUG | basic_memory.repository:__init__:21 - Initialized EntityRepository for Entity +2024-12-08 20:59:03.196 | DEBUG | basic_memory.repository:__init__:22 - Valid columns: ['id', 'name', 'entity_type', 'description', 'references', 'created_at', 'updated_at'] +2024-12-08 20:59:03.196 | DEBUG | basic_memory.repository.entity_repository:__init__:17 - Initialized EntityRepository +2024-12-08 20:59:03.196 | DEBUG | basic_memory.repository:__init__:21 - Initialized ObservationRepository for Observation +2024-12-08 20:59:03.196 | DEBUG | basic_memory.repository:__init__:22 - Valid columns: ['id', 'entity_id', 'content', 'created_at', 'context'] +2024-12-08 20:59:03.196 | DEBUG | basic_memory.repository:__init__:21 - Initialized RelationRepository for Relation +2024-12-08 20:59:03.196 | DEBUG | basic_memory.repository:__init__:22 - Valid columns: ['id', 'from_id', 'to_id', 'relation_type', 'created_at', 'context'] +2024-12-08 20:59:03.196 | DEBUG | basic_memory.services.entity_service:__init__:23 - Initialized EntityService with path: /Users/phernandez/.basic-memory/projects/default +2024-12-08 20:59:03.196 | DEBUG | basic_memory.services.memory_service:__init__:30 - Initialized MemoryService with path: /Users/phernandez/.basic-memory/projects/default +2024-12-08 20:59:03.197 | DEBUG | __main__:handle_call_tool:267 - Created project services +2024-12-08 20:59:03.197 | DEBUG | __main__:handle_search_nodes:86 - Searching nodes with query: basic-memory server +2024-12-08 20:59:03.197 | DEBUG | basic_memory.services.memory_service:search_nodes:173 - Searching nodes with query: basic-memory server +2024-12-08 20:59:03.197 | DEBUG | basic_memory.services.entity_service:search:27 - Searching entities with query: basic-memory server +2024-12-08 20:59:03.197 | DEBUG | basic_memory.repository.entity_repository:search:78 - Searching entities with query: basic-memory server +2024-12-08 20:59:03.205 | DEBUG | basic_memory.repository.entity_repository:search:95 - Found 0 matching entities +2024-12-08 20:59:03.205 | DEBUG | basic_memory.services.entity_service:search:30 - Found 0 matches +2024-12-08 20:59:03.205 | DEBUG | basic_memory.services.memory_service:search_nodes:176 - Found 0 matches for 'basic-memory server' +2024-12-08 20:59:03.205 | DEBUG | __main__:handle_search_nodes:89 - Found 0 matches for query 'basic-memory server' +2024-12-08 20:59:03.205 | DEBUG | __main__:create_response:48 - Creating MCP response from SearchNodesResponse +2024-12-08 20:59:03.205 | DEBUG | __main__:create_response:57 - Created response: type='resource' resource=TextResourceContents(uri=AnyUrl('basic-memory://response'), mimeType='application/vnd.basic-memory+json', text='{"matches":[],"query":"basic-memory server"}') +2024-12-08 20:59:03.205 | DEBUG | __main__:handle_call_tool:271 - Tool search_nodes completed successfully +2024-12-08 20:59:36.731 | INFO | __main__:run_server:299 - Starting MCP server basic-memory +2024-12-08 20:59:36.744 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 20:59:36.748 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 20:59:37.163 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 20:59:37.167 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 20:59:37.251 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 20:59:37.254 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 20:59:37.735 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 20:59:37.738 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:06.198 | INFO | __main__:run_server:299 - Starting MCP server basic-memory +2024-12-08 21:01:06.204 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:06.207 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:06.216 | INFO | __main__:run_server:299 - Starting MCP server basic-memory +2024-12-08 21:01:06.222 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:06.227 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:06.441 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:06.443 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:06.681 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:06.689 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:06.719 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:06.722 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:06.763 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:06.767 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:06.799 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:06.802 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:06.818 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:06.821 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:46.546 | INFO | __main__:run_server:299 - Starting MCP server basic-memory +2024-12-08 21:01:46.555 | INFO | __main__:run_server:299 - Starting MCP server basic-memory +2024-12-08 21:01:46.556 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:46.565 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:46.571 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:46.577 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:46.939 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:46.941 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:46.962 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:46.964 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:47.059 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:47.063 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:47.072 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:47.076 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:47.106 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:47.109 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:56.053 | INFO | __main__:run_server:299 - Starting MCP server basic-memory +2024-12-08 21:01:56.057 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:56.060 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:56.061 | INFO | __main__:run_server:299 - Starting MCP server basic-memory +2024-12-08 21:01:56.066 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:56.071 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:56.122 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:56.125 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:56.382 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:56.395 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:57.150 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:57.154 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:57.324 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:57.327 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:57.340 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:57.343 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:57.469 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:57.472 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:57.492 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:57.498 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:57.504 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:57.507 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:57.514 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:57.518 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:57.576 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:57.579 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:57.598 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:57.602 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:57.626 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:57.629 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:57.649 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:57.652 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:57.739 | INFO | __main__:run_server:299 - Starting MCP server basic-memory +2024-12-08 21:01:57.746 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:57.750 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:57.789 | INFO | __main__:run_server:299 - Starting MCP server basic-memory +2024-12-08 21:01:57.798 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:57.802 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:58.078 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:58.081 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:58.191 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:58.194 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:58.203 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:58.207 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:58.257 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:58.260 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:58.267 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:58.270 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:01:58.277 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:01:58.282 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:06.210 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:06.231 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:06.672 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:06.675 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:06.942 | INFO | __main__:run_server:299 - Starting MCP server basic-memory +2024-12-08 21:52:06.947 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:06.951 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:07.157 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:07.170 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:07.185 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:07.188 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:07.469 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:07.473 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:07.477 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:07.481 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:07.516 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:07.519 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:44.689 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:44.694 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:44.869 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:44.875 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:45.992 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:45.997 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:46.007 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:46.012 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:46.026 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:46.030 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:46.155 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:46.167 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:46.180 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:46.185 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:46.212 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:46.218 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:46.342 | INFO | __main__:run_server:299 - Starting MCP server basic-memory +2024-12-08 21:52:46.399 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:46.410 | INFO | __main__:run_server:299 - Starting MCP server basic-memory +2024-12-08 21:52:46.427 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:46.435 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:46.413 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:46.743 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:46.746 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:46.759 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:46.763 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:46.774 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:46.777 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:46.813 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:46.817 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:52:46.830 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:52:46.832 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:56:15.969 | INFO | __main__:run_server:299 - Starting MCP server basic-memory +2024-12-08 21:56:15.979 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:56:15.983 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:56:16.379 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:56:16.383 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:56:16.426 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:56:16.430 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools +2024-12-08 21:56:16.444 | DEBUG | __main__:handle_list_tools:207 - Listing available tools +2024-12-08 21:56:16.448 | DEBUG | __main__:handle_list_tools:245 - Returning 7 available tools diff --git a/out.log b/out.log deleted file mode 100644 index 18d398e9..00000000 --- a/out.log +++ /dev/null @@ -1 +0,0 @@ -2024-12-08 19:48:28.902 | INFO | __main__:run_server:264 - Starting MCP server basic-memory diff --git a/src/basic_memory/mcp/server.py b/src/basic_memory/mcp/server.py index 51eb863c..4d9cd936 100644 --- a/src/basic_memory/mcp/server.py +++ b/src/basic_memory/mcp/server.py @@ -20,7 +20,7 @@ from basic_memory.schemas import ( AddObservationsResponse, CreateRelationsResponse, DeleteEntitiesResponse, DeleteObservationsResponse, # Base models - EntityOut, ObservationOut + EntityOut, ObservationOut, RelationOut ) from basic_memory.services.memory_service import MemoryService from loguru import logger @@ -45,7 +45,8 @@ ToolHandler: TypeAlias = Callable[[MemoryService, Dict[str, Any]], Awaitable[Emb def create_response(response: BaseModel) -> EmbeddedResource: """Create standard MCP response from any response model.""" - return EmbeddedResource( + logger.debug(f"Creating MCP response from {response.__class__.__name__}") + result = EmbeddedResource( type="resource", resource=TextResourceContents( uri=BASIC_MEMORY_URI, @@ -53,6 +54,8 @@ def create_response(response: BaseModel) -> EmbeddedResource: text=response.model_dump_json() ) ) + logger.debug(f"Created response: {result}") + return result async def handle_create_entities( @@ -61,13 +64,17 @@ async def handle_create_entities( ) -> EmbeddedResource: """Handle create_entities tool call.""" # Validate input + logger.debug(f"Creating entities with args: {args}") input_args = CreateEntitiesInput.model_validate(args) + logger.debug(f"Validated input: {len(input_args.entities)} entities") # Call service with validated data entities = await service.create_entities(input_args.entities) + logger.debug(f"Created {len(entities)} entities") # Format response response = CreateEntitiesResponse(entities=[EntityOut.model_validate(entity) for entity in entities]) + logger.debug("Formatted create_entities response") return create_response(response) @@ -76,8 +83,10 @@ async def handle_search_nodes( args: Dict[str, Any] ) -> EmbeddedResource: """Handle search_nodes tool call.""" + logger.debug(f"Searching nodes with query: {args.get('query')}") input_args = SearchNodesInput.model_validate(args) results = await service.search_nodes(input_args.query) + logger.debug(f"Found {len(results)} matches for query '{input_args.query}'") response = SearchNodesResponse( matches=[EntityOut.model_validate(entity) for entity in results], query=input_args.query @@ -90,8 +99,10 @@ async def handle_open_nodes( args: Dict[str, Any] ) -> EmbeddedResource: """Handle open_nodes tool call.""" + logger.debug(f"Opening nodes: {args.get('names')}") input_args = OpenNodesInput.model_validate(args) entities = await service.open_nodes(input_args.names) + logger.debug(f"Opened {len(entities)} entities") response = OpenNodesResponse(entities=[EntityOut.model_validate(entity) for entity in entities]) return create_response(response) @@ -102,10 +113,13 @@ async def handle_add_observations( ) -> EmbeddedResource: """Handle add_observations tool call.""" # Validate input + logger.debug(f"Adding observations: {args}") input_args = AddObservationsInput.model_validate(args) + logger.debug(f"Adding {len(input_args.observations)} observations to entity {input_args.entity_id}") # Call service with validated data observations = await service.add_observations(input_args) + logger.debug(f"Added {len(observations)} observations") # Format response response = AddObservationsResponse( @@ -121,13 +135,16 @@ async def handle_create_relations( ) -> EmbeddedResource: """Handle create_relations tool call.""" # Validate input + logger.debug(f"Creating relations: {args}") input_args = CreateRelationsInput.model_validate(args) + logger.debug(f"Creating {len(input_args.relations)} relations") # Call service with validated data created = await service.create_relations(input_args.relations) + logger.debug(f"Created {len(created)} relations") # Format response - response = CreateRelationsResponse(relations=created) + response = CreateRelationsResponse(relations=[RelationOut.model_validate(created) for created in created]) return create_response(response) @@ -136,8 +153,10 @@ async def handle_delete_entities( args: Dict[str, Any] ) -> EmbeddedResource: """Handle delete_entities tool call.""" + logger.debug(f"Deleting entities: {args}") input_args = DeleteEntitiesInput.model_validate(args) deleted = await service.delete_entities(input_args.names) + logger.debug(f"Deleted entities: {deleted}") response = DeleteEntitiesResponse(deleted=deleted) return create_response(response) @@ -147,8 +166,10 @@ async def handle_delete_observations( args: Dict[str, Any] ) -> EmbeddedResource: """Handle delete_observations tool call.""" + logger.debug(f"Deleting observations: {args}") input_args = DeleteObservationsInput.model_validate(args) entity, deleted = await service.delete_observations(input_args.deletions) + logger.debug(f"Deleted {len(deleted)} observations from entity {entity}") response = DeleteObservationsResponse( entity=entity, deleted=deleted @@ -174,6 +195,7 @@ class MemoryServer(Server): def __init__(self, config: Optional[ProjectConfig] = None): super().__init__("basic-memory") self.config = config or ProjectConfig() + logger.debug(f"Initialized MemoryServer with config: {self.config}") self.register_handlers() def register_handlers(self): @@ -182,7 +204,8 @@ class MemoryServer(Server): @self.list_tools() async def handle_list_tools() -> List[Tool]: """Define the available tools.""" - return [ + logger.debug("Listing available tools") + tools = [ Tool( name="create_entities", description="Create multiple new entities in the knowledge graph", @@ -219,6 +242,8 @@ class MemoryServer(Server): inputSchema=DeleteObservationsInput.model_json_schema() ) ] + logger.debug(f"Returning {len(tools)} available tools") + return tools @self.call_tool() async def handle_call_tool( @@ -229,28 +254,38 @@ class MemoryServer(Server): ) -> List[EmbeddedResource]: """Handle tool calls by delegating to the memory service.""" try: + logger.debug(f"Handling tool call: {name} with args: {arguments}") # Check if tool exists if name not in TOOL_HANDLERS: + logger.error(f"Unknown tool requested: {name}") raise McpError(METHOD_NOT_FOUND, f"Unknown tool: {name}") service = await create_project_services( self.config, memory_service=memory_service ) + logger.debug("Created project services") tool_name = name # type: ignore - return [await TOOL_HANDLERS[tool_name](service, arguments)] + result = [await TOOL_HANDLERS[tool_name](service, arguments)] + logger.debug(f"Tool {name} completed successfully") + return result except ValueError as e: + logger.error(f"Invalid parameters for {name}: {e}") raise McpError(INVALID_PARAMS, str(e)) except EntityNotFoundError as e: + logger.error(f"Entity not found in {name}: {e}") raise McpError(INVALID_PARAMS, str(e)) except Exception as e: + logger.exception(f"Unexpected error in {name}: {e}") raise McpError(INTERNAL_ERROR, str(e)) # Store handlers as instance attributes for testing self.handle_list_tools = handle_list_tools self.handle_call_tool = handle_call_tool + logger.debug("Registered all handlers") + # Create server instance with default config server = MemoryServer() @@ -265,6 +300,6 @@ async def run_server(): await server.run(read_stream, write_stream, options) if __name__ == "__main__": - logger.add("out.log", backtrace=True, diagnose=True) + logger.add("basic-memory-mcp.log", rotation="100 MB", level="DEBUG", backtrace=True, diagnose=True) import asyncio asyncio.run(run_server()) \ No newline at end of file diff --git a/src/basic_memory/repository/__init__.py b/src/basic_memory/repository/__init__.py index 6c77b80f..1ae338d2 100644 --- a/src/basic_memory/repository/__init__.py +++ b/src/basic_memory/repository/__init__.py @@ -4,6 +4,7 @@ from sqlalchemy import select, func, Select, Executable, inspect, Result, Column from sqlalchemy.exc import NoResultFound from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Mapped +from loguru import logger from basic_memory.models import Base @@ -17,54 +18,94 @@ class Repository[T: Base]: self.Model = Model self.primary_key: Column[Any] = inspect(self.Model).mapper.primary_key[0] self.valid_columns = [column.key for column in inspect(self.Model).columns] + logger.debug(f"Initialized {self.__class__.__name__} for {Model.__name__}") + logger.debug(f"Valid columns: {self.valid_columns}") async def refresh(self, instance: T, relationships: list[str] | None = None) -> None: """Refresh instance and optionally specified relationships.""" - await self.session.refresh(instance, relationships or []) + logger.debug(f"Refreshing {self.Model.__name__} instance: {getattr(instance, 'id', None)}") + try: + await self.session.refresh(instance, relationships or []) + logger.debug(f"Refreshed relationships: {relationships}") + except Exception as e: + logger.exception(f"Failed to refresh {self.Model.__name__} instance") + raise async def find_all(self, skip: int = 0, limit: int = 100) -> Sequence[T]: """Fetch records from the database with pagination.""" - result = await self.session.execute( - select(self.Model).offset(skip).limit(limit) - ) - return result.scalars().all() + logger.debug(f"Finding all {self.Model.__name__} (skip={skip}, limit={limit})") + try: + result = await self.session.execute( + select(self.Model).offset(skip).limit(limit) + ) + items = result.scalars().all() + logger.debug(f"Found {len(items)} {self.Model.__name__} records") + return items + except Exception as e: + logger.exception(f"Failed to find all {self.Model.__name__}") + raise async def find_by_id(self, entity_id: str) -> Optional[T]: """Fetch an entity by its unique identifier.""" - try: - result = await self.session.execute( - select(self.Model).filter(self.primary_key == entity_id) - ) - return result.scalars().one() - except NoResultFound: - return None - - async def create(self, entity_data: dict, model: Type[Base] | None = None) -> T: - """Create a new entity in the database from the provided data.""" - model = model or self.Model - model_data = {k: v for k, v in entity_data.items() if k in self.valid_columns} - entity = model(**model_data) - self.session.add(entity) - await self.session.flush() - return entity - - async def update(self, entity_id: str, entity_data: dict) -> Optional[T]: - """Update an entity with the given data.""" + logger.debug(f"Finding {self.Model.__name__} by ID: {entity_id}") try: result = await self.session.execute( select(self.Model).filter(self.primary_key == entity_id) ) entity = result.scalars().one() + logger.debug(f"Found {self.Model.__name__}: {entity_id}") + return entity + except NoResultFound: + logger.debug(f"No {self.Model.__name__} found with ID: {entity_id}") + return None + except Exception as e: + logger.exception(f"Failed to find {self.Model.__name__} by ID: {entity_id}") + raise + + async def create(self, entity_data: dict, model: Type[Base] | None = None) -> T: + """Create a new entity in the database from the provided data.""" + model = model or self.Model + logger.debug(f"Creating {model.__name__} with data: {entity_data}") + try: + model_data = {k: v for k, v in entity_data.items() if k in self.valid_columns} + logger.debug(f"Filtered data for valid columns: {model_data}") + + entity = model(**model_data) + self.session.add(entity) + await self.session.flush() + + logger.debug(f"Created {model.__name__}: {getattr(entity, 'id', None)}") + return entity + except Exception as e: + logger.exception(f"Failed to create {model.__name__}") + raise + + async def update(self, entity_id: str, entity_data: dict) -> Optional[T]: + """Update an entity with the given data.""" + logger.debug(f"Updating {self.Model.__name__} {entity_id} with data: {entity_data}") + try: + result = await self.session.execute( + select(self.Model).filter(self.primary_key == entity_id) + ) + entity = result.scalars().one() + for key, value in entity_data.items(): if key in self.valid_columns: setattr(entity, key, value) await self.session.flush() + + logger.debug(f"Updated {self.Model.__name__}: {entity_id}") return entity except NoResultFound: + logger.debug(f"No {self.Model.__name__} found to update: {entity_id}") return None + except Exception as e: + logger.exception(f"Failed to update {self.Model.__name__}: {entity_id}") + raise async def delete(self, entity_id: str) -> bool: """Delete an entity from the database.""" + logger.debug(f"Deleting {self.Model.__name__}: {entity_id}") try: result = await self.session.execute( select(self.Model).filter(self.primary_key == entity_id) @@ -72,23 +113,53 @@ class Repository[T: Base]: entity = result.scalars().one() await self.session.delete(entity) await self.session.flush() + + logger.debug(f"Deleted {self.Model.__name__}: {entity_id}") return True except NoResultFound: + logger.debug(f"No {self.Model.__name__} found to delete: {entity_id}") return False + except Exception as e: + logger.exception(f"Failed to delete {self.Model.__name__}: {entity_id}") + raise async def count(self, query: Executable | None = None) -> int: """Count entities in the database table.""" - if query is None: - query = select(func.count()).select_from(self.Model) - result = await self.session.execute(query) - scalar = result.scalar() - return scalar if scalar is not None else 0 + try: + if query is None: + query = select(func.count()).select_from(self.Model) + result = await self.session.execute(query) + scalar = result.scalar() + count = scalar if scalar is not None else 0 + + logger.debug(f"Counted {count} {self.Model.__name__} records") + return count + except Exception as e: + logger.exception(f"Failed to count {self.Model.__name__}") + raise async def execute_query(self, query: Executable) -> Result[Any]: """Execute a query asynchronously.""" - return await self.session.execute(query) + logger.debug(f"Executing query: {query}") + try: + result = await self.session.execute(query) + logger.debug("Query executed successfully") + return result + except Exception as e: + logger.exception("Failed to execute query") + raise async def find_one(self, query: Select[tuple[T]]) -> Optional[T]: """Execute a query and retrieve a single record.""" - result = await self.execute_query(query) - return result.scalars().one_or_none() + logger.debug(f"Finding one {self.Model.__name__} with query: {query}") + try: + result = await self.execute_query(query) + entity = result.scalars().one_or_none() + if entity: + logger.debug(f"Found {self.Model.__name__}: {getattr(entity, 'id', None)}") + else: + logger.debug(f"No {self.Model.__name__} found") + return entity + except Exception as e: + logger.exception(f"Failed to find one {self.Model.__name__}") + raise \ No newline at end of file diff --git a/src/basic_memory/repository/entity_repository.py b/src/basic_memory/repository/entity_repository.py index 6f2a8a09..3195a82b 100644 --- a/src/basic_memory/repository/entity_repository.py +++ b/src/basic_memory/repository/entity_repository.py @@ -6,6 +6,7 @@ from sqlalchemy.orm import selectinload from basic_memory.models import Entity, Observation from basic_memory.repository import Repository +from loguru import logger class EntityRepository(Repository[Entity]): @@ -13,55 +14,86 @@ class EntityRepository(Repository[Entity]): def __init__(self, session): super().__init__(session, Entity) + logger.debug("Initialized EntityRepository") async def find_by_id(self, entity_id: str) -> Optional[Entity]: """Find entity by ID with all relationships eagerly loaded.""" + logger.debug(f"Finding entity by ID: {entity_id}") try: # First load base entity result = await self.session.execute( select(Entity).filter(Entity.id == entity_id) ) entity = result.scalars().one() + logger.debug(f"Found base entity: {entity.id}") # Force refresh of all relationships await self.refresh(entity, ['observations', 'outgoing_relations', 'incoming_relations']) + logger.debug(f"Refreshed entity relationships: {entity.id}") return entity except NoResultFound: + logger.debug(f"No entity found with ID: {entity_id}") return None + except Exception as e: + logger.exception(f"Error finding entity by ID: {entity_id}") + raise async def find_by_name(self, name: str) -> Optional[Entity]: """Find an entity by its unique name.""" - query = ( - select(Entity) - .filter(Entity.name == name) - ) - result = await self.session.execute(query) - entity = result.scalars().one_or_none() - if entity: - await self.refresh(entity, ['observations', 'outgoing_relations', 'incoming_relations']) - return entity + logger.debug(f"Finding entity by name: {name}") + try: + query = ( + select(Entity) + .filter(Entity.name == name) + ) + result = await self.session.execute(query) + entity = result.scalars().one_or_none() + if entity: + logger.debug(f"Found entity: {entity.id}") + await self.refresh(entity, ['observations', 'outgoing_relations', 'incoming_relations']) + logger.debug(f"Refreshed entity relationships: {entity.id}") + else: + logger.debug(f"No entity found with name: {name}") + return entity + except Exception as e: + logger.exception(f"Error finding entity by name: {name}") + raise async def search_by_type(self, entity_type: str, skip: int = 0, limit: int = 100) -> Sequence[Entity]: """Search for entities of a specific type.""" - query = select(Entity).filter(Entity.entity_type == entity_type).offset(skip).limit(limit) - result = await self.execute_query(query) - return result.scalars().all() + logger.debug(f"Searching entities by type: {entity_type} (skip={skip}, limit={limit})") + try: + query = select(Entity).filter(Entity.entity_type == entity_type).offset(skip).limit(limit) + result = await self.execute_query(query) + entities = result.scalars().all() + logger.debug(f"Found {len(entities)} entities of type {entity_type}") + return entities + except Exception as e: + logger.exception(f"Error searching entities by type: {entity_type}") + raise async def search(self, query: str) -> Sequence[Entity]: """Search entities using LIKE pattern matching.""" - stmt = select(Entity).distinct().where( - or_( - Entity.name.ilike(f"%{query}%"), - Entity.entity_type.ilike(f"%{query}%"), - Entity.observations.any( - Observation.content.ilike(f"%{query}%") + logger.debug(f"Searching entities with query: {query}") + try: + stmt = select(Entity).distinct().where( + or_( + Entity.name.ilike(f"%{query}%"), + Entity.entity_type.ilike(f"%{query}%"), + Entity.observations.any( + Observation.content.ilike(f"%{query}%") + ) ) + ).options( + selectinload(Entity.observations), + selectinload(Entity.outgoing_relations), + selectinload(Entity.incoming_relations) ) - ).options( - selectinload(Entity.observations), - selectinload(Entity.outgoing_relations), - selectinload(Entity.incoming_relations) - ) - result = await self.session.execute(stmt) - return list(result.scalars()) \ No newline at end of file + result = await self.session.execute(stmt) + entities = list(result.scalars()) + logger.debug(f"Found {len(entities)} matching entities") + return entities + except Exception as e: + logger.exception(f"Error searching entities: {query}") + raise \ No newline at end of file diff --git a/src/basic_memory/services/entity_service.py b/src/basic_memory/services/entity_service.py index 27d4337c..564ba2d1 100644 --- a/src/basic_memory/services/entity_service.py +++ b/src/basic_memory/services/entity_service.py @@ -7,6 +7,7 @@ from basic_memory.repository.entity_repository import EntityRepository from basic_memory.schemas import EntityIn, ObservationIn from basic_memory.models import Entity, Observation from basic_memory.fileio import EntityNotFoundError +from loguru import logger from . import ServiceError @@ -19,44 +20,88 @@ class EntityService: def __init__(self, project_path: Path, entity_repo: EntityRepository): self.project_path = project_path self.entity_repo = entity_repo + logger.debug(f"Initialized EntityService with path: {project_path}") async def search(self, query: str) -> List[Entity]: """Search entities using LIKE pattern matching.""" - return await self.entity_repo.search(query) + logger.debug(f"Searching entities with query: {query}") + try: + results = await self.entity_repo.search(query) + logger.debug(f"Found {len(results)} matches") + return results + except Exception as e: + logger.exception(f"Failed to search entities with query: {query}") + raise async def create_entity(self, entity: EntityIn) -> Entity: """Create a new entity in the database. Note: ID is generated by the EntityIn validator before reaching this method. """ - # Create base entity first - base_data = { - "id": entity.id, # Include the generated ID - "name": entity.name, - "entity_type": entity.entity_type, - "created_at": datetime.now(UTC), - } - created_entity = await self.entity_repo.create(base_data) - await self.entity_repo.refresh(created_entity, ['observations', 'outgoing_relations', 'incoming_relations']) - return created_entity + logger.debug(f"Creating entity in DB: {entity.id}") + try: + # Create base entity first + base_data = { + "id": entity.id, # Include the generated ID + "name": entity.name, + "entity_type": entity.entity_type, + "created_at": datetime.now(UTC), + } + logger.debug(f"Base entity data: {base_data}") + + created_entity = await self.entity_repo.create(base_data) + logger.debug(f"Created base entity: {created_entity.id}") + + await self.entity_repo.refresh(created_entity, ['observations', 'outgoing_relations', 'incoming_relations']) + logger.debug(f"Refreshed entity relationships: {created_entity.id}") + + return created_entity + except Exception as e: + logger.exception(f"Failed to create entity: {entity.id}") + raise async def get_entity(self, entity_id: str) -> Entity: """Get entity by ID.""" - db_entity = await self.entity_repo.find_by_id(entity_id) - if not db_entity: - raise EntityNotFoundError(f"Entity not found: {entity_id}") - - return db_entity + logger.debug(f"Getting entity by ID: {entity_id}") + try: + db_entity = await self.entity_repo.find_by_id(entity_id) + if not db_entity: + logger.error(f"Entity not found: {entity_id}") + raise EntityNotFoundError(f"Entity not found: {entity_id}") + + logger.debug(f"Found entity: {db_entity.id}") + return db_entity + except EntityNotFoundError: + raise + except Exception as e: + logger.exception(f"Failed to get entity: {entity_id}") + raise # TODO name is not unique async def get_by_name(self, name: str) -> Entity: """Get entity by name.""" - db_entity = await self.entity_repo.find_by_name(name) - if not db_entity: - raise EntityNotFoundError(f"Entity not found: {name}") - - return db_entity + logger.debug(f"Getting entity by name: {name}") + try: + db_entity = await self.entity_repo.find_by_name(name) + if not db_entity: + logger.error(f"Entity not found: {name}") + raise EntityNotFoundError(f"Entity not found: {name}") + + logger.debug(f"Found entity: {db_entity.id}") + return db_entity + except EntityNotFoundError: + raise + except Exception as e: + logger.exception(f"Failed to get entity by name: {name}") + raise async def delete_entity(self, entity_id: str) -> bool: """Delete entity from database.""" - return await self.entity_repo.delete(entity_id) \ No newline at end of file + logger.debug(f"Deleting entity: {entity_id}") + try: + result = await self.entity_repo.delete(entity_id) + logger.debug(f"Entity deleted: {entity_id}") + return result + except Exception as e: + logger.exception(f"Failed to delete entity: {entity_id}") + raise \ No newline at end of file diff --git a/src/basic_memory/services/memory_service.py b/src/basic_memory/services/memory_service.py index 75cf1091..7279a2f8 100644 --- a/src/basic_memory/services/memory_service.py +++ b/src/basic_memory/services/memory_service.py @@ -9,6 +9,7 @@ from basic_memory.schemas import ( ) from basic_memory.fileio import write_entity_file, read_entity_file from basic_memory.services import EntityService, RelationService, ObservationService +from loguru import logger class MemoryService: @@ -26,82 +27,126 @@ class MemoryService: self.entity_service = entity_service self.relation_service = relation_service self.observation_service = observation_service + logger.debug(f"Initialized MemoryService with path: {project_path}") async def create_entities(self, entities_in: List[EntityIn]) -> List[Entity]: """Create multiple entities with their observations.""" + logger.debug(f"Creating {len(entities_in)} entities") # Write files in parallel (filesystem is source of truth) async def write_file(entity: EntityIn): + logger.debug(f"Writing entity file for {entity.id}") await write_entity_file(self.entities_path, entity) + logger.debug(f"Wrote entity file: {self.entities_path}/{entity.file_name()}") file_writes = [write_file(entity) for entity in entities_in] + logger.debug("Starting parallel file writes") await asyncio.gather(*file_writes) + logger.debug("Completed all file writes") async def create_entity_in_db(entity_in: EntityIn): - await self.entity_service.create_entity(entity_in) - await self.observation_service.add_observations(entity_in, entity_in.observations) - [await self.relation_service.create_relation(relation_in) for relation_in in entity_in.relations] - # query the entity again to return relations - final_entity = await self.entity_service.get_entity(entity_in.id) - return final_entity + logger.debug(f"Creating entity in DB: {entity_in.id}") + try: + # Create base entity + await self.entity_service.create_entity(entity_in) + logger.debug(f"Created base entity: {entity_in.id}") + + # Add observations + await self.observation_service.add_observations(entity_in, entity_in.observations) + logger.debug(f"Added {len(entity_in.observations)} observations to {entity_in.id}") + + # Add relations + for relation in entity_in.relations: + await self.relation_service.create_relation(relation) + logger.debug(f"Added {len(entity_in.relations)} relations for {entity_in.id}") + + # Query final state + final_entity = await self.entity_service.get_entity(entity_in.id) + logger.debug(f"Retrieved final entity state: {final_entity}") + return final_entity + except Exception as e: + logger.exception(f"Failed to create entity in DB: {entity_in.id}") + raise # Update database index sequentially - entities = [await create_entity_in_db(entities_in) for entities_in in entities_in] - return entities + logger.debug("Starting DB updates") + try: + entities = [await create_entity_in_db(entity_in) for entity_in in entities_in] + logger.debug(f"Successfully created {len(entities)} entities in DB") + return entities + except Exception as e: + logger.exception("Failed to create entities in DB") + raise async def create_relations(self, relations_data: List[RelationIn]) -> List[Relation]: """Create multiple relations between entities.""" + logger.debug(f"Creating {len(relations_data)} relations") relations = [] for relation in relations_data: - # First read complete entities from filesystem - from_entity = await read_entity_file(self.entities_path, relation.from_id) - to_entity = await read_entity_file(self.entities_path, relation.to_id) + logger.debug(f"Processing relation: {relation.from_id} -> {relation.to_id}") + try: + # First read complete entities from filesystem + from_entity = await read_entity_file(self.entities_path, relation.from_id) + to_entity = await read_entity_file(self.entities_path, relation.to_id) + logger.debug(f"Read entities for relation: {from_entity.id}, {to_entity.id}") - # Add the new relation to the source entity - if not hasattr(from_entity, 'relations'): - from_entity.relations = [] - from_entity.relations.append(relation) + # Add the new relation to the source entity + if not hasattr(from_entity, 'relations'): + from_entity.relations = [] + from_entity.relations.append(relation) + logger.debug(f"Added relation to source entity: {from_entity.id}") - # Write updated entity files (filesystem is source of truth) - await asyncio.gather( - write_entity_file(self.entities_path, from_entity), - write_entity_file(self.entities_path, to_entity) - ) + # Write updated entity files (filesystem is source of truth) + logger.debug("Writing updated entity files") + await asyncio.gather( + write_entity_file(self.entities_path, from_entity), + write_entity_file(self.entities_path, to_entity) + ) + logger.debug("Wrote updated entity files") - # Now update the database index - relation = await self.relation_service.create_relation(relation) - relations.append(relation) + # Now update the database index + relation = await self.relation_service.create_relation(relation) + relations.append(relation) + logger.debug(f"Created relation in DB: {relation.id}") + except Exception as e: + logger.exception(f"Failed to create relation: {relation}") + raise + logger.debug(f"Successfully created {len(relations)} relations") return relations async def add_observations(self, observations_in: ObservationsIn) -> List[Observation]: - """Add observations to an existing entity. - - Args: - observations_in: input containing entity_id and observations + """Add observations to an existing entity.""" + logger.debug(f"Adding observations to entity: {observations_in.entity_id}") + try: + # First get the entity from DB to get its ID + db_entity = await self.entity_service.get_entity(observations_in.entity_id) + logger.debug(f"Found entity in DB: {db_entity.id}") - Returns: - List[Observation] with the newly created observations - """ - # First get the entity from DB to get its ID - db_entity = await self.entity_service.get_entity(observations_in.entity_id) - - # Read entity from filesystem using the ID - entity = await read_entity_file(self.entities_path, db_entity.id) + # Read entity from filesystem using the ID + entity = await read_entity_file(self.entities_path, db_entity.id) + logger.debug(f"Read entity from filesystem: {entity.id}") - # Create new observations for the entity - for obs in observations_in.observations: - entity.observations.append(obs) + # Create new observations for the entity + for obs in observations_in.observations: + entity.observations.append(obs) + logger.debug(f"Added {len(observations_in.observations)} observations to entity") - # Write updated entity file - await write_entity_file(self.entities_path, entity) - - # Update database index - added_observations = await self.observation_service.add_observations(entity, observations_in.observations) + # Write updated entity file + logger.debug("Writing updated entity file") + await write_entity_file(self.entities_path, entity) + logger.debug("Wrote updated entity file") + + # Update database index + added_observations = await self.observation_service.add_observations(entity, observations_in.observations) + logger.debug(f"Added {len(added_observations)} observations to DB") - db_entity = await self.entity_service.get_entity(entity.id) - return added_observations + db_entity = await self.entity_service.get_entity(entity.id) + return added_observations + except Exception as e: + logger.exception(f"Failed to add observations to entity: {observations_in.entity_id}") + raise async def delete_entities(self, entity_names: List[str]) -> None: pass @@ -114,21 +159,51 @@ class MemoryService: async def read_graph(self) -> List[Entity]: """Read the entire knowledge graph.""" - return await self.entity_service.get_all() + logger.debug("Reading entire knowledge graph") + try: + entities = await self.entity_service.get_all() + logger.debug(f"Read {len(entities)} entities from graph") + return entities + except Exception as e: + logger.exception("Failed to read graph") + raise async def search_nodes(self, query: str) -> List[Entity]: """Search for nodes in the knowledge graph.""" - return await self.entity_service.search(query) + logger.debug(f"Searching nodes with query: {query}") + try: + results = await self.entity_service.search(query) + logger.debug(f"Found {len(results)} matches for '{query}'") + return results + except Exception as e: + logger.exception(f"Failed to search nodes with query: {query}") + raise async def open_nodes(self, names: List[str]) -> List[Entity]: """Get specific nodes and their relationships.""" - async def read_node(name: str) -> Optional[Entity]: - # Get ID from name first - db_entity = await self.entity_service.get_entity(name) - if db_entity: - return await read_entity_file(self.entities_path, db_entity.id) - return None + logger.debug(f"Opening nodes: {names}") - entities = [entity for entity in await asyncio.gather(*(read_node(name) for name in names)) - if entity is not None] - return entities \ No newline at end of file + async def read_node(name: str) -> Optional[Entity]: + try: + # Get ID from name first + logger.debug(f"Looking up entity: {name}") + db_entity = await self.entity_service.get_entity(name) + if db_entity: + logger.debug(f"Found entity in DB: {db_entity.id}") + entity = await read_entity_file(self.entities_path, db_entity.id) + logger.debug(f"Read entity from filesystem: {entity.id}") + return entity + logger.debug(f"Entity not found: {name}") + return None + except Exception as e: + logger.exception(f"Failed to read node: {name}") + return None + + try: + entities = [entity for entity in await asyncio.gather(*(read_node(name) for name in names)) + if entity is not None] + logger.debug(f"Opened {len(entities)} entities") + return entities + except Exception as e: + logger.exception("Failed to open nodes") + raise \ No newline at end of file