Skip to content
GitHub

Guide

PackageRequiredPurpose
lexigramYesCore framework
lexigram-contractsYesProtocol definitions
lexigram-ai-agentsOptionalAgent system bridging
lexigram-ai-skillsOptionalSkills-to-MCP bridging

MCP servers let AI agents — like Claude Desktop or custom LLM applications — interact with your application through tools, resources, and prompts.

The package wraps the Model Context Protocol as a first-class Lexigram citizen: handlers, transports, and connectors are wired through the DI container with the same provider/module lifecycle as any other extension.

RoleClassPurpose
ServerMCPServerExposes tools/resources/prompts to AI clients. JSON-RPC message router.
ClientMCPClientConnects to external MCP servers (stdio or SSE) and calls their tools.

An MCP server exposes three capability types:

  • Tools — callable functions the AI can invoke (tools/list, tools/call).
  • Resources — data the AI can read (resources/list, resources/read).
  • Prompts — template-driven messages (prompts/list, prompts/get).

Handlers implement the routing for each capability:

HandlerRegistersDispatches to
ToolHandler@tool-decorated methods, MCPController subclasses, connectorsMCPToolProviderProtocol
ResourceHandler@resource-decorated methods, connectorsMCPResourceProviderProtocol
PromptHandler@prompt-decorated methodsMCPPromptProviderProtocol
TransportDirectionI/O
StdioTransportServer-sidestdin/stdout (local desktop tools)
SSETransportServer-sideHTTP + Server-Sent Events
StdioClientTransportClient-sidestdin/stdout subprocess
SSEClientTransportClient-sideHTTP POST + SSE
from lexigram.ai.mcp import MCPController, tool, MCPModule
from lexigram.contracts.data.vector.protocols import VectorStoreProtocol
class SearchController(MCPController):
def __init__(self, vector_store: VectorStoreProtocol) -> None:
self._store = vector_store
@tool("search_docs", description="Search documents by query")
async def search_docs(self, query: str, limit: int = 10) -> list[dict]:
result = await self._store.similarity_search(query, k=limit)
return result.unwrap_or([])
# Wiring
module = MCPModule.configure(controllers=[SearchController])
module = MCPModule.from_services(
services=[UserService, AnalyticsService],
include_methods=["search", "get_*"],
)

Service methods matching the glob are registered as MCP tools automatically.

Connecting to External MCP Servers (Client)

Section titled “Connecting to External MCP Servers (Client)”
from lexigram.ai.mcp.client import MCPClientModule, MCPConnection
module = MCPClientModule.configure(
connections=[
MCPConnection.stdio(["uvx", "mcp-server-git"], name="git"),
MCPConnection.sse("http://localhost:9000/mcp", name="local"),
],
)

Inject MCPClientRegistry for named access or MCPClient for the primary connection.

ai_mcp:
connectors:
filesystem:
root_dir: "/workspace" # enables FilesystemConnector
read_only: true
web_fetch:
enabled: true
github:
token: "${GITHUB_TOKEN}" # enables GitHubConnector

Connectors are enabled by supplying the required config keys — absent keys disable the connector.

from lexigram.contracts.mcp.protocols import MCPToolProviderProtocol
class MyToolProvider:
async def list_tools(self) -> list[dict[str, Any]]:
return [{"name": "hello", "description": "Say hello", "inputSchema": {}}]
async def call_tool(self, name: str, arguments: dict[str, Any]) -> dict[str, Any]:
return {"content": [{"type": "text", "text": "Hello!"}]}

Register it in a custom provider’s boot() against MCPToolProviderProtocol.

When lexigram-ai-skills is installed and its provider is registered, SkillToolAdapter is automatically created — all skills become MCP tools.

  • ✅ Use MCPModule.from_services() for quick prototyping, MCPController for production.
  • ✅ Set read_only: true on connectors that should never mutate state.
  • ✅ Set enable_streaming: False in tests to simplify assertions.
  • ❌ Don’t import MCPServer from the server module directly — resolve it from the container.
  • ❌ Don’t expose database write connectors (sql.read_only: false) to untrusted AI clients.