By Sagar Shankaran, Founder of CallSphere
Learn how MCP clients discover available servers, understand server manifests and tool catalogs, and build a lightweight server registry that lets agents dynamically connect to the right tools.
Key takeaways
In a simple setup, an agent connects to a hardcoded list of MCP servers. But as organizations deploy dozens of MCP servers — one for databases, one for Slack, one for file systems, one for monitoring — the question becomes: how does an agent know which servers exist and which tools they offer?
This is the server discovery problem. It parallels service discovery in microservices architectures: you need a registry that catalogs available servers, their capabilities, and their connection details so that agents (or humans configuring agents) can find the right tools without manual configuration.
The simplest discovery mechanism is a JSON configuration file that lists available servers. Both Claude Desktop and the OpenAI Agents SDK support this pattern:
flowchart LR
HOST(["MCP host<br/>Claude Desktop or IDE"])
CLIENT["MCP client"]
subgraph SERVERS["MCP Servers"]
S1["Filesystem server"]
S2["GitHub server"]
S3["Postgres server"]
SX["Custom tool server"]
end
LLM["LLM session"]
OUT(["Grounded action"])
HOST <--> CLIENT
CLIENT <-->|stdio or HTTP+SSE| S1
CLIENT <--> S2
CLIENT <--> S3
CLIENT <--> SX
CLIENT --> LLM --> OUT
style HOST fill:#f1f5f9,stroke:#64748b,color:#0f172a
style CLIENT fill:#4f46e5,stroke:#4338ca,color:#fff
style OUT fill:#059669,stroke:#047857,color:#fff
# mcp_servers.json — static server configuration
{
"servers": {
"database": {
"transport": "stdio",
"command": "python",
"args": ["servers/db_server.py"],
"description": "Query and manage application databases"
},
"filesystem": {
"transport": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"],
"description": "Read and write files in the data directory"
},
"monitoring": {
"transport": "http",
"url": "https://mcp.internal.company.com/monitoring",
"description": "Access application metrics and alerts"
}
}
}
This works for small teams but breaks down as the number of servers grows. Every time someone deploys a new MCP server, every agent configuration file needs to be updated.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
A server registry is a centralized catalog that MCP servers register with. Agents query the registry to discover available servers dynamically:
# registry_server.py — a simple MCP server registry
from mcp.server.fastmcp import FastMCP
import json
from datetime import datetime, timedelta
mcp_registry = FastMCP(name="MCPRegistry")
# In-memory registry (use a database in production)
_registry: dict[str, dict] = {}
@mcp_registry.tool()
async def register_server(
name: str,
description: str,
transport: str,
url: str | None = None,
command: str | None = None,
args: list[str] | None = None,
tags: list[str] | None = None,
) -> str:
"""Register an MCP server in the discovery registry.
Args:
name: Unique server name.
description: What this server does.
transport: Transport type - 'stdio' or 'http'.
url: Server URL (required for http transport).
command: Command to launch (required for stdio transport).
args: Command arguments (for stdio transport).
tags: Searchable tags describing server capabilities.
"""
_registry[name] = {
"name": name,
"description": description,
"transport": transport,
"url": url,
"command": command,
"args": args or [],
"tags": tags or [],
"registered_at": datetime.utcnow().isoformat(),
"last_heartbeat": datetime.utcnow().isoformat(),
}
return json.dumps({"registered": name})
@mcp_registry.tool()
async def discover_servers(
tag: str | None = None,
transport: str | None = None,
) -> str:
"""Discover available MCP servers, optionally filtered by tag or transport.
Args:
tag: Filter servers by this tag.
transport: Filter by transport type ('stdio' or 'http').
"""
cutoff = datetime.utcnow() - timedelta(minutes=5)
results = []
for server in _registry.values():
heartbeat = datetime.fromisoformat(server["last_heartbeat"])
if heartbeat < cutoff:
continue # Skip stale servers
if tag and tag not in server["tags"]:
continue
if transport and server["transport"] != transport:
continue
results.append(server)
return json.dumps({
"count": len(results),
"servers": results,
}, indent=2)
Each MCP server can expose a manifest resource that describes itself in detail. This goes beyond what initialize returns — it includes documentation, examples, and dependency information:
# In your MCP server, expose a manifest resource
@mcp_server.resource("manifest://server/info")
async def get_manifest() -> str:
"""Return the server manifest with capability details."""
import json
return json.dumps({
"name": "DatabaseServer",
"version": "2.1.0",
"description": "SQL query and management tools for PostgreSQL",
"author": "platform-team@company.com",
"documentation_url": "https://docs.internal/mcp/database",
"capabilities": {
"tools": [
{
"name": "query_db",
"category": "read",
"description": "Execute read-only SQL queries",
},
{
"name": "insert_record",
"category": "write",
"description": "Insert records into tables",
},
],
"resources": ["schema://tables", "metrics://db/stats"],
"prompts": ["analyze_table", "debug_slow_query"],
},
"dependencies": {
"requires_auth": True,
"auth_method": "api_key",
"rate_limit": "100 requests per minute",
},
}, indent=2)
Production MCP servers can register themselves with the registry on startup:
import httpx
async def auto_register(registry_url: str, server_info: dict):
"""Register this server with the central MCP registry."""
async with httpx.AsyncClient() as client:
response = await client.post(
f"{registry_url}/mcp",
json={
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "register_server",
"arguments": server_info,
},
},
)
print(f"Registration result: {response.json()}")
Combine auto-registration with periodic heartbeats, and the registry always reflects the current state of available servers. When a server goes down, its heartbeat expires and it disappears from discovery results.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
On the agent side, query the registry before building the agent's server list:
from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHTTP
async def build_agent_with_discovery(task_tags: list[str]):
"""Build an agent that connects to servers matching the task."""
# Query the registry for relevant servers
registry = MCPServerStreamableHTTP(
name="Registry",
params={"url": "http://registry:8000/mcp"},
)
async with registry:
# Use the registry to find servers tagged for our task
# Then connect the agent to those servers
pass # Implementation depends on your agent framework
As of early 2026, there is no official MCP registry specification. The patterns described here are community-developed approaches. The MCP specification focuses on the client-server protocol, leaving discovery as an implementation concern. Expect a standardized registry protocol to emerge as the ecosystem matures.
Include version information in server manifests and registry entries. When two servers expose tools with the same name, use the server name as a namespace prefix. Your agent configuration should specify which server version to prefer when conflicts arise.
For most production deployments, discover at configuration time and cache the server list. Runtime discovery adds latency and introduces a dependency on the registry being available. Reserve runtime discovery for long-running agents that need to adapt to new servers appearing in the ecosystem.
#MCP #ServiceDiscovery #ToolRegistry #AIAgents #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
A founder's guide to the personal AI assistant market: best AI assistant apps, business-grade options, and how CallSphere's voice agent fits in.
A founder's guide to free AI agents, low-code AI agent builders, and how to know when you should pay for a real platform like CallSphere.
Graphiti is the open-source temporal knowledge graph for AI agents in 2026. Learn how bi-temporal memory beats vector RAG for voice agents and long-running LLMs.
Chatbot app vs ChatGPT in 2026: a founder's clear take on the difference, when to use which, and how a real AI chatbot app development works.
How we built a fault-tolerant HVAC emergency triage and tech-dispatch platform on Kubernetes — three-tier CQRS, 11 micro-agents on the OpenAI Agents SDK + LangGraph, NATS JetStream, DTMF/SMS/WebSocket acceptance, circuit breakers, and an evaluation pipeline that catches regressions before they wake a tech at 3 AM.
How to design a multi-agent system using MCP for tools and A2A for cross-vendor coordination, with a CallSphere voice agent as a participating node.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.