Skip to content

Commit 588221a

Browse files
docs: Add examples showing ResourceContents direct return with metadata
Added example snippets demonstrating the main benefit of returning ResourceContents objects directly - the ability to include metadata via the _meta field: - FastMCP example: Shows various metadata use cases including timestamps, versions, authorship, image metadata, and query execution details - Low-level server example: Demonstrates metadata for documents, images, multi-part content, and code snippets The examples emphasize that metadata is the key advantage of this feature, allowing servers to provide rich contextual information about resources that helps clients better understand and work with the content. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 9fbeaf6 commit 588221a

File tree

2 files changed

+453
-0
lines changed

2 files changed

+453
-0
lines changed
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
"""
2+
Example showing how to return ResourceContents objects directly from
3+
low-level server resources.
4+
5+
The main benefit is the ability to include metadata (_meta field) with
6+
your resources, providing additional context about the resource content
7+
such as timestamps, versions, authorship, or any domain-specific metadata.
8+
"""
9+
10+
import asyncio
11+
from collections.abc import Iterable
12+
13+
from pydantic import AnyUrl
14+
15+
import mcp.server.stdio as stdio
16+
import mcp.types as types
17+
from mcp.server import NotificationOptions, Server
18+
19+
20+
# Create a server instance
21+
server = Server(
22+
name="LowLevel ResourceContents Example",
23+
version="1.0.0",
24+
)
25+
26+
27+
# Example 1: Return TextResourceContents directly
28+
@server.read_resource()
29+
async def read_resource(uri: AnyUrl) -> Iterable[types.TextResourceContents | types.BlobResourceContents]:
30+
"""Handle resource reading with direct ResourceContents return."""
31+
uri_str = str(uri)
32+
33+
if uri_str == "text://readme":
34+
# Return TextResourceContents with document metadata
35+
return [
36+
types.TextResourceContents(
37+
uri=uri,
38+
text="# README\n\nThis is a sample readme file.",
39+
mimeType="text/markdown",
40+
meta={
41+
"title": "Project README",
42+
"author": "Development Team",
43+
"lastModified": "2024-01-15T10:00:00Z",
44+
"version": "2.1.0",
45+
"language": "en",
46+
"license": "MIT",
47+
}
48+
)
49+
]
50+
51+
elif uri_str == "data://config.json":
52+
# Return JSON data with schema and validation metadata
53+
return [
54+
types.TextResourceContents(
55+
uri=uri,
56+
text='{\n "version": "1.0.0",\n "debug": false\n}',
57+
mimeType="application/json",
58+
meta={
59+
"schema": "https://example.com/schemas/config/v1.0",
60+
"validated": True,
61+
"environment": "production",
62+
"lastValidated": "2024-01-15T14:00:00Z",
63+
"checksum": "sha256:abc123...",
64+
}
65+
)
66+
]
67+
68+
elif uri_str == "image://icon.png":
69+
# Return binary data with comprehensive image metadata
70+
import base64
71+
# This is a 1x1 transparent PNG
72+
png_data = base64.b64decode(
73+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
74+
)
75+
return [
76+
types.BlobResourceContents(
77+
uri=uri,
78+
blob=base64.b64encode(png_data).decode(),
79+
mimeType="image/png",
80+
meta={
81+
"width": 1,
82+
"height": 1,
83+
"bitDepth": 8,
84+
"colorType": 6, # RGBA
85+
"compression": 0,
86+
"filter": 0,
87+
"interlace": 0,
88+
"fileSize": len(png_data),
89+
"hasAlpha": True,
90+
"generated": "2024-01-15T12:00:00Z",
91+
"generator": "Example MCP Server",
92+
}
93+
)
94+
]
95+
96+
elif uri_str == "multi://content":
97+
# Return multiple ResourceContents objects with part metadata
98+
return [
99+
types.TextResourceContents(
100+
uri=uri,
101+
text="Part 1: Introduction",
102+
mimeType="text/plain",
103+
meta={
104+
"part": 1,
105+
"title": "Introduction",
106+
"order": 1,
107+
"required": True,
108+
}
109+
),
110+
types.TextResourceContents(
111+
uri=uri,
112+
text="## Part 2: Main Content\n\nThis is the main section.",
113+
mimeType="text/markdown",
114+
meta={
115+
"part": 2,
116+
"title": "Main Content",
117+
"order": 2,
118+
"wordCount": 8,
119+
"headingLevel": 2,
120+
}
121+
),
122+
types.BlobResourceContents(
123+
uri=uri,
124+
blob="UGFydCAzOiBCaW5hcnkgRGF0YQ==", # "Part 3: Binary Data" in base64
125+
mimeType="application/octet-stream",
126+
meta={
127+
"part": 3,
128+
"title": "Binary Attachment",
129+
"order": 3,
130+
"encoding": "base64",
131+
"originalSize": 19,
132+
}
133+
),
134+
]
135+
136+
elif uri_str.startswith("code://"):
137+
# Extract language from URI for syntax highlighting
138+
language = uri_str.split("://")[1].split("/")[0]
139+
code_samples = {
140+
"python": ('def hello():\n print("Hello, World!")', "text/x-python"),
141+
"javascript": ('console.log("Hello, World!");', "text/javascript"),
142+
"html": ('<h1>Hello, World!</h1>', "text/html"),
143+
}
144+
145+
if language in code_samples:
146+
code, mime_type = code_samples[language]
147+
return [
148+
types.TextResourceContents(
149+
uri=uri,
150+
text=code,
151+
mimeType=mime_type,
152+
meta={
153+
"language": language,
154+
"syntaxHighlighting": True,
155+
"lineNumbers": True,
156+
"executable": language in ["python", "javascript"],
157+
"documentation": f"https://docs.example.com/languages/{language}",
158+
}
159+
)
160+
]
161+
162+
# Default case - resource not found
163+
return [
164+
types.TextResourceContents(
165+
uri=uri,
166+
text=f"Resource not found: {uri}",
167+
mimeType="text/plain",
168+
)
169+
]
170+
171+
172+
# List available resources
173+
@server.list_resources()
174+
async def list_resources() -> list[types.Resource]:
175+
"""List all available resources."""
176+
return [
177+
types.Resource(
178+
uri=AnyUrl("text://readme"),
179+
name="README",
180+
title="README file",
181+
description="A sample readme in markdown format",
182+
mimeType="text/markdown",
183+
),
184+
types.Resource(
185+
uri=AnyUrl("data://config.json"),
186+
name="config",
187+
title="Configuration",
188+
description="Application configuration in JSON format",
189+
mimeType="application/json",
190+
),
191+
types.Resource(
192+
uri=AnyUrl("image://icon.png"),
193+
name="icon",
194+
title="Application Icon",
195+
description="A sample PNG icon",
196+
mimeType="image/png",
197+
),
198+
types.Resource(
199+
uri=AnyUrl("multi://content"),
200+
name="multi-part",
201+
title="Multi-part Content",
202+
description="A resource that returns multiple content items",
203+
mimeType="multipart/mixed",
204+
),
205+
types.Resource(
206+
uri=AnyUrl("code://python/example"),
207+
name="python-code",
208+
title="Python Code Example",
209+
description="Sample Python code with proper MIME type",
210+
mimeType="text/x-python",
211+
),
212+
]
213+
214+
215+
# Also demonstrate with ReadResourceContents (old style) mixed in
216+
@server.list_resources()
217+
async def list_legacy_resources() -> list[types.Resource]:
218+
"""List resources that use the legacy ReadResourceContents approach."""
219+
return [
220+
types.Resource(
221+
uri=AnyUrl("legacy://text"),
222+
name="legacy-text",
223+
title="Legacy Text Resource",
224+
description="Uses ReadResourceContents wrapper",
225+
mimeType="text/plain",
226+
),
227+
]
228+
229+
230+
# Mix old and new styles to show compatibility
231+
from mcp.server.lowlevel.server import ReadResourceContents
232+
233+
234+
@server.read_resource()
235+
async def read_legacy_resource(uri: AnyUrl) -> Iterable[ReadResourceContents | types.TextResourceContents]:
236+
"""Handle legacy resources alongside new ResourceContents."""
237+
uri_str = str(uri)
238+
239+
if uri_str == "legacy://text":
240+
# Old style - return ReadResourceContents
241+
return [
242+
ReadResourceContents(
243+
content="This uses the legacy ReadResourceContents wrapper",
244+
mime_type="text/plain",
245+
)
246+
]
247+
248+
# Delegate to the new handler for other resources
249+
return await read_resource(uri)
250+
251+
252+
async def main():
253+
"""Run the server using stdio transport."""
254+
async with stdio.stdio_server() as (read_stream, write_stream):
255+
await server.run(
256+
read_stream,
257+
write_stream,
258+
server.create_initialization_options(
259+
notification_options=NotificationOptions(),
260+
experimental_capabilities={},
261+
),
262+
)
263+
264+
265+
if __name__ == "__main__":
266+
# Run with: python resource_contents_direct.py
267+
asyncio.run(main())

0 commit comments

Comments
 (0)