MCP Server
Connect your Relinkia knowledge graph to Claude Desktop or any MCP-compatible AI environment — and let the AI answer from your own notes.
Overview
The Relinkia MCP server exposes your personal knowledge graph as structured memory for AI tools. Claude can query your notes, concepts, and connections directly — without you having to copy-paste content into the conversation.
In standard mode, two tools are available to Claude once the server is connected:
Find notes on a specific topic. Use this for standalone queries unrelated to the current conversation.
Link the current conversation to your existing notes. Use this when the query depends on what has just been discussed.
Privacy by design. Claude never receives your raw note content. It receives short summaries (gists) and structured metadata. The full text of your notes never leaves Relinkia.
Requirements
Thinker (€12/month) or Thinker + BYOK (€5/month). The MCP server is not available on the free plan.
The MCP server becomes genuinely useful once you have at least 50 validated concepts. With fewer concepts, Claude has little to work with.
Claude Desktop, Claude.ai, Claude Code, or any client that supports the Model Context Protocol.
Setup
Claude Desktop
First, generate an API token: Relinkia → Settings → Integrations → Generate MCP token. Then edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"relinkia": {
"command": "npx",
"args": ["-y", "@relinkia/mcp-server"],
"env": {
"RELINKIA_API_TOKEN": "<your-api-token>"
}
}
}
}Claude.ai (Remote MCP)
Claude.ai uses OAuth to connect to Remote MCP servers — no API token needed. Go to Settings → Integrations → Add MCP server and follow these steps:
- Enter
https://mcp.relinkia.app/sseas the server URL. - Click Add — claude.ai automatically initiates the OAuth authorization flow.
- A Relinkia login and consent page opens in your browser.
- Log in to Relinkia (if not already logged in) and click Approve.
- You are redirected back to claude.ai — Relinkia tools are now available.
LangChain, n8n, or custom agents (REST)
The same tools are exposed over a standard REST API for non-MCP clients:
Base URL : https://api.relinkia.app/api/v1/agent/ Auth : Authorization: Bearer <your-api-token> Schema : /api/v1/agent/openapi.json
relinkia_search
Semantic search across your knowledge base. Uses a hybrid pipeline (full-text + vector embeddings + Cohere reranking).
When to use
The user asks to find notes on a specific topic, with no reference to the current conversation. Examples: “What have I written about stoicism?” — “Find my notes on minimalism.”
When NOT to use
If the query refers to what has just been discussed (“Do my notes relate to what we were just talking about?”) — use relinkia_get_context instead.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| q | string | Yes | Natural language search query |
| limit | integer (1–50) | No — default 20 | Maximum number of results |
| type | string (enum) | No | Filter by note type: fleeting, literature, permanent, index, project, synthesis, question |
| status | string (enum) | No | Filter by maturity: draft, active, mature, archived |
relinkia_get_context
Context-aware search enriched by the current conversation. Same pipeline as relinkia_search, but the embedding and reranking are augmented by a conversation summary you provide.
When to use
The query is tied to what has just been discussed. Examples: “Do my notes connect with what we just said?” — “Have I already thought about something like this?”
When NOT to use
If the user simply wants to find notes on a topic without reference to the current conversation — use relinkia_search. It is faster and sufficient for standalone queries.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| q | string | Yes | Natural language search query |
| conversation_context | string (≤ 1200 chars) | No | A short summary of the current conversation — written by the AI, not copy-pasted verbatim. Focuses on intent and themes, not word-for-word exchanges. Never stored after the call. |
| limit | integer (1–20) | No — default 10 | Maximum number of results |
Example call
relinkia_get_context({
q: "productive failure and learning",
conversation_context: "Discussion on rethinking school assessment
as a calibrated stimulus rather than a sanction. Parallel with
Taleb's antifragility applied to education. Question about
graduated exposure to error.",
limit: 5
})Understanding responses
Sources
Both tools return a sources array. Each source includes a title, a URL back to your note, a role, and the date it was last updated. Claude cites these as links — never as verbatim quotes.
"sources": [
{
"title": "Calibrated friction as stimulus",
"url": "https://relinkia.app/note/{id}",
"role": "argument_central",
"last_updated": "2026-02-11"
}
]Possible roles: argument_central, external_source, emerging_connection, counter_argument.
Temporal advisory
If a retrieved note has been superseded by a more recent one, the response includes a temporal_advisory. Claude will surface this before drawing on the older note — to avoid presenting an abandoned position as your current thinking.
"temporal_advisory": "The most recent note on this point (11 February) nuances what you wrote in November. Mention this before relying on the older note."
Disambiguation
When a query matches several distinct clusters without a clear dominant match, relinkia_get_context returns a disambiguation_needed status instead of guessing. Claude will then ask you which thread to follow before answering.
{
"status": "disambiguation_needed",
"interpretations": [
{
"cluster": "Antifragility in finance",
"note_count": 4,
"hint": "Taleb notes, hedging, optionality"
},
{
"cluster": "Antifragility in education",
"note_count": 7,
"hint": "More recent — graduated exposure to error"
}
],
"message_to_display": "You have two distinct threads on
antifragility. Which one should I connect to our discussion?"
}Quotas
Agent calls that trigger AI work draw from the same daily gesture quota as in-app gestures — there is no separate agent quota. Plain note search does not count as a gesture.
| Plan | Gestures / day (shared) | Price |
|---|---|---|
| Free | 15 | €0 |
| Thinker | 50 | €12/month |
| Thinker + BYOK | No daily limit* | €5/month |
* With BYOK, there is no daily limit: only an anti-abuse safety cap of 300 AI actions per day applies. MCP tools do not use your personal Anthropic key: they make no call to Anthropic. Their search and retrieval run on the Relinkia RAG infrastructure (embeddings, vector search, reranking), covered by the platform fee, and the background processing they trigger (concept extraction, discoveries) runs on Relinkia's accounts.
Owner Mode
Owner mode unlocks ten tools for account owners who want to use a personal AI agent to read and write their knowledge base directly. Unlike the standard mode — which returns gists and metadata — owner mode gives the agent access to the full text of notes and the ability to create or update them.
Typical use cases: maintaining a structured knowledge base with an AI assistant, syncing notes from external sources, or running automated pipelines that enrich your graph overnight.
Privacy note
Owner mode is designed for agents acting on your behalf. The full text of your notes is transmitted to the AI. Only use owner mode with agents you control and trust.
Authentication
Owner mode requires a dedicated owner token — separate from the standard API token. Generate it in: Settings → Integrations → Generate owner token.
Pass it as a Bearer token in the Authorization header:
Authorization: Bearer <your-owner-token>
For Claude Desktop or MCP server config, use the RELINKIA_OWNER_TOKEN env variable:
{
"mcpServers": {
"relinkia-owner": {
"command": "npx",
"args": ["-y", "@relinkia/mcp-server"],
"env": {
"RELINKIA_OWNER_TOKEN": "<your-owner-token>"
}
}
}
}The _context block
Every owner tool response carries a _context block. It tells the agent whether the scope exists and which canonical documents it holds, ranked for the current request.
Canonical document. A canonical document is a reference document of the scope: a note with a docKey whose type is synthesis, index or decision, or that is explicitly marked canonical: true. A docKey alone makes a note addressable (get_by_doc_key, update_canonical, supersession), not canonical: an addressable question, journal or contact note is not listed in canonicalDocs. A synthesis or index marked canonical: false is not listed either; a decision is always canonical. Set the status with the canonical parameter of create_note and update_note_metadata.
"_context": {
"scope": { "name": "project-x", "exists": true, "noteCount": 12, "decisionCount": 3 },
"canonicalDocs": [
{ "docKey": "project-x-decisions", "title": "Decisions", "type": "index",
"currentVersion": 2, "updatedAt": "2026-10-17T16:05:00.000Z", "isDecision": false,
"relevanceScore": 0.41, "embeddingStatus": "ready", "pinned": true },
{ "docKey": "project-x-architecture", "title": "Architecture", "type": "synthesis",
"currentVersion": 3, "updatedAt": "2026-10-18T09:12:00.000Z", "isDecision": false,
"relevanceScore": 0.82, "embeddingStatus": "ready" },
{ "docKey": "project-x-sources", "title": "Sources", "type": "index", "currentVersion": 1,
"updatedAt": "2026-10-18T09:40:00.000Z", "isDecision": false,
"relevanceScore": null, "embeddingStatus": "pending" }
],
"canonicalDocsTruncated": false,
"canonicalDocsRanking": "relevance",
"serverTime": "2026-10-18T09:41:02.000Z",
"protocolVersion": "canonical-docs/0.7"
}| Field | Meaning |
|---|---|
| type | Stored type of the note, on every item: synthesis, index, decision, or another type marked canonical. isDecision is kept and is true when the type is decision. |
| pinned | true on the pinned document only: the document the tool acts on directly (see Order of documents below). It is first because of the pin, not because of its score — its relevanceScore stays its own, so compare scores to judge relevance. The field is absent on every other item, and on every item of a call that acts on no particular document (search_notes, list_scopes, get_note_full). |
| canonicalDocsRanking | relevance (semantic ranking against the request) or recency (most recently updated first — used when no document of the scope has a vector yet, or when the request cannot be embedded). |
| relevanceScore | Cosine similarity clamped to [0,1] between the request and the best-matching passage of the document, when the ranking is relevance. A long document is split into passages and scored on the closest one, so a long or old document whose answer lies far from its beginning still comes first. Having more passages, a long document also has more chances to match: length favors it. null when no score was computed — the document has no vector, or the ranking is recency. Null means unknown, never irrelevant. |
| embeddingStatus | pending: indexing not finished (typical right after a write, or when the daily embedding cap is reached). ready: indexed. failed: the last indexing attempt failed; a retry may bring it back to pending, then ready. ready with a null score in a relevance ranking means the document is indexed but its vector is missing and being repaired: judge it by its title. |
| canonicalDocsTruncated | true when at least one canonical document of the scope is not in the list. The list holds at most 20 documents. Up to 200 canonical documents are ranked on each call; beyond that, only the most recently updated ones are ranked. A document left out stays reachable with search_notes or get_by_doc_key. |
| protocolVersion | Version of this block: canonical-docs/0.7. |
Order of documents. In a relevance ranking, documents with a score come first, highest score first; documents without a score follow, most recently updated first — they are never sorted as a score of 0. In a recency ranking, every document is ordered by update date. In both cases, the document the tool addresses directly (the docKey of create_note, get_by_doc_key, update_canonical or update_note_metadata) stays first, with or without a score, and carries pinned: true — a document you have just created is therefore always in the response, even in a scope with more than 20 canonical documents.
Two rankings. search_notes and canonicalDocs answer two different questions. search_notes is a keyword search: it finds the notes of the scope, of any type, that contain the words of the query, ranked by full-text relevance. canonicalDocs lists only the canonical documents, ranked by meaning (semantic similarity) to the request. The two orders differ by design: a note can be first in one and low or absent in the other, and neither order is wrong. Use search_notes for words, names and exact terms, and to go through every match; use canonicalDocs to find the reference document closest in meaning to your task.
The block may be cached for up to 60 seconds: a document can still show pending for that long after its indexing has finished. Every write through the owner tools refreshes it immediately.
get_note_full
Retrieves the complete text content of a note by ID or document key. Returns the full body as a concatenation of all sections — unlike the standard search tools which return only gists.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| noteId | string | One of noteId or docKey | MongoDB ID of the note |
| docKey | string (slug) | One of noteId or docKey | Document key (slug) assigned at creation |
Response
{
"noteId": "...",
"title": "Architecture decisions",
"body": "We chose event sourcing because...",
"scope": "project-x",
"docKey": "project-x-architecture",
"type": "note",
"currentVersion": 3
}search_notes
Keyword (full-text) search across owner notes within a scope — not the semantic ranking of canonicalDocs (see Two rankings above). Returns at most limit results per page (default 20, maximum 50), each with its noteId, title, docKey, score and a gist: the beginning of the note, at most 500 characters, with gistTruncated: true when the note is longer. Use get_note_full to fetch the full body of a specific result.
Pages. count is the number of results in the page. hasMore: true means more results exist: pass nextCursor as cursor, with the same q and scope, to get the next page, without duplicates or gaps as long as the scope does not change. nextCursor is null on the last page. A list that ends with hasMore: true is not the whole answer.
Score and order. The score is relative to the query: 1 for the best match of the query, the others as a fraction of it, between 0 and 1, on a scale fixed by the first page and kept on the following pages. It is not comparable between two queries. The score of search_notes and the relevanceScore of canonicalDocs are two different scales and must not be compared to each other. Results with the same score are ordered by noteId ascending, so the same call always returns the same order.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| q | string | Yes | Full-text search query |
| scope | string (slug) | Yes | Scope to search in. An unknown scope returns an empty list, not an error |
| limit | integer | No | Maximum number of results in the page, at least 1. Default 20, maximum 50: a larger value is reduced to 50. Any other value returns VALIDATION |
| cursor | string | No | Opaque token: the nextCursor of the previous response, unchanged, with the same q and scope. A cursor from another query returns VALIDATION; so does null, the nextCursor of a last page: it means there are no more results |
{
"items": [
{ "noteId": "683e4a2f1c9b...", "title": "Research report", "docKey": "ia-report",
"score": 1, "gist": "The first 500 characters of the note…", "gistTruncated": true },
{ "noteId": "683e4b071d2a...", "title": "Reading notes", "docKey": null,
"score": 0.62, "gist": "Hippocampus and memory consolidation…", "gistTruncated": false }
],
"count": 2,
"limit": 2,
"hasMore": true,
"nextCursor": "eyJ2IjoxLCJvIjoyLCJoIjoi...",
"_context": { ... }
}get_by_doc_key
Retrieves a note by its document key (slug). Useful for deterministic access to well-known notes such as journals, configuration notes, or index pages.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| docKey | string (slug) | Yes | Unique document key assigned at note creation. Must be a slug (lowercase, hyphens, max 80 characters). |
Returns the same format as get_note_full.
create_note
Creates a new note in the owner’s knowledge base. Returns the generated note ID and the type actually stored. Optionally assigns a permanent document key for future deterministic access.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| type | string (enum) | Yes | Declared by the caller and stored as is (no inference). Choose it by who wrote the content and its role: fleeting (a quick capture or unprocessed thought of your own), literature(your notes on, or extracts from, one external source you read — not a list of sources, not your own report), permanent (one idea, written in your own words), index (a list or map that points to other notes or sources: table of contents, reading list, list of sources or references per axis), project (notes tied to a specific project, deliverable or piece of work), synthesis (your own consolidated report or state of the art, drawn from several notes or sources), question (an open question to explore or answer later). Aliases: note (stored as permanent) and source (stored as literature). Decisions are created with create_decision: decision is rejected. |
| scope | string (slug, max 80) | Yes | Domain or project area for this note |
| title | string (max 500) | Yes | Note title |
| body | string | Yes | Note body in plain text or Markdown |
| docKey | string (slug, max 80) | No | Permanent document key. Must be unique across your notes. Enables deterministic access via get_by_doc_key. |
| canonical | boolean | No | Canonical status. Omit it to follow the type: with a docKey, a synthesis or index is canonical, other types are only addressable. true lists a note of another type as canonical and requires a docKey; false keeps a synthesis or index out of canonicalDocs. |
Response
HTTP 201
{ "noteId": "683e4a2f1c9b...", "storedType": "synthesis" }storedType is the type actually stored, after alias resolution: note → permanent, source → literature.
Over MCP, create_note returns the same fields plus the context envelope:
{
"noteId": "683e4a2f1c9b...",
"storedType": "synthesis",
"_context": { "protocolVersion": "canonical-docs/0.7", "scope": { ... }, "canonicalDocs": [ ... ] }
}_context.protocolVersion is canonical-docs/0.7: the version of the context envelope carried by every tool response, distinct from the version of the capabilities descriptor (canonical-docs/0.2).
Errors
| Code | Error | Meaning |
|---|---|---|
| 409 | DUPLICATE_KEY | A note with this docKey already exists. Choose a different key or omit it. |
| 422 | VALIDATION | type is decision: use create_decision; or canonical: true without a docKey. The body names the field and gives a message. Nothing is written. |
| 400 | Bad Request | A parameter does not match its format (for example a scope or docKey outside the slug grammar). Over MCP, the same cases return a VALIDATION error naming the field. |
update_canonical
Replaces the body of an existing note addressable by docKey, canonical or not (the response field canonical gives its effective canonical status). Uses optimistic concurrency: the server rejects the update if the note has been modified since you last read it. This prevents overwriting changes made by another agent or by the user in the app.
How optimistic concurrency works
Every note has a currentVersion counter that increments on every successful update. When you call update_canonical, you pass the version you read as baseVersion. If the server’s version still matches, the update proceeds and the version increments. If it does not match (someone else updated it), the server returns a 409 conflict.
Recommended workflow: (1) get_note_full → record currentVersion → (2) compute new body → (3) update_canonical with baseVersion = currentVersion → (4) on 409, re-read and retry.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| noteId | string | Yes | ID of the note to update |
| baseVersion | integer (≥ 0) | Yes | The currentVersionyou read before editing. Must match the server’s current version. |
| newBody | string | Yes | New canonical content (plain text or Markdown) |
| lastWriter | string | No | Identifies the writing surface (e.g. "claude-code", "claude-ai"). Appears in CONFLICT_STALE_VERSION messages. |
| supersedes | string (slug) | No | docKey of the document this note supersedes. Triggers the supersession chain — the old document gains a supersededBy field pointing to this note. |
Response
HTTP 200
{ "noteId": "683e4a2f1c9b...", "currentVersion": 4, "scope": "project-x",
"docKey": "project-x-architecture", "canonical": true }Errors
| Code | Error | Meaning |
|---|---|---|
| 409 | CONFLICT_STALE_VERSION | The note was modified after you read it. currentVersionin the response body gives the server’s current version — re-read and retry. |
| 404 | NOTE_NOT_FOUND | No note with this ID belonging to your account. |
| 422 | NOT_CANONICAL | The note has no docKey: only a note with a docKey can be updated here, whether or not it is canonical. |
update_note_metadata
Changes the metadata of an existing note without touching its body. The metadata fields are type, to fix a note imported with the wrong type, and canonical, to set whether the note is a canonical document; send one or both in one call. The note keeps its docKey, title, body, scope, links and tags; it gets a new version and a new history entry. Any note of yours can be changed, with or without a docKey, except decisions (a decision is always canonical). MCP only.
It uses the same optimistic concurrency as update_canonical: read the note with get_note_full (currentVersion, type) immediately before writing, and pass that version as baseVersion. Any other field than noteId, baseVersion, type, canonical and lastWriter is refused with VALIDATION.
The new type is part of the indexed text and of every vector point, and it selects how a long note is split: a successful change re-indexes the whole note. Until the indexing is done, the note shows embeddingStatus: pending in _context, where it is pinned first when it is canonical. Requesting the values the note already has writes nothing and returns changed: false with the current version. If the note shows embeddingStatus: failed after a change, do not call the tool again: the type is already written and the indexing is resumed automatically.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| noteId | string | Yes | ID of the note to change |
| baseVersion | integer (≥ 0) | Yes | The currentVersionyou read just before. Must match the server’s current version. |
| type | string | One of type, canonical | New type, same values as create_note: fleeting, literature, permanent, index, project, synthesis, question, and the aliases note and source. Never decision. |
| canonical | boolean or null | One of type, canonical | true: canonical whatever the type (requires a docKey). false: not canonical, still addressable. null: follow the default of the type. Never on a decision. Changing it alone does not re-index the note. |
| lastWriter | string | No | Identifies the writing surface. Appears in CONFLICT_STALE_VERSION messages. |
Response
{
"noteId": "683e4a2f1c9b...",
"currentVersion": 4,
"storedType": "synthesis",
"changed": true,
"canonical": true,
"_context": { "protocolVersion": "canonical-docs/0.7", "scope": { ... }, "canonicalDocs": [ ... ] }
}Errors
| Error | Meaning |
|---|---|
| CONFLICT_STALE_VERSION | The note was modified after you read it. The message gives the current version, the date and the last writer. Nothing is written: re-read the note and confirm with the user — never retry with an incremented version. |
| NOTE_NOT_FOUND | No note with this ID belonging to your account. The same answer is given for a note that belongs to someone else. |
| FORBIDDEN_NOT_OWNER | The caller is not the owner principal. |
| VALIDATION | type is decision or not in the list, the note is a decision (use update_decision), or a field other than the four parameters was sent. The message names the field. Nothing is written. |
list_scopes
Lists all scopes in the knowledge base with their volumetry — number of notes and decisions per scope.
When to use
Call list_scopes before accessing a scope whose name isn’t provided by the user — for example, to discover which scopes exist before searching or writing within one.
Parameters
This tool takes no parameters.
Response
[
{ "scope": "project-x", "noteCount": 12, "decisionCount": 3 },
{ "scope": "architecture", "noteCount": 8, "decisionCount": 1 }
]Decisions
Decisions are a distinct knowledge type from canonical notes. Where a canonical note can be superseded by a newer document, a decision is never superseded — it is only revised. Every revision is appended to a history[] array, preserving the full audit trail.
Use decisions to record choices that need a stable, auditable history — such as architectural decisions, configuration choices, or team agreements. Use canonical notes for knowledge that evolves and may be replaced.
create_decision
Creates a new decision with a stable semantic key. The key is immutable after creation and must be unique within the scope.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| scope | string (slug) | Yes | Domain or project area for this decision |
| decisionKey | string (slug) | Yes | Stable semantic key for this decision (e.g. "auth-protocol"). Immutable after creation. |
| body | string | Yes | Decision content in plain text or Markdown |
| lastWriter | string | No | Identifies the writing surface (e.g. "claude-code"). Stored in the history entry. |
Errors
| Code | Error | Meaning |
|---|---|---|
| 409 | CONFLICT_DUPLICATE_KEY | A decision with this key already exists in the scope. Use update_decision to revise it. |
update_decision
Revises an existing decision. The previous body is appended to history[] — decisions are append-only. Uses optimistic concurrency via baseVersion.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| scope | string (slug) | Yes | Scope containing the decision |
| decisionKey | string (slug) | Yes | Key of the decision to revise |
| body | string | Yes | New decision content |
| baseVersion | integer (≥ 0) | Yes | The currentVersionyou read before editing. Must match the server’s current version. |
| lastWriter | string | No | Identifies the writing surface. Appears in CONFLICT_STALE_VERSION messages. |
Errors
| Code | Error | Meaning |
|---|---|---|
| 409 | CONFLICT_STALE_VERSION | The decision was modified after you read it. Re-read and retry. |
get_decision
Returns the current body of a decision and its full revision history.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| scope | string (slug) | Yes | Scope containing the decision |
| decisionKey | string (slug) | Yes | Key of the decision to retrieve |
Response
{
"decisionKey": "auth-protocol",
"scope": "architecture",
"currentBody": "We use JWT with 1h expiry...",
"currentVersion": 2,
"updatedAt": "2026-07-15T10:23:00Z",
"history": [
{
"version": 1,
"body": "We use session cookies...",
"updatedAt": "2026-06-01T08:00:00Z",
"lastWriter": "claude-code"
}
]
}list_decisions
Lists all decisions in a scope with their current version and last update time.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| scope | string (slug) | Yes | Scope to list decisions from |
Response
[
{
"decisionKey": "auth-protocol",
"currentVersion": 2,
"updatedAt": "2026-07-15T10:23:00Z",
"lastWriter": "claude-code",
"summary": "Authentication protocol choice — JWT vs session cookies"
}
]Capabilities descriptor
Agents can query this endpoint at startup to discover what the server supports and adapt their behaviour accordingly — for example, skipping write calls if writeEnabled is false.
GET /api/ai/capabilities Authorization: Bearer <your-owner-token>
{
"protocolVersion": "canonical-docs/0.2",
"principal": "owner",
"ownerMode": true,
"fullReadEnabled": true,
"writeEnabled": true,
"supportedOperations": ["create_note", "update_canonical", "update_note_metadata", "list_scopes"],
"contextEnvelopeEnabled": true,
"scopeGrammar": "^[a-z0-9]+(-[a-z0-9]+)*$",
"creatableNoteTypes": ["fleeting", "literature", "permanent", "index", "project", "synthesis", "question"],
"noteTypeAliases": { "note": "permanent", "source": "literature" }
}