o orimora
⌘K
o
Markdown · LF

MCP server — implementation plan

Draft projects/orimora/core/mcp/ updated 12 min ago by om ± 1,420 words · 9.1 KB

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

Spike: MCP SDK evaluation · @modelcontextprotocol/sdk@0.5 om · 3d
Define tool schema (read/write/link) om · 1d
OAuth 2.1 + PKCE handshake om · 2d
Cursor + Claude Desktop integration tests in progress
Local Ollama transport next
Rate limiting + per-scope quotas backlog
Docs site · /guides/mcp-setup backlog

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

RiskSeverityMitigation
Claude Desktop MCP client driftHighPin schema version, add conformance tests on every release
Latency on cold start (mobile OAuth)MediumPre-warm the token cache, 5 min TTL on the auth handshake
Scope escalation via prompt injectionMediumStrip MCP tool output from context unless explicitly allow-listed
Local Ollama transport stdio quirksLowFallback 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.update be a patch or a full replace? — patch, with conflict detection
  • Rate limit per minute or per token? — per token, 60/min default, configurable
/
Insert block
Paragraph
Plain text block
H1
Heading 1
Section title
#
H2
Heading 2
Subsection
##
Task
Checkbox item
- [ ]
{ }
Code block
Fenced code, language tagged
```
Table
Markdown table
| |
Mermaid
Diagram from text
```m
Ask the AI
Inline prompt, no commit
⌘J