mcp service running

This commit is contained in:
phernandez
2024-12-08 22:16:32 -06:00
parent 5fc8baff9f
commit dadd73abd4
9 changed files with 654 additions and 144 deletions
+3
View File
@@ -35,3 +35,6 @@ ENV/
projects/*.db
projects/*.db-journal
/.coverage
*.log
+66 -2
View File
@@ -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-
}
}
}
```
```
### 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 <AdaptedConnection <Connection(Thread-2, started daemon 6227046400)>>, 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 <AdaptedConnection <Connection(Thread-3, started daemon 6243872768)>>, 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
+186
View File
@@ -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
-1
View File
@@ -1 +0,0 @@
2024-12-08 19:48:28.902 | INFO | __main__:run_server:264 - Starting MCP server basic-memory
+41 -6
View File
@@ -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())
+103 -32
View File
@@ -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
@@ -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())
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
+67 -22
View File
@@ -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)
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
+131 -56
View File
@@ -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
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