Skip to content

🔌 MCP Server API

SpecMem provides an MCP (Model Context Protocol) server for integration with Kiro and other MCP-compatible tools.

Overview

The MCP server exposes SpecMem functionality as tools that can be invoked by AI agents. It follows the Model Context Protocol specification.

Server Class

specmem.mcp.server.SpecMemMCPServer

MCP server exposing SpecMem tools to Kiro.

This server implements the Model Context Protocol to expose SpecMem functionality as tools that Kiro can invoke.

Example

server = SpecMemMCPServer(workspace_path=Path(".")) await server.initialize() result = await server.handle_tool_call("specmem_query", {"query": "auth"})

Source code in specmem/mcp/server.py
class SpecMemMCPServer:
    """MCP server exposing SpecMem tools to Kiro.

    This server implements the Model Context Protocol to expose
    SpecMem functionality as tools that Kiro can invoke.

    Example:
        server = SpecMemMCPServer(workspace_path=Path("."))
        await server.initialize()
        result = await server.handle_tool_call("specmem_query", {"query": "auth"})
    """

    def __init__(self, workspace_path: Path | str = "."):
        """Initialize the MCP server.

        Args:
            workspace_path: Path to the workspace root
        """
        self.workspace_path = Path(workspace_path).resolve()
        self._handlers = ToolHandlers()
        self._initialized = False

    async def initialize(self) -> dict[str, Any]:
        """Initialize the spec memory for the workspace.

        Creates or loads the SpecMemClient for the workspace.

        Returns:
            Dict with initialization status
        """
        if self._initialized:
            return {
                "status": "already_initialized",
                "workspace": str(self.workspace_path),
            }

        try:
            # Import here to avoid circular imports
            from specmem.client import SpecMemClient

            client = SpecMemClient(path=self.workspace_path)
            self._handlers.set_client(client)
            self._initialized = True

            logger.info(f"SpecMem initialized for {self.workspace_path}")

            return {
                "status": "initialized",
                "workspace": str(self.workspace_path),
            }
        except Exception as e:
            logger.error(f"Failed to initialize SpecMem: {e}")
            return {
                "status": "error",
                "error": str(e),
                "message": "Failed to initialize SpecMem. Run 'specmem init' first.",
            }

    @property
    def is_initialized(self) -> bool:
        """Check if the server is initialized."""
        return self._initialized

    def get_tools(self) -> list[dict[str, Any]]:
        """Get list of available tools.

        Returns:
            List of tool definitions
        """
        return TOOLS.copy()

    def get_tool_names(self) -> list[str]:
        """Get list of tool names.

        Returns:
            List of tool name strings
        """
        return get_tool_names()

    async def handle_tool_call(
        self,
        tool_name: str,
        arguments: dict[str, Any],
    ) -> dict[str, Any]:
        """Handle a tool call from Kiro.

        Routes the call to the appropriate handler based on tool name.

        Args:
            tool_name: Name of the tool to call
            arguments: Tool arguments

        Returns:
            Tool result as a dict
        """
        # Auto-initialize if not already done
        if not self._initialized:
            init_result = await self.initialize()
            if init_result.get("status") == "error":
                return {
                    "error": "not_initialized",
                    "message": init_result.get("message", "SpecMem not initialized"),
                }

        # Route to appropriate handler
        handler_map = {
            "specmem_query": self._handlers.handle_query,
            "specmem_impact": self._handlers.handle_impact,
            "specmem_context": self._handlers.handle_context,
            "specmem_tldr": self._handlers.handle_tldr,
            "specmem_coverage": self._handlers.handle_coverage,
            "specmem_validate": self._handlers.handle_validate,
        }

        handler = handler_map.get(tool_name)
        if handler is None:
            return {
                "error": "unknown_tool",
                "message": f"Unknown tool: {tool_name}",
                "available_tools": list(handler_map.keys()),
            }

        try:
            return await handler(arguments)
        except Exception as e:
            logger.error(f"Tool {tool_name} failed: {e}")
            return {
                "error": "tool_error",
                "tool": tool_name,
                "message": str(e),
            }

Attributes

is_initialized property

Check if the server is initialized.

Functions

__init__(workspace_path='.')

Initialize the MCP server.

Parameters:

Name Type Description Default
workspace_path Path | str

Path to the workspace root

'.'
Source code in specmem/mcp/server.py
def __init__(self, workspace_path: Path | str = "."):
    """Initialize the MCP server.

    Args:
        workspace_path: Path to the workspace root
    """
    self.workspace_path = Path(workspace_path).resolve()
    self._handlers = ToolHandlers()
    self._initialized = False

initialize() async

Initialize the spec memory for the workspace.

Creates or loads the SpecMemClient for the workspace.

Returns:

Type Description
dict[str, Any]

Dict with initialization status

Source code in specmem/mcp/server.py
async def initialize(self) -> dict[str, Any]:
    """Initialize the spec memory for the workspace.

    Creates or loads the SpecMemClient for the workspace.

    Returns:
        Dict with initialization status
    """
    if self._initialized:
        return {
            "status": "already_initialized",
            "workspace": str(self.workspace_path),
        }

    try:
        # Import here to avoid circular imports
        from specmem.client import SpecMemClient

        client = SpecMemClient(path=self.workspace_path)
        self._handlers.set_client(client)
        self._initialized = True

        logger.info(f"SpecMem initialized for {self.workspace_path}")

        return {
            "status": "initialized",
            "workspace": str(self.workspace_path),
        }
    except Exception as e:
        logger.error(f"Failed to initialize SpecMem: {e}")
        return {
            "status": "error",
            "error": str(e),
            "message": "Failed to initialize SpecMem. Run 'specmem init' first.",
        }

get_tools()

Get list of available tools.

Returns:

Type Description
list[dict[str, Any]]

List of tool definitions

Source code in specmem/mcp/server.py
def get_tools(self) -> list[dict[str, Any]]:
    """Get list of available tools.

    Returns:
        List of tool definitions
    """
    return TOOLS.copy()

get_tool_names()

Get list of tool names.

Returns:

Type Description
list[str]

List of tool name strings

Source code in specmem/mcp/server.py
def get_tool_names(self) -> list[str]:
    """Get list of tool names.

    Returns:
        List of tool name strings
    """
    return get_tool_names()

handle_tool_call(tool_name, arguments) async

Handle a tool call from Kiro.

Routes the call to the appropriate handler based on tool name.

Parameters:

Name Type Description Default
tool_name str

Name of the tool to call

required
arguments dict[str, Any]

Tool arguments

required

Returns:

Type Description
dict[str, Any]

Tool result as a dict

Source code in specmem/mcp/server.py
async def handle_tool_call(
    self,
    tool_name: str,
    arguments: dict[str, Any],
) -> dict[str, Any]:
    """Handle a tool call from Kiro.

    Routes the call to the appropriate handler based on tool name.

    Args:
        tool_name: Name of the tool to call
        arguments: Tool arguments

    Returns:
        Tool result as a dict
    """
    # Auto-initialize if not already done
    if not self._initialized:
        init_result = await self.initialize()
        if init_result.get("status") == "error":
            return {
                "error": "not_initialized",
                "message": init_result.get("message", "SpecMem not initialized"),
            }

    # Route to appropriate handler
    handler_map = {
        "specmem_query": self._handlers.handle_query,
        "specmem_impact": self._handlers.handle_impact,
        "specmem_context": self._handlers.handle_context,
        "specmem_tldr": self._handlers.handle_tldr,
        "specmem_coverage": self._handlers.handle_coverage,
        "specmem_validate": self._handlers.handle_validate,
    }

    handler = handler_map.get(tool_name)
    if handler is None:
        return {
            "error": "unknown_tool",
            "message": f"Unknown tool: {tool_name}",
            "available_tools": list(handler_map.keys()),
        }

    try:
        return await handler(arguments)
    except Exception as e:
        logger.error(f"Tool {tool_name} failed: {e}")
        return {
            "error": "tool_error",
            "tool": tool_name,
            "message": str(e),
        }

Usage

Programmatic Usage

from pathlib import Path
from specmem.mcp.server import SpecMemMCPServer

# Create server instance
server = SpecMemMCPServer(workspace_path=Path("."))

# Initialize
await server.initialize()

# Handle tool calls
result = await server.handle_tool_call(
    "specmem_query",
    {"query": "authentication requirements", "top_k": 5}
)
print(result)

Command Line

Run the MCP server via stdio transport:

specmem-mcp --workspace /path/to/project

Options:

Flag Description Default
--workspace, -w Workspace path Current directory
--log-level Log level (DEBUG, INFO, WARNING, ERROR) INFO

Tool Definitions

specmem_query

Query specifications by natural language.

{
    "name": "specmem_query",
    "description": "Query specifications by natural language",
    "inputSchema": {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "Natural language query"
            },
            "top_k": {
                "type": "integer",
                "default": 10,
                "description": "Maximum results to return"
            },
            "include_legacy": {
                "type": "boolean",
                "default": False,
                "description": "Include deprecated specs"
            }
        },
        "required": ["query"]
    }
}

specmem_impact

Get specs and tests affected by file changes.

{
    "name": "specmem_impact",
    "description": "Get specs and tests affected by file changes",
    "inputSchema": {
        "type": "object",
        "properties": {
            "files": {
                "type": "array",
                "items": {"type": "string"},
                "description": "File paths to analyze"
            },
            "depth": {
                "type": "integer",
                "default": 2,
                "description": "Traversal depth for relationships"
            }
        },
        "required": ["files"]
    }
}

specmem_context

Get optimized context bundle for files.

{
    "name": "specmem_context",
    "description": "Get optimized context bundle for files",
    "inputSchema": {
        "type": "object",
        "properties": {
            "files": {
                "type": "array",
                "items": {"type": "string"},
                "description": "Files to get context for"
            },
            "token_budget": {
                "type": "integer",
                "default": 4000,
                "description": "Maximum tokens for context"
            }
        },
        "required": ["files"]
    }
}

specmem_tldr

Get TL;DR summary of key specifications.

{
    "name": "specmem_tldr",
    "description": "Get TL;DR summary of key specifications",
    "inputSchema": {
        "type": "object",
        "properties": {
            "token_budget": {
                "type": "integer",
                "default": 500,
                "description": "Maximum tokens for summary"
            }
        }
    }
}

specmem_coverage

Get spec coverage analysis.

{
    "name": "specmem_coverage",
    "description": "Get spec coverage analysis",
    "inputSchema": {
        "type": "object",
        "properties": {
            "feature": {
                "type": "string",
                "description": "Optional feature name"
            }
        }
    }
}

specmem_validate

Validate specifications for quality issues.

{
    "name": "specmem_validate",
    "description": "Validate specifications for quality issues",
    "inputSchema": {
        "type": "object",
        "properties": {
            "spec_id": {
                "type": "string",
                "description": "Optional spec ID to validate"
            }
        }
    }
}

Response Format

Success Response

{
    "results": [...],
    "count": 5,
    "message": "Found 5 matching specifications"
}

Error Response

{
    "error": "error_type",
    "message": "Human-readable error message",
    "details": {...}
}

Error Types

Error Description
not_initialized SpecMem not initialized for workspace
unknown_tool Unknown tool name requested
invalid_path One or more file paths don't exist
tool_error Tool execution failed

Tool Helpers

get_tool_by_name

from specmem.mcp.tools import get_tool_by_name

tool = get_tool_by_name("specmem_query")
print(tool["description"])

get_tool_names

from specmem.mcp.tools import get_tool_names

names = get_tool_names()
# ['specmem_query', 'specmem_impact', 'specmem_context', ...]

MCP Configuration

Kiro Configuration

Add to .kiro/settings/mcp.json:

{
  "mcpServers": {
    "specmem": {
      "command": "uvx",
      "args": ["specmem-mcp"],
      "env": {
        "SPECMEM_LOG_LEVEL": "INFO"
      }
    }
  }
}

Environment Variables

Variable Description Default
SPECMEM_LOG_LEVEL Logging level INFO
SPECMEM_WORKSPACE Override workspace path Current directory

Protocol Details

The server uses stdio transport for communication:

  1. Reads JSON-RPC requests from stdin
  2. Writes JSON-RPC responses to stdout
  3. Logs to stderr

Request Format

{
    "id": "request-id",
    "method": "tools/call",
    "params": {
        "name": "specmem_query",
        "arguments": {
            "query": "authentication"
        }
    }
}

Response Format

{
    "id": "request-id",
    "result": {
        "results": [...],
        "count": 5
    }
}

See Also