Skip to main content
Glama
llego
by llego

Anchor MCP

План реализации небольшого MCP-сайдкара, который предоставляет безопасные инструменты Anchor Notes для ChatGPT через туннельный клиент, работающий в том же Docker Compose стеке, что и Anchor.

Основа исследования: вышестоящий репозиторий Anchor ZhFahim/anchor, ветка по умолчанию main, проверено 2026-08-20. Anchor — это бэкенд на Nest.js с аутентифицированными REST-эндпоинтами в /api/*.

Цель

Запустить MCP-сервер рядом с Anchor, чтобы внешние ассистенты могли просматривать, искать, читать, создавать, обновлять, импортировать заметки Anchor и прикреплять к ним файлы, не раскрывая напрямую базу данных Anchor или его приватный API.

Related MCP server: NotesBridge

Текущий статус

Первый этап реализован:

  • Эндпоинт Streamable HTTP MCP на POST /mcp.

  • Эндпоинт проверки состояния на GET /healthz.

  • Инструменты Anchor только для чтения: anchor_list_notes, anchor_search_notes, anchor_get_note, anchor_list_tags, anchor_list_attachments.

  • Опциональная MCP-защита через bearer-токен с использованием ANCHOR_MCP_TOKEN.

  • Вызовы Anchor API используют ANCHOR_TOKEN и ANCHOR_BASE_URL.

  • Dockerfile включён.

Инструменты записи намеренно пока не реализованы.

Разработка

На NixOS используйте nix-shell для команд Node/npm:

nix-shell -p nodejs --run 'npm install'
nix-shell -p nodejs --run 'npm run typecheck'
nix-shell -p nodejs --run 'npm run build'

Локальный запуск:

ANCHOR_BASE_URL=https://anchor.cri.su \
ANCHOR_TOKEN=... \
ANCHOR_MCP_TOKEN=... \
nix-shell -p nodejs --run 'npm run dev'

MCP-эндпоинт: http://localhost:8000/mcp. Если задан ANCHOR_MCP_TOKEN, вызывающие стороны должны отправлять Authorization: Bearer <token>.

Модель развёртывания

Предполагаемый стек состоит из трёх сервисов:

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

MCP-сервер должен быть доступен только в Docker-сети. Туннельный клиент — единственный внешний мост.

Подтверждённая поверхность Anchor API

Все эндпоинты ниже защищены AuthGuard в Anchor и ожидают Authorization: Bearer <token>. Guard принимает токены Anchor, которые соответствуют активному пользователю.

Заметки:

  • POST /api/notes

  • GET /api/notes?search=<query>&tagId=<tagId>&limit=<limit>

  • GET /api/notes/:id

  • PATCH /api/notes/:id

  • DELETE /api/notes/:id

  • DELETE /api/notes/:id/permanent

  • PATCH /api/notes/:id/restore

  • GET /api/notes/trash

  • GET /api/notes/archive

  • POST /api/notes/bulk/delete

  • POST /api/notes/bulk/archive

  • POST /api/notes/bulk/pin

  • POST /api/notes/bulk/tags

Теги:

  • POST /api/tags

  • GET /api/tags

  • GET /api/tags/:id

  • GET /api/tags/:id/notes

  • PATCH /api/tags/:id

  • DELETE /api/tags/:id

Вложения:

  • POST /api/notes/:noteId/attachments

  • GET /api/notes/:noteId/attachments

  • GET /api/notes/:noteId/attachments/:id

  • DELETE /api/notes/:noteId/attachments/:id

  • PATCH /api/notes/:noteId/attachments/reorder

Импорт/экспорт:

  • POST /api/import/notes

  • POST /api/import/notes/:noteId/attachments

  • GET /api/export

Sync API:

  • POST /api/sync

  • GET /api/sync/events как server-sent events

Совместный доступ:

  • POST /api/notes/:id/shares

  • GET /api/notes/:id/shares

  • PATCH /api/notes/:id/shares/:shareId

  • DELETE /api/notes/:id/shares/:shareId

MCP-сервер должен начинать с обычных эндпоинтов заметок/тегов/вложений/импорта. Sync API полезен для офлайн-клиентов с учётом конфликтов, но MCP-сайдкар может на первых порах его не использовать.

Формы данных

Тело создания заметки:

{
  "title": "string",
  "content": "optional string",
  "isPinned": false,
  "isArchived": false,
  "background": "optional string",
  "tagIds": ["tag-id"]
}

Тело обновления заметки — это частичное тело создания плюс опциональная оптимистичная блокировка:

{
  "title": "optional string",
  "content": "optional string",
  "isPinned": false,
  "isArchived": false,
  "background": "optional string",
  "tagIds": ["tag-id"],
  "baseVersion": 1
}

Anchor возвращает преобразованные заметки со следующими важными полями:

{
  "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": []
}

Тело импорта заметок:

{
  "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
}

Форма результата импорта:

{
  "results": [
    {
      "ref": "external reference",
      "status": "created | skipped | remapped | failed",
      "noteId": "uuid",
      "warning": "optional string",
      "error": "optional string"
    }
  ],
  "tags": { "created": 0, "reused": 0 }
}

Формы загрузки вложений:

  • Обычная загрузка вложения: multipart-поле file на POST /api/notes/:noteId/attachments.

  • Загрузка вложения при импорте: multipart-поле file плюс поле формы position на POST /api/import/notes/:noteId/attachments.

  • Ответ о вложении включает id, noteId, type, originalFilename, mimeType, fileSize, position, uploadedByUserId и createdAt.

Лимиты и валидация

Лимит списка заметок:

  • GET /api/notes ограничивает limit значениями 1..200.

Массовые лимиты:

  • noteIds: максимум 200.

  • tagIds: максимум 50.

Лимиты импорта:

  • Заметок в пакете: 50.

  • Длина строкового содержимого Delta: 1 000 000 байт/символов.

  • Длина заголовка: 1000.

  • Тегов на заметку: 50.

  • Тегов на пакет импорта: 500.

  • Длина имени тега: 100.

Лимиты вложений:

  • Максимальный размер файла: 50 МБ.

  • Разрешённые изображения: image/jpeg, image/png, image/webp, image/gif.

  • Разрешённый аудио: audio/mpeg, audio/wav, audio/mp4, audio/x-m4a, audio/ogg, audio/aac, audio/webm.

  • PDF, JSON, ZIP и универсальный application/octet-stream отклоняются текущим исходным кодом.

Фоновые идентификаторы, разрешённые при импорте:

  • 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.

Формат содержимого

Anchor хранит content заметки как строку. Существующая работа по импорту подтверждает, что для импорта форматированного текста это должен быть строковый JSON Quill Delta.

MCP-сервер должен предоставлять инструменты, дружественные к Markdown, и внутренне преобразовывать Markdown в Quill Delta. Позже он также сможет предоставлять нативные инструменты Delta для экспертного режима.

Рекомендуемая политика преобразования:

  • anchor_create_note принимает Markdown, преобразует в Delta, вызывает POST /api/notes.

  • anchor_update_note принимает Markdown, преобразует в Delta, вызывает PATCH /api/notes/:id с опциональным baseVersion.

  • anchor_import_notes принимает Markdown или нативный Delta, пакетирует через POST /api/import/notes.

  • anchor_get_note возвращает исходное содержимое плюс проекцию текста/Markdown по мере возможности для читаемости LLM.

Модель аутентификации

Исходный код Anchor извлекает bearer-токен из Authorization: Bearer <token>. Поэтому MCP-сайдкар должен поддерживать два уровня аутентификации:

  • ANCHOR_TOKEN: токен, который anchor-mcp использует при вызовах Anchor.

  • ANCHOR_MCP_TOKEN: токен, ожидаемый от туннельного клиента перед обработкой любого MCP-запроса.

MCP-сервер никогда не должен передавать произвольные токены вызывающих сторон в Anchor.

Ссылки на исходный код

Основные проверенные файлы в вышестоящем репозитории:

  • server/src/notes/controllers/notes.controller.ts

  • server/src/notes/controllers/note-attachments.controller.ts

  • server/src/notes/controllers/note-shares.controller.ts

  • server/src/tags/tags.controller.ts

  • server/src/import-export/import.controller.ts

  • server/src/import-export/export.controller.ts

  • server/src/sync/sync.controller.ts

  • server/src/sync/sync-events.controller.ts

  • server/src/notes/dto/create-note.dto.ts

  • server/src/notes/dto/update-note.dto.ts

  • server/src/import-export/dto/import-notes.dto.ts

  • server/src/import-export/dto/import-attachment.dto.ts

  • server/src/notes/constants/notes.constants.ts

  • server/src/import-export/constants/import.constants.ts

  • server/src/notes/utils/note-transformer.util.ts

  • server/src/notes/utils/attachment-storage.util.ts

Инструменты MCP

Инструменты чтения, фаза 1:

  • anchor_list_notes(limit, offset)

  • anchor_search_notes(query, limit)

  • anchor_get_note(note_id)

  • anchor_list_tags()

  • anchor_list_attachments(note_id)

Детали реализованных инструментов:

  • anchor_list_notes поддерживает limit, offset, include_content и tag_id. Поскольку Anchor предоставляет только листинг на основе лимита, offset + limit должно быть не более 200.

  • anchor_search_notes поддерживает query, limit, include_content и tag_id.

  • anchor_get_note поддерживает note_id и include_content.

  • anchor_list_tags не принимает входных данных.

  • anchor_list_attachments возвращает только метаданные и не скачивает байты вложений.

Инструменты записи, фаза 2:

  • 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)

Инструменты управления, фаза 3:

  • anchor_archive_notes(note_ids)

  • anchor_pin_notes(note_ids, is_pinned)

  • anchor_add_tags(note_ids, tag_ids)

  • anchor_export(), если туннельный клиент может обработать потоковый архив.

Избегать или ограничивать деструктивные инструменты:

  • anchor_delete_note(note_id, confirm) соответствует мягкому удалению и должен требовать confirm=true.

  • anchor_permanent_delete_note(note_id, confirm) следует на первых порах опустить.

  • anchor_delete_tag(tag_id, confirm) следует на первых порах опустить.

  • Не предоставлять сырой произвольный HTTP-прокси-инструмент.

Безопасность

  • Храните ANCHOR_TOKEN только в окружении Docker-стека или в .env; не встраивайте его в образ.

  • Добавьте отдельный ANCHOR_MCP_TOKEN для вызовов от туннельного клиента к anchor-mcp.

  • Привяжите MCP-сервер только к контейнерной сети; не добавляйте метки Traefik, если только не планируется намеренное раскрытие.

  • Держите инструменты узкими и типизированными. Не позволяйте вызывающим сторонам выбирать произвольные пути Anchor API.

  • Логируйте метаданные запросов, а не содержимое заметок или токены.

  • По умолчанию используйте инструменты только для чтения, пока путь туннельной аутентификации не будет проверен.

  • Требуйте явный confirm=true для мягкого удаления и массовых деструктивных действий.

  • Отказывайте в необратимом удалении, если только не задана отдельная настройка ENABLE_DANGEROUS_TOOLS=true.

Этапы реализации

  1. Создать минимальный TypeScript MCP HTTP-сервер.

  2. Добавить конфигурацию из окружения: ANCHOR_BASE_URL, ANCHOR_TOKEN, ANCHOR_MCP_TOKEN, хост/порт привязки.

  3. Реализовать /healthz для диагностики Docker и туннеля.

  4. Реализовать небольшой клиент Anchor API с типизированными методами и без произвольного обходного пути по путям.

  5. Реализовать anchor_list_notes, anchor_search_notes, anchor_get_note и anchor_list_tags.

  6. Добавить формирование ответов, которое отбрасывает тяжёлые поля, если они явно не запрошены.

  7. Реализовать вспомогательные функции преобразования Markdown в Delta и тесты.

  8. Реализовать создание/обновление с опциональной оптимистичной блокировкой через baseVersion.

  9. Реализовать пакетный импорт с учётом известных лимитов импорта.

  10. Реализовать загрузку вложений только для разрешённых изображений/аудио.

  11. Добавить Dockerfile и пример Compose, включая заглушку туннельного клиента.

  12. Добавить тесты с моками ответов Anchor и проверками валидации.

  13. Добавить эксплуатационную документацию по ротации токенов и подключению туннельного клиента ChatGPT.

Открытые вопросы

  • Точный образ туннельного клиента, переменные окружения и формат заголовка аутентификации.

  • Можно ли настроить или пропатчить Anchor, чтобы разрешить PDF и другие типы файлов.

  • Следует ли принимать содержимое заметок в Markdown и преобразовывать в Quill Delta, или MCP должен напрямую предоставлять нативный формат содержимого Anchor.

  • Сможет ли туннельный клиент достаточно хорошо передавать бинарные полезные нагрузки для загрузки вложений и скачивания экспорта.

  • Следует ли имитировать offset на стороне клиента, поскольку GET /api/notes предоставляет только limit, а не постраничную навигацию со смещением.

Рекомендуемый первый этап

Создайте MCP-сервер только для чтения с anchor_list_notes, anchor_search_notes, anchor_get_note и anchor_list_tags. Разверните его приватно в стеке Anchor за туннельным клиентом. Добавляйте создание/обновление/импорт только после проверки пути чтения и модели аутентификации.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server for AI agents to read, write, and organize notes in a local-first, human-in-the-loop note-taking app.
    1 npm
    2
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Read-only MCP bridge that exposes secure search and fetch tools over an Obsidian-compatible Markdown vault, enabling ChatGPT to query notes without write access.
    1
    Apache 2.0