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 \n This 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 \n This 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