Skip to main content
Glama
Lazaro549

DevNotes MCP

by Lazaro549

πŸ—’οΈ DevNotes MCP

A notes server built to exercise every advanced feature of the Model Context Protocol.

Follow-up project for Anthropic Academy's Model Context Protocol: Advanced Topics course.

πŸŽ“ What this is

DevNotes is a small, in-memory notes server β€” think a minimal Notion, exposed over MCP instead of a web UI. It's deliberately not trying to be a real product; every tool exists to be a clean, runnable example of one specific advanced MCP feature, and the notes themselves are just the excuse to have something worth calling a tool on.

Course topic

Where it lives

Sampling β€” server asks the client's LLM to do the work

summarize-note

Elicitation β€” server pauses to ask the user something

create-note (priority, if omitted)

Roots β€” client grants the server a specific local folder

index-roots

Progress & logging notifications

also index-roots, via sendNotification/sendLoggingMessage

Pagination

search-notes, cursor-based

Structured output

every tool returns structuredContent alongside its text

stdio vs. Streamable HTTP transports

stdio.ts / http.ts

Resumable transport

eventStore.ts, stateful HTTP mode

Stateless transport

http.ts --stateless

OAuth 2.1 + PKCE authorization

auth.ts, http.ts --oauth

Related MCP server: sourcify-context-server

πŸ—οΈ Architecture

flowchart LR
    subgraph client["interactiveClient.ts"]
        cap["declares: sampling, roots, elicitation"]
    end

    subgraph transport["pick one"]
        stdio["stdio.ts"]
        http["http.ts (Streamable HTTP)"]
    end

    subgraph server["createServer.ts"]
        t1["create-note<br/><i>elicitation</i>"]
        t2["search-notes<br/><i>pagination</i>"]
        t3["summarize-note<br/><i>sampling</i>"]
        t4["index-roots<br/><i>roots + notifications</i>"]
        store[("NotesStore<br/>(in-memory)")]
    end

    auth["auth.ts<br/>OAuth 2.1 + PKCE"]
    evt["eventStore.ts<br/>resumability"]

    client <-->|JSON-RPC over stdio or HTTP| transport
    transport --> server
    t1 & t2 & t3 & t4 --> store
    http -. "--oauth protects" .-> auth
    http -. "stateful mode uses" .-> evt

Sampling is the one genuinely bidirectional flow in the demo β€” the server calls back into whichever process holds the model credentials β€” so it's worth seeing on its own:

sequenceDiagram
    participant You
    participant Client as interactiveClient.ts
    participant Server as DevNotes server
    You->>Client: summarize-note
    Client->>Server: tools/call
    Server->>Client: sampling/createMessage
    alt ANTHROPIC_API_KEY set
        Client->>Anthropic API: messages.create()
        Anthropic API-->>Client: completion
    else no key configured
        Client->>Client: return a mock completion
    end
    Client-->>Server: CreateMessageResult
    Server-->>Client: tool result (the summary)
    Client-->>You: print it

The server never holds a model API key itself β€” that's the point of sampling. Only the client in this diagram needs one, and only optionally (see Quick start).

πŸš€ Quick start

npm install
cp .env.example .env   # optional β€” see below

Everything runs with zero configuration. .env only matters if you want real Claude completions for summarize-note instead of a mocked one β€” set ANTHROPIC_API_KEY there.

Local (stdio) β€” what Claude Desktop and the MCP Inspector use:

npm run client            # spawns the server itself, opens an interactive prompt
# or, to poke at it directly:
npm run inspector

Remote (Streamable HTTP):

npm run start:http                # stateful, resumable β€” default
npm run start:http:stateless      # no session id, horizontally scalable
npm run start:http:oauth          # requires a bearer token (see below)
npm run start:http:oauth-strict   # + RFC 8707 resource-indicator enforcement

npm run client:http               # talk to whichever one is running on :3000

Try roots by pointing the client at the bundled example notes:

npm run client -- --root ./examples
# then call index-roots from the prompt

Try the full OAuth flow: run npm run start:http:oauth, then open the MCP Inspector (npx @modelcontextprotocol/inspector) and connect to http://127.0.0.1:3000/mcp β€” its built-in "Connect" flow walks through dynamic client registration, the authorization redirect, and the PKCE token exchange for you. auth.ts has a header-comment listing exactly what it does and doesn't do β€” it's a real OAuth 2.1 authorization server, but an intentionally minimal one (in-memory storage, no real login screen); the same file explains what a production deployment needs to add back.

πŸ“ Project structure

src/
  server/
    createServer.ts     factory: builds one McpServer + registers all four tools
    store.ts             the in-memory NotesStore (create/search/importFromRoot)
    stdio.ts              entrypoint: stdio transport
    http.ts                entrypoint: Streamable HTTP, stateful/stateless/oauth
    auth.ts               demo OAuth 2.1 + PKCE authorization server
    eventStore.ts        in-memory EventStore for resumable HTTP
    tools/
      createNote.ts        elicitation
      searchNotes.ts       pagination
      summarizeNote.ts     sampling
      indexRoots.ts        roots + progress/logging notifications
  client/
    interactiveClient.ts  demo client: implements the handlers a server can call back into
examples/                sample notes for the index-roots demo
assets/                   the certificate this repo is a follow-up to

πŸ”’ Notes on the security-relevant bits

  • Roots are enforced entirely by the client β€” store.importFromRoot only ever reads the file:// URI it's handed, one directory level deep, capped at 200 files / 200 KB each. The server never picks its own path.

  • createMcpExpressApp enables DNS-rebinding protection automatically for 127.0.0.1 β€” see http.ts.

  • auth.ts is explicitly a demo authorization server (see the header comment in that file for the exact list of what it skips). In a real deployment you'd almost always point verifyAccessToken at an existing IdP rather than writing one.

  • Every server-initiated request (createMessage, listRoots, elicitInput) passes relatedRequestId, so responses route to the right session under concurrent HTTP load instead of the first one that happens to be listening.

πŸ—ΊοΈ Learning path

Part of a series built while working through Anthropic Academy:

  1. claude-api-starter β€” Building with the Claude API

  2. mcp-advanced-starter β€” MCP: Advanced Topics (this repo)

  3. claude-code-starter β€” Claude Code in Action

πŸ’Έ Donations

  • πŸ‡¦πŸ‡· ARS (Argentina) Alias: lazaro.503.alaba.mp

  • 🌎 USD (local transfers within Argentina only) Alias: ahogada.duras.foca

License

MIT β€” see LICENSE.

Related MCP Connectors

Related MCP Servers

  • F
    license
    B
    quality
    D
    maintenance
    A learning-focused MCP server that demonstrates core MCP concepts through a simple notepad application, enabling users to create, update, delete, and search notes while exploring tools, resources, and prompts functionality.
    4
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    A working MCP server template for notes management (add, get, search, delete) with in-memory store, demonstrating tools, resources, and prompts for easy forking and customization.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    A learning-oriented MCP server for managing notes, providing tools to create, list, get, update, delete, and search notes, along with resources and prompt templates.
    5 npm
    -