158 lines
5.5 KiB
Python
158 lines
5.5 KiB
Python
"""
|
|
Local MCP server for use with Claude Code (stdio transport).
|
|
|
|
This runs a fully local Hindsight instance with embedded PostgreSQL (pg0).
|
|
No external database or server required.
|
|
|
|
Run with:
|
|
hindsight-local-mcp
|
|
|
|
Or with uvx:
|
|
uvx hindsight-api@latest hindsight-local-mcp
|
|
|
|
Configure in Claude Code's MCP settings:
|
|
{
|
|
"mcpServers": {
|
|
"hindsight": {
|
|
"command": "uvx",
|
|
"args": ["hindsight-api@latest", "hindsight-local-mcp"],
|
|
"env": {
|
|
"HINDSIGHT_API_LLM_API_KEY": "your-openai-key"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Environment variables:
|
|
HINDSIGHT_API_LLM_API_KEY: Required. API key for LLM provider.
|
|
HINDSIGHT_API_LLM_PROVIDER: Optional. LLM provider (default: "openai").
|
|
HINDSIGHT_API_LLM_MODEL: Optional. LLM model (default: "gpt-4o-mini").
|
|
HINDSIGHT_API_MCP_LOCAL_BANK_ID: Optional. Memory bank ID (default: "mcp").
|
|
HINDSIGHT_API_LOG_LEVEL: Optional. Log level (default: "warning").
|
|
HINDSIGHT_API_MCP_INSTRUCTIONS: Optional. Additional instructions appended to both retain and recall tools.
|
|
|
|
Example custom instructions (these are ADDED to the default behavior):
|
|
To also store assistant actions:
|
|
HINDSIGHT_API_MCP_INSTRUCTIONS="Also store every action you take, including tool calls, code written, and decisions made."
|
|
|
|
To also store conversation summaries:
|
|
HINDSIGHT_API_MCP_INSTRUCTIONS="Also store summaries of important conversations and their outcomes."
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
import sys
|
|
|
|
from mcp.server.fastmcp import FastMCP
|
|
|
|
from hindsight_api.config import (
|
|
DEFAULT_MCP_LOCAL_BANK_ID,
|
|
DEFAULT_MCP_RECALL_DESCRIPTION,
|
|
DEFAULT_MCP_RETAIN_DESCRIPTION,
|
|
ENV_MCP_INSTRUCTIONS,
|
|
ENV_MCP_LOCAL_BANK_ID,
|
|
)
|
|
from hindsight_api.mcp_tools import MCPToolsConfig, register_mcp_tools
|
|
|
|
# Configure logging - default to warning to avoid polluting stderr during MCP init
|
|
# MCP clients interpret stderr output as errors, so we suppress INFO logs by default
|
|
_log_level_str = os.environ.get("HINDSIGHT_API_LOG_LEVEL", "warning").lower()
|
|
_log_level_map = {
|
|
"critical": logging.CRITICAL,
|
|
"error": logging.ERROR,
|
|
"warning": logging.WARNING,
|
|
"info": logging.INFO,
|
|
"debug": logging.DEBUG,
|
|
}
|
|
logging.basicConfig(
|
|
level=_log_level_map.get(_log_level_str, logging.WARNING),
|
|
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
|
|
stream=sys.stderr, # MCP uses stdout for protocol, logs go to stderr
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def create_local_mcp_server(bank_id: str, memory=None) -> FastMCP:
|
|
"""
|
|
Create a stdio MCP server with retain/recall tools.
|
|
|
|
Args:
|
|
bank_id: The memory bank ID to use for all operations.
|
|
memory: Optional MemoryEngine instance. If not provided, creates one with pg0.
|
|
|
|
Returns:
|
|
Configured FastMCP server instance.
|
|
"""
|
|
# Import here to avoid slow startup if just checking --help
|
|
from hindsight_api import MemoryEngine
|
|
|
|
# Create memory engine with pg0 embedded database if not provided
|
|
if memory is None:
|
|
memory = MemoryEngine(db_url="pg0://hindsight-mcp")
|
|
|
|
# Get custom instructions from environment variable (appended to both tools)
|
|
extra_instructions = os.environ.get(ENV_MCP_INSTRUCTIONS, "")
|
|
|
|
retain_description = DEFAULT_MCP_RETAIN_DESCRIPTION
|
|
recall_description = DEFAULT_MCP_RECALL_DESCRIPTION
|
|
|
|
if extra_instructions:
|
|
retain_description = f"{DEFAULT_MCP_RETAIN_DESCRIPTION}\n\nAdditional instructions: {extra_instructions}"
|
|
recall_description = f"{DEFAULT_MCP_RECALL_DESCRIPTION}\n\nAdditional instructions: {extra_instructions}"
|
|
|
|
mcp = FastMCP("hindsight")
|
|
|
|
# Configure and register tools using shared module
|
|
config = MCPToolsConfig(
|
|
bank_id_resolver=lambda: bank_id,
|
|
include_bank_id_param=False, # Local MCP uses fixed bank_id
|
|
tools={"retain", "recall"}, # Local MCP only has retain and recall
|
|
retain_description=retain_description,
|
|
recall_description=recall_description,
|
|
retain_fire_and_forget=True, # Local MCP uses fire-and-forget pattern
|
|
)
|
|
|
|
register_mcp_tools(mcp, memory, config)
|
|
|
|
return mcp
|
|
|
|
|
|
async def _initialize_and_run(bank_id: str):
|
|
"""Initialize memory and run the MCP server."""
|
|
from hindsight_api import MemoryEngine
|
|
|
|
# Create and initialize memory engine with pg0 embedded database
|
|
# Note: We avoid printing to stderr during init as MCP clients show it as "errors"
|
|
memory = MemoryEngine(db_url="pg0://hindsight-mcp")
|
|
await memory.initialize()
|
|
|
|
# Create and run the server
|
|
mcp = create_local_mcp_server(bank_id, memory=memory)
|
|
await mcp.run_stdio_async()
|
|
|
|
|
|
def main():
|
|
"""Main entry point for the stdio MCP server."""
|
|
import asyncio
|
|
|
|
from hindsight_api.config import ENV_LLM_API_KEY, get_config
|
|
|
|
# Check for required environment variables
|
|
config = get_config()
|
|
if not config.llm_api_key:
|
|
print(f"Error: {ENV_LLM_API_KEY} environment variable is required", file=sys.stderr)
|
|
print("Set it in your MCP configuration or shell environment", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# Get bank ID from environment, default to "mcp"
|
|
bank_id = os.environ.get(ENV_MCP_LOCAL_BANK_ID, DEFAULT_MCP_LOCAL_BANK_ID)
|
|
|
|
# Note: We don't print to stderr as MCP clients display it as "error output"
|
|
# Use HINDSIGHT_API_LOG_LEVEL=debug for verbose startup logging
|
|
|
|
# Run the async initialization and server
|
|
asyncio.run(_initialize_and_run(bank_id))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|