mcp-demo
mcp-demo
A demo Model Context Protocol server built with TypeScript + Hono + Bun, showcasing current MCP capabilities with fake logic — nothing here does anything real.
New to MCP? Start with docs/concepts.md for a short intro to what MCP provides and how to choose between Tools, Resources, and Prompts.
See docs/architecture.md for the session model and module split. Run
bun run typecheckto type-check the project.
Run it
bun install
# terminal 1 — the server (http://localhost:3000, MCP endpoints at /mcp and /sse)
bun run dev
# terminal 2 — scripted walkthrough of the capabilities (no LLM needed),
# pick a transport:
bun run client # Streamable HTTP (current standard)
bun run client:sse # legacy HTTP+SSE (deprecated, kept for education)
bun run client:stdio # stdio (client spawns src/transports/stdio.ts itself)
# or explore interactively with the MCP Inspector
bun run inspect # Streamable HTTP → http://localhost:3000/mcp, SSE → /sse
bunx @modelcontextprotocol/inspector bun run src/transports/stdio.ts # stdiobun run dev uses --hot reload. Set PORT=4000 to change the port, MCP_URL to point the client elsewhere.
Transports
All three MCP transports are implemented — the same server capabilities are exposed over each:
Transport | Status | Endpoint / entry | Docs |
Streamable HTTP | current standard (2025-03-26) |
| |
HTTP+SSE | legacy (2024-11-05), deprecated |
| |
stdio | current, for local servers |
|
Capability map
MCP capability | Where it's demonstrated |
Tools |
|
Progress |
|
Cancellation |
|
Elicitation |
|
Resources (static) |
|
Resource templates |
|
Subscriptions |
|
Prompts |
|
Completion |
|
Logging |
|
List-changed |
|
Ping |
|
Sessions & resumability | Stateful |
Layout
src/
server/ the MCP server itself — one module per capability family
index.ts createMcpServer(): wires the capability modules together
tools.ts echo, roll-dice, long-task, ask-user, emit-logs
resources.ts static + templated + live subscribable resources
prompts.ts code-review, commit-message (completable args)
bonus.ts bonus tool/resource/prompt + toggle-bonus (demonstrates list_changed)
subscriptions.ts per-session resource-subscribe tracking + live-stats ticker
data.ts fake in-memory data
transports/ the wire layers that expose the same server over the network/process boundary
http.ts Hono app: /mcp (Streamable HTTP) + /sse + /messages (legacy SSE), /health — main entry
sse.ts HonoSseTransport: Bun-native implementation of the legacy HTTP+SSE wire protocol
stdio.ts stdio entry point (server over stdin/stdout)
utils.ts helpers shared by server and client (sleep, errorMessage)
client/
demo.ts thin orchestrator: pick transport → connect → run → teardown
walkthrough.ts runWalkthrough(): one step per MCP capability
handlers.ts createDemoClient(): fake elicitation + notification logging
helpers.ts step/show/toolText/resourceText formatters
docs/
architecture.md session model, module split, how to add a capability
concepts.md MCP overview and how to choose between Tools, Resources, and Prompts
streamable-http.md / sse.md / stdio.md — how each transport works and why MCP uses itSee docs/architecture.md for the full session model and how the pieces fit.
Notes / gotchas encountered
The server is stateful: each
initializegets its ownMcpServer+StreamableHTTPTransport, so subscriptions and log levels are per-session.resources/subscribeis not handled by the SDK — the server tracks subscribed URIs itself (src/server/subscriptions.ts).Log-level filtering only applies when
sendLoggingMessage(params, sessionId)is given the session id.Don't override the client's
ProgressNotificationSchemahandler if you want the per-requestonprogresscallback — the SDK's internal handler is what routes to it.The demo client fakes the
elicitationresponse, so the full walkthrough runs without external services. In the Inspector, elicitation shows a form dialog.