Skip to main content
Back to Guides

Resources and Prompts in MCP

Beyond tools: understanding how MCP servers can expose data and pre-built prompts to AI models.

Alex Rivera
Updated January 13, 2025
11 min read

While tools are the most commonly used MCP feature, Resources and Prompts offer powerful additional capabilities. Understanding all three primitives helps you build more effective MCP integrations that provide richer context and better user experiences.

1. The Three MCP Primitives

Every MCP server can expose three types of capabilities:

  • Tools: Actions the AI can perform (create, update, delete, query)
  • Resources: Data the AI can read (files, configs, application state)
  • Prompts: Reusable prompt templates for common tasks

Think of it This Way

Tools are verbs (do something). Resources are nouns (read something). Prompts are templates (standardized ways to ask things).

2. Resources: Exposing Data

Resources give AI models read access to data. Unlike tools, resources are passive—they don't perform actions, just return information. This makes them ideal for providing context.

Use Cases for Resources

  • Configuration files: Let the AI understand your project settings
  • Documentation: Expose README files or API docs
  • Application state: Share current status or cached data
  • Database schemas: Help the AI understand your data structure
  • Environment info: System details, versions, capabilities

Defining Resources (TypeScript)

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { ListResourcesRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";

const server = new Server({ name: "my-server", version: "1.0.0" }, {
  capabilities: { resources: {} }
});

// List available resources
server.setRequestHandler(ListResourcesRequestSchema, async () => {
  return {
    resources: [
      {
        uri: "config://app/settings",
        name: "Application Settings",
        description: "Current application configuration",
        mimeType: "application/json"
      },
      {
        uri: "schema://database/users",
        name: "Users Table Schema",
        description: "Schema definition for the users table",
        mimeType: "application/json"
      }
    ]
  };
});

// Handle resource reads
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const { uri } = request.params;
  
  if (uri === "config://app/settings") {
    return {
      contents: [{
        uri,
        mimeType: "application/json",
        text: JSON.stringify({
          debug: true,
          maxItems: 100,
          apiVersion: "2.0"
        }, null, 2)
      }]
    };
  }
  
  throw new Error(`Unknown resource: ${uri}`);
});

Defining Resources (Python)

from mcp.server import Server

app = Server("my-server")

@app.resource("config://settings")
async def get_settings() -> str:
    """Current application settings"""
    return """{
    "debug": true,
    "maxItems": 100,
    "apiVersion": "2.0"
}"""

@app.resource("schema://database")
async def get_schema() -> str:
    """Database schema definition"""
    schema = await db.get_schema()
    return json.dumps(schema, indent=2)

3. Resource URIs and Templates

Resources are identified by URIs. You can use custom schemes for your domain:

  • file:///path/to/file.txt — Local files
  • config://app/settings — Application configuration
  • db://schema/users — Database schemas
  • git://repo/branch/HEAD — Git resources
  • api://docs/endpoints — API documentation

Dynamic Resources with Templates

Resources can include templates for dynamic content:

// Resource template with parameters
server.setRequestHandler(ListResourcesRequestSchema, async () => {
  return {
    resources: [
      {
        uri: "user://{userId}/profile",
        name: "User Profile",
        description: "Profile information for a specific user",
        mimeType: "application/json"
      }
    ]
  };
});

server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const { uri } = request.params;
  
  // Parse the URI to extract userId
  const match = uri.match(/^user:\/\/(.+)\/profile$/);
  if (match) {
    const userId = match[1];
    const profile = await users.getProfile(userId);
    return {
      contents: [{
        uri,
        mimeType: "application/json",
        text: JSON.stringify(profile, null, 2)
      }]
    };
  }
  
  throw new Error(`Unknown resource: ${uri}`);
});

4. Prompts: Standardized Templates

Prompts are pre-defined message templates that help users interact with your server more effectively. They ensure consistent, well-structured requests.

Why Use Prompts?

  • Consistency: Ensure requests follow a standard format
  • Guidance: Help users understand how to use complex tools
  • Error reduction: Reduce poorly formed queries
  • Domain expertise: Encode best practices in templates

Defining Prompts

import { ListPromptsRequestSchema, GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js";

server.setRequestHandler(ListPromptsRequestSchema, async () => {
  return {
    prompts: [
      {
        name: "analyze-table",
        description: "Analyze a database table and suggest optimizations",
        arguments: [
          {
            name: "tableName",
            description: "Name of the table to analyze",
            required: true
          }
        ]
      },
      {
        name: "code-review",
        description: "Review code for bugs, style issues, and improvements",
        arguments: [
          {
            name: "file",
            description: "Path to the file to review",
            required: true
          },
          {
            name: "focus",
            description: "What to focus on (security, performance, style)",
            required: false
          }
        ]
      }
    ]
  };
});

server.setRequestHandler(GetPromptRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;
  
  if (name === "analyze-table") {
    const tableName = args?.tableName;
    return {
      messages: [{
        role: "user",
        content: {
          type: "text",
          text: `Please analyze the "${tableName}" table in my database.

Look at:
1. The table schema and column types
2. Current indexes and their usage
3. Row count and approximate size
4. Query patterns if available

Suggest any optimizations for:
- Index improvements
- Schema changes
- Partitioning strategies
- Query optimization`
        }
      }]
    };
  }
  
  throw new Error(`Unknown prompt: ${name}`);
});

Prompts Include Context

Prompts can automatically include resources or fetch data to provide rich context. This makes them more powerful than simple string templates—they can pull in relevant information dynamically.

5. Multi-Message Prompts

Prompts can include multiple messages to set up a conversation:

server.setRequestHandler(GetPromptRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;
  
  if (name === "code-review") {
    const filePath = args?.file;
    const focus = args?.focus || "general";
    
    // Read the file content
    const contents = await fs.readFile(filePath, "utf-8");
    
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: `I need you to review this code file: ${filePath}

\`\`\`
${contents}
\`\`\`

Focus area: ${focus}`
          }
        },
        {
          role: "assistant",
          content: {
            type: "text",
            text: "I'll review this code thoroughly, focusing on " + focus + ". Let me analyze it section by section."
          }
        },
        {
          role: "user",
          content: {
            type: "text",
            text: "Please start with the most critical issues first."
          }
        }
      ]
    };
  }
});

6. Combining All Three

The most powerful MCP servers use all three primitives together:

Example: Database Server

TypeExamples
ResourcesSchema definitions, table statistics, index info, query history
ToolsRun query, create table, add index, backup database
PromptsOptimize table, debug slow query, design schema

Complete Example: Git Server

const server = new Server(
  { name: "git-server", version: "1.0.0" },
  { capabilities: { tools: {}, resources: {}, prompts: {} } }
);

// RESOURCES: Read-only data
// - git://status - Current repo status
// - git://log/{count} - Recent commits
// - git://diff/{ref} - Diff for a reference
// - git://branches - List of branches

// TOOLS: Actions
// - commit(message, files) - Create a commit
// - checkout(branch) - Switch branches
// - merge(branch) - Merge a branch
// - push(remote, branch) - Push changes

// PROMPTS: Guided workflows
// - "write-commit-message" - Generate commit message from staged changes
// - "review-pr" - Review a pull request
// - "resolve-conflict" - Help resolve merge conflicts

7. Best Practices

Guidelines for Each Primitive

  • Resources: Use for context the AI needs to make decisions. Keep them fast—they may be fetched frequently.
  • Tools: Use for anything that changes state or performs actions. Include clear descriptions.
  • Prompts: Use to guide users toward effective interactions. Encode domain expertise.

Resource Best Practices

  • Cache expensive resources when appropriate
  • Use meaningful URI schemes that reflect your domain
  • Include accurate mimeType for proper rendering
  • Keep resource responses reasonably sized

Prompt Best Practices

  • Make prompts specific and actionable
  • Include all necessary context in the prompt
  • Use multi-message prompts for complex workflows
  • Document what each prompt is designed to accomplish

8. Real-World Examples

Documentation Server

// Resources: Expose documentation
@app.resource("docs://api/{endpoint}")
async def get_api_docs(endpoint: str) -> str:
    """API documentation for a specific endpoint"""
    return await load_docs(endpoint)

// Tools: Search and navigate
@app.tool()
async def search_docs(query: str) -> str:
    """Search documentation for a topic"""
    results = await search_index(query)
    return format_results(results)

// Prompts: Guided help
@app.prompt("explain-endpoint")
async def explain_endpoint(endpoint: str) -> list:
    """Get a detailed explanation of an API endpoint"""
    docs = await load_docs(endpoint)
    return [{
        "role": "user",
        "content": f"Explain this API endpoint in detail:\n{docs}"
    }]

Conclusion

Resources and Prompts complement Tools to create a complete AI integration. Resources provide the context AI needs to understand your system, while Prompts guide users toward effective interactions. Master all three to build truly powerful MCP servers that provide rich, contextual experiences.

Needs Review

Last updated

January 10, 2025

376 days ago

This content may be outdated

This content may contain outdated information. Please verify details before use.