# Kybase (npm · kybase-mcp)

Self-hosted Markdown memory for AI agents: notes you can read and edit, with hybrid search.

- Trust score: 68/100 (medium)
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-09-26

## Components

- npm · `kybase-mcp`: 68/100 (this document), [markdown](https://verifymcp.io/servers/kyrzin-kybase/kybase-mcp.md), [page](https://verifymcp.io/servers/kyrzin-kybase/kybase-mcp)

## Channel facts

- Registry: `npm`
- Package: `kybase-mcp`
- Version: `0.2.1`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-09-26.

- **Supply Chain Security**: 98/100
  - No malware found by supply-chain analysis.
  - No known CVEs affecting this package version or its production dependencies.
  - No install/post-install scripts declared.
  - 44 of 124 dependencies flagged as unhealthy.
- **Provenance & Transparency**: 48/100
  - Source repository is publicly reachable at the declared URL.
  - Provenance check failed: no build-provenance attestation is published.
  - Clear OSI-approved license (AGPL-3.0-only).
  - Actively maintained (last published 2 days ago).
  - Publishes a security disclosure policy (SECURITY.md).
- **Schema Quality & AI Usability**: 68/100
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 4628 tokens (~257/item across 18 items; 18 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 0/100
  - Stability not yet verified: not enough scan history yet (needs a 30-day window).
- **Tool Coverage**: 100/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 100% of tool parameters carry a description.
- **Tool Safety**: 75/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - 0 of 2 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation; "delete_note" implies "delete" and declares no destructiveHint at all, which the MCP spec reads as destructive by default.
  - An AI judge read all 19 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

**Unverified: 1 category.** A category scored 0 because we could not verify it: a data source with nothing on this package, evidence we could not reach, or a check we could not run. We only credit what we can confirm.

## Install

### How do I install the Kybase MCP server?

Kybase runs locally as an npm package, launched with npx -y kybase-mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add kyrzin-kybase -- npx -y kybase-mcp
```

### Cursor

```json
{
  "mcpServers": {
    "kyrzin-kybase": {
      "command": "npx",
      "args": [
        "-y",
        "kybase-mcp"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "kyrzin-kybase": {
      "command": "npx",
      "args": [
        "-y",
        "kybase-mcp"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add kyrzin-kybase -- npx -y kybase-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "kyrzin-kybase": {
      "type": "local",
      "command": [
        "npx",
        "-y",
        "kybase-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add kyrzin-kybase --command npx --arg -y --arg kybase-mcp
```

### Hermes

```yaml
mcp_servers:
  kyrzin-kybase:
    command: "npx"
    args: ["-y", "kybase-mcp"]
```

### Netclaw

```json
{
  "McpServers": {
    "kyrzin-kybase": {
      "Transport": "stdio",
      "Command": "npx",
      "Arguments": [
        "-y",
        "kybase-mcp"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add kyrzin-kybase -t stdio -c npx -a -y kybase-mcp
```

### Other

```json
{
  "mcpServers": {
    "kyrzin-kybase": {
      "command": "npx",
      "args": [
        "-y",
        "kybase-mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-09-25 (score 68, 0)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-09-24 (score 68, +11)

- [security improvement] Known CVEs: unverified → pass
- [functional improvement] Dependency health: unverified → 0.84

### 2026-09-23 (score 57)

First indexed and scored.

## MCP tools (18)

### `list_notes` (~332 tokens)

List notes newest first, filtered by folder, tag or date. This is the tool for "what is new" and "what changed lately": search_notes ranks by relevance and never by recency.

created_* is when a note was made, updated_* when its own text last changed — a rename elsewhere rewriting a [[link]] inside it does not count as an edit here. Each row carries content_length, so you can tell a long note from a short one before spending a get_note call. With trashed:true the other filters are ignored.

Input parameters:

- `created_after` (string): ISO timestamp — only notes created at or after this
- `created_before` (string): ISO timestamp — only notes created at or before this
- `folder_id` (string): Filter by folder UUID — that folder itself, not its subfolders
- `folder_path` (string): Same filter by path (e.g. "Projects/Kybase") instead of UUID — that folder itself, not its subfolders
- `limit` (integer): Maximum notes to return per page. Applies in trashed mode too
- `offset` (integer): Skip this many notes — pass back next_offset from the previous page
- `sort` (string): Which date drives the ordering
- `tag` (string): Filter by tag
- `trashed` (boolean): List soft-deleted notes instead of live ones
- `updated_after` (string): ISO timestamp — only notes whose own content actually changed at or after this
- `updated_before` (string): ISO timestamp — only notes whose own content actually changed at or before this

### `get_note` (~373 tokens)

Read one note, by id or title.

\- Long notes come back windowed — 20000 chars by default: check `content_truncated` and pass `next_offset` back as `offset` for the rest. A windowed reply carries `headings` — the H1–H3 outline with character offsets — so name one in `section` to get that heading and its body alone instead of paging through the note.
\- `resolve_links:true` also returns the notes this one links to, id/title only unless you add include_content:true, whose text is capped at 4000 chars — call get_note on an id for the whole of one. Targets that match no note are listed as unresolved.
\- Pass the `updated_at` you read back as `expected_updated_at` when you write. `content_updated_at` is the one that moves only on a real edit to this note: a rename elsewhere rewriting a [[link]] inside it touches `updated_at` but not that.

Input parameters:

- `id` (string): The note's UUID. Live notes only — a trashed note is not found until restore_note brings it back
- `include_content` (boolean): With resolve_links: include full text of linked notes, not just id/title/folder_path
- `limit` (integer): Max characters of content to return
- `offset` (integer): Character offset into content to start from
- `resolve_links` (boolean): Also resolve [[wikilinks]] inside the note one level deep
- `section` (string): Return only this section (heading text or slug, case-insensitive) and its body
- `title` (string): Alternative to id: exact match first, then unique prefix, then unique substring. An ambiguous title comes back as the candidate list to retry with

### `create_note` (~218 tokens)

Create a new note. Embedding is generated automatically in the background. The server instructions' wikilink and tag rules apply: search_notes for the topic first and link the related notes it finds, and call list_tags before coining a new tag.

Input parameters:

- `content` (string): Markdown body. Empty by default, so a note can be created first and filled with append_to_note
- `folder_id`: Folder UUID. Omit or pass null for the vault root; use folder_path instead when you have the path rather than the id
- `folder_path` (string): Folder path (e.g. "Projects/Kybase") as alternative to folder_id
- `tags` (array): Tags for the new note: lowercase, kebab-case, in the language already in use. Call list_tags first and reuse an existing tag where one fits
- `title` (string, required): Unique across live notes, case-insensitively; a clash is refused rather than merged. This is the string other notes link to as [[Title]]

### `update_note` (~261 tokens)

Update note fields. Re-embeds if title or content changed. Updates wikilinks if title changed. The server instructions' wikilink and tag rules apply when substantially rewriting — in particular, call list_tags before coining a new tag. Pass expected_updated_at (the updated_at you read) to be refused instead of overwriting a change made in between.

Input parameters:

- `content` (string): Replaces the whole body. To add to a note use append_to_note, to change part of one use replace_in_note — both leave the rest untouched
- `expected_updated_at` (string): ISO updated_at from when you read the note; refuses the write if it changed since
- `folder_id`: Move the note to this folder; null moves it to the vault root. Omit to leave it where it is
- `id` (string, required): The note's UUID. Live notes only; a note in the trash has to be restored before it can be edited
- `tags` (array): Replaces the entire tag list — anything left out is removed. To add one tag, send the existing tags plus the new one
- `title` (string): New title. Renaming rewrites every [[link]] pointing here in other notes

### `append_to_note` (~359 tokens)

Add text to a note without resending the rest — prefer it over update_note for journals, logs and running lists. A blank line separates your text from what was there. The note is locked for the read-modify-write, so two sessions appending at the same moment keep both additions instead of the later one overwriting the earlier. Re-embeds in the background like any content change.

Input parameters:

- `at` (string): Where the text lands. Defaults to section_end when section is given, else note_end. note_end: the very end. note_start: above the first heading nested under the opening one — NOT offset 0, except on…
- `content` (string, required): Text to add. Trailing whitespace is trimmed and a blank line is inserted before it, so the addition never runs into the preceding paragraph
- `id` (string): The note's UUID. Alternative to title
- `section` (string): Target this section (heading text or slug) instead of the whole note. Valid values come from a search hit's `section`, get_note's `headings`, or a heading line in the note's own text — the slug form…
- `title` (string): Alternative to id; resolved like get_note

### `replace_in_note` (~325 tokens)

Replace exact text in a note without resending the rest. Either find/replace or old_string/new_string — the same pair, either spelling.

For several replacements pass `edits` rather than the singular fields (one lock and one re-embed for the batch, not one per call); the two forms cannot be combined in one call. Edits apply in order, and each `find` is matched against the note as the edits before it already changed it — an earlier edit can create the text a later one needs, or destroy it, so sequence them. If any step's count is wrong the whole batch is refused and the note is left untouched, and the error names the failing index.

Input parameters:

- `edits` (array): Multiple find/replace steps applied in order in a single call — see main description.
- `expected_count` (integer): How many times `find` is expected to occur (default 1). The edit is refused if the real count differs, so a loose `find` cannot quietly rewrite more than intended
- `expected_updated_at` (string): ISO updated_at from when you read the note; refuses the write if it changed since
- `find` (string): Text to replace. Alias: old_string
- `id` (string): The note's UUID. Alternative to title
- `new_string` (string): Alias for replace
- `old_string` (string): Alias for find
- `replace` (string): Replacement text. Alias: new_string
- `title` (string): Alternative to id; resolved like get_note

### `delete_note` (~86 tokens)

Soft-delete a note by id — it disappears from list_notes/search/get_note/the graph, but is recoverable with restore_note for 30 days before being purged for good. Use list_notes with trashed:true to see what's currently in the trash.

Input parameters:

- `id` (string, required): The note's UUID. An unknown or already-trashed id is refused rather than reported as deleted

### `restore_note` (~87 tokens)

Undo delete_note: brings a soft-deleted note back. Errors if the note isn't in the trash (never deleted, already restored, or purged past the retention window), or if a live note has since taken the same title (rename one of them first, then retry).

Input parameters:

- `id` (string, required): UUID of a note currently in the trash — the same id delete_note was given

### `search_notes` (~688 tokens)

Search notes: hybrid (keyword + meaning) by default. Returns ranked excerpts, not whole notes.

\- Use type="text" for an exact identifier, filename, code symbol or quoted phrase; "hybrid" for questions and topics.
\- READ THE SECTION, NOT THE NOTE. A hit's `section` is the heading its excerpt came from — pass that string to get_note's `section` to get that part alone. If a long hit has no `section`, get_note with limit:1000 returns its `headings` outline; pick one and re-read with `section`. For prose without headings, a hit's `excerpt_offset` goes to get_note as `offset`.
\- A hit is a candidate, not an answer. `relevance` is relative to the best hit in this response, so the top result always reads 1.0 — an ordering, not a verdict; `best_score` is that hit's own raw similarity. `matched_by` says which arms found it: semantic_score alone means "about something similar", never that it confirms your question. Quote the excerpt or open the note — a score is never evidence. `exact:true` means the query occurs verbatim in that note; `text_tier` of "or" or "substring" means the strict words missed and a looser pass filled in — recall, not confirmation.
\- Dates here filter, they do not rank. For "what changed lately" use list_notes, which sorts by recency; it is also how you browse by folder or tag, since a query is required here.
\- `has_more` with `next_offset` pages the results.

Input parameters:

- `created_after` (string): ISO timestamp — only notes created at or after this
- `created_before` (string): ISO timestamp — only notes created at or before this
- `explain` (boolean): Include raw per-arm scores and created_at for debugging ranking
- `folder_id` (string): Restrict to notes in this folder itself, not its subfolders
- `folder_path` (string): Same restriction by path (e.g. "Projects/Kybase") instead of UUID — that folder itself, not its subfolders
- `limit` (integer): Hits per page. Prefer has_more with offset over asking for one large page
- `offset` (integer): Skip this many hits — with has_more in the response, how you read past the first page
- `query` (string, required): What to look for: words or a question for hybrid/semantic, an exact identifier, path or phrase for type "text"
- `rerank` (boolean): Set false to skip the cross-encoder and answer from the fused order — several times faster, and not measurably worse. Applies to type "hybrid" only: text and semantic never rerank, and this flag chan…
- `tag` (string): Restrict to notes with this tag
- `type` (string): "hybrid" fuses keyword and meaning-based matching and is the right default; "text" is keyword-only and exact; "semantic" is meaning-only
- `updated_after` (string): ISO timestamp — only notes whose own content actually changed at or after this
- `updated_before` (string): ISO timestamp — only notes whose own content actually changed at or before this

### `indexing_status` (~110 tokens)

Semantic index progress: total/indexed/pending, complete=true when pending is 0. Pending notes are still found by text search; ones never embedded stay out of semantic results until the background pass reaches them.

Also names the active embedding model and the configured similarity cutoff (`semantic_min_similarity`, null when there is none — the default, meaning nothing is refused for being too dissimilar). Cosines are not comparable between models, which is why the model is named rather than left to be guessed from a score.

### `list_tags` (~66 tokens)

Tags in use with the number of notes carrying each, most-used first. The default limit cuts off the one-off tail — a tag used once is not one worth reusing — so raise it to see those.

Input parameters:

- `limit` (integer): Max tags to return, most-used first

### `list_folders` (~109 tokens)

List folders with the full path already resolved — no need to walk parent_id yourself. Pass a folder's own id as parent_id to create_folder/update_folder to nest under it. Sorted by path, so a page is a contiguous slice of the tree read top to bottom; the reply is `{folders, has_more, next_offset?}`.

Input parameters:

- `limit` (integer): Folders per page
- `offset` (integer): Skip this many folders — pass back next_offset from the previous page

### `create_folder` (~66 tokens)

Create a new folder. Optionally nested under a parent.

Input parameters:

- `name` (string, required): Folder name. Unique among its siblings — the same name under a different parent is fine
- `parent_id`: Parent folder UUID. Omit or pass null to create it at the top level

### `update_folder` (~125 tokens)

Rename a folder and/or move it under a different parent (set parent_id to null for top level). Provide at least one of name/parent_id. The response includes the resolved `path` so a rename or move can be confirmed without a follow-up list_folders call.

Input parameters:

- `id` (string, required): UUID of the folder to rename or move
- `name` (string): New name. Must stay unique among this folder's siblings
- `parent_id`: New parent folder UUID; null moves it to the top level. Moving a folder into its own descendant is refused

### `delete_folder` (~98 tokens)

Delete a folder and its full subtree of child folders (cascade). Every note inside — including notes in nested subfolders — is soft-deleted into the trash along with it (see delete_note), recoverable via restore_note within the retention window. To preserve organization instead, move notes/subfolders out first.

Input parameters:

- `id` (string, required): UUID of the folder to delete along with its subfolders. Notes inside are moved to the trash, not destroyed

### `get_backlinks` (~192 tokens)

Notes that link to this one via [[Title]] wikilinks, by id or title. Each comes back as id/title/folder_path plus a snippet around the link; include_content:true returns their full text instead, which is expensive when many notes link here — prefer get_note on the ids you actually want. offset and limit count notes, not characters.

Input parameters:

- `id` (string): UUID of the note whose incoming links you want
- `include_content` (boolean): Return each linking note's full text instead of a snippet around the link. Expensive when many notes link here
- `limit` (integer): Linking notes per page. Several links from the same note count as one
- `offset` (integer): Skip this many linking notes — pass back next_offset from the previous page
- `title` (string): Alternative to id; resolved like get_note (exact, then prefix, then substring)

### `get_neighbors` (~284 tokens)

What is around ONE note in the [[wikilink]] graph, out to `depth` hops: a flat list of titles, no node indices to decode and no whole-vault payload. For the shape of that neighbourhood — which of them link to each other — use get_graph with root_title instead.

Traversal is undirected: a note linking HERE counts as much as one linked FROM here. `links_out`/`links_in` describe the direct relation and appear only at depth 1 — two hops out, "which way does the arrow point" has no answer. Each note appears once, at the shortest depth that reaches it.

These are links people wrote, not similarity: a note on the same subject that nobody linked is not here, and an empty result is a fact about the writing rather than about the topic. Rows carry the title, which is what get_note, get_backlinks and this tool all take.

Input parameters:

- `depth` (integer): Hops to walk. 1 = directly linked notes; each extra hop widens the set fast
- `id` (string): UUID of the note whose surroundings you want
- `limit` (integer): Maximum neighbours to return, nearest depth first. `total` still counts them all
- `title` (string): Alternative to id; resolved like get_note (exact, then prefix, then substring)

### `get_graph` (~388 tokens)

Many notes at once and the edges between them — the heavy graph tool, and the only one that reaches for the whole vault. For one note's surroundings use get_neighbors; for who links to it, get_backlinks.

Nodes are `{t}` (t = title). Edges reference nodes BY POSITION in the `nodes` array: `edges[0] = [2, 5]` means nodes[2] links to nodes[5]. semantic_edges are undirected and come from embedding similarity rather than written links, with the cosine as a third number. `unresolved_links` are [[wikilink]] targets matching no note title — dangling, so they have no node index.

Unscoped this reaches for the ENTIRE vault and stops at max_nodes: `truncated: true` means you hold a recency-ordered prefix, NOT the shape of the vault. Scope with folder_id or root_title+depth rather than raising the cap.

Input parameters:

- `depth` (integer): Hop count for root_title; ignored without it
- `folder_id` (string): Restrict to notes in this folder and its descendant folders
- `include_semantic` (boolean): Include semantic_edges at all
- `max_nodes` (integer): Ceiling on nodes returned, most recently edited first. The reply says `truncated: true` when it applies — scope with folder_id or root_title instead of raising this
- `min_score` (number): Cosine floor for semantic_edges — lower to see more (noisier) edges
- `root_title` (string): Keep only nodes within `depth` wikilink-hops of this note — resolved like get_note: exact, then unique prefix, then unique substring, case-insensitive
- `unresolved_only` (boolean): If true, return only { unresolved_links } without nodes and edges (fast check for broken links)

## Diagnostics

Captured diagnostic sections: Provenance, Dependencies. The full working is on the page: https://verifymcp.io/servers/kyrzin-kybase/kybase-mcp#diagnostics

## Score history

- 2026-09-26: 68
- 2026-09-25: 68
- 2026-09-24: 68
- 2026-09-23: 57

## Common questions

### What is the Kybase MCP server?

Kybase is an MCP server listed in the public MCP registry as io.github.Kyrzin/kybase. Self-hosted Markdown memory for AI agents: notes you can read and edit, with hybrid search. This page covers its npm package (kybase-mcp).

### Is the Kybase MCP server safe to use?

Kybase scores 68 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 26 September 2026. It declares no install or post-install scripts. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the Kybase MCP server expose?

Kybase exposes 18 tools: list_notes, get_note, create_note, update_note, append_to_note, and 13 more. Their descriptions and schemas cost roughly 4,167 tokens of context every time the server is loaded.

### Is the Kybase MCP server still maintained?

Kybase is still listed as active in the MCP registry. We last reached this channel on 26 September 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What licence is the Kybase MCP server under?

Kybase declares the AGPL-3.0-only licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.

## Links

- npm package: https://www.npmjs.com/package/kybase-mcp
- Socket report: https://socket.dev/npm/package/kybase-mcp
- Repository: https://github.com/Kyrzin/kybase
- Website: https://kyrzin.github.io/kybase/
- Changelog RSS feed: https://verifymcp.io/servers/kyrzin-kybase/kybase-mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/kyrzin-kybase/kybase-mcp.json
- HTML version of this page: https://verifymcp.io/servers/kyrzin-kybase/kybase-mcp
