Repository navigation
Conversation
Add a remote MCP endpoint so MCP clients (Claude Code, Cursor, claude.ai) can read and edit NoteX notes on behalf of a user. - mcp_tokens table, McpTokenService and /user/mcp-tokens routes to create, list and revoke personal tokens. Only the sha256 hash is stored; the plain token is returned once on creation. Tokens have read or write permission and track last usage and tool call count. - POST /mcp serves MCP over Streamable HTTP in stateless mode with `Authorization: Bearer ntx_…`. Tools list_notes, get_note, create_note and update_note call existing routes through fastify.inject() with the token owner's access token, so route policies and validation apply. Read tokens see only list_notes and get_note. - userIdResolver ignores MCP tokens, so other routes don't accept them. - Add @modelcontextprotocol/sdk and bump zod to ^3.25 required by it. - docs/mcp-plan.md tracks the implementation plan and deviations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step 3 (MCP tokens section in notes.web settings) is done on feat/mcp-tokens in notes.web. Record its deviations and the manual checks of the endpoint over real HTTP and of the settings page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 tasks done
Coverage Report
File Coverage |
There was a problem hiding this comment.
🟡 Changes recommended
Concurrent updates can lose edits, and successful MCP responses omit configured CORS headers.
2 open issues
What changed in this PR
Adds a remote MCP endpoint to NoteX’s existing API, allowing clients to read and edit notes using personal tokens.
Changes:
- Adds token creation, revocation, permissions, storage, and usage tracking.
- Implements four MCP tools through existing note routes.
- Adds integration tests, configuration, dependencies, and deployment documentation.
| File | Description |
|---|---|
| yarn.lock | Locks MCP dependencies and updated Zod. |
| src/repository/storage/postgres/orm/sequelize/mcpToken.ts | Implements token persistence and usage tracking. |
| src/repository/storage/mcpToken.storage.ts | Exports token storage. |
| src/repository/mcpToken.repository.ts | Provides token repository operations. |
| src/repository/index.ts | Initializes token storage and repository. |
| src/presentation/mcp/types.ts | Defines tool context and response helpers. |
| src/presentation/mcp/tools/updateNote.ts | Adds replace and append operations. |
| src/presentation/mcp/tools/listNotes.ts | Lists recent, created, and nested notes. |
| src/presentation/mcp/tools/getNote.ts | Returns note content and metadata. |
| src/presentation/mcp/tools/createNote.ts | Creates root or nested notes. |
| src/presentation/mcp/server.ts | Registers tools by token permission. |
| src/presentation/mcp/notexApi.ts | Calls existing authenticated API routes. |
| src/presentation/mcp/noteRef.ts | Resolves note references and builds links. |
| src/presentation/mcp/mcp.test.ts | Tests authentication and MCP tools. |
| src/presentation/mcp/index.ts | Implements authenticated MCP transport. |
| src/presentation/mcp/content.ts | Handles block IDs, tools, and titles. |
| src/presentation/http/schema/McpToken.ts | Defines token validation and serialization. |
| src/presentation/http/router/mcpTokens.ts | Adds token management endpoints. |
| src/presentation/http/router/mcpTokens.test.ts | Tests token lifecycle and ownership. |
| src/presentation/http/middlewares/common/userIdResolver.ts | Separates MCP tokens from access tokens. |
| src/presentation/http/http-api.ts | Registers MCP routes and token schema. |
| src/infrastructure/config/index.ts | Defines MCP web URL configuration. |
| src/domain/service/mcpToken.ts | Issues, hashes, resolves, and revokes tokens. |
| src/domain/index.ts | Initializes the token service. |
| src/domain/entities/mcpToken.ts | Defines token entities and public fields. |
| package.json | Adds MCP SDK and updates Zod. |
| migrations/tenant/0036-mcp-tokens@add-mcp-tokens-table.sql | Creates token table, constraints, and indexes. |
| docs/mcp-plan.md | Documents implementation and deployment decisions. |
| app-config.yaml | Sets the local web URL. |
Review effort: Balanced
- PATCH /note/:notePublicId accepts an optional expectedUpdatedAt. The note is updated only if updated_at still matches, checked in the same UPDATE query (millisecond precision), otherwise the route returns 409. Note history is written after a successful update. - update_note always passes the version it read, so concurrent replaces and appends get a conflict instead of silently losing changes. - /mcp copies headers set by Fastify hooks to the raw response, so CORS headers survive reply.hijack(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Adds a remote MCP endpoint so MCP clients (Claude Code, Cursor, claude.ai custom connectors) can read and edit NoteX notes on behalf of a user. Auth is a personal MCP token passed in the
Authorizationheader, no OAuth for now.The full plan, decisions and deviations are in
docs/mcp-plan.md.MCP tokens
mcp_tokenstable (migration0036): token hash (sha256), 4-char preview,read/writepermission, label, last usage time, tool call count.GET/POST/DELETE /user/mcp-tokens. The plainntx_…token is returned only once on creation; users can revoke only their own tokens.userIdResolverignoresntx_…tokens, so other routes do not accept them./mcpendpointPOST /mcpserves MCP over Streamable HTTP in stateless mode with JSON responses (@modelcontextprotocol/sdk).GETandDELETEreturn 405.list_notes(recent / created / nested in a parent),get_note(by URL or id, including custom hostnames),create_note(root or inside a parent),update_note(replace/append, optionalexpectedUpdatedAtconflict check). No delete, no search.tools[]from the note and user tools.fastify.inject()with the token owner's access token, so route policies and validation apply unchanged.readtokens see onlylist_notesandget_note.Other changes
PATCH /note/:notePublicIdaccepts an optionalexpectedUpdatedAt: the note is updated only if its version still matches (checked in the sameUPDATE), otherwise 409.update_notealways passes it, so concurrent updates can't overwrite each other. Note history is written after a successful update.^3.25.0(required by the SDK).httpApi.mcp.webUrl(defaulthttps://notex.so) for links to notes.Deploy notes
0036-mcp-tokens@add-mcp-tokens-table.sql).httpApi.mcp.webUrlif the web app is not onhttps://notex.so.Related
POST /notewithparentIddoesn't check edit rights on the parent, andcreate_noteuses that route. The fix is on branchfix/check-parent-note-permission.Test plan
yarn typecheck,yarn eslint srcyarn vitest run: 193 passed, incl. newmcpTokens.test.ts(10),mcp.test.ts(19) and a PATCH version conflict test/mcpover real HTTP with curl:initialize,tools/list,create_note,list_notes, 405 on GET, 401 for a revoked token, read token sees two tools🤖 Generated with Claude Code