MCP: API Key Auth + Admin GraphQL Tools
Date: 2026-05-06
Status: Approved
Problem
MCP currently supports two auth methods: personal tokens (t2g_*) and federation JWT. API keys (used by the Obsidian sync plugin) are not accepted. This means an agent that receives a pre-configured vault zip with an API key cannot use MCP without a separate personal token. Additionally, there is no way for an agent to call admin GraphQL operations through MCP.
Goal
- Accept API keys in the MCP endpoint.
- Add opt-in
graphql_introspectionandgraphql_requestMCP tools, gated behind a per-key flag. - Document the agent-admin workflow in
docs/{en,ru}/user/agent_admin.md.
Design
Auth flow (mcp/endpoint.go)
New detection order:
1. Personal token — Bearer t2g_* or ?token=t2g_* → usertoken.Data in ctx
2. X-API-Key header → db.ApiKey in ctx (new ctx value)
3. Bearer without t2g_ prefix → federation JWT (unchanged)
For API key resolution:
mcp.Envgains a single method:ResolveAPIKey(ctx context.Context, value, action string) (*db.ApiKey, error). The implementation incmd/server/main.gohandles hashing (sha256 → hex, fallback to plaintext for old keys), lookup, UpsertAPIKeyLogAction, UpsertAPIKeyLogIP, and InsertAPIKeyLog internally.- After resolution, store only the admin tools flag:
contextWithMCPAdminTools(ctx, bool)/mcpAdminToolsEnabled(ctx) bool. The full*db.ApiKeyis not kept in context. - Content access level: admin (all notes and subgraphs visible).
DB migration
ALTER TABLE api_keys ADD COLUMN enable_mcp_admin_tools boolean
Default is NULL (false). No NOT NULL constraint — consistent with SQLite nullable boolean pattern in this codebase.
GraphQL tools
Both tools appear in tools/list only when mcpAdminToolsEnabled(ctx) == true. Not available for personal token sessions (out of scope for this iteration).
graphql_introspection(pattern string)
Executes a full __schema introspection, then filters:
- Keep types and root operations (query/mutation fields) whose name matches
pattern(treated as regexp, fallback to substring match). - For each matched item, include all transitively referenced types (input types, output types, field types).
- Returns filtered schema as JSON — grep-like: matched nodes + their context.
graphql_request(query string, variables? object)
Pass-through GraphQL execution:
- Runs
querywith optionalvariablesthrough the internal GraphQL handler. - Executes as admin (full access, no whitelist).
- Returns raw JSON response.
mcp.Env gains: GraphQLIntrospect(ctx context.Context) ([]byte, error) and GraphQLRequest(ctx context.Context, query string, variables map[string]any) ([]byte, error).
Admin UI
Add enableMcpAdminTools field to the ApiKey GraphQL type. Add a mutation to toggle it (reuse or extend existing API key update mutation). The checkbox appears in the API keys admin page.
Documentation
docs/{en,ru}/user/mcp.md — new section "API key authentication" alongside the existing "Personal access tokens" section. Covers:
X-API-Key: <key>header usage- How to enable admin tools (checkbox in API keys settings)
- Two new tools in the Methods table:
graphql_introspection(pattern)andgraphql_request(query, variables?)with descriptions of what they do and a link toagent_admin.mdfor full context
docs/en/user/agent_admin.md + docs/ru/user/agent_admin.md — new files describing the agent-admin concept:
- An agent receives a pre-configured Obsidian vault zip with the sync plugin already set up with an API key.
- The same key authenticates to MCP — no extra setup needed.
- With
enable_mcp_admin_toolsenabled, the agent can callgraphql_introspectionto find relevant operations andgraphql_requestto execute them. - Example scenario: applying frontmatter patches, managing notes, configuring webhooks — without the agent needing to know the admin UI structure upfront.
- Future: GraphQL schema will be enriched with comments and examples so
searchcan find usage examples and the agent can act on them.
Out of scope
- GraphQL schema comments and usage examples (separate task).
graphql_requestaccess for personal token users.- Rate limiting or per-operation whitelisting for
graphql_request.