mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
mcp service running
This commit is contained in:
@@ -35,3 +35,6 @@ ENV/
|
||||
projects/*.db
|
||||
projects/*.db-journal
|
||||
/.coverage
|
||||
|
||||
|
||||
*.log
|
||||
@@ -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
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
2024-12-08 19:48:28.902 | INFO | __main__:run_server:264 - Starting MCP server basic-memory
|
||||
@@ -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())
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user