Anchor MCP
A lightweight MCP sidecar exposing safe, read-only Anchor note tools to ChatGPT via a tunnel client in the same Docker stack. It currently supports listing, searching, reading notes, tags, and attachment metadata without exposing Anchor's private API or database.
README
Anchor MCP
Implementation plan for a small MCP sidecar that exposes safe Anchor Notes tools to ChatGPT through a tunnel client running in the same Docker Compose stack as Anchor.
Research basis: Anchor upstream repository ZhFahim/anchor, default branch main, inspected 2026-08-20. Anchor is a Nest.js backend with authenticated REST endpoints under /api/*.
Goal
Run an MCP server next to Anchor so external assistants can list, search, read, create, update, import, and attach files to Anchor notes without exposing Anchor's database or private API directly.
Current Status
First milestone is implemented:
- Streamable HTTP MCP endpoint at
POST /mcp. - Health endpoint at
GET /healthz. - Read-only Anchor tools:
anchor_list_notes,anchor_search_notes,anchor_get_note,anchor_list_tags,anchor_list_attachments. - Optional MCP bearer guard using
ANCHOR_MCP_TOKEN. - Anchor API calls use
ANCHOR_TOKENandANCHOR_BASE_URL. - Dockerfile is included.
Write tools are intentionally not implemented yet.
Development
On NixOS, use nix-shell for Node/npm commands:
nix-shell -p nodejs --run 'npm install'
nix-shell -p nodejs --run 'npm run typecheck'
nix-shell -p nodejs --run 'npm run build'
Run locally:
ANCHOR_BASE_URL=https://anchor.cri.su \
ANCHOR_TOKEN=... \
ANCHOR_MCP_TOKEN=... \
nix-shell -p nodejs --run 'npm run dev'
The MCP endpoint is http://localhost:8000/mcp. If ANCHOR_MCP_TOKEN is set, callers must send Authorization: Bearer <token>.
Deployment Model
The intended stack has three services:
services:
anchor:
# Existing Anchor service.
anchor-mcp:
build: /path/to/anchor-mcp
environment:
ANCHOR_BASE_URL: http://anchor:3000
ANCHOR_TOKEN: ${ANCHOR_TOKEN}
ANCHOR_MCP_TOKEN: ${ANCHOR_MCP_TOKEN}
expose:
- "8000"
depends_on:
- anchor
chatgpt-tunnel-client:
# Outbound tunnel client.
environment:
MCP_TARGET_URL: http://anchor-mcp:8000/mcp
MCP_TARGET_TOKEN: ${ANCHOR_MCP_TOKEN}
depends_on:
- anchor-mcp
The MCP server should only be reachable on the Docker network. The tunnel client is the only external bridge.
Confirmed Anchor API Surface
All endpoints below are guarded by Anchor's AuthGuard and expect Authorization: Bearer <token>. The guard accepts Anchor tokens that resolve to an active user.
Notes:
POST /api/notesGET /api/notes?search=<query>&tagId=<tagId>&limit=<limit>GET /api/notes/:idPATCH /api/notes/:idDELETE /api/notes/:idDELETE /api/notes/:id/permanentPATCH /api/notes/:id/restoreGET /api/notes/trashGET /api/notes/archivePOST /api/notes/bulk/deletePOST /api/notes/bulk/archivePOST /api/notes/bulk/pinPOST /api/notes/bulk/tags
Tags:
POST /api/tagsGET /api/tagsGET /api/tags/:idGET /api/tags/:id/notesPATCH /api/tags/:idDELETE /api/tags/:id
Attachments:
POST /api/notes/:noteId/attachmentsGET /api/notes/:noteId/attachmentsGET /api/notes/:noteId/attachments/:idDELETE /api/notes/:noteId/attachments/:idPATCH /api/notes/:noteId/attachments/reorder
Import/export:
POST /api/import/notesPOST /api/import/notes/:noteId/attachmentsGET /api/export
Sync API:
POST /api/syncGET /api/sync/eventsas server-sent events
Sharing:
POST /api/notes/:id/sharesGET /api/notes/:id/sharesPATCH /api/notes/:id/shares/:shareIdDELETE /api/notes/:id/shares/:shareId
The MCP server should start with normal notes/tags/attachments/import endpoints. The sync API is useful for conflict-aware offline clients, but an MCP sidecar can avoid it initially.
Data Shapes
Create note body:
{
"title": "string",
"content": "optional string",
"isPinned": false,
"isArchived": false,
"background": "optional string",
"tagIds": ["tag-id"]
}
Update note body is a partial create body plus optional optimistic lock:
{
"title": "optional string",
"content": "optional string",
"isPinned": false,
"isArchived": false,
"background": "optional string",
"tagIds": ["tag-id"],
"baseVersion": 1
}
Anchor returns transformed notes with these important fields:
{
"id": "uuid",
"title": "string",
"content": "string or null",
"version": 1,
"isPinned": false,
"isArchived": false,
"background": null,
"state": "active",
"createdAt": "iso timestamp",
"updatedAt": "iso timestamp",
"userId": "uuid",
"tagIds": ["tag-id"],
"permission": "owner",
"attachmentCount": 0,
"imagePreviewIds": []
}
Import notes body:
{
"notes": [
{
"ref": "external stable reference, max 256 chars",
"id": "optional uuid",
"title": "string",
"content": "stringified Quill Delta JSON",
"isPinned": false,
"isArchived": false,
"isTrashed": false,
"background": "optional background id",
"tagNames": ["tag name"],
"createdAt": "iso timestamp",
"updatedAt": "iso timestamp"
}
],
"tags": [{ "name": "tag", "color": "#8B5CF6" }],
"skipExisting": true
}
Import result shape:
{
"results": [
{
"ref": "external reference",
"status": "created | skipped | remapped | failed",
"noteId": "uuid",
"warning": "optional string",
"error": "optional string"
}
],
"tags": { "created": 0, "reused": 0 }
}
Attachment upload shapes:
- Normal note upload: multipart
filefield toPOST /api/notes/:noteId/attachments. - Import attachment upload: multipart
filepluspositionform field toPOST /api/import/notes/:noteId/attachments. - Attachment response includes
id,noteId,type,originalFilename,mimeType,fileSize,position,uploadedByUserId, andcreatedAt.
Limits And Validation
Notes list limit:
GET /api/notesclampslimitto1..200.
Bulk limits:
noteIds: max 200.tagIds: max 50.
Import limits:
- Notes per batch: 50.
- Stringified Delta content length: 1,000,000 bytes/chars.
- Title length: 1000.
- Tags per note: 50.
- Tags per import batch: 500.
- Tag name length: 100.
Attachment limits:
- Max file size: 50 MB.
- Allowed images:
image/jpeg,image/png,image/webp,image/gif. - Allowed audio:
audio/mpeg,audio/wav,audio/mp4,audio/x-m4a,audio/ogg,audio/aac,audio/webm. - PDFs, JSON, ZIP, and generic
application/octet-streamare rejected by current source.
Background IDs allowed by import:
color_red,color_orange,color_yellow,color_green,color_teal,color_blue,color_dark_blue,color_purple,color_pink,color_brown.pattern_dots,pattern_grid,pattern_lines,pattern_waves,pattern_groceries,pattern_music,pattern_travel,pattern_code.
Content Format
Anchor stores note content as a string. Existing import work confirms this should be stringified Quill Delta JSON for rich-text import.
The MCP server should expose Markdown-friendly tools and convert Markdown to Quill Delta internally. It can also expose expert-mode native Delta tools later.
Recommended conversion policy:
anchor_create_noteaccepts Markdown, converts to Delta, callsPOST /api/notes.anchor_update_noteaccepts Markdown, converts to Delta, callsPATCH /api/notes/:idwith optionalbaseVersion.anchor_import_notesaccepts Markdown or native Delta, batches throughPOST /api/import/notes.anchor_get_notereturns raw content plus a best-effort text/Markdown projection for LLM readability.
Authentication Model
Anchor source uses bearer-token extraction from Authorization: Bearer <token>. The MCP sidecar should therefore maintain two auth layers:
ANCHOR_TOKEN: token used byanchor-mcpwhen calling Anchor.ANCHOR_MCP_TOKEN: token expected from the tunnel client before any MCP request is served.
The MCP server should never forward arbitrary caller tokens to Anchor.
Source References
Primary files inspected upstream:
server/src/notes/controllers/notes.controller.tsserver/src/notes/controllers/note-attachments.controller.tsserver/src/notes/controllers/note-shares.controller.tsserver/src/tags/tags.controller.tsserver/src/import-export/import.controller.tsserver/src/import-export/export.controller.tsserver/src/sync/sync.controller.tsserver/src/sync/sync-events.controller.tsserver/src/notes/dto/create-note.dto.tsserver/src/notes/dto/update-note.dto.tsserver/src/import-export/dto/import-notes.dto.tsserver/src/import-export/dto/import-attachment.dto.tsserver/src/notes/constants/notes.constants.tsserver/src/import-export/constants/import.constants.tsserver/src/notes/utils/note-transformer.util.tsserver/src/notes/utils/attachment-storage.util.ts
MCP Tools
Phase 1 read tools:
anchor_list_notes(limit, offset)anchor_search_notes(query, limit)anchor_get_note(note_id)anchor_list_tags()anchor_list_attachments(note_id)
Implemented tool details:
anchor_list_notessupportslimit,offset,include_content, andtag_id. Because Anchor only exposes limit-based listing,offset + limitmust be at most 200.anchor_search_notessupportsquery,limit,include_content, andtag_id.anchor_get_notesupportsnote_idandinclude_content.anchor_list_tagstakes no input.anchor_list_attachmentsreturns metadata only and does not download attachment bytes.
Phase 2 write tools:
anchor_create_note(title, markdown)anchor_update_note(note_id, markdown, base_version)anchor_import_notes(notes)anchor_create_tag(name, color)anchor_upload_attachment(note_id, file, filename, mime_type)
Phase 3 management tools:
anchor_archive_notes(note_ids)anchor_pin_notes(note_ids, is_pinned)anchor_add_tags(note_ids, tag_ids)anchor_export()if the tunnel client can handle a streamed archive.
Avoid or gate destructive tools:
anchor_delete_note(note_id, confirm)maps to soft delete and should requireconfirm=true.anchor_permanent_delete_note(note_id, confirm)should be omitted initially.anchor_delete_tag(tag_id, confirm)should be omitted initially.- Do not expose a raw arbitrary HTTP proxy tool.
Security
- Store
ANCHOR_TOKENonly in the Docker stack environment or.env; do not bake it into the image. - Add a separate
ANCHOR_MCP_TOKENfor calls from the tunnel client toanchor-mcp. - Bind the MCP server to the container network only; do not add Traefik labels unless intentionally exposing it.
- Keep tools narrow and typed. Do not allow callers to choose arbitrary Anchor API paths.
- Log request metadata, not note content or tokens.
- Default to read-only tools until the tunnel auth path is verified.
- Require explicit
confirm=truefor soft-delete and bulk destructive actions. - Refuse permanent deletion unless a separate
ENABLE_DANGEROUS_TOOLS=truesetting is present.
Implementation Phases
- Create a minimal TypeScript MCP HTTP server.
- Add configuration from environment:
ANCHOR_BASE_URL,ANCHOR_TOKEN,ANCHOR_MCP_TOKEN, bind host/port. - Implement
/healthzfor Docker and tunnel diagnostics. - Implement a small Anchor API client with typed methods and no arbitrary path escape hatch.
- Implement
anchor_list_notes,anchor_search_notes,anchor_get_note, andanchor_list_tags. - Add response shaping that strips heavy fields unless explicitly requested.
- Implement Markdown-to-Delta conversion helpers and tests.
- Implement create/update with optional optimistic locking via
baseVersion. - Implement import batching with the known import limits.
- Implement attachment upload for allowed images/audio only.
- Add Dockerfile and Compose example including the tunnel client placeholder.
- Add tests with mocked Anchor responses and validation failures.
- Add operational docs for rotating tokens and wiring the ChatGPT tunnel client.
Open Questions
- Exact tunnel-client image, environment variables, and auth header format.
- Whether Anchor can be configured or patched to allow PDFs and other file types.
- Whether note content should be accepted as Markdown and converted to Quill Delta, or whether the MCP should expose Anchor's native content format directly.
- Whether the tunnel client can pass binary payloads well enough for attachment upload and export download.
- Whether
offsetshould be simulated client-side becauseGET /api/notesonly exposeslimit, not offset pagination.
Recommended First Milestone
Build a read-only MCP server with anchor_list_notes, anchor_search_notes, anchor_get_note, and anchor_list_tags. Deploy it privately in the Anchor stack behind the tunnel client. Add create/update/import only after the read path and authentication model are verified.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.