Gram by Speakeasy
Introducing Gram by Speakeasy. Gram is everything you
need to power integrations for Agents and LLMs. Easily create, curate and host
MCP servers for internal and external use. Build custom tools and remix
toolsets across 1p and 3p APIs. Try building a MCP server now!
What are MCP resources?
MCP resources are read-only, addressable content entities exposed by the server. They allow MCP clients to retrieve structured, contextual data (such as logs, configuration data, or external documents) that can be passed to models for reasoning. Because resources are strictly observational and not actionable, they must be deterministic, idempotent, and free of side effects.
Resources can expose:
- Log files
- JSON config data
- Real-time market stats
- File contents
- Structured blobs (for example, PDFs or images)
Resources are accessed via URI schemes like note://, config://, or stock://, and read using the resources/read method.
Resource lifecycles and request and response formats
Each resource lifecycle follows this pattern:
- The server registers a static resource or URI template (for example,
stock://{symbol}/earnings). - The client calls
resources/listto discover available resources or templates. - The client sends a
resources/readrequest with a specific resource URI. - The server loads the content for that resource.
- The server returns the content as either
textorblob.
Here is an example of a resource read request:
{
"method": "resources/read",
"params": {
"uri": "stock://AAPL/earnings"
},
"id": 8
}The server responds with a text resource:
{
"jsonrpc": "2.0",
"id": 8,
"result": {
"contents": [
{
"uri": "stock://AAPL/earnings",
"mimeType": "application/json",
"text": "{ \"fiscalDateEnding\": \"2023-12-31\", \"reportedEPS\": \"3.17\" }"
}
]
}
}Resources can also return blob values for binary content like base64-encoded PDFs or images.
Declaring a resource in Python
Resources are regular functions marked with decorators that define their role:
@list_resources()exposes static resources. You return a list oftypes.Resourceitems for static URIs.@list_resource_templates()exposes dynamic resources. You return a list oftypes.ResourceTemplateitems for dynamic, parameterized URIs.@read_resource()implements logic for resolving a resource by URI.
Here’s an example using the Alpha Vantage API to return earnings data. Since we want to support arbitrary stock symbols, we’ll use a resource template to let clients specify the symbol.
import asyncio
from mcp.server import Server
from mcp import types
from mcp.server import stdio
from mcp.server import InitializationOptions, NotificationOptions
from pydantic import AnyUrl
import requests
app = Server("stock-earnings-server")
@app.list_resource_templates()
async def list_earnings_resources_handler(symbol: str) -> list[types.ResourceTemplate]:
return [
types.ResourceTemplate(
uriTemplate="stock://{symbol}/earnings",
name="Stock Earnings",
description="Quarterly and annual earnings for a given stock symbol",
mimeType="application/json"
)
]
@app.read_resource()
async def read_earnings_resource_handler(uri: AnyUrl) -> str:
parsed = str(uri)
if not parsed.startswith("stock://") or not parsed.endswith("/earnings"):
raise ValueError("Unsupported resource URI")
symbol = parsed.split("://")[1].split("/")[0].upper()
url = (
"https://www.alphavantage.co/query"
"?function=EARNINGS"
f"&symbol={symbol}"
"&apikey=demo"
)
response = requests.get(url)
return response.textIn the example above, we:
- Declare a resource template
stock://{symbol}/earningsthat clients can use to request dynamic resources for any stock symbol. - Use
@list_resource_templatesto expose the template to clients, which tells the agent how to construct valid URIs. - Use
@read_resourceto handle execution, where we fetch real-time earnings from the Alpha Vantage API for the provided stock symbol. - Return a valid JSON text response, which is wrapped by MCP in a
TextContentresource and can be used as context for prompts or decisions.
If the resource was static – for example, if the client only tracks Apple stock – we’d use the @list_resources decorator and return a list of types.Resource items instead:
...
@app.list_resources()
async def list_apple_earnings_resource() -> list[types.Resource]:
return [
types.Resource(
uri="stock://AAPL/earnings",
name="Apple Earnings",
description="Quarterly and annual earnings for Apple Inc.",
mimeType="application/json"
)
]Best practices and pitfalls to avoid
Here are some best practices for implementing MCP resources:
- Use clear, descriptive URI schemes like
note://,stock://, orconfig://. - Keep resource data consistent and read-only.
- Validate inputs or file paths to prevent injections or errors.
- Set correct MIME types so clients can parse content properly.
- Support dynamic resources with URI templates.
Here are some pitfalls to avoid:
- Treating resources as action triggers (use tools instead).
- Returning extremely large payloads (use pagination instead).
- Exposing sensitive data (unless scoped by roots or authenticated context).
- Relying on global state (unless explicitly isolated per session).
MCP resources are intended for structured, factual, and often cached information. They are perfect for background context, factual grounding, or real-world reference material.
Resource annotations
Resources can include optional annotations that provide additional metadata to guide clients on how to use them:
from mcp.server import Server
from mcp import types
app = Server("annotated-resources-server")
@app.list_resources()
async def list_resources() -> list[types.Resource]:
return [
types.Resource(
uri="docs://company/earnings-2024.pdf",
name="2024 Earnings Report",
mimeType="application/pdf",
annotations={
# Specify intended audience (user, assistant, or both)
"audience": ["user", "assistant"],
# Importance ranking from 0 (least) to 1 (most)
"priority": 0.8
}
)
]Annotations help clients decide:
- Which audience should see the resource (the
user, theassistant, or both). - How important the resource is (on a scale of
0.0to1.0).
Clients can use annotations to sort, filter, or highlight resources appropriately, but annotations are hints, not guarantees of behavior.
Pagination support
When dealing with large sets of resources, MCP supports pagination using cursors:
from mcp.server import Server
from mcp import types
app = Server("paginated-resources-server")
@app.list_resources()
async def list_resources(cursor: str = None) -> tuple[list[types.Resource], str]:
# Fetch resources from your backend, database, etc.
all_resources = fetch_all_resources()
# Implementation of pagination
page_size = 10
start_index = 0
if cursor:
# Parse cursor to get starting position
start_index = int(cursor)
# Get the current page of resources
current_page = all_resources[start_index:start_index + page_size]
# Calculate next cursor if there are more items
next_cursor = None
if start_index + page_size < len(all_resources):
next_cursor = str(start_index + page_size)
return current_page, next_cursorWhen implementing pagination:
- Remember the
cursorparameter can be any string, but typically encodes a position. - Return a tuple with both resources and the next cursor.
- Return
Nonefor the cursor when there are no more pages. - Keep cursor values opaque to clients. They shouldn’t assume structure.
- Handle invalid cursors gracefully.
Pagination helps manage memory usage for large resource collections and improves client responsiveness.
Resources vs tools
MCP resources are read-only and addressable via URIs like note://xyz or stock://AAPL/earnings. They are designed to preload context into the agent’s working memory or support summarization and analysis workflows.
MCP tools are actionable and invoked by the client with parameters to perform an action like writing a file, placing an order, or creating a task.
To avoid decision paralysis, define resources according to what the client should know and tools according to what the client can do.
Practical implementation example
In a WhatsApp MCP server, you could expose all image attachments that the server downloads as resources. This way, the MCP client can access the images without having to call a tool every time. The MCP client can simply request the resource by its URL, and the MCP server will return the image data.
Here’s how to implement a resource using the TypeScript SDK:
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { MediaRegistry } from "whatsapp";
mcpServer.resource(
"media",
new ResourceTemplate("media://{messageId}", {
list: () => {
const allMedia = mediaRegistry.getAllMediaItems();
const resources = [];
for (const { mediaItems } of allMedia) {
for (const mediaItem of mediaItems) {
resources.push({
uri: `media://${mediaItem.messageId}`,
name: mediaItem.name,
description: mediaItem.description,
mimeType:
mediaItem.mimetype !== "unknown" ? mediaItem.mimetype : undefined,
});
}
}
return { resources };
},
}),
async (uri: URL, { messageId }) => {
const messageIdRaw = Array.isArray(messageId) ? messageId[0] : messageId;
const messageIdString = decodeURIComponent(messageIdRaw);
const mediaData =
await messageService.downloadMessageMedia(messageIdString);
return {
contents: [
{
uri: uri.href,
blob: mediaData.data,
mimeType: mediaData.mimetype,
},
],
};
},
);This code defines a resource called media that can be accessed by the MCP client. The resource has a list method that returns a list of all media items. The MCP client can then request a specific media item by its URI, and the MCP server will return the media data.
Notifying clients about resource changes
The MCP server can send a notifications/resources/list_changed message to notify the MCP client that the list of resources has changed:
import { MediaItem, MediaRegistry } from "whatsapp";
// When the MCP server detects that a new media item has been added
mediaRegistry.onMediaListChanged((mediaItems: MediaItem[]) => {
mcpServer.sendResourceListChanged();
});You can also send a notification when a specific resource has changed:
mediaRegistry.onMediaItemChanged((mediaItem: MediaItem) => {
void mcpServer.server.sendResourceUpdated({
uri: resourceUri,
});
});How the LLM client handles resources is up to the client implementation. The MCP server just needs to expose the resources and notify the client when they change.
Last updated on