mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b181783cdf |
@@ -6,6 +6,7 @@ all tools with the MCP server.
|
||||
"""
|
||||
|
||||
# Import tools to register them with MCP
|
||||
from basic_memory.mcp.tools.basic_memory_diagnostics import basic_memory_diagnostics
|
||||
from basic_memory.mcp.tools.delete_note import delete_note
|
||||
from basic_memory.mcp.tools.read_content import read_content
|
||||
from basic_memory.mcp.tools.build_context import build_context
|
||||
@@ -29,6 +30,7 @@ from basic_memory.mcp.tools.project_management import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"basic_memory_diagnostics",
|
||||
"build_context",
|
||||
"canvas",
|
||||
"create_memory_project",
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
"""Diagnostic tool for Basic Memory MCP server."""
|
||||
|
||||
import json
|
||||
import platform
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
import basic_memory
|
||||
from basic_memory.mcp.server import mcp
|
||||
from basic_memory.mcp.project_session import get_active_project
|
||||
|
||||
|
||||
@mcp.tool(
|
||||
description="""Get comprehensive diagnostic information for Basic Memory installation.
|
||||
|
||||
Use this tool to:
|
||||
- Check Basic Memory version and build information
|
||||
- Get system details (Python, OS, architecture)
|
||||
- View current configuration settings
|
||||
- Get project context information
|
||||
- Troubleshoot installation issues
|
||||
|
||||
This information is helpful for:
|
||||
- Verifying which version is running
|
||||
- Troubleshooting compatibility issues
|
||||
- Gathering system info for support requests
|
||||
- Confirming configuration settings
|
||||
""",
|
||||
)
|
||||
async def basic_memory_diagnostics(project: Optional[str] = None) -> str:
|
||||
"""Get diagnostic information about Basic Memory installation and system.
|
||||
|
||||
This tool provides comprehensive diagnostic information including version,
|
||||
system details, configuration, and project context.
|
||||
|
||||
Args:
|
||||
project: Optional project name to get project-specific context
|
||||
|
||||
Returns:
|
||||
Formatted diagnostic information with version, system, and config details
|
||||
"""
|
||||
logger.info("MCP tool call tool=basic_memory_diagnostics")
|
||||
|
||||
diagnostic_lines = []
|
||||
|
||||
try:
|
||||
# Header
|
||||
diagnostic_lines.extend([
|
||||
"# Basic Memory Diagnostics",
|
||||
"",
|
||||
])
|
||||
|
||||
# Version Information
|
||||
diagnostic_lines.extend([
|
||||
"## Version Information",
|
||||
"",
|
||||
f"**Basic Memory Version**: {basic_memory.__version__}",
|
||||
f"**API Version**: {basic_memory.__api_version__}",
|
||||
"",
|
||||
])
|
||||
|
||||
# System Information
|
||||
diagnostic_lines.extend([
|
||||
"## System Information",
|
||||
"",
|
||||
f"**Python Version**: {sys.version.split()[0]}",
|
||||
f"**Platform**: {platform.system()} {platform.release()}",
|
||||
f"**Architecture**: {platform.machine()}",
|
||||
f"**Python Executable**: {sys.executable}",
|
||||
"",
|
||||
])
|
||||
|
||||
# Configuration Information
|
||||
diagnostic_lines.extend([
|
||||
"## Configuration",
|
||||
"",
|
||||
])
|
||||
|
||||
try:
|
||||
from basic_memory.config import app_config
|
||||
|
||||
# Configuration file path
|
||||
config_file_path = getattr(app_config, '_config_file_path', 'Unknown')
|
||||
diagnostic_lines.extend([
|
||||
f"**Config File Path**: {config_file_path}",
|
||||
"",
|
||||
])
|
||||
|
||||
# Dump configuration as JSON
|
||||
config_dict = {
|
||||
"env": app_config.env,
|
||||
"projects": app_config.projects,
|
||||
"default_project": app_config.default_project,
|
||||
"log_level": app_config.log_level,
|
||||
"debug": app_config.debug,
|
||||
"database_url": app_config.database_url,
|
||||
}
|
||||
|
||||
diagnostic_lines.extend([
|
||||
"**Configuration JSON**:",
|
||||
"```json",
|
||||
json.dumps(config_dict, indent=2, default=str),
|
||||
"```",
|
||||
"",
|
||||
])
|
||||
|
||||
except Exception as e:
|
||||
diagnostic_lines.extend([
|
||||
f"❌ **Unable to access configuration**: {str(e)}",
|
||||
"",
|
||||
"**Troubleshooting:**",
|
||||
"- The configuration system may not be initialized",
|
||||
"- Check if Basic Memory is properly installed",
|
||||
"- Verify configuration file permissions",
|
||||
"",
|
||||
])
|
||||
|
||||
# Project Context
|
||||
if project:
|
||||
diagnostic_lines.extend([
|
||||
"## Project Context",
|
||||
"",
|
||||
])
|
||||
|
||||
try:
|
||||
active_project = get_active_project(project)
|
||||
diagnostic_lines.extend([
|
||||
f"**Requested Project**: {project}",
|
||||
f"**Active Project Name**: {active_project.name}",
|
||||
f"**Project Path**: {active_project.home}",
|
||||
f"**Project Exists**: {'✅ Yes' if Path(active_project.home).exists() else '❌ No'}",
|
||||
"",
|
||||
])
|
||||
except Exception as e:
|
||||
diagnostic_lines.extend([
|
||||
f"**Requested Project**: {project}",
|
||||
f"❌ **Project Error**: {str(e)}",
|
||||
"",
|
||||
"**Troubleshooting:**",
|
||||
"- Check if the project name is correct",
|
||||
"- Verify the project is configured in config.json",
|
||||
"- Ensure project directory exists and is accessible",
|
||||
"",
|
||||
])
|
||||
else:
|
||||
# Show default project info
|
||||
diagnostic_lines.extend([
|
||||
"## Default Project Context",
|
||||
"",
|
||||
])
|
||||
|
||||
try:
|
||||
from basic_memory.config import app_config
|
||||
default_project_name = app_config.default_project
|
||||
diagnostic_lines.extend([
|
||||
f"**Default Project**: {default_project_name}",
|
||||
])
|
||||
|
||||
if default_project_name and default_project_name in app_config.projects:
|
||||
project_path = app_config.projects[default_project_name]
|
||||
diagnostic_lines.extend([
|
||||
f"**Default Project Path**: {project_path}",
|
||||
f"**Project Exists**: {'✅ Yes' if Path(project_path).exists() else '❌ No'}",
|
||||
])
|
||||
|
||||
diagnostic_lines.append("")
|
||||
|
||||
except Exception as e:
|
||||
diagnostic_lines.extend([
|
||||
f"❌ **Unable to get default project info**: {str(e)}",
|
||||
"",
|
||||
])
|
||||
|
||||
# Footer with usage information
|
||||
diagnostic_lines.extend([
|
||||
"---",
|
||||
"",
|
||||
"**Usage**: This diagnostic information can help troubleshoot issues and verify your Basic Memory installation.",
|
||||
"**Support**: Include this information when reporting issues or requesting support.",
|
||||
])
|
||||
|
||||
return "\n".join(diagnostic_lines)
|
||||
|
||||
except Exception as e:
|
||||
return f"""# Basic Memory Diagnostics - Error
|
||||
|
||||
❌ **Unable to generate diagnostic information**: {str(e)}
|
||||
|
||||
**Troubleshooting:**
|
||||
- The system may still be starting up
|
||||
- Try waiting a few seconds and running diagnostics again
|
||||
- Check logs for detailed error information
|
||||
- Verify Basic Memory is properly installed
|
||||
- Consider restarting if the issue persists
|
||||
|
||||
**Minimal Information Available:**
|
||||
- Python Version: {sys.version.split()[0]}
|
||||
- Platform: {platform.system()} {platform.release()}
|
||||
- Basic Memory Version: {getattr(basic_memory, '__version__', 'Unknown')}
|
||||
"""
|
||||
@@ -0,0 +1,213 @@
|
||||
"""Tests for basic_memory_diagnostics MCP tool."""
|
||||
|
||||
import json
|
||||
import platform
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
import basic_memory
|
||||
from basic_memory.mcp.tools.basic_memory_diagnostics import basic_memory_diagnostics
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_basic_memory_diagnostics_basic_functionality():
|
||||
"""Test basic diagnostic functionality without project parameter."""
|
||||
result = await basic_memory_diagnostics.fn()
|
||||
|
||||
# Check that key sections are present
|
||||
assert "# Basic Memory Diagnostics" in result
|
||||
assert "## Version Information" in result
|
||||
assert "## System Information" in result
|
||||
assert "## Configuration" in result
|
||||
assert "## Default Project Context" in result
|
||||
|
||||
# Check version information
|
||||
assert f"**Basic Memory Version**: {basic_memory.__version__}" in result
|
||||
assert f"**API Version**: {basic_memory.__api_version__}" in result
|
||||
|
||||
# Check system information
|
||||
assert f"**Python Version**: {sys.version.split()[0]}" in result
|
||||
assert f"**Platform**: {platform.system()} {platform.release()}" in result
|
||||
assert f"**Architecture**: {platform.machine()}" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_basic_memory_diagnostics_with_project_parameter():
|
||||
"""Test diagnostic with valid project parameter."""
|
||||
# Mock project session to return a valid project
|
||||
mock_project = MagicMock()
|
||||
mock_project.name = "test-project"
|
||||
mock_project.home = "/path/to/test-project"
|
||||
|
||||
with patch("basic_memory.mcp.tools.basic_memory_diagnostics.get_active_project") as mock_get_project:
|
||||
mock_get_project.return_value = mock_project
|
||||
|
||||
with patch("pathlib.Path.exists") as mock_exists:
|
||||
mock_exists.return_value = True
|
||||
|
||||
result = await basic_memory_diagnostics.fn(project="test-project")
|
||||
|
||||
# Check project-specific information
|
||||
assert "## Project Context" in result
|
||||
assert "**Requested Project**: test-project" in result
|
||||
assert "**Active Project Name**: test-project" in result
|
||||
assert "**Project Path**: /path/to/test-project" in result
|
||||
assert "**Project Exists**: ✅ Yes" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_basic_memory_diagnostics_with_invalid_project():
|
||||
"""Test diagnostic with invalid project parameter."""
|
||||
with patch("basic_memory.mcp.tools.basic_memory_diagnostics.get_active_project") as mock_get_project:
|
||||
mock_get_project.side_effect = Exception("Project not found")
|
||||
|
||||
result = await basic_memory_diagnostics.fn(project="invalid-project")
|
||||
|
||||
# Check error handling for invalid project
|
||||
assert "## Project Context" in result
|
||||
assert "**Requested Project**: invalid-project" in result
|
||||
assert "❌ **Project Error**: Project not found" in result
|
||||
assert "**Troubleshooting:**" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_basic_memory_diagnostics_configuration_success():
|
||||
"""Test diagnostic with successful configuration access."""
|
||||
# Mock app config
|
||||
mock_config = MagicMock()
|
||||
mock_config.env = "test"
|
||||
mock_config.projects = {"main": "/path/to/main", "secondary": "/path/to/secondary"}
|
||||
mock_config.default_project = "main"
|
||||
mock_config.log_level = "INFO"
|
||||
mock_config.debug = False
|
||||
mock_config.database_url = "sqlite:///test.db"
|
||||
mock_config._config_file_path = "/path/to/config.json"
|
||||
|
||||
with patch("basic_memory.mcp.tools.basic_memory_diagnostics.app_config", mock_config):
|
||||
result = await basic_memory_diagnostics.fn()
|
||||
|
||||
# Check configuration information
|
||||
assert "**Config File Path**: /path/to/config.json" in result
|
||||
assert "**Configuration JSON**:" in result
|
||||
assert '"env": "test"' in result
|
||||
assert '"main": "/path/to/main"' in result
|
||||
assert '"default_project": "main"' in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_basic_memory_diagnostics_configuration_error():
|
||||
"""Test diagnostic when configuration access fails."""
|
||||
with patch("basic_memory.mcp.tools.basic_memory_diagnostics.app_config") as mock_config:
|
||||
# Simulate import error for configuration
|
||||
mock_config.side_effect = ImportError("Configuration not available")
|
||||
|
||||
result = await basic_memory_diagnostics.fn()
|
||||
|
||||
# Check error handling for configuration
|
||||
assert "❌ **Unable to access configuration**:" in result
|
||||
assert "**Troubleshooting:**" in result
|
||||
assert "configuration system may not be initialized" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_basic_memory_diagnostics_exception_handling():
|
||||
"""Test diagnostic handles unexpected exceptions gracefully."""
|
||||
# Mock basic_memory module to raise an exception when accessing version
|
||||
with patch.object(basic_memory, '__version__', side_effect=Exception("Unexpected error")):
|
||||
result = await basic_memory_diagnostics.fn()
|
||||
|
||||
# Should return error format but still include minimal info
|
||||
assert "# Basic Memory Diagnostics - Error" in result
|
||||
assert "❌ **Unable to generate diagnostic information**:" in result
|
||||
assert "**Troubleshooting:**" in result
|
||||
assert "**Minimal Information Available:**" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_basic_memory_diagnostics_default_project_info():
|
||||
"""Test diagnostic shows default project information correctly."""
|
||||
# Mock app config with default project
|
||||
mock_config = MagicMock()
|
||||
mock_config.default_project = "main"
|
||||
mock_config.projects = {"main": "/path/to/main"}
|
||||
|
||||
with patch("basic_memory.mcp.tools.basic_memory_diagnostics.app_config", mock_config):
|
||||
with patch("pathlib.Path.exists") as mock_exists:
|
||||
mock_exists.return_value = True
|
||||
|
||||
result = await basic_memory_diagnostics.fn()
|
||||
|
||||
# Check default project information
|
||||
assert "## Default Project Context" in result
|
||||
assert "**Default Project**: main" in result
|
||||
assert "**Default Project Path**: /path/to/main" in result
|
||||
assert "**Project Exists**: ✅ Yes" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_basic_memory_diagnostics_default_project_missing():
|
||||
"""Test diagnostic handles missing default project gracefully."""
|
||||
# Mock app config with default project that doesn't exist
|
||||
mock_config = MagicMock()
|
||||
mock_config.default_project = "main"
|
||||
mock_config.projects = {"main": "/path/to/nonexistent"}
|
||||
|
||||
with patch("basic_memory.mcp.tools.basic_memory_diagnostics.app_config", mock_config):
|
||||
with patch("pathlib.Path.exists") as mock_exists:
|
||||
mock_exists.return_value = False
|
||||
|
||||
result = await basic_memory_diagnostics.fn()
|
||||
|
||||
# Check missing project indication
|
||||
assert "**Project Exists**: ❌ No" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_basic_memory_diagnostics_json_serialization():
|
||||
"""Test that configuration JSON serialization works correctly."""
|
||||
# Mock app config with complex data types
|
||||
mock_config = MagicMock()
|
||||
mock_config.env = "test"
|
||||
mock_config.projects = {"main": "/path/to/main"}
|
||||
mock_config.default_project = "main"
|
||||
mock_config.log_level = "DEBUG"
|
||||
mock_config.debug = True
|
||||
mock_config.database_url = "sqlite:///memory.db"
|
||||
|
||||
with patch("basic_memory.mcp.tools.basic_memory_diagnostics.app_config", mock_config):
|
||||
result = await basic_memory_diagnostics.fn()
|
||||
|
||||
# Extract and validate JSON content
|
||||
json_start = result.find("```json\n") + 8
|
||||
json_end = result.find("\n```", json_start)
|
||||
json_content = result[json_start:json_end]
|
||||
|
||||
# Should be valid JSON
|
||||
parsed_config = json.loads(json_content)
|
||||
assert parsed_config["env"] == "test"
|
||||
assert parsed_config["projects"]["main"] == "/path/to/main"
|
||||
assert parsed_config["default_project"] == "main"
|
||||
assert parsed_config["debug"] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_basic_memory_diagnostics_project_nonexistent_path():
|
||||
"""Test diagnostic with project that has non-existent path."""
|
||||
# Mock project session to return a project with non-existent path
|
||||
mock_project = MagicMock()
|
||||
mock_project.name = "test-project"
|
||||
mock_project.home = "/nonexistent/path"
|
||||
|
||||
with patch("basic_memory.mcp.tools.basic_memory_diagnostics.get_active_project") as mock_get_project:
|
||||
mock_get_project.return_value = mock_project
|
||||
|
||||
with patch("pathlib.Path.exists") as mock_exists:
|
||||
mock_exists.return_value = False
|
||||
|
||||
result = await basic_memory_diagnostics.fn(project="test-project")
|
||||
|
||||
# Check that non-existent path is indicated
|
||||
assert "**Project Exists**: ❌ No" in result
|
||||
Reference in New Issue
Block a user