MCP

Intent Surfaces

A reference manual for people who design and build MCP (Model Context Protocol) ecosystems

MCP

Intent Surfaces

A reference manual for people who design and build MCP (Model Context Protocol) ecosystems

MCP

Intent Surfaces

A reference manual for people who design and build MCP (Model Context Protocol) ecosystems

The Three Sacred Objects

The Three Sacred Objects

The Three Sacred Objects

Tools, Resources, and Prompts walk into a bar • Understanding the trinity of MCP capabilities • How each piece plays its part in the grand orchestra

Tools, Resources, and Prompts walk into a bar • Understanding the trinity of MCP capabilities • How each piece plays its part in the grand orchestra

Tools, Resources, and Prompts walk into a bar • Understanding the trinity of MCP capabilities • How each piece plays its part in the grand orchestra

The Periodic Table of AI Interaction

The Periodic Table of AI Interaction

Every breakthrough in science starts with classification. Mendeleev didn't discover elements he discovered that elements had patterns. Darwin didn't discover species he discovered that species had relationships.

Every breakthrough in science starts with classification. Mendeleev didn't discover elements he discovered that elements had patterns. Darwin didn't discover species he discovered that species had relationships.

Anthropic didn't invent AI interactions they discovered that every interaction falls into exactly three categories:

Anthropic didn't invent AI interactions they discovered that every interaction falls into exactly three categories:

Resources

Things AI can observe

(READ)

Tools

Things AI can manipulate (WRITE/EXECUTE

Prompt

Things humans can invoke (TEMPLATES)

API Call

API Call

That's it. Every API call, every integration, every "Can AI do X?" question they all reduce to these three primitives. It's chemistry-level fundamental.

That's it. Every API call, every integration, every "Can AI do X?" question they all reduce to these three primitives. It's chemistry-level fundamental.

api_setup
1
2
3
4
5
// The entire universe of AI interaction in 3line

type Resource = { read: () => Data }
type Tool = { execute: (params) => Result }
type Prompt = { template: string, args: Schema }

Resources: The Observable Universe

Resources: The Observable Universe

Resources are MCP's way of saying

Resources are MCP's way of saying

"look, but don't touch."

"look, but don't touch."

They're the read-only windows into your world files, databases, APIs, anything that has state you want to observe


The genius? Resources use URIs the same concept that makes the web work. Your AI doesn't need to know how to read Postgres vs. Notion vs. files. It just needs to know how to ask for a URI.


Here's what declaring a resource actually looks like

They're the read-only windows into your world files, databases, APIs, anything that has state you want to observe


The genius? Resources use URIs the same concept that makes the web work. Your AI doesn't need to know how to read Postgres vs. Notion vs. files. It just needs to know how to ask for a URI.


Here's what declaring a resource actually looks like

Initialize Resources

Initialize Resources

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
  "jsonrpc": "2.0",
  "method": "resources/list",
  "result": {
    "resources": [
      {
        "uri": "file:///workspace/src",
        "name": "Source Code",
        "mimeType": "text/x-directory",
        "description": "Project source files"
      },
      {
        "uri": "postgres://analytics/customers",
        "name": "Customer Data", 
        "mimeType": "application/x-postgres-table",
        "description": "Read-only customer analytics"
      },
      {
        "uri": "notion://pages/roadmap",
        "name": "Product Roadmap",
        "mimeType": "text/markdown", 
        "description": "Current quarter planning"
      }
    ]
  }
}

Real Implementation: File System Resource

Real Implementation: File System Resource

File System

File System

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class FileSystemResource implements MCPResource {
  async read(uri: string): Promise<ResourceContent> {
    const path = uri.replace('file://', '');
    
    // Security: Validate path is within allowed directory
    if (!path.startsWith(this.basePath)) {
      throw new Error('Access denied: Path outside workspace');
    }
    
    const stats = await fs.stat(path);
    
    if (stats.isDirectory()) {
      const entries = await fs.readdir(path);
      return {
        mimeType: 'text/x-directory',
        data: entries.map(entry => ({
          name: entry,
          uri: `file://${path}/${entry}`
        }))
      };
    } else {
      const content = await fs.readFile(path, 'utf-8');
      return {
        mimeType: mime.lookup(path) || 'text/plain',
        data: content
      };
    }
  }
}

project-modiqo

├── index.ts

├── utils.ts

└── config.ts

3.1 Tree Foldering

3.1 Tree Foldering

Tools: The Instruments of Change

Tools: The Instruments of Change

If Resources are the eyes of MCP, Tools are the hands. They're how AI takes action in the world—but with great power comes great protocol design. Every tool must declare:

If Resources are the eyes of MCP, Tools are the hands. They're how AI takes action in the world—but with great power comes great protocol design. Every tool must declare:

1. What it does

2. What parameters it needs

3. What it returns

4. What could go wrong

1. What it does

2. What parameters it needs

3. What it returns

4. What could go wrong

Instrument Json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
  "name": "send_slack_message",
  "description": "Post a message to a Slack channel",
  "inputSchema": {
    "type": "object",
    "properties": {
      "channel": {
        "type": "string",
        "description": "Channel name (without #)",
        "pattern": "^[a-z0-9-_]+$"
      },
      "message": {
        "type": "string", 
        "description": "Message text (supports basic markdown)",
        "maxLength": 4000
      },
      "thread_ts": {
        "type": "string",
        "description": "Optional: Thread timestamp to reply to"
      }
    },
    "required": ["channel", "message"]
  },
  "returnSchema": {
    "type": "object",
    "properties": {
      "ts": { "type": "string" },
      "channel": { "type": "string" },
      "permalink": { "type": "string" }
    }
  }
}

The Tool Lifecycle

The Tool Lifecycle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 1. AI decides to use a tool
const decision = await ai.analyze("User wants to notify team about deployment");
// → "I should send a Slack message"

// 2. AI prepares tool call
const toolCall = {
  jsonrpc: "2.0",
  method: "tools/call",
  params: {
    name: "send_slack_message",
    arguments: {
      channel: "deployments",
      message: "🚀 Deployment to production completed successfully!"
    }
  },
  id: "call_001"
};

// 3. MCP validates against schema
validator.validate(toolCall.params.arguments, tool.inputSchema);
// ✓ Valid

// 4. Server executes tool
const result = await slackAPI.postMessage({
  channel: "#deployments",
  text: toolCall.params.arguments.message
});

// 5. Return standardized response
{
  jsonrpc: "2.0",
  result: {
    ts: "1234567890.123456",
    channel: "C1234567890", 
    permalink: "https://slack.com/archives/C1234567890/p1234567890123456"
  },
  id: "call_001"
}

3.2 Live Cycle

3.2 Live Cycle

Real-World Tool: Database Query Tool

Real-World Tool: Database Query Tool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class DatabaseQueryTool(MCPTool):
    def __init__(self, connection_string: str):
        self.db = psycopg2.connect(connection_string)
        
    @property
    def schema(self):
        return {
            "name": "query_database",
            "description": "Execute read-only SQL queries",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "SQL query (SELECT only)"
                    },
                    "params": {
                        "type": "array",
                        "description": "Query parameters for safety"
                    }
                },
                "required": ["query"]
            }
        }
    
    async def execute(self, arguments: dict) -> dict:
        query = arguments["query"]
        params = arguments.get("params", [])
        
        # Security: Only allow SELECT statements
        if not query.strip().upper().startswith("SELECT"):
            raise PermissionError("Only SELECT queries allowed")
        
        # Security: Use parameterized queries
        cursor = self.db.cursor()
        cursor.execute(query, params)
        
        columns = [desc[0] for desc in cursor.description]
        rows = cursor.fetchall()
        
        return {
            "columns": columns,
            "rows": rows,
            "rowCount": len(rows)
        }

3.3 Query Tool

3.3 Query Tool

Prompts: The Human Touch

Prompts: The Human Touch

Prompts are MCP's most misunderstood primitive. They're not AI prompts—they're human prompts. They're templates for common workflows that users can invoke.Think of Prompts as "recipes" that combine Resources and Tools into meaningful actions:

Prompts are MCP's most misunderstood primitive. They're not AI prompts—they're human prompts. They're templates for common workflows that users can invoke.Think of Prompts as "recipes" that combine Resources and Tools into meaningful actions:

1. Check my GitHub commits

2. Review completed Jira tickets

3. ...

1. Check my GitHub commits

2. Review completed Jira tickets

3. ...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
  "name": "daily_standup",
  "description": "Generate daily standup report",
  "arguments": [
    {
      "name": "date",
      "description": "Date for standup (defaults to today)",
      "required": false
    }
  ],
  "template": `
    Please generate my daily standup report for {date}:
    
    1. Check my GitHub commits: @{tool:list_commits date={date}}
    2. Review completed Jira tickets: @{tool:query_jira status=done date={date}}
    3. Read my calendar: @{resource:calendar://today}
    4. Check Slack mentions: @{tool:search_slack mentions=true date={date}}
    
    Format as:
    - What I did yesterday
    - What I'm doing today  
    - Any blockers
  `
}

Prompt Expansion

Prompt Expansion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// User says: "Run my daily standup"
const prompt = await mcp.getPrompt("daily_standup");
const expanded = await prompt.expand({ date: "2025-03-15" });

// MCP expands the template:
/*
Please generate my daily standup report for 2025-03-15:

1. Check my GitHub commits: [
  { sha: "abc123", message: "Fixed auth bug" },
  { sha: "def456", message: "Updated dependencies" }
]

2. Review completed Jira tickets: [
  { key: "PROJ-123", summary: "Implement MCP integration" }
]

3. Read my calendar: [
  { time: "10am", title: "Architecture Review" },
  { time: "2pm", title: "1:1 with Sarah" }
]

4. Check Slack mentions: [
  { channel: "engineering", message: "Great work on the MCP docs!" }
]

Format as:
- What I did yesterday
- What I'm doing today
- Any blockers
*/

3.4 Prompt Expansion

3.4 Prompt Expansion

The Symphony of Integration

The Symphony of Integration

The real magic happens when all three work together. Here's a complete MCP workflow in action:

The real magic happens when all three work together. Here's a complete MCP workflow in action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// A code review workflow combining all three primitives

// 1. RESOURCE: Read the pull request
const pr = await mcp.getResource("github://pulls/123");
/*
{
  title: "Add MCP integration",
  files: ["src/mcp/client.ts", "src/mcp/server.ts"],
  diff: "...",
  author: "sarah"
}
*/

// 2. RESOURCE: Read related files
const context = await mcp.getResource("file:///workspace/src/mcp");
/*
{
  structure: ["client.ts", "server.ts", "types.ts"],
  content: { ... }
}
*/

// 3. TOOL: Run tests
const testResults = await mcp.callTool("run_tests", {
  files: pr.files
});
/*
{
  passed: 47,
  failed: 0,
  coverage: 94.2
}
*/

// 4. TOOL: Static analysis
const lintResults = await mcp.callTool("eslint", {
  files: pr.files  
});
/*
{
  errors: 0,
  warnings: 2,
  details: [...]
}
*/

// 5. PROMPT: Generate review
const review = await mcp.expandPrompt("code_review", {
  pr: pr,
  context: context,
  tests: testResults,
  lint: lintResults
});

// 6. TOOL: Post review
await mcp.callTool("post_github_review", {
  pr_id: 123,
  review: review.content
});

Resources

Resources

Tools

Tools

Prompt

Prompt

3.5 Workflow

3.5 Workflow

Security: The Fourth Dimension

Security: The Fourth Dimension

Resource Security

1
2
3
4
5
6
7
8
9
10
{
  "uri": "postgres://analytics/customers",
  "permissions": {
    "read": true,
    "write": false,
    "allowed_queries": ["SELECT"],
    "denied_fields": ["ssn", "credit_card"],
    "rate_limit": "100/hour"
  }
}

Tool Security

1
2
3
4
5
6
7
8
9
10
{
  "name": "send_email",
  "permissions": {
    "require_confirmation": true,
    "allowed_domains": ["@company.com"],
    "max_recipients": 10,
    "rate_limit": "20/day",
    "audit_log": true
  }
}

Prompt Security

1
2
3
4
5
6
7
8
9
10
{
  "name": "send_email",
  "permissions": {
    "require_confirmation": true,
    "allowed_domains": ["@company.com"],
    "max_recipients": 10,
    "rate_limit": "20/day",
    "audit_log": true
  }
}

Real Production Example: Customer Support Bot

Real Production Example: Customer Support Bot

Here's how a real company uses all three primitives:

Here's how a real company uses all three primitives:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# Resources: Read-only access to support data
resources = [
    MCPResource(
        uri="zendesk://tickets/open",
        name="Open Support Tickets"
    ),
    MCPResource(
        uri="postgres://knowledge_base",
        name="Help Articles"  
    ),
    MCPResource(
        uri="slack://channels/customer-support",
        name="Support Channel History"
    )
]

# Tools: Actions the bot can take
tools = [
    MCPTool(
        name="update_ticket",
        schema={
            "status": ["open", "pending", "resolved"],
            "priority": ["low", "normal", "high", "urgent"]
        }
    ),
    MCPTool(
        name="send_response",
        schema={
            "ticket_id": "string",
            "message": "string",
            "attachments": "array"
        }
    )
]

# Prompts: Common support workflows
prompts = [
    MCPPrompt(
        name="escalate_to_human",
        template="""
        Escalate ticket {ticket_id} to human support:
        
        1. Read ticket: @{resource:zendesk://tickets/{ticket_id}}
        2. Find similar resolved tickets: @{tool:search_similar}
        3. Update priority: @{tool:update_ticket priority=high}
        4. Notify team: @{tool:send_slack_message channel=urgent-support}
        
        Include context about why escalation is needed.
        """
    )
]

# In action:
async def handle_support_ticket(ticket_id: str):
    # Resource: Read the ticket
    ticket = await mcp.get_resource(f"zendesk://tickets/{ticket_id}")
    
    # Resource: Search knowledge base
    articles = await mcp.get_resource(
        f"postgres://knowledge_base?q={ticket.subject}"
    )
    
    if articles:
        # Tool: Send automated response
        await mcp.call_tool("send_response", {
            "ticket_id": ticket_id,
            "message": f"Found {len(articles)} relevant help articles",
            "attachments": [a.url for a in articles]
        })
    else:
        # Prompt: Escalate to human
        await mcp.expand_prompt("escalate_to_human", {
            "ticket_id": ticket_id
        })

3.6 Working customer support

3.6 Working customer support

The State of the Art

The State of the Art

As of March 2025, here's what the three primitives have enabled:

As of March 2025, here's what the three primitives have enabled:

Resource Implementations

File Systems

Local, S3, Google Drive, Dropbox

Local, S3, Google Drive, Dropbox

Local, S3, Google Drive, Dropbox

Databases

Postgres, MySQL, MongoDB, Redis

Postgres, MySQL, MongoDB, Redis

Postgres, MySQL, MongoDB, Redis

APIs

Stripe, Twilio, OpenWeatherMap

Stripe, Twilio, OpenWeatherMap

Stripe, Twilio, OpenWeatherMap

Dev Tools

Git, Docker, Kubernetes

Git, Docker, Kubernetes

Git, Docker, Kubernetes

Tool

Implementations

Communication

Email, Slack, Discord, Teams

Email, Slack, Discord, Teams

Email, Slack, Discord, Teams

Development

Compile, Test, Deploy, Rollback

Compile, Test, Deploy, Rollback

Compile, Test, Deploy, Rollback

Business

Create Invoice, Update CRM, Generate Report

Create Invoice, Update CRM, Generate Report

Create Invoice, Update CRM, Generate Report

Creative

Generate Image, Edit Video, Compose Music

Generate Image, Edit Video, Compose Music

Generate Image, Edit Video, Compose Music

Prompt

Libraries

Engineering

Code Review, Debug Session, Architecture Design

Code Review, Debug Session, Architecture Design

Code Review, Debug Session, Architecture Design

Business

Sales Followup, Quarterly Review, Risk Assessment

Sales Followup, Quarterly Review, Risk Assessment

Sales Followup, Quarterly Review, Risk Assessment

Personal

Daily Planning, Learning Path, Health Check

Daily Planning, Learning Path, Health Check

Daily Planning, Learning Path, Health Check

3.7 Table State

3.7 Table State