delete old mcp tests, add docs to tools

This commit is contained in:
phernandez
2024-12-25 14:28:35 -06:00
parent bb47fbd48b
commit 141bcd85b8
11 changed files with 195 additions and 730 deletions
+195 -28
View File
@@ -1,7 +1,7 @@
"""Basic Memory MCP server using fastmcp - proxies to FastAPI endpoints."""
import sys
from typing import Any, List
from typing import Any, List, Optional, Dict
from fastmcp import FastMCP
from loguru import logger
@@ -54,8 +54,24 @@ async def log_api_call(method: str, url: str, data: Any, response: Any):
@mcp.tool()
async def create_entities(entities: list[dict]) -> dict:
"""Create new entities in the knowledge graph."""
async def create_entities(entities: List[dict]) -> dict:
"""Create new entities in the knowledge graph.
Args:
entities: List of entity dictionaries, each containing:
- name: Entity name
- entity_type: Classification (e.g., 'component', 'specification')
- description: Optional description
- observations: Optional list of initial observations
Example:
create_entities([{
"name": "Knowledge Format",
"entity_type": "specification",
"description": "Document format specification",
"observations": ["Uses markdown format", "Supports YAML frontmatter"]
}])
"""
url = "/knowledge/entities"
data = {"entities": entities}
response = await client.post(url, json=data)
@@ -63,8 +79,24 @@ async def create_entities(entities: list[dict]) -> dict:
@mcp.tool()
async def create_relations(relations: list[dict]) -> dict:
"""Create relations between entities."""
async def create_relations(relations: List[dict]) -> dict:
"""Create relations between existing entities.
Args:
relations: List of relation dictionaries, each containing:
- from_id: Source entity path_id
- to_id: Target entity path_id
- relation_type: Type of relationship in active voice
- context: Optional context for the relation
Example:
create_relations([{
"from_id": "test/parser_test",
"to_id": "component/parser",
"relation_type": "validates",
"context": "Unit test coverage"
}])
"""
url = "/knowledge/relations"
data = {"relations": relations}
response = await client.post(url, json=data)
@@ -72,10 +104,32 @@ async def create_relations(relations: list[dict]) -> dict:
@mcp.tool()
async def add_observations(path_id: str, observations: list[str]) -> dict:
"""Add observations to an entity."""
async def add_observations(path_id: str, observations: List[dict], context: Optional[str] = None) -> dict:
"""Add observations to an entity.
Args:
path_id: Entity path ID
observations: List of observations, each containing:
- content: The observation text
- category: Optional category ('tech', 'design', 'feature', 'note', 'issue', 'todo')
context: Optional shared context for all observations
Example:
add_observations(
"specification/knowledge_format",
observations=[
{"content": "Uses markdown format", "category": "tech"},
{"content": "Designed for readability", "category": "design"}
],
context="Initial design"
)
"""
url = "/knowledge/observations"
data = {"path_id": path_id, "observations": observations}
data = {
"path_id": path_id,
"observations": observations,
"context": context
}
response = await client.post(url, json=data)
await log_api_call("POST", url, data, response)
return response.json()
@@ -86,24 +140,59 @@ async def add_observations(path_id: str, observations: list[str]) -> dict:
@mcp.tool()
async def get_entity(path_id: str) -> dict:
"""Get a specific entity by path_id."""
"""Get a specific entity by its path_id.
Args:
path_id: Entity path ID (e.g., 'component/memory_service')
Returns:
Complete entity information including observations and relations.
Example:
get_entity("specification/knowledge_format")
"""
url = f"/knowledge/entities/{path_id}"
response = await client.get(url)
return response.json()
@mcp.tool()
async def search_nodes(query: str) -> dict:
"""Search for entities in the knowledge graph."""
async def search_nodes(query: str, category: Optional[str] = None) -> dict:
"""Search for entities in the knowledge graph.
Args:
query: Search text to match against entities
category: Optional category to filter observations by
Returns:
Matching entities with their observations and relations.
Example:
search_nodes("markdown format", category="tech") # Find tech observations about markdown
search_nodes("implementation") # Search all categories
"""
url = "/knowledge/search"
data = {"query": query}
data = {"query": query, "category": category}
response = await client.post(url, json=data)
return response.json()
@mcp.tool()
async def open_nodes(path_ids: List[str]) -> dict:
"""Search for entities in the knowledge graph."""
"""Load multiple entities by their path_ids.
Args:
path_ids: List of entity path IDs to load
Returns:
Dictionary of loaded entities with their observations and relations.
Example:
open_nodes([
"specification/knowledge_format",
"component/parser"
])
"""
url = "/knowledge/nodes"
data = {"path_ids": path_ids}
response = await client.post(url, json=data)
@@ -115,7 +204,14 @@ async def open_nodes(path_ids: List[str]) -> dict:
@mcp.tool()
async def delete_entities(path_ids: List[str]) -> dict:
"""Search for entities in the knowledge graph."""
"""Delete entities from the knowledge graph.
Args:
path_ids: List of entity path IDs to delete
Example:
delete_entities(["test/obsolete_test", "component/old_component"])
"""
url = "/knowledge/entities/delete"
data = {"path_ids": path_ids}
response = await client.post(url, json=data)
@@ -123,8 +219,19 @@ async def delete_entities(path_ids: List[str]) -> dict:
@mcp.tool()
async def delete_observations(path_id: str, observations: list[str]) -> dict:
"""Delete observations from an entity."""
async def delete_observations(path_id: str, observations: List[str]) -> dict:
"""Delete specific observations from an entity.
Args:
path_id: Entity path ID
observations: List of observation content strings to delete
Example:
delete_observations(
"component/parser",
["Obsolete implementation detail", "Old design note"]
)
"""
url = "/knowledge/observations/delete"
data = {
"path_id": path_id,
@@ -132,18 +239,32 @@ async def delete_observations(path_id: str, observations: list[str]) -> dict:
}
response = await client.post(
url, json=data
)
)
return response.json()
@mcp.tool()
async def delete_relations(relations: list[dict]) -> dict:
"""Delete relations between entities."""
async def delete_relations(relations: List[dict]) -> dict:
"""Delete relations between entities.
Args:
relations: List of relation dictionaries to delete, each containing:
- from_id: Source entity path_id
- to_id: Target entity path_id
- relation_type: Type of relationship
Example:
delete_relations([{
"from_id": "test/old_test",
"to_id": "component/parser",
"relation_type": "validates"
}])
"""
url = "/knowledge/relations/delete"
data = {"relations": relations}
response = await client.post(
url, json=data
)
)
return response.json()
@@ -151,8 +272,21 @@ async def delete_relations(relations: list[dict]) -> dict:
@mcp.tool()
async def create_document(path: str, content: str, metadata: dict = None) -> dict:
"""Create a new document."""
async def create_document(path: str, content: str, metadata: Optional[Dict] = None) -> dict:
"""Create a new document.
Args:
path: Document path (must end in .md)
content: Document content as markdown text
metadata: Optional metadata dictionary
Example:
create_document(
"docs/format.md",
"# Format Specification\n\nDetails here...",
metadata={"author": "AI team"}
)
"""
url = "/documents/create"
data = {"path": path, "content": content, "metadata": metadata}
response = await client.post(url, json=data)
@@ -161,15 +295,35 @@ async def create_document(path: str, content: str, metadata: dict = None) -> dic
@mcp.tool()
async def get_document(path: str) -> dict:
"""Get a document by path_id."""
"""Get a document by its path.
Args:
path: Document path to retrieve
Example:
get_document("docs/format.md")
"""
url = f"/documents/{path}"
response = await client.get(url)
return response.json()
@mcp.tool()
async def update_document(path: str, content: str, metadata: dict = None) -> dict:
"""Update an existing document."""
async def update_document(path: str, content: str, metadata: Optional[Dict] = None) -> dict:
"""Update an existing document.
Args:
path: Document path
content: New document content
metadata: Optional new metadata
Example:
update_document(
"docs/format.md",
"# Updated Format\n\nNew content...",
metadata={"updated_by": "AI team"}
)
"""
url = f"/documents/{path}"
data = {"path": path, "content": content, "metadata": metadata}
response = await client.put(url, json=data)
@@ -178,7 +332,14 @@ async def update_document(path: str, content: str, metadata: dict = None) -> dic
@mcp.tool()
async def list_documents() -> list:
"""List all documents."""
"""List all documents in the system.
Returns:
List of document paths and metadata.
Example:
list_documents() # Get all document paths
"""
url = "/documents/list"
response = await client.get(url)
return response.json()
@@ -186,13 +347,19 @@ async def list_documents() -> list:
@mcp.tool()
async def delete_document(path: str) -> dict:
"""Delete an existing document."""
"""Delete a document.
Args:
path: Path of document to delete
Example:
delete_document("docs/obsolete.md")
"""
url = f"/documents/{path}"
response = await client.delete(url)
if response.status_code == 204:
return {"deleted": True}
if __name__ == "__main__":
setup_logging()
logger.info("Starting Basic Memory MCP server")
-40
View File
@@ -1,40 +0,0 @@
# """Tests for the MCP server implementation using FastAPI TestClient."""
#
# import pytest
# from mcp.types import EmbeddedResource
#
# from basic_memory.mcp.server import handle_call_tool
# from basic_memory.schemas import CreateEntityResponse, EntityResponse
#
#
# @pytest.mark.asyncio
# async def test_add_observations(app, test_entity_data, client):
# """Test adding observations to an existing entity."""
#
# # First create an entity
# create_result = await handle_call_tool("create_entities", test_entity_data)
# create_response = CreateEntityResponse.model_validate_json(create_result[0].resource.text) # pyright: ignore [reportAttributeAccessIssue]
# entity_id = create_response.entities[0].id
#
# # Add new observation
# result = await handle_call_tool(
# "add_observations", {"entity_id": entity_id, "observations": ["A new observation"]}
# )
#
# # Verify response format
# assert len(result) == 1
# assert isinstance(result[0], EmbeddedResource)
# assert result[0].type == "resource"
#
# # Verify observation was added
# response = EntityResponse.model_validate_json(result[0].resource.text) # pyright: ignore [reportAttributeAccessIssue]
# assert response.id == entity_id
# assert len(response.observations) == 2 # 1 already present
# assert response.observations[1].content == "A new observation"
#
# # Verify through API
# api_response = await client.get(f"/knowledge/entities/{entity_id}")
# assert api_response.status_code == 200
# entity = api_response.json()
# assert len(entity["observations"]) == 2 # Original + new
# assert "A new observation" in [o["content"] for o in entity["observations"]]
-107
View File
@@ -1,107 +0,0 @@
# """Tests for the MCP server implementation using FastAPI TestClient."""
#
# import pytest
# from mcp.types import EmbeddedResource
#
# from basic_memory.mcp.server import MIME_TYPE, handle_call_tool
# from basic_memory.schemas import CreateEntityResponse, SearchNodesResponse
#
#
# @pytest.mark.asyncio
# async def test_create_single_entity(app):
# """Test creating a single entity."""
# entity_data = {
# "entities": [
# {"name": "SingleTest", "entity_type": "test", "observations": ["Test observation"]}
# ]
# }
#
# result = await handle_call_tool("create_entities", entity_data)
#
# # Verify response format
# assert len(result) == 1
# assert isinstance(result[0], EmbeddedResource)
# assert result[0].type == "resource"
# assert result[0].resource.mimeType == MIME_TYPE
#
# # Verify entity creation
# response = CreateEntityResponse.model_validate_json(result[0].resource.text) # pyright: ignore [reportAttributeAccessIssue]
# assert len(response.entities) == 1
# entity = response.entities[0]
# assert entity.name == "SingleTest"
# assert entity.entity_type == "test"
# assert len(entity.observations) == 1
# assert entity.observations[0].content == "Test observation"
# assert entity.id is not None
#
# # Verify entity can be found via search
# search_result = await handle_call_tool("search_nodes", {"query": "SingleTest"})
# search_response = SearchNodesResponse.model_validate_json(search_result[0].resource.text) # pyright: ignore [reportAttributeAccessIssue]
# assert len(search_response.matches) == 1
# assert search_response.matches[0].name == "SingleTest"
#
#
# @pytest.mark.asyncio
# async def test_create_multiple_entities(app):
# """Test creating multiple entities in one call."""
# entity_data = {
# "entities": [
# {"name": "BulkTest1", "entity_type": "test", "observations": ["First bulk test"]},
# {"name": "BulkTest2", "entity_type": "test", "observations": ["Second bulk test"]},
# {"name": "BulkTest3", "entity_type": "demo", "observations": ["Third bulk test"]},
# ]
# }
#
# result = await handle_call_tool("create_entities", entity_data)
#
# # Verify response
# assert len(result) == 1
# response = CreateEntityResponse.model_validate_json(result[0].resource.text) # pyright: ignore [reportAttributeAccessIssue]
#
# # Verify all entities were created
# assert len(response.entities) == 3
#
# # Check specific entities
# entities = {e.name: e for e in response.entities}
# assert "BulkTest1" in entities
# assert "BulkTest2" in entities
# assert "BulkTest3" in entities
#
# # Verify IDs were generated correctly
# assert entities["BulkTest1"].id is not None
# assert entities["BulkTest2"].id is not None
# assert entities["BulkTest3"].id is not None
#
# # Verify observations were saved
# assert len(entities["BulkTest1"].observations) == 1
# assert entities["BulkTest1"].observations[0].content == "First bulk test"
#
# # Verify entities can be found via search
# search_result = await handle_call_tool("search_nodes", {"query": "BulkTest"})
# search_response = SearchNodesResponse.model_validate_json(search_result[0].resource.text) # pyright: ignore [reportAttributeAccessIssue]
# assert len(search_response.matches) == 3
#
#
# @pytest.mark.asyncio
# async def test_create_entity_with_all_fields(app):
# """Test creating entity with all possible fields populated."""
# entity_data = {
# "entities": [
# {
# "name": "FullEntity",
# "entity_type": "test",
# "description": "A complete test entity",
# "observations": ["First observation", "Second observation"],
# }
# ]
# }
#
# result = await handle_call_tool("create_entities", entity_data)
# response = CreateEntityResponse.model_validate_json(result[0].resource.text) # pyright: ignore [reportAttributeAccessIssue]
#
# entity = response.entities[0]
# assert entity.name == "FullEntity"
# assert entity.description == "A complete test entity"
# assert len(entity.observations) == 2
# assert entity.observations[0].content == "First observation"
# assert entity.observations[1].content == "Second observation"
-48
View File
@@ -1,48 +0,0 @@
# """Tests for the MCP server implementation using FastAPI TestClient."""
#
# import pytest
#
# from basic_memory.mcp.server import handle_call_tool
# from basic_memory.schemas import SearchNodesResponse, CreateEntityResponse
#
#
# @pytest.mark.asyncio
# async def test_create_relations(app):
# """Test creating relations between entities."""
# # Create two test entities
# entity_data = {
# "entities": [
# {"name": "TestEntityA", "entity_type": "test", "observations": ["Entity A"]},
# {"name": "TestEntityB", "entity_type": "test", "observations": ["Entity B"]},
# ]
# }
#
# create_entity_result = await handle_call_tool("create_entities", entity_data)
# create_entity_response = CreateEntityResponse.model_validate_json(
# create_entity_result[0].resource.text # pyright: ignore [reportAttributeAccessIssue]
# )
#
# from_entity = create_entity_response.entities[0]
# to_entity = create_entity_response.entities[1]
# # Create relation between them
# relation_data = {
# "relations": [
# {
# "from_id": from_entity.id,
# "to_id": to_entity.id,
# "relation_type": "relates_to",
# }
# ]
# }
#
# result = await handle_call_tool("create_relations", relation_data)
#
# # Verify through search
# search_result = await handle_call_tool("search_nodes", {"query": "TestEntityA"})
# response = SearchNodesResponse.model_validate_json(search_result[0].resource.text) # pyright: ignore [reportAttributeAccessIssue]
#
# assert len(response.matches) == 1
# entity = response.matches[0]
# assert len(entity.relations) == 1
# assert entity.relations[0].to_id == to_entity.id
# assert entity.relations[0].relation_type == "relates_to"
-35
View File
@@ -1,35 +0,0 @@
# """Tests for MCP delete_entities tool."""
#
# import pytest
#
# from basic_memory.mcp.server import handle_call_tool
# from basic_memory.schemas import SearchNodesResponse, CreateEntityResponse
#
#
# @pytest.mark.asyncio
# async def test_delete_entities(app):
# """Test deleting entities."""
# # Create test entities
# entities = {
# "entities": [
# {"name": "DeleteTest1", "entity_type": "test", "observations": ["To be deleted 1"]},
# {"name": "DeleteTest2", "entity_type": "test", "observations": ["To be deleted 2"]},
# ]
# }
# create_entity_result = await handle_call_tool("create_entities", entities)
# create_entity_response = CreateEntityResponse.model_validate_json(
# create_entity_result[0].resource.text # pyright: ignore [reportAttributeAccessIssue]
# )
#
# # Delete first entity
# await handle_call_tool(
# "delete_entities", {"entity_ids": [create_entity_response.entities[0].id]}
# )
#
# # Verify through search
# search_result = await handle_call_tool("search_nodes", {"query": "DeleteTest"})
# search_response = SearchNodesResponse.model_validate_json(search_result[0].resource.text) # pyright: ignore [reportAttributeAccessIssue]
#
# # Only second entity should remain
# assert len(search_response.matches) == 1
# assert search_response.matches[0].name == "DeleteTest2"
-47
View File
@@ -1,47 +0,0 @@
# """Tests for MCP delete_observations tool."""
#
# import pytest
#
# from basic_memory.mcp.server import handle_call_tool
# from basic_memory.schemas import CreateEntityResponse, SearchNodesResponse
#
#
# @pytest.mark.asyncio
# async def test_delete_observations(app):
# """Test deleting specific observations from an entity."""
# # Create entity with multiple observations
# entity_data = {
# "entities": [
# {
# "name": "ObsDeleteTest",
# "entity_type": "test",
# "observations": [
# "Keep this observation",
# "Delete this observation",
# "Also keep this",
# ],
# }
# ]
# }
# create_result = await handle_call_tool("create_entities", entity_data)
# create_response = CreateEntityResponse.model_validate_json(create_result[0].resource.text) # pyright: ignore [reportAttributeAccessIssue]
# entity_id = create_response.entities[0].id
#
# # Delete specific observation
# await handle_call_tool(
# "delete_observations", {"entity_id": entity_id, "deletions": ["Delete this observation"]}
# )
#
# # Verify through search
# search_result = await handle_call_tool("search_nodes", {"query": "ObsDeleteTest"})
# search_response = SearchNodesResponse.model_validate_json(
# search_result[0].resource.text # pyright: ignore [reportAttributeAccessIssue]
# )
#
# # Check remaining observations
# entity = search_response.matches[0]
# observations = [o.content for o in entity.observations]
# assert len(observations) == 2
# assert "Delete this observation" not in observations
# assert "Keep this observation" in observations
# assert "Also keep this" in observations
-58
View File
@@ -1,58 +0,0 @@
# """Tests for MCP delete_relations tool."""
#
# import pytest
#
# from basic_memory.mcp.server import handle_call_tool
# from basic_memory.schemas import SearchNodesResponse, CreateEntityResponse
#
#
# @pytest.mark.asyncio
# async def test_delete_relations(app):
# """Test deleting relations between entities."""
# # Create test entities with relation
# entities = {
# "entities": [
# {"name": "RelSource", "entity_type": "test", "observations": ["Source entity"]},
# {"name": "RelTarget", "entity_type": "test", "observations": ["Target entity"]},
# ]
# }
# create_entity_result = await handle_call_tool("create_entities", entities)
# create_entity_response = CreateEntityResponse.model_validate_json(
# create_entity_result[0].resource.text # pyright: ignore [reportAttributeAccessIssue]
# )
# from_entity = create_entity_response.entities[0]
# to_entity = create_entity_response.entities[1]
#
# # Create relation
# relation = {
# "relations": [
# {
# "from_id": from_entity.id,
# "to_id": to_entity.id,
# "relation_type": "relates_to",
# }
# ]
# }
# await handle_call_tool("create_relations", relation)
#
# # Delete the relation
# await handle_call_tool(
# "delete_relations",
# {
# "relations": [
# {
# "from_id": from_entity.id,
# "to_id": to_entity.id,
# "relation_type": "relates_to",
# }
# ]
# },
# )
#
# # Verify through search
# search_result = await handle_call_tool("search_nodes", {"query": "relsource"})
# search_response = SearchNodesResponse.model_validate_json(search_result[0].resource.text) # pyright: ignore [reportAttributeAccessIssue]
#
# # Source entity should exist but have no relations
# assert len(search_response.matches) == 1
# assert len(search_response.matches[0].relations) == 0
-242
View File
@@ -1,242 +0,0 @@
# """Tests for the MCP server implementation using FastAPI TestClient."""
#
# import pytest
# from mcp.shared.exceptions import McpError
# from mcp.types import INVALID_PARAMS, METHOD_NOT_FOUND
#
# from basic_memory.mcp.server import handle_call_tool
#
#
# @pytest.mark.asyncio
# async def test_invalid_tool_name(app):
# """Test calling a non-existent tool."""
# with pytest.raises(McpError) as exc:
# await handle_call_tool("not_a_tool", {})
# assert "Unknown tool" in str(exc.value)
#
#
# @pytest.mark.asyncio
# async def test_missing_required_field(app):
# """Test validation when required fields are missing."""
# with pytest.raises(McpError) as exc:
# await handle_call_tool("search_nodes", {})
# assert "query" in str(exc.value).lower()
# assert exc.value.args[0] == INVALID_PARAMS
#
# with pytest.raises(McpError) as exc:
# await handle_call_tool("create_entities", {})
# assert "entities" in str(exc.value).lower()
# assert exc.value.args[0] == INVALID_PARAMS
#
# with pytest.raises(McpError) as exc:
# await handle_call_tool("create_document", {})
# assert exc.value.args[0] == INVALID_PARAMS
# error_msg = str(exc.value).lower()
# assert "path" in error_msg or "content" in error_msg
#
#
# @pytest.mark.asyncio
# async def test_empty_arrays(app):
# """Test validation of array fields that can't be empty."""
# with pytest.raises(McpError) as exc:
# await handle_call_tool("create_entities", {"entities": []})
# assert INVALID_PARAMS == exc.value.args[0]
#
# with pytest.raises(McpError) as exc:
# await handle_call_tool("open_nodes", {"entity_ids": []})
# assert INVALID_PARAMS == exc.value.args[0]
#
#
# @pytest.mark.asyncio
# async def test_invalid_field_types(app):
# """Test validation when fields have wrong types."""
# with pytest.raises(McpError) as exc:
# await handle_call_tool("search_nodes", {"query": 123})
# assert "string" in str(exc.value).lower()
# assert exc.value.args[0] == INVALID_PARAMS
#
# with pytest.raises(McpError) as exc:
# await handle_call_tool("create_entities", {"entities": "not an array"})
# assert "array" in str(exc.value).lower() or "list" in str(exc.value).lower()
# assert exc.value.args[0] == INVALID_PARAMS
#
# with pytest.raises(McpError) as exc:
# await handle_call_tool("get_document", {"id": "not an integer"})
# assert "integer" in str(exc.value).lower()
# assert exc.value.args[0] == INVALID_PARAMS
#
#
# @pytest.mark.asyncio
# async def test_invalid_nested_fields(app):
# """Test validation of nested object fields."""
# with pytest.raises(McpError) as exc:
# await handle_call_tool(
# "create_entities",
# {
# "entities": [
# {
# "name": "Test",
# # Missing required entity_type
# "observations": [],
# }
# ]
# },
# )
# assert "entity_type" in str(exc.value).lower()
# assert exc.value.args[0] == INVALID_PARAMS
#
# with pytest.raises(McpError) as exc:
# await handle_call_tool(
# "create_document",
# {
# "path": "test.md",
# "content": "test",
# "doc_metadata": "not an object" # Should be dict/null
# },
# )
# assert "doc_metadata" in str(exc.value).lower()
# assert exc.value.args[0] == INVALID_PARAMS
#
#
# @pytest.mark.asyncio
# async def test_invalid_relation_format_to_id(app):
# """Test validation of relation data."""
# with pytest.raises(McpError) as exc:
# await handle_call_tool(
# "create_relations",
# {
# "relations": [
# {
# "from_id": 1,
# # Missing to_id
# "relation_type": "relates_to",
# }
# ]
# },
# )
# assert "to_id" in str(exc.value).lower()
#
#
# @pytest.mark.asyncio
# async def test_invalid_relation_format_relation_type(app):
# # Invalid relation type
# with pytest.raises(McpError) as exc:
# await handle_call_tool(
# "create_relations",
# {
# "relations": [
# {
# "from_id": 1,
# "to_id": 2,
# "relation_type": "", # Empty relation type
# }
# ]
# },
# )
# assert "relation_type" in str(exc.value).lower()
#
#
# @pytest.mark.asyncio
# async def test_observation_validation_len(app):
# """Test validation specific to observations."""
# # Empty observations
# with pytest.raises(McpError) as exc:
# await handle_call_tool(
# "add_observations",
# {
# "entity_id": 1,
# "observations": ["", ""], # Empty observations
# },
# )
# assert "observations" in str(exc.value).lower()
#
#
# @pytest.mark.asyncio
# async def test_observation_validation_delete(app):
# # Empty deletions
# with pytest.raises(McpError) as exc:
# await handle_call_tool(
# "delete_observations",
# {
# "entity_id": 1,
# "deletions": [], # Empty deletions
# },
# )
# assert INVALID_PARAMS == exc.value.args[0]
#
#
# @pytest.mark.asyncio
# async def test_edge_case_validation_search_len(app):
# """Test edge cases in validation."""
# # Very long strings
# with pytest.raises(McpError) as exc:
# await handle_call_tool(
# "search_nodes",
# {"query": "x" * 10000}, # Extremely long query
# )
#
#
# @pytest.mark.asyncio
# async def test_document_endpoint_validation(app):
# """Test validation specific to document endpoints."""
# # Invalid ID format for get_document
# with pytest.raises(McpError) as exc:
# await handle_call_tool("get_document", {"id": -1})
# assert INVALID_PARAMS == exc.value.args[0]
# assert "greater than 0" in str(exc.value).lower()
#
# # Invalid document path
# with pytest.raises(McpError) as exc:
# await handle_call_tool(
# "create_document",
# {
# "path": "", # Empty path
# "content": "test content"
# }
# )
# assert INVALID_PARAMS == exc.value.args[0]
# assert "path" in str(exc.value).lower()
#
# # Update without content
# with pytest.raises(McpError) as exc:
# await handle_call_tool(
# "update_document",
# {
# "id": 1,
# "doc_metadata": None
# }
# )
# assert INVALID_PARAMS == exc.value.args[0]
# assert "content" in str(exc.value).lower()
#
#
# # We'll skip this test for now since it requires database setup
# @pytest.mark.skip(reason="Requires database setup")
# @pytest.mark.asyncio
# async def test_document_http_methods(app):
# """Test that document endpoints use correct HTTP methods."""
# # Test GET endpoints
# await handle_call_tool("list_documents", {})
# await handle_call_tool("get_document", {"id": 1})
#
# # Test POST endpoint
# response = await handle_call_tool(
# "create_document",
# {
# "path": "test.md",
# "content": "test content"
# }
# )
#
# # Test PUT endpoint
# await handle_call_tool(
# "update_document",
# {
# "id": 1,
# "content": "updated content",
# "doc_metadata": None
# }
# )
#
# # Test DELETE endpoint
# await handle_call_tool("delete_document", {"id": 1})
-63
View File
@@ -1,63 +0,0 @@
# """Tests for MCP open_nodes tool."""
#
# import pytest
# from mcp.types import EmbeddedResource
#
# from basic_memory.mcp.server import MIME_TYPE, handle_call_tool
# from basic_memory.schemas import OpenNodesResponse, CreateEntityResponse
#
#
# @pytest.mark.asyncio
# async def test_open_nodes(app):
# """Test retrieving specific nodes by name."""
# # Create test entities
# entity_data = {
# "entities": [
# {
# "name": "OpenTestA",
# "entity_type": "test",
# "observations": ["First test entity"],
# },
# {
# "name": "OpenTestB",
# "entity_type": "test",
# "observations": ["Second test entity"],
# },
# {
# "name": "OpenTestC",
# "entity_type": "test",
# "observations": ["Third test entity"],
# },
# ]
# }
#
# create_entity_result = await handle_call_tool("create_entities", entity_data)
# create_entity_response = CreateEntityResponse.model_validate_json(
# create_entity_result[0].resource.text # pyright: ignore [reportAttributeAccessIssue]
# )
# entity_a = create_entity_response.entities[0]
# entity_b = create_entity_response.entities[1]
#
# # Open specific nodes
# result = await handle_call_tool("open_nodes", {"entity_ids": [entity_a.id, entity_b.id]})
#
# # Verify response format
# assert len(result) == 1
# assert isinstance(result[0], EmbeddedResource)
# assert result[0].type == "resource"
# assert result[0].resource.mimeType == MIME_TYPE
#
# # Verify entities returned
# response = OpenNodesResponse.model_validate_json(result[0].resource.text) # pyright: ignore [reportAttributeAccessIssue]
# assert len(response.entities) == 2
#
# # Entities should be returned in same order as requested
# assert response.entities[0].name == "OpenTestA"
# assert response.entities[1].name == "OpenTestB"
#
# # Verify entity content
# entity = response.entities[0]
# assert entity.id == entity_a.id
# assert entity.entity_type == "test"
# assert len(entity.observations) == 1
# assert entity.observations[0].content == "First test entity"
-37
View File
@@ -1,37 +0,0 @@
# """Tests for the MCP server implementation using FastAPI TestClient."""
#
# import pytest
# from mcp.types import EmbeddedResource
#
# from basic_memory.mcp.server import MIME_TYPE, handle_call_tool
# from basic_memory.schemas import SearchNodesResponse
#
#
# @pytest.mark.asyncio
# async def test_search_nodes(app, test_entity_data, client):
# """Test searching for an entity after creating it."""
#
# # First create an entity
# await handle_call_tool("create_entities", test_entity_data)
#
# # Then search for it
# result = await handle_call_tool("search_nodes", {"query": "Test Entity"})
#
# # Verify response format
# assert len(result) == 1
# assert isinstance(result[0], EmbeddedResource)
# assert result[0].type == "resource"
# assert result[0].resource.mimeType == MIME_TYPE
#
# # Verify search results
# response = SearchNodesResponse.model_validate_json(result[0].resource.text) # pyright: ignore [reportAttributeAccessIssue]
# assert len(response.matches) == 1
# assert response.matches[0].name == "Test Entity"
# assert response.query == "Test Entity"
#
# # Verify through API
# api_response = await client.post("/knowledge/search", json={"query": "Test Entity"})
# assert api_response.status_code == 200
# data = api_response.json()
# assert len(data["matches"]) == 1
# assert data["matches"][0]["name"] == "Test Entity"
-25
View File
@@ -1,25 +0,0 @@
# """Test to show MCP tool documentation."""
#
# import json
# import pytest
# from mcp.types import Tool
# from basic_memory.mcp.server import handle_list_tools
#
#
# @pytest.mark.asyncio
# async def test_list_tools():
# """List available tools and their documentation."""
# tools = await handle_list_tools()
# assert isinstance(tools, list)
# assert all(isinstance(t, Tool) for t in tools)
#
# print("\nAvailable MCP Tools:\n")
#
# # Print each tool's documentation
# for tool in tools:
# print(f"Tool: {tool.name}")
# print(f"Description: {tool.description}")
# print("Required fields:", tool.inputSchema.get("required", []))
# print()
# print("Schema:", json.dumps(tool.inputSchema, indent=2))
# print("-" * 80 + "\n")