Goal
Ship a Model Context Protocol server that exposes Orimora's read, write, and link operations to any MCP-capable client — Claude Desktop, Claude Code, Cursor, local models via Ollama. The server is the same code path the editor uses, no scraping, no detour.
Scope for v0.9
- Read:
doc.get,doc.list,doc.search - Write:
doc.create,doc.update,doc.append - Link:
backlink.list,link.add,link.remove - OAuth 2.1 with PKCE for mobile and remote clients
Out of scope (v0.9)
- Real-time collaboration cursors over MCP
- Streaming responses for long docs (> 1 MB)
- Multi-tenant isolation beyond workspace-level
Architecture
The server is a thin layer over the existing service layer. It owns no business logic; it just translates MCP-shaped requests into the same calls the SvelteKit handlers use.
// mcp/handlers/doc.ts export async function docGet(req: DocGetRequest) { const doc = await docService.getById(req.id, { workspace: req.workspace, includeBacklinks: req.backlinks ?? false, }); if (!doc) throw new McpError("not_found", `doc ${req.id} not found`); return { content: doc.content, meta: doc.meta }; }
Tasks
Decisions
D1 — Transport
stdio for local, streamable-http for remote. Rejected pure HTTP+SSE because Cursor's MCP client still requires stdio for the local case. streamable-http is a strict superset.
D2 — Auth model
OAuth 2.1 with PKCE, refresh tokens, short-lived access. The MCP server is a confidential client on the server side, public on the desktop side. Tokens are encrypted at rest with the workspace key, never with the global one.
D3 — Granular permissions
Every tool call carries a scope token. The client requests a subset of read, write, link, search. The user approves per workspace. The server enforces the scope on every call, not just at handshake — so a revoked scope takes effect on the next request, not the next session.
Risks
| Risk | Severity | Mitigation |
|---|---|---|
| Claude Desktop MCP client drift | High | Pin schema version, add conformance tests on every release |
| Latency on cold start (mobile OAuth) | Medium | Pre-warm the token cache, 5 min TTL on the auth handshake |
| Scope escalation via prompt injection | Medium | Strip MCP tool output from context unless explicitly allow-listed |
| Local Ollama transport stdio quirks | Low | Fallback to JSON-RPC over named pipe |
Open questions
- Do we ship a hosted MCP endpoint or require self-host? — leaning self-host, AGPL
- Should
doc.updatebe a patch or a full replace? — patch, with conflict detection - Rate limit per minute or per token? — per token, 60/min default, configurable