ARD MCP Server: Discover & Route Agent Tools Across the Web
Google's Agentic Resource Discovery (ARD) protocol, built under the Linux Foundation AAIF, lets an agent ask what is available before anything runs. This guide builds a stateless ARD-style MCP discovery server that indexes plugins, MCP servers, and skills and answers query-by-task searches with ranked, cacheable results.
Deepak Bagada
CEO, SaaSNext
- ARD (Agentic Resource Discovery) is an AAIF/Linux Foundation protocol, led by Google, that answers what is available for a task before any tool is invoked.
- Plugins are a first-class ARD resource type; the proposed application/agent-plugins+json MIME type lets an AI Catalog entry point at a plugin.json.
- A stateless MCP server can index plugins, MCP servers, and skills and rank query-by-task matches with SEP-2549 cacheScope/ttlMs hints.
- Deploy per MCP 2026-07-28 request/response semantics with OAuth 2.0 (RFC 8707 resource indicators) for remote catalogs.
ARD MCP Server: Find & Route Agent Tools Across the Web
What is Agentic Resource Discovery (ARD)?
Every agent that runs more than two or three MCP servers hits the same wall: the client can only call tools it has been explicitly wired to know about. Everything else in the organization — the marketing stack, the data platform, the internal APIs — stays invisible until somebody hand-edits a config file. In August 2026, the Linux Foundation's Agentic AI Foundation (AAIF), with Google leading the design work, published a protocol that removes that wall: Agentic Resource Discovery (ARD).
ARD is an open discovery protocol built around one operation: the client asks "What is available for this task?" and receives a ranked set of matching resources. The word resource is deliberately broad. In ARD's model, an agent, an MCP server, a Skill, and — new under the Agent Plugins 1.0.0 spec — a Plugin are all first-class agentic resource types. That single abstraction is what lets one discovery layer span everything from a single-tool MCP server to a full plugin bundle to a complete agent.
Crucially, ARD sits entirely before invocation. Discovery never runs a tool, never spawns a server, and never executes a skill. It returns pointers, metadata, and confidence scores; the client decides what to invoke next. Discovery is cheap, cacheable, and safe to run on every single user turn, while invocation stays expensive, authorized, and scoped.
The companion entry format ARD indexes is the AI Catalog — a machine-readable file published at a well-known URL that enumerates the resources an entity exposes. A proposed change in the ARD design registers the MIME type application/agent-plugins+json, so a catalog entry can point at a plugin.json exactly the way it points at an agent card or an mcp.json. That detail is the bridge between the discovery world and the Agent Plugins packaging world: a catalog does not need to understand how a plugin is built internally, only where its manifest lives.
Why find-and-route is the missing layer
Before ARD, tool utilization in most agent deployments is poor. Teams install a server, forget it exists, and the agent never surfaces it at the moment it matters. Find-and-route changes the economics:
- Query by task, not browse by name. Instead of remembering that
pdf-summarizerexists, the user describes the outcome: "summarize this PDF." The catalog returns ranked candidates across plugins, MCP servers, skills, and agents. - Routing without execution. Because ARD sits before invocation, a client can query several catalogs, assemble a shortlist, and then invoke the winner through MCP's normal
tools/callflow. - Freshness at the edge. The SEP-2549 caching extension —
cacheScopeplusttlMs— lets a stateless server attach cache hints to every discovery response, so per-turn lookups stay fast without serving stale resources.
The ARD MCP server in this guide turns those properties into a deployable tool. It indexes plugins, MCP servers, and skills from a catalog, answers query-by-task searches, and returns ranked matches annotated with SEP-2549 cache hints — all built stateless per the MCP 2026-07-28 spec (request/response, no session).
Indexing, ranking, and cache hints in practice
The server keeps two in-memory structures: an id-keyed map of every registered resource and a task-keyword index that maps verbs and nouns back to the entries that serve them. Scoring is intentionally transparent: exact task-keyword hits weigh most, followed by name matches and description overlap. Every stored entry also carries a cacheScope (request, session, or global) and a ttlMs bound, which the server echoes into each match so clients can cache discovery responses safely.
A response to catalog_search_by_task looks like this:
{
"task": "summarize this PDF",
"matches": [
{
"id": "plug-1",
"type": "plugin",
"name": "doc-suite",
"source": "https://registry.example.com/doc-suite/plugin.json",
"mime": "application/agent-plugins+json",
"score": 3.5,
"cacheScope": "global",
"ttlMs": 300000
},
{
"id": "srv-7",
"type": "mcp_server",
"name": "pdf-tools",
"source": "https://mcp.example.com/pdf/mcp.json",
"mime": null,
"score": 2.5,
"cacheScope": "session",
"ttlMs": 60000
}
]
}
Because ARD resolves nothing at discovery time, these results are safe to cache and cheap to produce — which is exactly why the protocol is designed to run on every turn, not just on demand.
Tool reference
| Tool | What it does | Returns |
|---|---|---|
catalog_search_by_task |
Query-by-task discovery across the index | Ranked matches + cache hints |
catalog_register_resource |
Ingest a resource (URL or inline JSON) into the catalog | Entry id |
catalog_resolve_plugin |
Resolve a plugin entry to its plugin.json location |
application/agent-plugins+json target |
catalog_get_entry |
Fetch one entry by id | Full metadata |
catalog_list_types |
Enumerate supported resource types | agent, mcp_server, skill, plugin |
catalog_unregister_resource |
Remove an entry | Status |
catalog_search_by_task inputSchema
{
"name": "catalog_search_by_task",
"description": "Search the ARD catalog for resources that can perform a task.",
"inputSchema": {
"type": "object",
"properties": {
"task": { "type": "string", "description": "Free-form task, e.g. 'summarize this PDF'" },
"types": {
"type": "array",
"items": { "type": "string", "enum": ["plugin", "mcp_server", "skill", "agent"] },
"description": "Optional resource-type filter"
},
"limit": { "type": "integer", "minimum": 1, "maximum": 25, "default": 10 }
},
"required": ["task"]
}
}
catalog_register_resource inputSchema
{
"name": "catalog_register_resource",
"inputSchema": {
"type": "object",
"properties": {
"source": { "type": "string", "description": "AI Catalog URL, plugin.json URL, or inline JSON" },
"type": { "type": "string", "enum": ["plugin", "mcp_server", "skill", "agent"] },
"name": { "type": "string", "description": "Human-readable resource name" },
"tasks": { "type": "array", "items": { "type": "string" }, "description": "Task keywords used for ranking" }
},
"required": ["source", "type", "name"]
}
}
catalog_resolve_plugin inputSchema
{
"name": "catalog_resolve_plugin",
"inputSchema": {
"type": "object",
"properties": {
"entry_id": { "type": "string", "description": "Catalog entry id of a plugin resource" }
},
"required": ["entry_id"]
}
}
The full TypeScript server
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
type ResourceType = "plugin" | "mcp_server" | "skill" | "agent";
interface ResourceEntry {
id: string;
type: ResourceType;
name: string;
description: string;
tasks: string[];
source: string;
mime?: string; // e.g. application/agent-plugins+json
cacheScope: "global" | "session" | "request"; // SEP-2549
ttlMs: number; // SEP-2549
}
const catalog = new Map<string, ResourceEntry>();
function score(task: string, entry: ResourceEntry): number {
const t = task.toLowerCase();
let s = entry.tasks.some((k) => t.includes(k.toLowerCase())) ? 2 : 0;
s += t.includes(entry.name.toLowerCase()) ? 1.5 : 0;
s += entry.description.toLowerCase().includes(t) ? 1 : 0;
return s;
}
const server = new McpServer({ name: "ard-discovery", version: "0.1.0" });
server.registerTool(
"catalog_search_by_task",
{
description: "Query-by-task discovery over the ARD catalog.",
inputSchema: {
type: "object",
properties: {
task: { type: "string" },
types: { type: "array", items: { type: "string", enum: ["plugin", "mcp_server", "skill", "agent"] } },
limit: { type: "integer", minimum: 1, maximum: 25, default: 10 }
},
required: ["task"]
}
},
async ({ task, types, limit = 10 }) => {
const matches = [...catalog.values()]
.filter((e) => !types || types.includes(e.type))
.map((e) => ({
id: e.id, type: e.type, name: e.name, source: e.source,
mime: e.mime ?? null, score: score(task, e),
cacheScope: e.cacheScope, ttlMs: e.ttlMs
}))
.sort((a, b) => b.score - a.score)
.slice(0, limit);
return {
content: [{ type: "text", text: JSON.stringify({ task, matches }, null, 2) }]
};
}
);
// catalog_register_resource, catalog_resolve_plugin, catalog_get_entry,
// catalog_list_types and catalog_unregister_resource follow the same
// registerTool pattern with the inputSchema reference above.
const transport = new StdioServerTransport();
await server.connect(transport);
mcpServers config for Claude Desktop and Cursor
The exact same block works in Claude Desktop's claude_desktop_config.json and in Cursor's .cursor/mcp.json:
{
"mcpServers": {
"ard-discovery": {
"command": "npx",
"args": ["-y", "@dailyaiworld/ard-discovery-server"],
"env": {
"ARD_CATALOG_URL": "https://catalog.dailyaiworld.com/ai-catalog.json",
"ARD_CACHE_TTL_MS": "60000"
}
}
}
}
Because the server is stateless, both clients can point at the same catalog and share the index; each request is authorized on its own, so no session state leaks between applications.
OAuth 2.0 and the security model
- Discovery is read-only.
catalog_search_by_tasknever executes remote code, so the worst a poisoned catalog can do is rank a bad resource — which invocation-time authorization still catches. - RFC 8707 resource indicators. When a catalog is remote and protected, the client requests a token with the catalog URL in the
resourceparameter, yielding audience-scoped access tokens instead of broad grants. - Minimal scopes.
catalog:readfor search and read operations;catalog:writefor register and unregister. Deny by default. - No sessions, per MCP 2026-07-28. Requests are request/response only; every call carries its own credentials, and caching is bounded by
ttlMs, never by session state. - Validate before you trust. Treat any
plugin.jsonormcp.jsonreached through a catalog as untrusted data. Checksum-pin critical entries and re-validate on every update.
Deployment notes
Run the server stateless so one instance — or many behind a load balancer — serves every client. Seed the index at boot from an AI Catalog URL and refresh it on a timer; for multi-region deployments, put the catalog behind a CDN and let SEP-2549 global cache hints do the heavy lifting. When you outgrow stdio, expose the same tools over Streamable HTTP and terminate TLS at the gateway.
Where ARD fits in your stack
Pair ARD discovery with the tools in the MCP directory, route its results through the patterns in our workflows, and watch the latest AI news for the final ARD spec as AAIF moves it toward release. The biggest wins come from wiring ARD into your agent bootstrap: resolve resources at plan time, cache them with SEP-2549 hints, and only then invoke.
FAQ
What exactly is Agentic Resource Discovery?
ARD is an open discovery protocol being built by Google through the Linux Foundation's Agentic AI Foundation. It lets a client ask "what is available for this task?" and receive matching resources — agents, MCP servers, skills, and plugins — ranked by fit. It operates entirely before invocation: nothing executes during discovery.
How is ARD different from MCP?
MCP is the invocation protocol: it defines how a client calls tools on a server. ARD is the discovery protocol: it tells the client what resources exist and which ones fit a task. They compose — ARD finds, MCP executes.
What is SEP-2549 cacheScope/ttlMs?
SEP-2549 is the MCP caching extension used in the 2026 MCP spec. cacheScope declares whether a response may be reused within a request, a session, or globally, and ttlMs bounds how long a cached response stays fresh. For a stateless discovery server, returning these hints per match lets clients cache catalog lookups aggressively without serving stale resources.
By Deepak Bagada, CEO at SaaSNext & Principal AI Architect.
Enjoyed this breakdown? Get our morning dispatch in your inbox.
Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.
Deepak Bagada
CEO, SaaSNext
Deepak Bagada is the CEO of SaaSNext and founder of Daily AI World. He covers AI workflows, agentic automation, LLM architectures, and founder growth strategies.
Inference Spending Surpasses Training for First Time
Next Story →GitHub Enterprise Rolls Out Strict MCP Allowlists
Related Intelligence Analysis
Vercel AI SDK Tool Calling React: 5 Steps (2026)
Vercel AI SDK tool calling React integration is a programming pattern that executes server-side functions based on large language model decisions and streams the results to a React frontend. By combining streamText with...
Fact-Density vs. Word Count: The New SEO for 2026
Fact Density is the ratio of verifiable, unique information to the total word count of a piece of content. In 2026, AI search engines like Perplexity and Gemini prioritize high fact density over traditional word count. A...
NVIDIA Audex vs Qwen3.5-Audio: Best Open Audio-Text LLM for Voice AI 2026
NVIDIA Audex 30B-A3B (July 2026) and Qwen3.5-35B-A3B are the two leading open audio-text LLMs. Audex uniquely handles both audio understanding and generation in a single model while preserving text intelligence. Qwen3.5-...