0
# Resources System
1
2
Resources expose read-only data sources with support for static resources and dynamic templates with URI parameters. The FastMCP resources system provides a clean way to expose data that LLMs can access for context and information retrieval.
3
4
## Capabilities
5
6
### Resource Classes
7
8
Base classes for creating and managing resources with different content types and access patterns.
9
10
```python { .api }
11
class Resource:
12
def __init__(
13
self,
14
uri: str,
15
name: str,
16
description: str,
17
mime_type: str | None = None
18
):
19
"""
20
Base resource class for static resources.
21
22
Parameters:
23
- uri: Resource URI for client access
24
- name: Human-readable resource name
25
- description: Resource description for LLM understanding
26
- mime_type: MIME type of resource content
27
"""
28
29
class FunctionResource(Resource):
30
"""Resource implementation for function-based resources."""
31
32
class TextResource:
33
"""Resource for text-based content."""
34
35
class BinaryResource:
36
"""Resource for binary content (images, files, etc.)."""
37
38
class FileResource:
39
"""Resource that serves file content from filesystem."""
40
41
class HttpResource:
42
"""Resource that proxies HTTP requests to external URLs."""
43
44
class DirectoryResource:
45
"""Resource that serves directory listings or file content."""
46
```
47
48
### Resource Templates
49
50
Dynamic resources that accept URI parameters for flexible data access.
51
52
```python { .api }
53
class ResourceTemplate:
54
def __init__(
55
self,
56
uri_template: str,
57
name: str,
58
description: str,
59
mime_type: str | None = None
60
):
61
"""
62
Resource template for dynamic resources with URI parameters.
63
64
Parameters:
65
- uri_template: URI template with {parameters} placeholders
66
- name: Human-readable template name
67
- description: Template description for LLM understanding
68
- mime_type: MIME type of resource content
69
"""
70
```
71
72
### Resource Manager
73
74
Manages resource registration, templates, and resolution within a FastMCP server.
75
76
```python { .api }
77
class ResourceManager:
78
def add_resource(self, resource: Resource) -> None:
79
"""
80
Add a static resource to the manager.
81
82
Parameters:
83
- resource: Resource instance to add
84
"""
85
86
def add_template(self, template: ResourceTemplate) -> None:
87
"""
88
Add a resource template for dynamic resources.
89
90
Parameters:
91
- template: ResourceTemplate instance to add
92
"""
93
94
def get_resource(self, uri: str) -> Resource | None:
95
"""
96
Get a resource by URI.
97
98
Parameters:
99
- uri: Resource URI to retrieve
100
101
Returns:
102
Resource instance or None if not found
103
"""
104
105
def list_resources(self) -> list[Resource]:
106
"""
107
List all registered static resources.
108
109
Returns:
110
List of all resource instances
111
"""
112
113
def list_templates(self) -> list[ResourceTemplate]:
114
"""
115
List all registered resource templates.
116
117
Returns:
118
List of all resource template instances
119
"""
120
```
121
122
## Usage Examples
123
124
### Basic Static Resources
125
126
```python
127
from fastmcp import FastMCP
128
129
mcp = FastMCP("Info Server")
130
131
@mcp.resource("config://version")
132
def get_version():
133
"""Get the current server version."""
134
return "1.0.0"
135
136
@mcp.resource("config://settings")
137
def get_settings():
138
"""Get server configuration settings."""
139
return {
140
"debug": True,
141
"max_connections": 100,
142
"timeout": 30
143
}
144
145
@mcp.resource("info://status")
146
def get_status():
147
"""Get current server status."""
148
import psutil
149
import time
150
151
return {
152
"uptime": time.time() - mcp.start_time,
153
"memory_usage": psutil.virtual_memory().percent,
154
"cpu_usage": psutil.cpu_percent(),
155
"active_connections": len(mcp.connections)
156
}
157
```
158
159
### Dynamic Resource Templates
160
161
```python
162
from fastmcp import FastMCP
163
164
mcp = FastMCP("Data Server")
165
166
@mcp.resource("users://{user_id}/profile")
167
def get_user_profile(user_id: int):
168
"""
169
Get user profile information by ID.
170
171
Parameters:
172
- user_id: Unique user identifier
173
174
Returns:
175
User profile data as JSON
176
"""
177
# This would typically query a database
178
users_db = {
179
1: {"name": "Alice", "email": "alice@example.com", "status": "active"},
180
2: {"name": "Bob", "email": "bob@example.com", "status": "inactive"},
181
3: {"name": "Charlie", "email": "charlie@example.com", "status": "active"}
182
}
183
184
user = users_db.get(user_id)
185
if not user:
186
raise ValueError(f"User {user_id} not found")
187
188
return {
189
"user_id": user_id,
190
**user,
191
"last_updated": "2024-01-01T00:00:00Z"
192
}
193
194
@mcp.resource("data://{dataset}/{table}")
195
def get_table_data(dataset: str, table: str):
196
"""
197
Get data from a specific dataset table.
198
199
Parameters:
200
- dataset: Dataset name
201
- table: Table name within dataset
202
203
Returns:
204
Table data as JSON
205
"""
206
# Validate dataset and table
207
valid_datasets = ["sales", "marketing", "finance"]
208
if dataset not in valid_datasets:
209
raise ValueError(f"Invalid dataset: {dataset}")
210
211
# Mock data structure
212
data = {
213
"sales": {
214
"orders": [
215
{"id": 1, "amount": 100.0, "date": "2024-01-01"},
216
{"id": 2, "amount": 250.0, "date": "2024-01-02"}
217
],
218
"customers": [
219
{"id": 1, "name": "Customer A", "region": "North"},
220
{"id": 2, "name": "Customer B", "region": "South"}
221
]
222
},
223
"marketing": {
224
"campaigns": [
225
{"id": 1, "name": "Q1 Campaign", "budget": 10000},
226
{"id": 2, "name": "Q2 Campaign", "budget": 15000}
227
]
228
}
229
}
230
231
if table not in data.get(dataset, {}):
232
raise ValueError(f"Table {table} not found in dataset {dataset}")
233
234
return {
235
"dataset": dataset,
236
"table": table,
237
"data": data[dataset][table],
238
"record_count": len(data[dataset][table])
239
}
240
241
@mcp.resource("files://{path}")
242
def get_file_content(path: str):
243
"""
244
Get file content by path (with security restrictions).
245
246
Parameters:
247
- path: File path relative to allowed directory
248
249
Returns:
250
File content as text or binary data
251
"""
252
import os
253
from pathlib import Path
254
255
# Security: restrict to allowed directory
256
allowed_dir = Path("./data")
257
safe_path = allowed_dir / path
258
259
# Prevent directory traversal
260
if not safe_path.resolve().is_relative_to(allowed_dir.resolve()):
261
raise ValueError("Access denied: path outside allowed directory")
262
263
if not safe_path.exists():
264
raise ValueError(f"File not found: {path}")
265
266
if safe_path.is_dir():
267
# Return directory listing
268
files = [f.name for f in safe_path.iterdir() if f.is_file()]
269
dirs = [d.name for d in safe_path.iterdir() if d.is_dir()]
270
return {
271
"type": "directory",
272
"path": path,
273
"files": files,
274
"directories": dirs
275
}
276
277
# Return file content
278
try:
279
with open(safe_path, 'r', encoding='utf-8') as f:
280
content = f.read()
281
return {
282
"type": "text",
283
"path": path,
284
"content": content,
285
"size": len(content)
286
}
287
except UnicodeDecodeError:
288
# Binary file
289
with open(safe_path, 'rb') as f:
290
content = f.read()
291
return {
292
"type": "binary",
293
"path": path,
294
"size": len(content),
295
"content_base64": content.hex() # Or base64.b64encode(content).decode()
296
}
297
```
298
299
### Complex Resource with Context
300
301
```python
302
from fastmcp import FastMCP, Context
303
import json
304
305
mcp = FastMCP("API Proxy Server")
306
307
@mcp.resource("api://{service}/{endpoint}")
308
async def proxy_api_request(service: str, endpoint: str, ctx: Context):
309
"""
310
Proxy requests to external APIs with authentication and logging.
311
312
Parameters:
313
- service: Service name (maps to API base URL)
314
- endpoint: API endpoint path
315
- ctx: Execution context for HTTP requests and logging
316
317
Returns:
318
API response data
319
"""
320
# Service URL mapping
321
service_urls = {
322
"weather": "https://api.weather.com/v1",
323
"news": "https://api.news.com/v2",
324
"stocks": "https://api.stocks.com/v1"
325
}
326
327
if service not in service_urls:
328
await ctx.error(f"Unknown service: {service}")
329
raise ValueError(f"Service {service} not supported")
330
331
base_url = service_urls[service]
332
full_url = f"{base_url}/{endpoint}"
333
334
await ctx.info(f"Proxying request to {service}: {endpoint}")
335
336
try:
337
# Make HTTP request with authentication
338
headers = {
339
"Authorization": f"Bearer {get_api_key(service)}",
340
"User-Agent": "FastMCP-Proxy/1.0"
341
}
342
343
response = await ctx.http_request(
344
method="GET",
345
url=full_url,
346
headers=headers
347
)
348
349
if response.status_code == 200:
350
await ctx.info(f"Successfully fetched data from {service}")
351
data = response.json()
352
353
return {
354
"service": service,
355
"endpoint": endpoint,
356
"data": data,
357
"timestamp": "2024-01-01T00:00:00Z",
358
"status": "success"
359
}
360
else:
361
await ctx.error(f"API request failed: {response.status_code}")
362
return {
363
"service": service,
364
"endpoint": endpoint,
365
"error": f"HTTP {response.status_code}",
366
"status": "error"
367
}
368
369
except Exception as e:
370
await ctx.error(f"Request failed: {str(e)}")
371
return {
372
"service": service,
373
"endpoint": endpoint,
374
"error": str(e),
375
"status": "error"
376
}
377
378
def get_api_key(service: str) -> str:
379
"""Get API key for service (would load from secure config)."""
380
api_keys = {
381
"weather": "weather_api_key_here",
382
"news": "news_api_key_here",
383
"stocks": "stocks_api_key_here"
384
}
385
return api_keys.get(service, "")
386
```
387
388
### File-Based Resources
389
390
```python
391
from fastmcp import FastMCP
392
from fastmcp.utilities.types import Image, File
393
import mimetypes
394
from pathlib import Path
395
396
mcp = FastMCP("File Server")
397
398
@mcp.resource("docs://{filename}")
399
def get_document(filename: str):
400
"""
401
Serve documentation files.
402
403
Parameters:
404
- filename: Name of documentation file
405
406
Returns:
407
Document content with appropriate MIME type
408
"""
409
docs_dir = Path("./docs")
410
file_path = docs_dir / filename
411
412
# Security check
413
if not file_path.resolve().is_relative_to(docs_dir.resolve()):
414
raise ValueError("Access denied")
415
416
if not file_path.exists():
417
raise ValueError(f"Document not found: {filename}")
418
419
mime_type, _ = mimetypes.guess_type(str(file_path))
420
421
with open(file_path, 'r', encoding='utf-8') as f:
422
content = f.read()
423
424
return {
425
"filename": filename,
426
"content": content,
427
"mime_type": mime_type or "text/plain",
428
"size": len(content)
429
}
430
431
@mcp.resource("images://{image_name}")
432
def get_image(image_name: str):
433
"""
434
Serve image files as Image objects.
435
436
Parameters:
437
- image_name: Name of image file
438
439
Returns:
440
Image object with binary data
441
"""
442
images_dir = Path("./images")
443
image_path = images_dir / image_name
444
445
if not image_path.exists():
446
raise ValueError(f"Image not found: {image_name}")
447
448
with open(image_path, 'rb') as f:
449
image_data = f.read()
450
451
mime_type, _ = mimetypes.guess_type(str(image_path))
452
453
return Image(
454
data=image_data,
455
mime_type=mime_type or "application/octet-stream"
456
)
457
458
@mcp.resource("downloads://{file_name}")
459
def get_download_file(file_name: str):
460
"""
461
Serve downloadable files as File objects.
462
463
Parameters:
464
- file_name: Name of file to download
465
466
Returns:
467
File object with metadata
468
"""
469
downloads_dir = Path("./downloads")
470
file_path = downloads_dir / file_name
471
472
if not file_path.exists():
473
raise ValueError(f"File not found: {file_name}")
474
475
mime_type, _ = mimetypes.guess_type(str(file_path))
476
477
if mime_type and mime_type.startswith('text/'):
478
# Text file
479
with open(file_path, 'r', encoding='utf-8') as f:
480
content = f.read()
481
else:
482
# Binary file
483
with open(file_path, 'rb') as f:
484
content = f.read()
485
486
return File(
487
data=content,
488
name=file_name,
489
mime_type=mime_type
490
)
491
```
492
493
### Database Resources
494
495
```python
496
from fastmcp import FastMCP
497
import sqlite3
498
import json
499
500
mcp = FastMCP("Database Server")
501
502
@mcp.resource("db://{table}")
503
def get_table_contents(table: str):
504
"""
505
Get contents of a database table.
506
507
Parameters:
508
- table: Name of database table
509
510
Returns:
511
Table data as JSON with metadata
512
"""
513
# Whitelist allowed tables for security
514
allowed_tables = ["users", "orders", "products", "categories"]
515
if table not in allowed_tables:
516
raise ValueError(f"Access to table '{table}' not allowed")
517
518
conn = sqlite3.connect("database.db")
519
conn.row_factory = sqlite3.Row # Enable column access by name
520
cursor = conn.cursor()
521
522
try:
523
# Get table schema
524
cursor.execute(f"PRAGMA table_info({table})")
525
columns = [row['name'] for row in cursor.fetchall()]
526
527
# Get table data
528
cursor.execute(f"SELECT * FROM {table} LIMIT 100") # Limit for safety
529
rows = cursor.fetchall()
530
531
# Convert to list of dictionaries
532
data = [dict(row) for row in rows]
533
534
return {
535
"table": table,
536
"columns": columns,
537
"row_count": len(data),
538
"data": data,
539
"query_timestamp": "2024-01-01T00:00:00Z"
540
}
541
542
except sqlite3.Error as e:
543
raise ValueError(f"Database error: {str(e)}")
544
finally:
545
conn.close()
546
547
@mcp.resource("db://query/{query_name}")
548
def get_predefined_query(query_name: str):
549
"""
550
Execute predefined database queries.
551
552
Parameters:
553
- query_name: Name of predefined query
554
555
Returns:
556
Query results as JSON
557
"""
558
# Predefined queries for security
559
queries = {
560
"user_stats": "SELECT COUNT(*) as total_users, COUNT(CASE WHEN status='active' THEN 1 END) as active_users FROM users",
561
"recent_orders": "SELECT * FROM orders WHERE created_at > datetime('now', '-7 days') ORDER BY created_at DESC",
562
"top_products": "SELECT p.*, COUNT(o.product_id) as order_count FROM products p LEFT JOIN orders o ON p.id = o.product_id GROUP BY p.id ORDER BY order_count DESC LIMIT 10"
563
}
564
565
if query_name not in queries:
566
raise ValueError(f"Query '{query_name}' not found")
567
568
conn = sqlite3.connect("database.db")
569
conn.row_factory = sqlite3.Row
570
cursor = conn.cursor()
571
572
try:
573
cursor.execute(queries[query_name])
574
rows = cursor.fetchall()
575
data = [dict(row) for row in rows]
576
577
return {
578
"query_name": query_name,
579
"sql": queries[query_name],
580
"result_count": len(data),
581
"data": data,
582
"executed_at": "2024-01-01T00:00:00Z"
583
}
584
585
except sqlite3.Error as e:
586
raise ValueError(f"Query error: {str(e)}")
587
finally:
588
conn.close()
589
```
590
591
## Resource Content Types
592
593
Resources can return various content types:
594
595
```python
596
# Text content
597
@mcp.resource("text://example")
598
def text_resource():
599
return "Plain text content"
600
601
# JSON data
602
@mcp.resource("json://example")
603
def json_resource():
604
return {"key": "value", "numbers": [1, 2, 3]}
605
606
# Binary data as Image
607
@mcp.resource("image://example")
608
def image_resource():
609
with open("example.png", "rb") as f:
610
return Image(f.read(), mime_type="image/png")
611
612
# Binary data as File
613
@mcp.resource("file://example")
614
def file_resource():
615
return File(
616
data=b"binary content",
617
name="example.bin",
618
mime_type="application/octet-stream"
619
)
620
```