Free lab Real Python 3. Zero installs. Your code stays in this browser. Open the playground

Knowledge Base MCP Server

Go beyond tools: expose your notes as MCP resources, add a ranked search tool, and publish reusable prompt templates, so an assistant can read and reason over your own knowledge base.

Advanced 4 steps 180 XP ~60 min

How it works in the real world

MCP servers can offer three kinds of things, and real servers usually offer all three:

  1. Resources: read-only data addressed by URI (notes://python-tips), like files the assistant can open.
  2. Tools: actions with arguments, such as searching those notes.
  3. Prompts: named templates a user can pick ("summarise this note") that the server fills in with real content.

You will build each one over an in-memory set of notes, then route them all through one dispatcher with proper error codes.

Build progress0 / 4 steps
1

Expose notes as resources

Answer resources/list with one entry per note: uri notes://SLUG, the note's name (its first line without the #) and mimeType text/markdown. Then read notes://mcp-basics and print its text.

Blank · autosaved
PYstep_1.py
Run your code to check this step…
2

A ranked search tool

Write search(query, limit=2): split the query and each note into lowercase words (re.findall(r"[a-z]+", ...)), score a note by how many times the query words appear in it, drop notes that score 0, and return the best first (ties broken by slug). Print slug (score) lines for three queries.

Blank · autosaved
PYstep_2.py
Run your code to check this step…
3

Prompt templates

Publish one prompt, summarize_note, with a required note_id argument. get_prompt returns a single user message whose text is Summarise this note in two bullet points:, a blank line, then the note. A missing note_id raises ValueError("note_id is required").

Blank · autosaved
PYstep_3.py
Run your code to check this step…
4

One server, one dispatcher

Route everything through serve(msg): resources/list, resources/read, tools/call for search (results as one text block, one hit per line) and prompts/get. Unknown URIs return error -32002 Resource not found; a prompt with missing arguments returns -32602. Print each reply.

Blank · autosaved
PYstep_4.py
Run your code to check this step…

Take it further

Point this at a real folder of Markdown files and your assistant can search and summarise your own notes. Nothing below is graded: it runs on your own machine, not in this tab.

Stretch goals

  1. Load every .md file from a folder with pathlib.Path.glob instead of a hard-coded dict.
  2. Replace word counts with TF-IDF scoring (the Mini Search Engine project builds it) so common words matter less.
  3. Add an add_note tool, and think hard about whether an assistant should be allowed to write files.
  4. Send notifications/resources/list_changed when a file in the folder changes.

notes_server.py: resources, a tool and a prompt with FastMCP

# pip install "mcp[cli]"
from pathlib import Path

from mcp.server.fastmcp import FastMCP

NOTES_DIR = Path("notes")  # a folder of .md files
mcp = FastMCP("notes")


@mcp.resource("notes://{slug}")
def read_note(slug: str) -> str:
    """Read one note by its file name (without .md)."""
    return (NOTES_DIR / f"{slug}.md").read_text()


@mcp.tool()
def search(query: str, limit: int = 3) -> str:
    """Find the notes that mention the query words most often."""
    terms = query.lower().split()
    scored = []
    for path in NOTES_DIR.glob("*.md"):
        words = path.read_text().lower().split()
        score = sum(words.count(t) for t in terms)
        if score:
            scored.append((score, path.stem))
    scored.sort(reverse=True)
    return "\n".join(f"{slug} ({score})" for score, slug in scored[:limit]) or "no matches"


@mcp.prompt()
def summarize_note(note_id: str) -> str:
    """Summarise one note in two bullet points."""
    return "Summarise this note in two bullet points:\n\n" + read_note(note_id)


if __name__ == "__main__":
    mcp.run()

Ask an AI to review your build

I built an MCP server that exposes Markdown notes as resources (notes://slug), a ranked search tool and a summarize_note prompt template. Here is my code: [paste it]. Review the design: which of these should be resources versus tools, what happens with path traversal like notes://../secrets, and how should search scale to ten thousand notes?

Your next ten minutes

Write Python that does something useful.

Start free. No install, no card, no passive video marathon.

Start learning free → Explore the path