Speakeasy Logo
Skip to Content

Gram — The MCP Cloud

Your fast path to production MCP. build, deploy and scale your MCP servers with ease using Gram's cloud platform.

Start building MCP

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:

  1. The server registers a static resource or URI template (for example, stock://{symbol}/earnings).
  2. The client calls resources/list to discover available resources or templates.
  3. The client sends a resources/read request with a specific resource URI.
  4. The server loads the content for that resource.
  5. The server returns the content as either text or blob.

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 of types.Resource items for static URIs.
  • @list_resource_templates() exposes dynamic resources. You return a list of types.ResourceTemplate items 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.text

In the example above, we:

  • Declare a resource template stock://{symbol}/earnings that clients can use to request dynamic resources for any stock symbol.
  • Use @list_resource_templates to expose the template to clients, which tells the agent how to construct valid URIs.
  • Use @read_resource to 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 TextContent resource 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://, or config://.
  • 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, the assistant, or both).
  • How important the resource is (on a scale of 0.0 to 1.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_cursor

When implementing pagination:

  • Remember the cursor parameter can be any string, but typically encodes a position.
  • Return a tuple with both resources and the next cursor.
  • Return None for 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