Agents and tools
Tools, resources and prompts
MCP servers expose three primitives: tools the model calls, resources the app can read, and prompt templates the user can insert.
An MCP server can expose three kinds of capability. Tools do work. Resources are readable data. Prompts are reusable templates. Mixing them up is how servers either dump megabytes into context or force the model to guess names it could have picked from a list.
Concept
Before you start: a tool is a named action with a JSON Schema for its inputs, the same idea as function calling. MCP messages use JSON-RPC 2.0: typed requests such as tools/list and tools/call.
The big idea
| Primitive | Who starts it | Purpose |
|---|---|---|
| Tools | Model, via the host | Side-effecting or computed actions |
| Resources | App or user, sometimes model-assisted | Readable data at a URI (file://…, postgres://…) |
| Prompts | User or app | Templates and workflows the host can insert |
Tools need the same discipline as any agent tool: a clear description, pagination, compact outputs. Resources separate data from actions. Hosts can show them in a picker or stuff selected content into context, without a read_whatever tool for every path. Prompts package canned workflows ("triage this sev") and stay user-triggered, so control stays explicit.
Some servers also support sampling (the server asks the host to run a completion) and elicitation (the server asks the host to collect user input). Those invert the usual trust direction. Treat them as power features that need host policy.
Walking through a metrics server
Imagine a metrics connector.
- Resource
metrics://services— a list of service names for the UI picker. - Resource
metrics://services/{name}/dashboard— a markdown summary the user can attach. - Tool
query_range—{service, metric, start, end}→ compact JSON.
If you only expose query_range, the model guesses service names and may scan blindly. If you only expose giant resources, you dump megabytes into context. The split lets the host show a picker while the model still runs precise queries.
A billing tool looks the same as function calling. This is a sketch of the handler, not a full SDK server:
def get_subscription(customer_id):
catalog = {"cus_123": {"plan": "pro", "status": "active", "renews_on": "2026-11-01"}}
sub = catalog.get(customer_id)
if not sub:
return {"error": "not_found", "customer_id": customer_id}
return sub
print(get_subscription("cus_123"))
print(get_subscription("cus_999")){'plan': 'pro', 'status': 'active', 'renews_on': '2026-11-01'}
{'error': 'not_found', 'customer_id': 'cus_999'}The host lists the tool, the model selects get_subscription, the host calls the server, and the JSON re-enters the conversation as a tool result.
Design notes that matter once several hosts share the server:
- Prefer task-level tools over a generic
proxy_http. Hosts load all enabled tools into context. - Namespace names if hosts merge servers (
billing_get_subscription). - Bump the server version when behavior changes. Hosts and evals cache assumptions.
Common mistakes
- One kitchen-sink tool. A generic HTTP proxy pollutes every session and hides the real job.
- Resources that are too big. A whole database dump is not a resource the model should read.
- No pagination on lists. Long results crowd out the rest of the prompt.
Check yourself
- Why not expose only
query_rangefor metrics? The model then has to guess service names instead of picking them from a catalog resource. - Who should trigger an MCP prompt template? The user or the app, so the workflow stays an explicit choice.
Key takeaways
- Tools are actions. Resources are readable data. Prompts are insertable templates.
- Split catalogs into resources and precise queries into tools.
- Keep the tool surface small, named clearly and paginated.
Further learning
Next, see how the host actually talks to the server in stdio and HTTP transports.
Related
Hosts, clients and servers
MCP turns N times M custom adapters into N plus M. A host owns the app, a client talks to one server, and a server exposes tools and data.
stdio and HTTP transports
Two ways to move MCP messages: a local subprocess over stdin/stdout, or a remote HTTP server with OAuth.
Tool use and function calling
Letting models call your code: tool schemas, the tool loop, and designing tools models use well.