Skip to main content

MCP & OAuth Integration

Inkweld exposes creative writing project data to AI assistants through the Model Context Protocol (MCP). Authentication is handled via OAuth 2.1 with PKCE, enabling secure third-party access to user projects.

Architecture

AI Client (Claude, etc.)

├── Discovery: GET /.well-known/oauth-protected-resource
│ └── Finds authorization_servers → AS metadata

├── DCR: POST /oauth/register
│ └── Registers as OAuth client (one-time)

├── Auth: Browser redirect → /oauth/authorize
│ └── User selects projects + grants consent

├── Token: POST /oauth/token (PKCE exchange)
│ └── Receives JWT access_token + refresh_token

└── MCP: POST /api/v1/ai/mcp (Streamable HTTP)
└── JSON-RPC with Bearer token

MCP Endpoint

PropertyValue
URL/api/v1/ai/mcp
TransportStreamable HTTP (stateless, POST for JSON-RPC)
Protocol Version2026-07-28
AuthBearer token (OAuth JWT or legacy API key)

The server implements the stateless MCP protocol (2026-07-28): modern clients have no initialize handshake and no Mcp-Session-Id header. Every request carries its protocol version, client info, and client capabilities in _meta, and the server identifies itself in each result's _meta. GET/DELETE on the endpoint return 405 Method Not Allowed. Stateless 2026-07-28 clients should call server/discover first to learn the supported protocol versions and capabilities.

For backward compatibility, the server is dual-era: legacy clients (those using the pre-stateless initialize handshake, e.g. MCP Inspector 0.22.0) are still served the classic initialize response with an Mcp-Session-Id header, while requests that carry their protocol version in _meta are served statelessly.

Available Tools

Read Operations

ToolDescription
get_project_treeGet the element tree structure for a project
search_elementsFull-text search across project elements
search_worldbuildingSearch worldbuilding content with optional full data
search_relationshipsFind relationships for a specific element
get_element_fullGet complete element data including worldbuilding
get_document_contentGet prose content from a document element
get_relationships_graphGet all relationships as a graph structure
get_project_metadataGet project metadata and settings
get_publish_plansGet saved publish/export configurations
list_project_mediaList project media files and media:// URLs
list_image_profilesList enabled image profiles and profile IDs for generation
get_media_contentRead media:// content as image, text, or base64

Write Operations

ToolDescription
create_elementCreate new story elements
update_elementModify existing elements
delete_elementRemove elements from the tree
move_elementsMove elements to a new parent
reorder_elementChange element position within siblings
sort_elementsSort children alphabetically
update_worldbuildingUpdate worldbuilding data for an element
update_document_contentReplace prose content of a document element
create_relationshipCreate relationships between elements
delete_relationshipRemove a relationship
tag_elementAdd, remove, or set tags on an element
create_snapshotCreate a snapshot of a document's current state

When creating a worldbuilding entry via create_element, pass schemaId along with type: "WORLDBUILDING" so the element is template-bound (for example, character-v1 or location-v1). If schemaId is omitted, the element is treated as generic worldbuilding.

Image Operations

ToolDescription
apply_imageUnified image tool: generate (AI), upload (base64), or reference existing media — and optionally assign to project cover or element image

Call list_image_profiles first and pass profileId when using source: "generate".

Available Resources

Resources list the projects the user has authorized access to:

  • Project Listinkweld://projects lists all authorized projects
  • Individual Projectsinkweld://project/{user}/{slug} provides project details and available permissions
tip

Use tools like search_elements, add_element, and update_element to work with project content. Pass the project key (e.g., alice/my-novel) as a parameter to specify which project to operate on.

OAuth 2.1 Flow

Discovery Endpoints

EndpointRFCPurpose
/.well-known/oauth-protected-resourceRFC 9728Protected Resource Metadata
/.well-known/oauth-authorization-serverRFC 8414Authorization Server Metadata
/.well-known/openid-configurationOIDCOpenID Connect discovery (fallback)

Supported Standards

  • OAuth 2.1 with mandatory PKCE (S256)
  • Dynamic Client Registration (RFC 7591) — no pre-registration needed
  • Token Rotation — refresh tokens are rotated on each use with a grace period
  • JWT Access Tokens — HS256 signed, contain session and user info

Scopes

ScopeDescription
mcp:toolsAccess to MCP tools
mcp:resourcesAccess to MCP resources
read:projectRead project metadata
read:elementsRead story elements
read:worldbuildingRead worldbuilding data
read:schemasRead element schemas
write:elementsCreate/modify elements
write:worldbuildingModify worldbuilding data

Grant Model

During the consent flow, users select which projects to share and choose an access level per project:

RolePermissions
Viewerread:project, read:elements, read:worldbuilding, read:schemas
EditorAll viewer permissions + write:elements, write:worldbuilding
AdminAll permissions

Users may instead choose "Access all my projects" plus a default access level. In that mode the session (mcp_oauth_sessions.access_all_projects / default_role) expands to every project the user owns (current and future) at the default role, instead of an explicit project_collaborators row per project. Explicit per-project grants are still honoured as overrides where present. This choice is carried from the consent page through the authorization code (mcp_oauth_codes) to the session.

Dual-Runtime Support

The MCP/OAuth system runs on both:

  • Bun (local development) — uses LevelDB for Yjs document storage
  • Cloudflare Workers (production) — uses Durable Objects for Yjs documents

Environment variables (BASE_URL, DATABASE_KEY, etc.) are accessed via c.env on Workers with a process.env fallback for Bun.

Connected Apps Management

Users manage authorized OAuth clients from User Settings → Authorized Apps (also deep-linkable at /settings). Both entry points render the same shared ProjectGrantListComponent used by the consent page, so there is a single implementation of the project + access-level UI:

  • View all connected apps
  • Change access levels per project
  • Toggle "Access all my projects" and set a default access level
  • Add / revoke individual project access
  • Revoke an app entirely

The OAuth consent page and the Authorized Apps management tab both use the shared ProjectGrantListComponent (frontend/src/app/components/project-grant-list/) so project-grant editing stays consistent across the two flows.

Configuration

Required Environment Variables

VariablePurpose
DATABASE_KEYJWT signing secret (min 32 chars)
BASE_URLServer base URL (issuer for JWTs)
ALLOWED_ORIGINSComma-separated frontend URLs (for consent redirect)

Token Lifetimes

TokenLifetime
Access Token1 hour
Refresh Token30 days
Authorization Code5 minutes

Legacy API Key Auth

The MCP endpoint also supports legacy API key authentication (iw_proj_* prefix) for backward compatibility. These keys provide direct access to a single project without the OAuth flow.