Tools, Resources, and Prompts

Combining Capabilities

4 min read

Each capability on its own is straightforward. The design work starts when one server offers all three and you have to decide which job goes where.

A Complete Example: Knowledge Base Server

Here is the same knowledge base exposed three ways at once. Notice that each capability answers a different question about the same documents:

One knowledge base, three surfaces

Resources — "let me read that"
Tools — "go find it" / "change it"
Prompts — "do the usual thing"
Storage

The pairing that makes this work is search_knowledge and doc://{id}. Resources are addressable but not discoverable by the model — it has no way to guess a document ID. The search tool exists specifically to turn a question into the URIs the resource layer can serve. Ship one without the other and half the server is unreachable.

from mcp.server import Server
from mcp.types import Tool, Resource, Prompt, TextContent

server = Server(name="knowledge-base")

# 1. RESOURCES: Expose documents
@server.list_resources()
async def list_resources():
    docs = await db.get_all_documents()
    return [
        Resource(
            uri=f"doc://{doc.id}",
            name=doc.title,
            description=f"Document: {doc.title}",
            mimeType="text/markdown"
        )
        for doc in docs
    ]

@server.read_resource()
async def read_resource(uri: str):
    doc_id = uri.replace("doc://", "")
    doc = await db.get_document(doc_id)
    return [TextContent(type="text", text=doc.content)]

# 2. TOOLS: Search and modify
@server.list_tools()
async def list_tools():
    return [
        Tool(
            name="search_knowledge",
            description="Search the knowledge base",
            inputSchema={
                "type": "object",
                "properties": {
                    "query": {"type": "string"},
                    "limit": {"type": "integer", "default": 5}
                },
                "required": ["query"]
            }
        ),
        Tool(
            name="add_document",
            description="Add a new document to the knowledge base",
            inputSchema={
                "type": "object",
                "properties": {
                    "title": {"type": "string"},
                    "content": {"type": "string"},
                    "tags": {"type": "array", "items": {"type": "string"}}
                },
                "required": ["title", "content"]
            }
        )
    ]

# 3. PROMPTS: Standardized interactions
@server.list_prompts()
async def list_prompts():
    return [
        Prompt(
            name="summarize_topic",
            description="Summarize all documents on a topic",
            arguments=[
                PromptArgument(name="topic", required=True)
            ]
        )
    ]

Workflow: AI Using All Capabilities

Trace one real request end to end. The order is not arbitrary — search has to come first, because nothing else knows which documents exist:

"Summarise our AI security policies"

User picks the prompt

summarize_topic with topic="AI security". Optional — a user who knows what they want can skip straight to asking

search_knowledge

Tool call. Returns doc://policy-123 and doc://policy-456. This is the discovery step — without it the model has no IDs to read

read doc://policy-123

Resource read. Full text, no arguments, no side effects

read doc://policy-456

Same again. Reads are cheap and independent, so they can run together

Summarise

No server involvement. The model works from text it actually retrieved rather than from memory

Steps two and three are the ones worth internalising. Search returns identifiers, not content; the resource layer turns identifiers into content. Collapsing those into a single tool that returns full documents seems tidier until a search matches thirty files and the context window is gone.

Resource + Tool Coordination

Design resources and tools to work together:

# Resource: Read-only view
Resource(uri="user://123", name="User 123 Profile")

# Tool: Modify user
Tool(name="update_user", description="Update user profile")

Best Practices

PracticeReason
Clear separationResources for reading, tools for actions
Consistent URIsMakes resources predictable
Related naminguser://123 with update_user tool
Document relationshipsExplain in descriptions

Now let's apply these patterns in a hands-on lab. :::

Quiz

Module 3 Quiz: Tools, Resources, and Prompts

Take Quiz
Was this lesson helpful?

Sign in to rate