Skip to content

Armory

The Protocol Gateway that aggregates multiple tool sources and exposes them through a unified MCP interface, with built-in protocol translation and token optimization.

Why "Armory"?

Just as a forge creates tools and an armory stores them, the Armory is where you go to access all your tools. It aggregates tools from various sources and provides them through a single, consistent interface.

The OpenRouter Analogy

Armory is to MCP servers what OpenRouter is to LLMs — one unified interface to many backends.

AspectOpenRouterArmory
AggregatesLLM providersMCP servers & tool sources
ProtocolOpenAI-compatible APIMCP (Streamable HTTP)
Client seesOne API, many modelsOne endpoint, many tools

Architecture

Hybrid Gateway Pattern

Armory exposes multiple MCP endpoints, giving clients flexibility:

Armory Tool Call Flow

Endpoints:

  • /mcp → Aggregated (all tools with prefixes)
  • /mcp/weather → Direct access to weather server
  • /mcp/search → Direct access to search server
  • /.well-known/mcp.json → Discovery metadata

Why hybrid? Clients wanting simplicity connect to /mcp (one connection, all tools). Clients wanting isolation connect to /mcp/{server} (specific server only).

Key Features

Unified MCP Interface

Orchestrators connect to the Armory as a single MCP server. They don't need to know about the various backends - they just see a collection of tools.

python
# Pydantic AI connects to Armory as MCP server
from pydantic_ai import Agent
from pydantic_ai.mcp import MCPServerSSE

armory = MCPServerSSE('http://localhost:8000/mcp')
agent = Agent('openai:gpt-4o', toolsets=[armory])

async with agent.run_mcp_servers():
    # All tools from all backends are available
    result = await agent.run("Search and summarize")

Protocol Translation

The Armory translates between different protocol formats automatically:

Protocol Translation

Result Transformation (JSON → TOON)

Tool results are converted to TOON format for token efficiency:

python
class ResultTransformer:
    def transform(self, result: Any) -> str:
        # TOON for tabular data (30-40% savings)
        if self._is_tabular(result):
            return toon.encode(result)

        # JSON for complex nested structures
        return json.dumps(result)

Before (JSON):

json
[{"name": "John", "email": "john@x.com", "status": "active"},
 {"name": "Jane", "email": "jane@x.com", "status": "pending"}]

After (TOON):

@users [2] {name|email|status}
John | john@x.com | active
Jane | jane@x.com | pending

~40% fewer tokens, and LLMs actually understand TOON better than JSON (73.9% accuracy vs 69.7% in benchmarks).

Transport: Streamable HTTP

The Armory uses MCP's Streamable HTTP transport (not the deprecated SSE transport):

TransportStatusUse Case
stdioActiveLocal subprocess
HTTP+SSEDeprecatedLegacy remote
Streamable HTTPCurrentRemote (single endpoint)

Benefits of Streamable HTTP:

  • Single endpoint - /mcp handles everything
  • Resumable streams - Last-Event-ID header
  • Session management - Mcp-Session-Id header
  • Infrastructure friendly - Works with proxies, load balancers

Protocol Adapters

AdapterBackend TypeTranslation
MCPAdapterMCP ServersMostly passthrough
RESTAdapterREST APIsHTTP methods, auth headers
OpenAIFCAdapterOpenAI FC servicesOpenAI function format
LocalAdapterPython functionsDirect invocation

MCP Server Strategy

Don't reinvent the wheel. Use existing MCP servers where available, build custom only when needed.

CategoryServersNotes
Use ExistingBrave Search, GitHub (remote), fetch, filesystem, timeWell-tested, maintained by others
Build Customweather (Open-Meteo), uptime checker, notesFastMCP + Python, learn the patterns

Free APIs for Custom Servers

Configuration

yaml
# armory.yaml
server:
  host: "0.0.0.0"
  port: 8000
  transport: "streamable_http"

tool_rag:
  enabled: true
  embedding_model: "text-embedding-3-small"
  default_top_k: 10

result_transformer:
  toon_enabled: true

sources:
  # Existing MCP servers (npm packages)
  - type: mcp
    name: "brave-search"
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-brave-search"]
    env:
      BRAVE_API_KEY: "${BRAVE_API_KEY}"

  - type: mcp
    name: "filesystem"
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-filesystem", "/allowed/path"]

  # Remote MCP servers (hosted by provider)
  - type: mcp
    name: "github"
    url: "https://api.github.com/mcp"
    auth: oauth

  # Custom MCP servers (FastMCP)
  - type: mcp
    name: "weather"
    command: "python"
    args: ["-m", "forge_mcp_servers.weather"]

  - type: mcp
    name: "notes"
    command: "python"
    args: ["-m", "forge_mcp_servers.notes"]

Usage

python
from agentic_forge import Armory

# Load from config
armory = Armory.from_config("armory.yaml")

# Or configure programmatically
armory = Armory()
await armory.register_mcp_server("http://localhost:3001/mcp", prefix="fs_")
await armory.register_rest_api("https://api.weather.com", tools=[...])
await armory.register_local_function(my_function)

# Start the server
await armory.start()  # Exposes MCP at http://localhost:8000/mcp

Benefits

ProblemSolution
Multiple tool sourcesSingle MCP interface
Different protocolsProtocol adapters translate
JSON wastes tokensTOON transformation
Too many tools in contextTool RAG filters dynamically
Managing MCP serversCentralized configuration

Building efficient AI agents