The most useful MCP server examples are small and teach one idea each. The official reference servers (Filesystem, Git, Fetch, Memory, Sequential Thinking, Time and Everything) show how a local server should scope itself. Vendor servers from GitHub, Linear, Sentry, Notion, Stripe and Supabase show the remote, OAuth-protected API wrapper. Then there are a few less common shapes, like browser control, long-running tasks and gateways. Each one solves a different design problem.
Many lists sort these by vendor. This one sorts them by the pattern they teach, because that's what you're copying when you build your own. Every pattern gets real servers you can run today and a minimal snippet. Every Python snippet was run against the official SDK (mcp 2.3.0) on November 3, 2026. Every vendor endpoint in the tables was checked against its docs and probed live the same day. If you want a ranked shortlist for Claude rather than patterns, read the best MCP servers for Claude instead.
MCP server examples at a glance
| Pattern | Real examples | What it teaches | Usual transport |
|---|---|---|---|
| Scoped local resource | Filesystem, Git | Confine the server to what the user granted, and label every tool | stdio |
| State the model can't keep | Memory, Sequential Thinking, Time | Give the model a store, a scratchpad or a fact it can't know | stdio |
| Remote API wrapper | GitHub, Linear, Sentry, Notion, Stripe, Supabase | Turn a product's API into a curated set of tools behind OAuth | Streamable HTTP |
| Web reader and browser | Fetch, Playwright | Hand the model structured text instead of pixels | stdio |
| Long-running task | DialMCP | Return a handle at once, and let the client follow the work | Streamable HTTP |
| Gateway | Docker MCP Gateway | Put many servers behind one connection | stdio or HTTP |
| Test server | Everything | Exercise every protocol feature so you can test a client | Both |
A caveat that applies to the first two rows: the steering group says its reference servers are "educational examples for developers building their own MCP servers, not as production-ready solutions." Read them for the design and run them for learning. For a real service, use the server the vendor publishes.
How to try any of these in two minutes
You don't need a chat app to look inside a server. The MCP Inspector launches a stdio server from its command, or connects to a URL, and lets you list and call its tools by hand:
# A stdio server: the Inspector launches it for you
npx @modelcontextprotocol/inspector uvx mcp-server-git --repository .
# The kitchen-sink test server, over Streamable HTTP
npx @modelcontextprotocol/server-everything streamableHttp
npx @modelcontextprotocol/inspector --transport http --server-url http://localhost:3001/mcp
A stdio server started on its own in a terminal looks frozen. That's expected: it is waiting for a client on stdin. To keep one, add it to your client's config. In Claude Desktop, local servers go in the mcpServers block covered in our Claude Desktop config guide. Remote servers don't go in that file. Add them as custom connectors under Customize > Connectors instead.
Pattern 1: the scoped local resource (Filesystem, Git)
The simplest useful server wraps something on your own machine and refuses to go beyond the part you allowed. Filesystem takes its allowed directories as command-line arguments. Git takes a --repository path. Both run as a subprocess of the client over stdio, so there's no port to open and no network login.
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/notes"]
},
"git": {
"command": "uvx",
"args": ["mcp-server-git", "--repository", "/Users/me/code/my-repo"]
}
}
}
The interesting part is how the two servers police that boundary.
- The allow-list comes from the launch command. A 2025-era client could replace Filesystem's allow-list at runtime through Roots. But Roots is deprecated as of the 2026-07-28 protocol, and the Filesystem README says that on a 2026-07-28 connection "the command-line directories are the whole allow-list."
- Leaving the flag off has a cost. The Git README warns that without
--repository, the server "reads and writes any Git repository the server process can reach." Set it whenever the server sees content you don't fully trust. - Every tool says what it does. Filesystem labels all its tools with annotations.
read_text_fileisreadOnlyHint: true.write_fileis marked destructive but idempotent, because it overwrites but is safe to repeat.edit_fileandmove_fileare destructive and not idempotent. Every tool setsopenWorldHint: false, because it never leaves the allowed folders.
Here's the pattern in its smallest form. It's one read-only tool, confined to one folder, with an error the model can read when it asks for something outside:
from pathlib import Path
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError
from mcp.types import ToolAnnotations
ROOT = Path("~/notes").expanduser().resolve()
mcp = MCPServer("notes-reader")
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=False))
def read_note(path: str) -> str:
"""Read a text file under ~/notes. Paths are relative to that folder."""
target = (ROOT / path).resolve()
if not target.is_relative_to(ROOT):
raise ToolError(f"{path} is outside the notes folder")
return target.read_text()
if __name__ == "__main__":
mcp.run()
The resolve() call matters. It follows .. and symlinks before the check, so ../../etc/passwd comes back as a tool error instead of a file. Annotations are hints to the client, not enforcement. The path check is what actually protects you. Our MCP tools guide covers annotations and errors in more depth.
Pattern 2: state the model can't keep (Memory, Sequential Thinking, Time)
Some servers don't wrap an outside system at all. They give the model something it lacks: a place to store things, a structure for thinking, or a fact it can't know on its own.
- Memory keeps a knowledge graph of entities, relations and observations, with tools like
create_entities,add_observationsandsearch_nodes. It writes everything to a JSONL file that you choose withMEMORY_FILE_PATH. Each client starts its own server process, so two apps pointed at the same file are two writers. The server takes a lock file for each write so that neither loses data. - Sequential Thinking has a single tool,
sequential_thinking. The model calls it once per step, with fields for the thought, its number, an estimated total, and whether it revises or branches from an earlier one. The server doesn't think for the model. It just holds the structure, and the history lasts as long as the connection. - Time has
get_current_timeandconvert_time, and it takes exact IANA zone names likeEurope/Warsaw. It's tiny, but it covers a real gap, since a model has no clock.
{
"mcpServers": {
"memory": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-memory"],
"env": { "MEMORY_FILE_PATH": "/Users/me/.mcp/memory.jsonl" }
}
}
}
The lesson for your own server is about where state lives. Memory keeps it in a file it owns, not in the conversation, so it survives restarts and can be shared by any client that's pointed at the file. If your server holds state, decide where it lives and who else can write to it before you write the first tool.
Pattern 3: the remote API wrapper (GitHub, Linear, Supabase and more)
This is the pattern most production servers follow. A company takes its existing API, picks the operations an agent should have, and serves them at a URL behind OAuth. You add the URL, sign in once in the browser, and the client stores the token. These six are first-party, and each endpoint answered an unauthenticated request with HTTP 401 on November 3, 2026, which means it's live and asking for a login:
| Server | Endpoint | Ways to limit it |
|---|---|---|
| GitHub | https://api.githubcopilot.com/mcp/ | Per-toolset URLs under /mcp/x/, plus /readonly on any of them |
| Linear | https://mcp.linear.app/mcp | A read-only endpoint, /mcp/readonly |
| Sentry | https://mcp.sentry.dev/mcp | Scope to an org or a project in the path |
| Notion | https://mcp.notion.com/mcp | OAuth by default. Business and Enterprise workspace owners can issue an MCP token |
| Stripe | https://mcp.stripe.com | OAuth, or an agent API key with only the permissions it needs |
| Supabase | https://mcp.supabase.com/mcp | ?project_ref= and ?read_only=true |
The habit to copy is the scoping switch. GitHub notes that turning on only the toolsets you need "can help the LLM with tool choice and reduce the context size." A wrapper that exposes the entire API costs context on every request and makes it easier to pick the wrong tool. In a client that takes URLs, the narrow versions look like this:
{
"mcpServers": {
"github": { "type": "http", "url": "https://api.githubcopilot.com/mcp/readonly" },
"supabase": { "type": "http", "url": "https://mcp.supabase.com/mcp?read_only=true" },
"dialmcp": { "type": "http", "url": "https://mcp.dialmcp.com/mcp" }
}
}
That's a Claude Code-style .mcp.json. Other clients name the keys differently, and our client setup page has the variants. At its core, a wrapper is an HTTP call with a typed result. This one runs locally over stdio and reads an optional token from the environment, which the spec expects for stdio. A hosted version would add OAuth, which is a transport-level concern covered in our MCP authorization guide.
import os
import httpx
from pydantic import BaseModel
from mcp.server import MCPServer
from mcp.types import ToolAnnotations
mcp = MCPServer("github-lite")
HEADERS = {"Accept": "application/vnd.github+json"}
if token := os.environ.get("GITHUB_TOKEN"):
HEADERS["Authorization"] = f"Bearer {token}"
class Repo(BaseModel):
full_name: str
description: str | None
stars: int
open_issues: int
@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True))
async def get_repo(owner: str, repo: str) -> Repo:
"""Look up a public GitHub repository's description, stars and open issues."""
async with httpx.AsyncClient(headers=HEADERS, timeout=10) as http:
r = await http.get(f"https://api.github.com/repos/{owner}/{repo}")
r.raise_for_status()
d = r.json()
return Repo(full_name=d["full_name"], description=d["description"],
stars=d["stargazers_count"], open_issues=d["open_issues_count"])
The return type is a Pydantic model, so the SDK publishes an output schema and sends structuredContent along with the text. When we ran this, it returned the live description, star count and open-issue count for modelcontextprotocol/servers. For why MCP adds this layer over plain REST at all, see MCP vs API.
Pattern 4: the web reader and the browser (Fetch, Playwright)
Two servers give a model the web, and they make the same choice: give the model text, not screenshots.
- Fetch turns a page into markdown and returns it in pieces. Its
fetchtool takesmax_length(5,000 characters by default) andstart_index, so the model can read a long page in chunks until it finds what it needs. By default it obeys robots.txt for requests the model makes, and it refuses hosts that resolve to private addresses. - Playwright MCP, from Microsoft, drives a real browser through "structured accessibility snapshots, bypassing the need for screenshots or visually-tuned models." The model sees the page as a tree of roles and labels with references it can click. You can turn on coordinate-based vision mode with
--caps vision, but it's off by default. Start it withnpx @playwright/mcp@latest.
The design lesson is to send the model the smallest representation it can act on. A Fetch result can be capped and paged. A full accessibility tree costs tokens on every step, though, and the Playwright README itself now points coding agents toward its CLI for that reason. If your server returns big payloads, page them, as Fetch does.
Pattern 5: the long-running task (DialMCP)
Most tools return in a second or two. Some work takes minutes, and a tool call that just waits out the whole job is fragile: the client may time out, and the model can't report progress in the meantime. The pattern is to start the work, return a handle at once, and give the client a second tool to follow it.
DialMCP is built this way, because a phone call can't finish inside one request. place_call dials and returns a call_id straight away, plus a private listen_url the user can open to hear the call live. get_call returns the status and a transcript that fills in while the call is live. Once the call is over, it adds the resolution: the outcome, a summary and anything the other side committed to. Its wait_seconds argument (up to 50) lets the server hold the reply until something changes, so the client doesn't have to poll every second. A typical call takes two to five minutes. The tool reference has the full status list. Here's the same shape with a stand-in job:
import asyncio
import uuid
from pydantic import BaseModel
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError
mcp = MCPServer("slow-jobs")
class Job(BaseModel):
job_id: str
status: str # "running" or "completed"
result: str | None = None
JOBS: dict[str, Job] = {}
async def work(job: Job, seconds: int) -> None:
await asyncio.sleep(seconds) # stand-in for the real slow thing
job.status, job.result = "completed", f"done after {seconds}s"
@mcp.tool()
async def start_job(seconds: int) -> Job:
"""Start a slow job. Returns a job_id at once; follow it with get_job."""
job = Job(job_id=uuid.uuid4().hex[:8], status="running")
JOBS[job.job_id] = job
asyncio.get_running_loop().create_task(work(job, seconds))
return job
@mcp.tool()
async def get_job(job_id: str, wait_seconds: int = 0) -> Job:
"""Current state of a job. wait_seconds (0-50) holds the reply until it finishes."""
job = JOBS.get(job_id)
if job is None:
raise ToolError(f"unknown job_id {job_id}")
for _ in range(min(max(wait_seconds, 0), 50) * 10):
if job.status != "running":
break
await asyncio.sleep(0.1)
return job
In our test, start_job returned running immediately. A plain get_job still said running, and get_job with wait_seconds=5 came back completed once the job finished. The in-memory dict is fine for a demo. A real remote server should keep jobs in a shared store, keyed to the signed-in user, so any instance can answer and nobody can read another user's job. That's the "handle" approach our MCP architecture guide recommends now that the protocol has no sessions. The Tasks extension standardizes the same idea at the protocol level, but client support still varies, so a handle you can poll works everywhere today.
Pattern 6: the gateway (Docker MCP Gateway)
A gateway is a server that fronts other servers. The client sees one connection. Behind it, the gateway starts, authenticates and routes to many upstream servers. Docker's version runs catalog servers in isolated containers:
# One entry in your client config, many servers behind it
docker mcp gateway run
# Or serve several clients over HTTP
docker mcp gateway run --port 8080 --transport streaming
The default transport is stdio, for one local client. The streaming transport serves several. Gateways are where teams add shared credentials, audit logs and allow-lists. The hard part is OAuth to remote upstreams. A well-built gateway holds a separate token for each user and upstream instead of passing the client's token through. Our MCP gateway guide compares the open-source options and covers what happened when we put one in front of DialMCP. If you only use a handful of servers, you probably don't need one.
The test server: Everything
Everything isn't meant to be useful. It's a reference and test server that exercises prompts, resources, tools and other protocol features, over both stdio and Streamable HTTP. It's the only server in the reference repo that serves HTTP, which makes it the quickest way to see a remote transport locally. If you're writing a client, a gateway or a test harness, point it here first.
MCP use cases: what people actually connect
The patterns map onto a fairly short list of everyday MCP use cases:
- Coding work: read and change a repo (Git, GitHub), look up current library docs (Context7), check errors (Sentry). These are scoped local servers plus API wrappers.
- Team tools: file and update issues (Linear), search and write pages (Notion). These are API wrappers, and they're best added read-only first.
- Data: query a database or project (Supabase). This is an API wrapper, where the read-only switch matters most.
- Research and the web: read pages (Fetch) and complete flows in a browser (Playwright).
- Personal context: remember preferences across chats (Memory) and do time math (Time).
- Real-world errands: call a business, wait on hold, book or cancel something (DialMCP). This is the long-running task pattern.
If a use case isn't on the list, a server for it may already exist. The official MCP Registry is the place to search before you build your own.
More MCP examples: SDK samples and the weather server
If you'd rather read code than run servers, the protocol's own repos are full of examples:
- The quickstart weather server (
modelcontextprotocol/quickstart-resources) is the same two-tool server,get_forecastandget_alertson the US National Weather Service API, in Python, TypeScript, Go, Rust and Ruby. Each comes with a matching chatbot client. - The TypeScript SDK's example index lists runnable servers for one thing each, including stateful and stateless Streamable HTTP, an OAuth resource server, structured output, form and URL elicitation, and sampling.
- The Rust SDK has counter servers over stdio and Streamable HTTP, and an elicitation demo.
For a full walkthrough rather than fragments, our Python MCP server guide builds a complete notes server with tests, and our MCP SDK guide compares the language SDKs.
Which pattern should your server copy?
Ask where the thing your server touches lives and how long a call takes. If it's on the user's machine, copy pattern 1: stdio, an allow-list on the command line, and honest annotations. If it's a hosted product, copy pattern 3. Serve it over Streamable HTTP with OAuth, expose a small set of tools, and add a read-only mode. If a call can outlast a request, add pattern 5's handle and a follow-up tool. And if what you're really building is plumbing for other servers, that's pattern 6.
Most real servers mix two of these. DialMCP is a remote wrapper (pattern 3) whose main tool is a long-running task (pattern 5), and it adds safety rules that run on the server instead of living in the prompt. Calling hours, rate limits and the opt-out list are checked before anything is dialed, and the voice agent opens every call by saying it's an AI. If you want to see that combination from the client side, add https://mcp.dialmcp.com/mcp to any client that takes a remote URL and ask your agent to make a call.
Try a remote MCP server that does real work. DialMCP lets Claude, ChatGPT, Codex, Cursor or VS Code place real phone calls from your own verified number and hand back a transcript and a structured outcome. Add https://mcp.dialmcp.com/mcp as a connector and sign in. US and Canada, free during launch.
FAQ
What are some examples of MCP servers?
The official reference servers are Filesystem, Git, Fetch, Memory, Sequential Thinking, Time and Everything. They all run locally over stdio, and Everything also serves Streamable HTTP. Popular vendor servers include GitHub, Linear, Sentry, Notion, Stripe and Supabase, which are remote servers you sign in to with OAuth, plus Microsoft's Playwright MCP for browser control. DialMCP is an example of a remote server that runs a long task, a real phone call.
What are the official MCP reference servers?
The MCP steering group maintains seven: Everything (a test server), Fetch, Filesystem, Git, Memory, Sequential Thinking and Time. They live in the modelcontextprotocol/servers repository. Older ones such as GitHub, Slack, PostgreSQL and Puppeteer were archived. Some were replaced by vendor-maintained servers, such as Brave Search.
What are common MCP use cases?
Coding work (repos, docs, error tracking), team tools such as issue trackers and wikis, database queries, reading the web and driving a browser, personal memory across chats, and real-world tasks like placing a phone call. Most of these map to a scoped local server, a remote API wrapper or a long-running task.
Are the reference MCP servers production-ready?
No. The repository describes them as educational examples for developers building their own servers, not production-ready solutions. They're good for learning and testing. For a real service, use the server its vendor publishes, and check its security model against your own needs.
What is the simplest MCP server example to start with?
A single read-only tool over stdio, such as a function that reads files from one folder. With the official Python SDK, that's an MCPServer instance, one decorated function and mcp.run(). Test it with the MCP Inspector before adding it to a client, then add more tools once the first one behaves.