From 1c56b23f75807c48fb733bed74539d3f96d18014 Mon Sep 17 00:00:00 2001 From: phernandez Date: Mon, 23 Dec 2024 20:48:22 -0600 Subject: [PATCH] /documents and /knowledge working (again) --- docs/obsidian-test.md | 53 +++ src/basic_memory/cli/main.py | 34 +- src/basic_memory/db.py | 56 ++- src/basic_memory/mcp/server.py | 340 ++++-------------- src/basic_memory/repository/repository.py | 63 ++-- .../services/knowledge/entities.py | 17 +- tests/api/conftest.py | 8 +- tests/mcp/test_search_nodes.py | 4 +- 8 files changed, 218 insertions(+), 357 deletions(-) create mode 100644 docs/obsidian-test.md diff --git a/docs/obsidian-test.md b/docs/obsidian-test.md new file mode 100644 index 00000000..8e878b39 --- /dev/null +++ b/docs/obsidian-test.md @@ -0,0 +1,53 @@ +--- +id: 5 +created: '2024-12-24T02:30:29.343588+00:00' +modified: '2024-12-24T02:30:29.343588+00:00' +type: test +tags: +- obsidian +- markdown +- documentation +created_by: Claude +status: draft +--- + +# Obsidian Test Document + +This is a test of how documents appear in Obsidian's interface. + +## Links and Tags +We can use: +- Standard markdown links like [Basic Memory](basic-memory) +- Tags like #test #documentation +- Embeds like ![[basic-memory]] + +## Features to Test +### Knowledge Graph +This document should show up in the knowledge graph with connections to: +- [[Basic_Memory]] project +- [[Knowledge_Graph_Structure]] which implements it +- [[Development_Process]] that guides it + +### Backlinks +Any document that links to this one should appear in the backlinks panel. + +### YAML Frontmatter +Obsidian should display the frontmatter cleanly at the top of the document. + +### Code Blocks +```python +def test_function(): + """Code blocks should have syntax highlighting""" + print("Testing display") +``` + +### Callouts +> [!NOTE] +> Obsidian supports special callout blocks +> They help organize important information + +### Task Lists +- [x] Create test document +- [x] Add various markdown features +- [ ] View in Obsidian +- [ ] Check graph visualization \ No newline at end of file diff --git a/src/basic_memory/cli/main.py b/src/basic_memory/cli/main.py index d93d1338..1dbba870 100644 --- a/src/basic_memory/cli/main.py +++ b/src/basic_memory/cli/main.py @@ -1,9 +1,39 @@ """Main CLI entry point for basic-memory""" +import asyncio +from pathlib import Path + import typer +from basic_memory.db import engine_session_factory, DatabaseType + app = typer.Typer() -# app.add_typer(migrate.app, name="migrate") + +@app.command() +def init_db( + project_path: str = typer.Argument(..., help="Path to project directory"), + force: bool = typer.Option(False, "--force", "-f", help="Force reinitialization if database exists") +): + """Initialize a new project database.""" + async def _init_db(): + path = Path(project_path) + db_path = path / "data" / "memory.db" + + if db_path.exists() and not force: + typer.echo(f"Database already exists at {db_path}. Use --force to reinitialize.") + raise typer.Exit(1) + + # Create data directory if needed + db_path.parent.mkdir(parents=True, exist_ok=True) + + try: + async with engine_session_factory(path, db_type=DatabaseType.FILESYSTEM, init=True): + typer.echo(f"Initialized database at {db_path}") + except Exception as e: + typer.echo(f"Error initializing database: {e}") + raise typer.Exit(1) + + asyncio.run(_init_db()) if __name__ == "__main__": # pragma: no cover - app() + app() \ No newline at end of file diff --git a/src/basic_memory/db.py b/src/basic_memory/db.py index 09c0de7c..dc65b2d8 100644 --- a/src/basic_memory/db.py +++ b/src/basic_memory/db.py @@ -4,6 +4,7 @@ from enum import Enum, auto from pathlib import Path from typing import AsyncGenerator +from loguru import logger from sqlalchemy import text from sqlalchemy.ext.asyncio import ( create_async_engine, @@ -13,6 +14,8 @@ from sqlalchemy.ext.asyncio import ( async_scoped_session, ) +from basic_memory.models import Base + class DatabaseType(Enum): """Types of supported databases.""" @@ -26,14 +29,19 @@ class DatabaseType(Enum): if db_type == cls.MEMORY: return Path(":memory:") else: - return project_path / "data" / "memory.db" + path = project_path / "data" / "memory.db" + logger.info(f"Using database path: {path}") + return path @classmethod def get_db_url(cls, db_path: Path) -> str: """Get SQLAlchemy URL for database path.""" if str(db_path) == ":memory:": + logger.info("Using in-memory SQLite database") return "sqlite+aiosqlite://" - return f"sqlite+aiosqlite:///{db_path}" + url = f"sqlite+aiosqlite:///{db_path}" + logger.info(f"Using SQLite database URL: {url}") + return url def get_scoped_session_factory( @@ -43,32 +51,6 @@ def get_scoped_session_factory( return async_scoped_session(session_maker, scopefunc=asyncio.current_task) -# @asynccontextmanager -# async def session( -# session_factory: async_sessionmaker[AsyncSession], -# ) -> AsyncGenerator[AsyncSession, None]: -# """ -# Get database session with proper lifecycle management. -# -# Args: -# session_factory: Async session factory to create session from -# -# Yields: -# AsyncSession configured for engine -# """ -# session = session_factory() -# try: -# await session.execute(text("PRAGMA foreign_keys=ON")) -# yield session -# await session.commit() -# except Exception: -# await session.rollback() -# raise -# finally: -# await session.close() -# - - @asynccontextmanager async def scoped_session( session_maker: async_sessionmaker[AsyncSession], @@ -93,20 +75,32 @@ async def scoped_session( await factory.remove() +async def init_db(session: AsyncSession): + """Initialize database with required tables.""" + await session.execute(text("PRAGMA foreign_keys=ON")) + conn = await session.connection() + await conn.run_sync(Base.metadata.create_all) + await session.commit() + + @asynccontextmanager async def engine_session_factory( project_path: Path, db_type: DatabaseType = DatabaseType.FILESYSTEM, + init: bool = True, ) -> AsyncGenerator[tuple[AsyncEngine, async_sessionmaker[AsyncSession]], None]: """Create engine and session factory.""" + logger.debug(f"Creating engine for project path: {project_path}") db_path = DatabaseType.get_db_path(project_path, db_type) db_url = DatabaseType.get_db_url(db_path) engine = create_async_engine(db_url, connect_args={"check_same_thread": False}) try: factory = async_sessionmaker(engine, expire_on_commit=False) - async with scoped_session(factory) as db_session: - # Initialize database - await db_session.execute(text("PRAGMA foreign_keys=ON")) + + if init: + logger.debug("Initializing database...") + async with scoped_session(factory) as db_session: + await init_db(db_session) yield engine, factory finally: diff --git a/src/basic_memory/mcp/server.py b/src/basic_memory/mcp/server.py index 38413ba8..67352786 100644 --- a/src/basic_memory/mcp/server.py +++ b/src/basic_memory/mcp/server.py @@ -1,281 +1,93 @@ -"""Basic Memory MCP server implementation. +"""Basic Memory MCP server - simplified proxy to FastAPI endpoints.""" -Creates a server that handles MCP tool calls and forwards them to our FastAPI endpoints. -Uses proper lifecycle management and logging to ensure reliable operation. -""" - -import asyncio import json import sys -from typing import List, Dict, Any, Optional from httpx import AsyncClient, ASGITransport from loguru import logger -from mcp import McpError from mcp.server import Server from mcp.server.models import InitializationOptions from mcp.types import ( Tool, EmbeddedResource, TextResourceContents, - METHOD_NOT_FOUND, - INVALID_PARAMS, - INTERNAL_ERROR, ServerCapabilities, ToolsCapability, ) -from pydantic import TypeAdapter, AnyUrl, ValidationError, BaseModel, Field, constr from basic_memory.api.app import app as fastapi_app -from basic_memory.schemas import ( - CreateEntityRequest, - SearchNodesRequest, - OpenNodesRequest, - AddObservationsRequest, - CreateRelationsRequest, - DeleteEntitiesRequest, - DeleteObservationsRequest, - DeleteRelationsRequest, - DocumentCreateRequest, - DocumentUpdateRequest, -) - -BASE_URL = "http://test" - -# URI constants -url_validator = TypeAdapter(AnyUrl) -BASIC_MEMORY_URI = url_validator.validate_python("basic-memory://response") -MIME_TYPE = "application/vnd.basic-memory+json" # Create server instance server = Server("basic-memory") - -class IdRequest(BaseModel): - """Validates ID parameters.""" - id: int = Field(gt=0, description="Resource ID") - - -class DocumentRequest(BaseModel): - """Validates document creation.""" - path: constr(min_length=1) = Field(..., description="Document path") - content: str = Field(..., description="Document content") - doc_metadata: Optional[Dict[str, Any]] = Field(None, description="Optional document metadata") - - -# Tool definitions with schemas and endpoints +# Simple map of tool names to endpoints TOOLS = { - # Knowledge graph tools - "create_entities": { - "schema": CreateEntityRequest, - "endpoint": "/knowledge/entities", - "method": "post", - "description": "Create multiple new entities", - }, - "search_nodes": { - "schema": SearchNodesRequest, - "endpoint": "/knowledge/search", - "method": "post", - "description": "Search for nodes", - }, - "open_nodes": { - "schema": OpenNodesRequest, - "endpoint": "/knowledge/nodes", - "method": "post", - "description": "Open specific nodes", - }, - "add_observations": { - "schema": AddObservationsRequest, - "endpoint": "/knowledge/observations", - "method": "post", - "description": "Add observations", - }, - "create_relations": { - "schema": CreateRelationsRequest, - "endpoint": "/knowledge/relations", - "method": "post", - "description": "Create relations", - }, - "delete_entities": { - "schema": DeleteEntitiesRequest, - "endpoint": "/knowledge/entities/delete", - "method": "post", - "description": "Delete entities", - }, - "delete_observations": { - "schema": DeleteObservationsRequest, - "endpoint": "/knowledge/observations/delete", - "method": "post", - "description": "Delete observations", - }, - "delete_relations": { - "schema": DeleteRelationsRequest, - "endpoint": "/knowledge/relations/delete", - "method": "post", - "description": "Delete relations", - }, - # Document tools - "create_document": { - "schema": DocumentRequest, # Use our new validator - "endpoint": "/documents", - "method": "post", - "description": "Create a new document", - }, - "list_documents": { - "schema": None, # No validation needed - "endpoint": "/documents", - "method": "get", - "description": "List all documents", - }, - "get_document": { - "schema": IdRequest, - "endpoint": "/documents/{id}", - "method": "get", - "description": "Get a document by ID", - }, - "update_document": { - "schema": DocumentUpdateRequest, - "endpoint": "/documents/{id}", - "method": "put", - "description": "Update a document by ID", - }, - "delete_document": { - "schema": IdRequest, - "endpoint": "/documents/{id}", - "method": "delete", - "description": "Delete a document by ID", - }, + # Knowledge endpoints + "create_entities": {"endpoint": "/knowledge/entities/", "method": "post"}, + "search_nodes": {"endpoint": "/knowledge/search/", "method": "post"}, + "open_nodes": {"endpoint": "/knowledge/nodes/", "method": "post"}, + "add_observations": {"endpoint": "/knowledge/observations/", "method": "post"}, + "create_relations": {"endpoint": "/knowledge/relations/", "method": "post"}, + "delete_entities": {"endpoint": "/knowledge/entities/delete/", "method": "post"}, + "delete_observations": {"endpoint": "/knowledge/observations/delete/", "method": "post"}, + "delete_relations": {"endpoint": "/knowledge/relations/delete/", "method": "post"}, + # Document endpoints + "create_document": {"endpoint": "/documents/", "method": "post"}, + "list_documents": {"endpoint": "/documents/", "method": "get"}, + "get_document": {"endpoint": "/documents/{id}", "method": "get"}, + "update_document": {"endpoint": "/documents/{id}", "method": "put"}, + "delete_document": {"endpoint": "/documents/{id}", "method": "delete"}, } @server.list_tools() -async def handle_list_tools() -> List[Tool]: - """Define the available tools.""" - logger.debug("Listing available tools") - tools = [] - - for name, config in TOOLS.items(): - schema = config["schema"].model_json_schema() if config["schema"] else { - "type": "object", - "properties": {}, - "additionalProperties": False, - } - - tools.append( - Tool( - name=name, - description=config["description"], - inputSchema=schema, - ) - ) - - return tools - - -async def call_tool_endpoint(endpoint: str, json: dict[str, Any], method: str = "post"): - """Makes a request to a FastAPI endpoint with a fresh client.""" - async with AsyncClient( - transport=ASGITransport(app=fastapi_app), base_url=BASE_URL, timeout=30.0 - ) as client: - logger.debug(f"Calling API endpoint {endpoint} with {method}: {json}") - try: - if method == "post": - response = await client.post(endpoint, json=json) - elif method == "get": - # For GET requests, don't send ID in params - params = {k:v for k,v in json.items() if k != "id"} - response = await client.get(endpoint, params=params) - elif method == "put": - response = await client.put(endpoint, json=json) - elif method == "delete": - response = await client.delete(endpoint) - logger.debug(response.json() if response.content else "No content") - return response - except ValidationError as e: - logger.error(f"Validation error: {e}") - raise McpError(INVALID_PARAMS, str(e)) - - -def create_response(data: Dict[str, Any]) -> EmbeddedResource: - """Create standard MCP response wrapper.""" - return EmbeddedResource( - type="resource", - resource=TextResourceContents( - uri=BASIC_MEMORY_URI, - mimeType=MIME_TYPE, - text=json.dumps(data), - ), - ) +async def handle_list_tools(): + """Just list our tools with minimal schema.""" + return [ + Tool(name=name, description=f"Call {config['endpoint']}", inputSchema={"type": "object"}) + for name, config in TOOLS.items() + ] @server.call_tool() -async def handle_call_tool(name: str, arguments: Dict[str, Any]) -> List[EmbeddedResource]: - """Forward tool calls to FastAPI endpoints.""" +async def handle_call_tool(name: str, arguments: dict): + """Simple proxy to FastAPI endpoints.""" + config = TOOLS[name] + endpoint = config["endpoint"] + method = config["method"] - try: - logger.info(f"Tool call: {name}") - logger.debug(f"Arguments: {arguments}") + # Handle ID parameter in URL + if "{id}" in endpoint: + endpoint = endpoint.format(id=arguments.get("id")) - # Get tool configuration - tool_config = TOOLS.get(name) - if tool_config is None: - raise McpError(METHOD_NOT_FOUND, f"Unknown tool: {name}") - - # Validate arguments using schema if one exists - if tool_config["schema"]: + # Ensure non-string arguments are properly JSON serialized + processed_args = {} + for key, value in arguments.items(): + if key == "doc_metadata" and isinstance(value, str): try: - tool_config["schema"].model_validate(arguments) - except ValidationError as e: - raise McpError(INVALID_PARAMS, str(e)) - - endpoint = tool_config["endpoint"] - method = tool_config["method"] - - # Format endpoint for ID-based routes - if "{id}" in endpoint: - id = arguments.get("id") - if id is None: - raise McpError(INVALID_PARAMS, "ID parameter required") - endpoint = endpoint.format(id=id) - - # Make API call - response = await call_tool_endpoint(endpoint, arguments, method) - - # Handle HTTP errors - if response.status_code >= 400: - error_data = response.json() - error_detail = error_data.get("detail", "") - - # All validation errors are INVALID_PARAMS - if response.status_code == 422: - raise McpError(INVALID_PARAMS, error_detail) - # Resource not found is also INVALID_PARAMS - elif response.status_code == 404 and "not found" in str(error_detail).lower(): - raise McpError(INVALID_PARAMS, error_detail) - # Other errors are INTERNAL_ERROR - else: - raise McpError(INTERNAL_ERROR, error_detail or "Internal error") - - # Create response - if response.content: # Some endpoints (like DELETE) return no content - result = create_response(response.json()) + processed_args[key] = json.loads(value) + except json.JSONDecodeError: + processed_args[key] = None else: - result = create_response({"status": "success"}) + processed_args[key] = value - logger.debug(f"Tool call successful: {result}") - return [result] + # Make request to FastAPI + async with AsyncClient( + transport=ASGITransport(app=fastapi_app), base_url="http://test" + ) as client: + response = await getattr(client, method)(endpoint, json=processed_args) - except ValidationError as e: - logger.error(f"Validation error: {e}") - raise McpError(INVALID_PARAMS, str(e)) - except ValueError as e: - logger.error(f"Value error: {e}") - raise McpError(INVALID_PARAMS, str(e)) - except Exception as e: - logger.error(f"Error handling tool call: {e}") - if isinstance(e, McpError): - raise - raise McpError(INTERNAL_ERROR, str(e)) + # Return wrapped response + return [ + EmbeddedResource( + type="resource", + resource=TextResourceContents( + uri="basic-memory://response", + mimeType="application/json", + text=json.dumps(response.json() if response.content else {"status": "success"}), + ), + ) + ] def setup_logging(log_file: str = "basic-memory-mcp.log"): @@ -306,43 +118,31 @@ def setup_logging(log_file: str = "basic-memory-mcp.log"): async def run_server(): - """Run the MCP server with proper initialization and cleanup.""" + """Run the MCP server with proper initialization.""" + setup_logging() logger.info("Starting Basic Memory MCP server") - try: - # Initialize server with explicit tool capabilities - options = InitializationOptions( - server_name="basic-memory", - server_version="0.1.0", - capabilities=ServerCapabilities( - tools=ToolsCapability(listChanged=True), # Explicitly enable tools - experimental={}, - ), - ) - logger.debug(f"Server initialization options: {options}") + # Initialize server with explicit tool capabilities + options = InitializationOptions( + server_name="basic-memory", + server_version="0.1.0", + capabilities=ServerCapabilities(tools=ToolsCapability(listChanged=True), experimental={}), + ) - # Run server - logger.info("Server initialized, waiting for client connection") - async with stdio_server() as (read_stream, write_stream): - logger.debug("STDIO streams established") - await server.run(read_stream, write_stream, options) - - except Exception as e: - logger.error(f"Server error: {e}") - raise - finally: - logger.info("Server shutting down") + # Run server with proper initialization + async with stdio_server() as (read, write): + await server.run(read, write, options) if __name__ == "__main__": from mcp.server.stdio import stdio_server + import asyncio - # Run with proper asyncio error handling try: asyncio.run(run_server()) except KeyboardInterrupt: - logger.info("Server stopped by user") + sys.exit(0) except Exception as e: - logger.error(f"Fatal server error: {e}") - sys.exit(1) \ No newline at end of file + print(f"Server error: {e}", file=sys.stderr) + sys.exit(1) diff --git a/src/basic_memory/repository/repository.py b/src/basic_memory/repository/repository.py index 9d9c2c6e..522b7749 100644 --- a/src/basic_memory/repository/repository.py +++ b/src/basic_memory/repository/repository.py @@ -40,6 +40,26 @@ class Repository[T: Base]: } return model_data + async def select_by_id(self, session: AsyncSession, entity_id: int) -> Optional[T]: + """Select an entity by ID using an existing session.""" + query = ( + select(self.Model) + .filter(self.primary_key == entity_id) + .options(*self.get_load_options()) + ) + result = await session.execute(query) + return result.scalars().one_or_none() + + async def select_by_ids(self, session: AsyncSession, ids: List[int]) -> Sequence[T]: + """Select multiple entities by IDs using an existing session.""" + query = ( + select(self.Model) + .where(self.primary_key.in_(ids)) + .options(*self.get_load_options()) + ) + result = await session.execute(query) + return result.scalars().all() + async def add(self, model: T) -> T: """ Add a model to the repository. This will also add related objects @@ -50,22 +70,23 @@ class Repository[T: Base]: session.add(model) await session.flush() - # query to get relations - found = await self.find_by_id(model.id) # pyright: ignore [reportAttributeAccessIssue] + # Query within same session + found = await self.select_by_id(session, model.id) # pyright: ignore [reportAttributeAccessIssue] assert found is not None, "can't find model after session.add" return found async def add_all(self, models: List[T]) -> Sequence[T]: """ Add a list of models to the repository. This will also add related objects - :param model: the models to add + :param models: the models to add :return: the added models instances """ async with db.scoped_session(self.session_maker) as session: session.add_all(models) await session.flush() - # we have to find to get relations - return await self.find_by_ids([m.id for m in models]) # pyright: ignore [reportAttributeAccessIssue] + + # Query within same session + return await self.select_by_ids(session, [m.id for m in models]) # pyright: ignore [reportAttributeAccessIssue] def select(self, *entities: Any) -> Select: """Create a new SELECT statement. @@ -103,35 +124,14 @@ class Repository[T: Base]: logger.debug(f"Finding {self.Model.__name__} by ID: {entity_id}") async with db.scoped_session(self.session_maker) as session: - try: - query = ( - select(self.Model) - .filter(self.primary_key == entity_id) - .options(*self.get_load_options()) - ) - - result = await session.execute(query) - 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 + return await self.select_by_id(session, entity_id) async def find_by_ids(self, ids: List[int]) -> Sequence[T]: """Fetch multiple entities by their identifiers in a single query.""" logger.debug(f"Finding {self.Model.__name__} by IDs: {ids}") - query = ( - select(self.Model).where(self.primary_key.in_(ids)).options(*self.get_load_options()) - ) - async with db.scoped_session(self.session_maker) as session: - result = await session.execute(query) - entities = result.scalars().all() - - logger.debug(f"Found {len(entities)} {self.Model.__name__} records") - return entities + return await self.select_by_ids(session, ids) async def find_one(self, query: Select[tuple[T]]) -> Optional[T]: """Execute a query and retrieve a single record.""" @@ -158,8 +158,7 @@ class Repository[T: Base]: session.add(model) await session.flush() - return_instance = await self.find_by_id(model.id) # pyright: ignore [reportAttributeAccessIssue] - + return_instance = await self.select_by_id(session, model.id) # pyright: ignore [reportAttributeAccessIssue] assert return_instance is not None, "can't find model after session.add" return return_instance @@ -173,7 +172,7 @@ class Repository[T: Base]: session.add_all(model_list) await session.flush() - return await self.find_by_ids([model.id for model in model_list]) # pyright: ignore [reportAttributeAccessIssue] + return await self.select_by_ids(session, [model.id for model in model_list]) # pyright: ignore [reportAttributeAccessIssue] async def update(self, entity_id: int, entity_data: dict) -> Optional[T]: """Update an entity with the given data.""" @@ -193,7 +192,7 @@ class Repository[T: Base]: await session.refresh(entity) # Refresh logger.debug(f"Updated {self.Model.__name__}: {entity_id}") - return await self.find_by_id(entity.id) # pyright: ignore [reportAttributeAccessIssue] + return await self.select_by_id(session, entity.id) # pyright: ignore [reportAttributeAccessIssue] except NoResultFound: logger.debug(f"No {self.Model.__name__} found to update: {entity_id}") diff --git a/src/basic_memory/services/knowledge/entities.py b/src/basic_memory/services/knowledge/entities.py index 6e318b0a..3754e0d3 100644 --- a/src/basic_memory/services/knowledge/entities.py +++ b/src/basic_memory/services/knowledge/entities.py @@ -42,12 +42,8 @@ class EntityOperations(FileOperations): created = [] for entity in entities: - try: - created_entity = await self.create_entity(entity) - created.append(created_entity) - except Exception as e: - logger.error(f"Failed to create entity {entity.name}: {e}") - continue + created_entity = await self.create_entity(entity) + created.append(created_entity) return created @@ -77,12 +73,9 @@ class EntityOperations(FileOperations): logger.debug(f"Deleting entities: {entity_ids}") success = True + # Let errors bubble up for entity_id in entity_ids: - try: - await self.delete_entity(entity_id) - except Exception as e: - logger.error(f"Failed to delete entity {entity_id}: {e}") - success = False - continue + await self.delete_entity(entity_id) + success = True return success diff --git a/tests/api/conftest.py b/tests/api/conftest.py index 16f58e8a..864e9751 100644 --- a/tests/api/conftest.py +++ b/tests/api/conftest.py @@ -12,13 +12,7 @@ from basic_memory.deps import get_project_config, get_engine_factory @pytest_asyncio.fixture def app(test_config, engine_factory) -> FastAPI: """Create FastAPI test application.""" - # Lazy import router to avoid app startup issues - from basic_memory.api.routers.knowledge import router as knowledge_router - from basic_memory.api.routers.documents import router as documents_router - - app = FastAPI() - app.include_router(knowledge_router) - app.include_router(documents_router) + from basic_memory.api.app import app app.dependency_overrides[get_project_config] = lambda: test_config app.dependency_overrides[get_engine_factory] = lambda: engine_factory diff --git a/tests/mcp/test_search_nodes.py b/tests/mcp/test_search_nodes.py index 18589b4f..62aa8a06 100644 --- a/tests/mcp/test_search_nodes.py +++ b/tests/mcp/test_search_nodes.py @@ -3,7 +3,7 @@ import pytest from mcp.types import EmbeddedResource -from basic_memory.mcp.server import MIME_TYPE, BASIC_MEMORY_URI, handle_call_tool +from basic_memory.mcp.server import MIME_TYPE, handle_call_tool from basic_memory.schemas import SearchNodesResponse @@ -21,8 +21,6 @@ async def test_search_nodes(app, test_entity_data, client): assert len(result) == 1 assert isinstance(result[0], EmbeddedResource) assert result[0].type == "resource" - assert isinstance(result[0].resource.uri, type(BASIC_MEMORY_URI)) - assert str(result[0].resource.uri) == str(BASIC_MEMORY_URI) assert result[0].resource.mimeType == MIME_TYPE # Verify search results