ractogateway.mcp
RactoGateway MCP — Model Context Protocol server, client, and agentic loop.
This package provides four primary classes:
RactoMCPServerExpose any
ToolRegistryas an MCP server (stdio or SSE transport). Works with tools built for any of the three provider developer kits.RactoMCPClientConnect to an MCP server (stdio / SSE / streamable-HTTP) and import its tools as a
ToolRegistrycompatible with every kit.MCPMultiClientConnect to N MCP servers simultaneously and present their tools as a single merged
ToolRegistry.MCPAgentAutomatic agentic loop (LLM → tool call → execute → continue) that works with OpenAI, Google Gemini, and Anthropic Claude developer kits.
Configuration models:
MCPServerConfigServer name / version metadata.
MCPClientConfigTransport, command / URL, env vars.
MCPToolResultContent + error flag returned by tool calls.
Quick start
Server (expose tools via MCP, works with Claude Desktop / Cursor / …):
from ractogateway import ToolRegistry
from ractogateway.mcp import RactoMCPServer
registry = ToolRegistry()
@registry.register
def get_weather(city: str) -> str:
'''Return current weather for a city.'''
return f"Sunny in {city}"
server = RactoMCPServer.from_registry(registry, name="weather-tools")
server.run() # stdio, blocking — add to claude_desktop_config.json
Client + any developer kit:
from ractogateway.mcp import RactoMCPClient, MCPClientConfig, MCPAgent
from ractogateway.openai_developer_kit import OpenAIDeveloperKit
from ractogateway._models.chat import ChatConfig
cfg = MCPClientConfig(transport="stdio", command="python",
args=["-m", "my_server"])
registry = RactoMCPClient(cfg).list_tools_sync()
# Works for OpenAI, Google Gemini, and Anthropic Claude:
kit = OpenAIDeveloperKit(model="gpt-4o")
agent = MCPAgent(kit, registry, max_turns=6)
result = agent.run(ChatConfig(user_message="What is the weather in Tokyo?"))
print(result.content)
Multi-server aggregation:
from ractogateway.mcp import MCPMultiClient, MCPClientConfig
configs = [
MCPClientConfig(transport="stdio", command="python",
args=["-m", "math_server"]),
MCPClientConfig(transport="sse", url="http://localhost:8001/sse"),
]
async with MCPMultiClient(configs) as multi:
registry = await multi.to_registry()
Installation
pip install ractogateway[mcp] # core (stdio + SSE client)
pip install ractogateway[mcp-sse] # server SSE transport (+ starlette/uvicorn)
- class ractogateway.mcp.MCPClientConfig(**data)[source]
Bases:
BaseModelConfiguration for connecting to an MCP server.
- Parameters:
transport (Literal['stdio', 'sse', 'streamable-http']) – MCP transport to use.
"stdio"is standard for subprocess-based servers (e.g. Claude Desktop)."sse"/"streamable-http"are for HTTP-based servers.command (str | None) – Executable to launch the server process — required for stdio.
args (list[str]) – Command-line arguments passed to command (stdio only).
env (dict[str, str]) – Extra environment variables injected into the server process (stdio only). Merged on top of the inherited environment.
url (str | None) – Server URL — required for sse / streamable-http. Example:
"http://localhost:8000/sse".
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- transport: Literal['stdio', 'sse', 'streamable-http']
- model_config: ClassVar[ConfigDict] = {'frozen': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class ractogateway.mcp.MCPServerConfig(**data)[source]
Bases:
BaseModelConfiguration for a
RactoMCPServerinstance.- Parameters:
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- name: str
- description: str
- version: str
- model_config: ClassVar[ConfigDict] = {'frozen': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class ractogateway.mcp.MCPToolResult(**data)[source]
Bases:
BaseModelResult returned from calling a remote MCP tool.
- Parameters:
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- content: str
- is_error: bool
- model_config: ClassVar[ConfigDict] = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class ractogateway.mcp.RactoMCPServer(name, *, description='', version='0.1.0')[source]
Bases:
objectExpose a
ToolRegistry(or individual functions) as a Model Context Protocol server.Supported transports
stdio (default) — standard I/O; ideal for Claude Desktop and any subprocess-based MCP client.
sse — HTTP Server-Sent Events; for remote / browser-based clients. Requires
pip install ractogateway[mcp-sse].
- type name:
- param name:
Server name visible to MCP clients.
- type description:
- param description:
Optional human-readable description.
- type version:
- param version:
Server version string.
Example
from ractogateway import ToolRegistry from ractogateway.mcp import RactoMCPServer registry = ToolRegistry() @registry.register def add(a: int, b: int) -> int: '''Add two integers.''' return a + b server = RactoMCPServer.from_registry(registry, name="math-tools") server.run() # stdio, blocking
- classmethod from_registry(registry, *, name='ractogateway-server', description='RactoGateway MCP Server', version='0.1.0')[source]
Build a server from a populated
ToolRegistry.Every registered callable becomes an MCP tool. Tools registered only as Pydantic models (no backing callable) are silently skipped.
- Parameters:
registry (
ToolRegistry) – A ToolRegistry with one or more registered tools.name (
str) – MCP server name shown to clients.description (
str) – Optional server description.version (
str) – Server version string.
- Return type:
- Returns:
RactoMCPServer – Ready-to-run server instance.
- add_tool(fn, *, name=None, description=None)[source]
Register a single function as an MCP tool.
The function’s type annotations drive the JSON Schema; its docstring provides the description (overridable via description).
- run(transport='stdio', *, host='0.0.0.0', port=8000)[source]
Start the MCP server (blocking).
- Parameters:
- Return type:
- get_asgi_app()[source]
Return a Starlette ASGI app for SSE transport.
Use this to mount the MCP server into an existing web application rather than starting a standalone server with
run().Requires
pip install ractogateway[mcp-sse].- Return type:
Example
import uvicorn from ractogateway.mcp import RactoMCPServer server = RactoMCPServer.from_registry(registry, name="tools") app = server.get_asgi_app() uvicorn.run(app, host="0.0.0.0", port=8000)
- class ractogateway.mcp.RactoMCPClient(config)[source]
Bases:
objectConnect to an MCP server and consume its tools as
ToolSchemaobjects.This is an async context manager. Keep it alive to reuse the underlying connection for multiple tool calls (O(1) per call after connection setup). For single calls from synchronous code, use the
*_sync()convenience methods.- Parameters:
config (
MCPClientConfig) – Connection configuration (transport, command / URL, env, …).(recommended) (Example — async)
------------------------------
:: –
- config = MCPClientConfig(transport=”stdio”, command=”python”,
args=[“-m”, “my_server”])
- async with RactoMCPClient(config) as client:
# Reuse this connection for all calls. tools = await client.list_tools() result = await client.call_tool(“search”, {“query”: “AI”}) registry = await client.to_registry()
one-shot (Example — sync)
-----------------------
:: – client = RactoMCPClient(config) tools = client.list_tools_sync()
- async list_tools()[source]
List all tools exposed by the MCP server.
- Return type:
- Returns:
list[ToolSchema] – Provider-agnostic tool schemas — ready to be registered in any
ToolRegistryor passed directly to a developer kit viaChatConfig(tools=…).- Raises:
RuntimeError – If called outside an
async withblock.
- async call_tool(name, arguments=None)[source]
Call a remote MCP tool.
- Parameters:
- Return type:
- Returns:
MCPToolResult –
contentcontains all text blocks joined by"\n".is_errorisTruewhen the server signals a tool error.- Raises:
RuntimeError – If called outside an
async withblock.
- async to_registry()[source]
Return a
ToolRegistrypopulated with all server tools.Each callable in the registry makes a fresh one-shot MCP connection when invoked. This keeps the returned registry self-contained and usable outside an
async withblock.For high-throughput usage, hold the
RactoMCPClientcontext manager alive and callcall_tool()directly.- Return type:
- Returns:
ToolRegistry – Registry compatible with all three developer kits via
ChatConfig(tools=registry).
- list_tools_sync()[source]
Synchronous wrapper: connect, list tools, disconnect.
- Return type:
- Returns:
list[ToolSchema] – All tool schemas exposed by the server.
- Raises:
RuntimeError – If called from within a running event loop.
- call_tool_sync(name, arguments=None)[source]
Synchronous wrapper: connect, call tool, disconnect.
- Parameters:
- Return type:
- Returns:
MCPToolResult – Tool output.
- Raises:
RuntimeError – If called from within a running event loop.
- class ractogateway.mcp.MCPMultiClient(configs)[source]
Bases:
objectConnect to multiple MCP servers and present them as a single tool surface.
Tools from all servers are merged into one flat namespace. If two servers advertise the same tool name, the later server’s definition wins (and a warning is embedded in the tool description noting the override).
Routing is O(1): an internal
dict[tool_name → server_index]maps each tool back to its origin server forcall_tooldispatch.- Parameters:
configs (
list[MCPClientConfig]) – OneMCPClientConfigper server. At least one config is required.
- async list_tools()[source]
Return the merged list of tool schemas from all servers.
- Return type:
- Returns:
list[ToolSchema] – Deduplicated (last-server-wins) tool schemas sorted by name.
- async call_tool(name, arguments=None)[source]
Call a tool on whichever server originally advertised it.
Routing is O(1) via the internal
tool_name → server_indexmap.- Parameters:
- Return type:
- Returns:
MCPToolResult – Tool output.
- Raises:
KeyError – If name is not in the merged tool namespace.
RuntimeError – If called outside an
async withblock.
- async to_registry()[source]
Return a merged
ToolRegistrywith remote callables.Each callable in the registry makes a fresh one-shot connection to the correct origin server when invoked. This keeps the registry self-contained and usable outside an
async withblock.- Return type:
- Returns:
ToolRegistry – Merged registry compatible with all three developer kits.
- list_tools_sync()[source]
Synchronous wrapper: connect all, list merged tools, disconnect all.
- Raises:
RuntimeError – If called from within a running event loop.
- Return type:
- call_tool_sync(name, arguments=None)[source]
Synchronous wrapper: connect all, call tool, disconnect all.
- Raises:
RuntimeError – If called from within a running event loop.
- Return type:
- property server_count: int
Number of configured MCP servers.
- class ractogateway.mcp.MCPAgent(kit, registry, *, max_turns=10)[source]
Bases:
objectAgentic tool-execution loop compatible with all three developer kits.
Runs the LLM → tool-call → execute → continue loop automatically, returning the final
LLMResponseonce the LLM produces a non-tool response or max_turns is reached.- Parameters:
kit (
Any) – Any developer kit withchat()/achat()methods:OpenAIDeveloperKit,GoogleDeveloperKit, orAnthropicDeveloperKit.registry (
ToolRegistry) – Tool registry containing callables for each tool the LLM can call. Typically populated viaRactoMCPClient.to_registry()orMCPMultiClient.to_registry().max_turns (
int) – Maximum number of tool-call rounds before the loop stops and returns the last response. Prevents infinite recursion.
Example
from ractogateway.openai_developer_kit import OpenAIDeveloperKit from ractogateway.mcp import MCPAgent, RactoMCPClient, MCPClientConfig from ractogateway._models.chat import ChatConfig cfg = MCPClientConfig(transport="stdio", command="python", args=["-m", "my_server"]) reg = RactoMCPClient(cfg).list_tools_sync() kit = OpenAIDeveloperKit(model="gpt-4o") agent = MCPAgent(kit, reg, max_turns=6) result = agent.run(ChatConfig(user_message="Search for recent AI papers")) print(result.content)
- classmethod from_mcp(kit, configs, *, max_turns=10)[source]
Build an agent by fetching tools from one or more MCP servers.
Opens a one-shot connection per config, fetches tools, closes the connection, and constructs the agent with the merged registry.
Note
This is a sync classmethod; it uses
asyncio.run()and therefore cannot be called from within a running event loop. In async code, build the registry yourself:async with MCPMultiClient(configs) as multi: registry = await multi.to_registry() agent = MCPAgent(kit, registry)
- Parameters:
kit (
Any) – Any RactoGateway developer kit.configs (
list[MCPClientConfig]) – MCP server connection configs.max_turns (
int) – Maximum tool-call rounds.
- Return type:
- Returns:
MCPAgent – Ready to call
run()orarun().
- run(config)[source]
Run the agentic loop synchronously.
Injects the tool registry from this agent into config (overriding
config.toolsif already set).- Parameters:
config (
ChatConfig) – Initial chat config.promptmust be set here or on the kit.- Return type:
- Returns:
LLMResponse – Final response after tool calls are resolved.
- async arun(config)[source]
Run the agentic loop asynchronously.
Supports async tool callables (
async def); sync callables are called directly.- Parameters:
config (
ChatConfig) – Initial chat config.- Return type:
- Returns:
LLMResponse – Final response after tool calls are resolved.
- property registry: ToolRegistry
The
ToolRegistryused by this agent.
- property max_turns: int
Maximum number of tool-call rounds per
run()call.