Implementing Resources
Resources expose data to LLMs in a read-only manner. Think of them as GET endpoints that provide access to files, databases, APIs, or any other data source.
Resource Fundamentals
Resources in MCP are identified by URIs and can be either static (fixed content) or dynamic (generated on-demand). They're perfect for giving LLMs access to documentation, configuration files, database records, or API responses.
Basic Resource Structure
// Create a simple resource
resource := mcp.NewResource(
"docs://readme", // URI - unique identifier
"Project README", // Name - human-readable
mcp.WithResourceDescription("Main project documentation"),
mcp.WithMIMEType("text/markdown"),
)
Static Resources
Static resources have fixed URIs and typically serve predetermined content.
File-Based Resources
Expose files from your filesystem:
func main() {
s := server.NewMCPServer("File Server", "1.0.0",
server.WithResourceCapabilities(true),
)
// Add a static file resource
s.AddResource(
mcp.NewResource(
"file://README.md",
"Project README",
mcp.WithResourceDescription("Main project documentation"),
mcp.WithMIMEType("text/markdown"),
),
handleReadmeFile,
)
server.ServeStdio(s)
}
func handleReadmeFile(ctx context.Context, req mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
content, err := os.ReadFile("README.md")
if err != nil {
return nil, fmt.Errorf("failed to read README: %w", err)
}
return &mcp.ReadResourceResult{
Contents: []mcp.ResourceContent{
{
URI: req.Params.URI,
MIMEType: "text/markdown",
Text: string(content),
},
},
}, nil
}
Configuration Resources
Expose application configuration:
// Configuration resource
s.AddResource(
mcp.NewResource(
"config://app",
"Application Configuration",
mcp.WithResourceDescription("Current application settings"),
mcp.WithMIMEType("application/json"),
),
handleAppConfig,
)
func handleAppConfig(ctx context.Context, req mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
config := map[string]interface{}{
"database_url": os.Getenv("DATABASE_URL"),
"debug_mode": os.Getenv("DEBUG") == "true",
"version": "1.0.0",
"features": []string{
"authentication",
"caching",
"logging",
},
}
configJSON, err := json.Marshal(config)
if err != nil {
return nil, err
}
return &mcp.ReadResourceResult{
Contents: []mcp.ResourceContent{
mcp.TextResourceContent{
URI: req.Params.URI,
MIMEType: "application/json",
Text: string(configJSON),
},
},
}, nil
}
Dynamic Resources
Dynamic resources use URI templates with parameters, allowing for flexible, parameterized access to data.
URI Templates
Use {parameter}
syntax for dynamic parts:
// User profile resource with dynamic user ID
s.AddResource(
mcp.NewResource(
"users://{user_id}",
"User Profile",
mcp.WithResourceDescription("User profile information"),
mcp.WithMIMEType("application/json"),
),
handleUserProfile,
)
func handleUserProfile(ctx context.Context, req mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
// Extract user_id from URI
userID := extractUserID(req.Params.URI) // "users://123" -> "123"
// Fetch user data (from database, API, etc.)
user, err := getUserFromDB(userID)
if err != nil {
return nil, fmt.Errorf("user not found: %w", err)
}
jsonData, err := json.Marshal(user)
if err != nil {
return nil, err
}
return []mcp.ResourceContents{
mcp.TextResourceContents{
URI: req.Params.URI,
MIMEType: "application/json",
Text: string(jsonData),
},
}, nil
}
func extractUserID(uri string) string {
// Extract ID from "users://123" format
parts := strings.Split(uri, "://")
if len(parts) == 2 {
return parts[1]
}
return ""
}
Database Resources
Expose database records dynamically:
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
// Database table resource
s.AddResource(
mcp.NewResource(
"db://{table}/{id}",
"Database Record",
mcp.WithResourceDescription("Access database records by table and ID"),
mcp.WithMIMEType("application/json"),
),
handleDatabaseRecord,
)
func handleDatabaseRecord(ctx context.Context, req mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
table, id := parseDBURI(req.Params.URI) // "db://users/123" -> "users", "123"
// Validate table name for security
allowedTables := map[string]bool{
"users": true,
"products": true,
"orders": true,
}
if !allowedTables[table] {
return nil, fmt.Errorf("table not accessible: %s", table)
}
// Query database
query := fmt.Sprintf("SELECT * FROM %s WHERE id = ?", table)
row := db.QueryRowContext(ctx, query, id)
var data map[string]interface{}
if err := scanRowToMap(row, &data); err != nil {
return nil, fmt.Errorf("record not found: %w", err)
}
jsonData, err := json.Marshal(data)
if err != nil {
return nil, err
}
return &mcp.ReadResourceResult{
Contents: []mcp.ResourceContent{
mcp.TextResourceContent{
URI: req.Params.URI,
MIMEType: "application/json",
Text: string(jsonData),
},
},
}, nil
}
}
API Resources
Proxy external APIs through resources:
// Weather API resource
s.AddResource(
mcp.NewResource(
"weather://{location}",
"Weather Data",
mcp.WithResourceDescription("Current weather for a location"),
mcp.WithMIMEType("application/json"),
),
handleWeatherData,
)
func handleWeatherData(ctx context.Context, req mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
location := extractLocation(req.Params.URI)
// Call external weather API
apiURL := fmt.Sprintf("https://api.weather.com/v1/current?location=%s&key=%s",
url.QueryEscape(location), os.Getenv("WEATHER_API_KEY"))
resp, err := http.Get(apiURL)
if err != nil {
return nil, fmt.Errorf("weather API error: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
return &mcp.ReadResourceResult{
Contents: []mcp.ResourceContent{
{
URI: req.Params.URI,
MIMEType: "application/json",
Text: string(body),
},
},
}, nil
}
Content Types
Resources can serve different types of content with appropriate MIME types.
Text Content
func handleTextResource(ctx context.Context, req mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
content := "This is plain text content"
return &mcp.ReadResourceResult{
Contents: []mcp.ResourceContent{
{
URI: req.Params.URI,
MIMEType: "text/plain",
Text: content,
},
},
}, nil
}
JSON Content
func handleJSONResource(ctx context.Context, req mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
data := map[string]interface{}{
"message": "Hello, World!",
"timestamp": time.Now().Unix(),
"status": "success",
}
jsonData, err := json.Marshal(data)
if err != nil {
return nil, err
}
return []mcp.ResourceContents{
mcp.TextResourceContents{
URI: req.Params.URI,
MIMEType: "application/json",
Text: string(jsonData),
},
}, nil
}
Binary Content
func handleImageResource(ctx context.Context, req mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
imageData, err := os.ReadFile("logo.png")
if err != nil {
return nil, err
}
// Encode binary data as base64
encoded := base64.StdEncoding.EncodeToString(imageData)
return &mcp.ReadResourceResult{
Contents: []mcp.ResourceContent{
{
URI: req.Params.URI,
MIMEType: "image/png",
Blob: encoded,
},
},
}, nil
}
Multiple Content Types
A single resource can return multiple content representations:
func handleMultiFormatResource(ctx context.Context, req mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
data := map[string]interface{}{
"name": "John Doe",
"age": 30,
"city": "New York",
}
// JSON representation
jsonData, _ := json.Marshal(data)
// Text representation
textData := fmt.Sprintf("Name: %s\nAge: %d\nCity: %s",
data["name"], data["age"], data["city"])
return &mcp.ReadResourceResult{
Contents: []mcp.ResourceContent{
{
URI: req.Params.URI,
MIMEType: "application/json",
Text: string(jsonData),
},
{
URI: req.Params.URI,
MIMEType: "text/plain",
Text: textData,
},
},
}, nil
}
Error Handling
Proper error handling ensures robust resource access:
Common Error Patterns
func handleResourceWithErrors(ctx context.Context, req mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
// Validate URI format
if !isValidURI(req.Params.URI) {
return nil, fmt.Errorf("invalid URI format: %s", req.Params.URI)
}
// Check permissions
if !hasPermission(ctx, req.Params.URI) {
return nil, fmt.Errorf("access denied to resource: %s", req.Params.URI)
}
// Handle resource not found
data, err := fetchResourceData(req.Params.URI)
if err != nil {
if errors.Is(err, ErrResourceNotFound) {
return nil, fmt.Errorf("resource not found: %s", req.Params.URI)
}
return nil, fmt.Errorf("failed to fetch resource: %w", err)
}
jsonData, err := json.Marshal(data)
if err != nil {
return nil, err
}
return &mcp.ReadResourceResult{
Contents: []mcp.ResourceContents{
mcp.TextResourceContents{
URI: req.Params.URI,
MIMEType: "application/json",
Text: string(jsonData),
},
},
}, nil
}
Timeout Handling
func handleResourceWithTimeout(ctx context.Context, req mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
// Create timeout context
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
// Use context in operations
data, err := fetchDataWithContext(ctx, req.Params.URI)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, fmt.Errorf("resource fetch timeout: %s", req.Params.URI)
}
return nil, err
}
jsonData, err := json.Marshal(data)
if err != nil {
return nil, err
}
return &mcp.ReadResourceResult{
Contents: []mcp.ResourceContent{
mcp.TextResourceContent{
URI: req.Params.URI,
MIMEType: "application/json",
Text: string(jsonData),
},
},
}, nil
}
}
Resource Listing
Implement resource discovery for clients:
func main() {
s := server.NewMCPServer("Resource Server", "1.0.0",
server.WithResourceCapabilities(true),
)
// Add multiple resources
resources := []struct {
uri string
name string
description string
mimeType string
handler server.ResourceHandler
}{
{"docs://readme", "README", "Project documentation", "text/markdown", handleReadme},
{"config://app", "App Config", "Application settings", "application/json", handleConfig},
{"users://{id}", "User Profile", "User information", "application/json", handleUser},
}
for _, r := range resources {
s.AddResource(
mcp.NewResource(r.uri, r.name,
mcp.WithResourceDescription(r.description),
mcp.WithMIMEType(r.mimeType),
),
r.handler,
)
}
server.ServeStdio(s)
}
Caching Resources
Implement caching for expensive resources:
type CachedResourceHandler struct {
cache map[string]cacheEntry
mutex sync.RWMutex
ttl time.Duration
}
type cacheEntry struct {
data *mcp.ReadResourceResult
timestamp time.Time
}
func (h *CachedResourceHandler) HandleResource(ctx context.Context, req mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
h.mutex.RLock()
if entry, exists := h.cache[req.Params.URI]; exists {
if time.Since(entry.timestamp) < h.ttl {
h.mutex.RUnlock()
return entry.data, nil
}
}
h.mutex.RUnlock()
// Fetch fresh data
data, err := h.fetchFreshData(ctx, req)
if err != nil {
return nil, err
}
// Cache the result
h.mutex.Lock()
h.cache[req.Params.URI] = cacheEntry{
data: data,
timestamp: time.Now(),
}
h.mutex.Unlock()
return data, nil
}
Next Steps
- Tools - Learn to implement interactive functionality
- Prompts - Create reusable interaction templates
- Advanced Features - Explore hooks, middleware, and more