plexus について
plexusはAIエージェント向けのセルフホスト可能なナレッジグラフです。情報を型付きエンティティと期間付きの関係として保存し、人はダッシュボードで閲覧し、エージェントはMCP経由で編集できます。
主な機能
- 01型付きエンティティと関係
- 02MCP経由のグラフ書き込み
- 03全文検索と関係の探索
- 04期間付き関係と履歴検索
- 05ユーザー、トークン、権限範囲の管理
- 06グラフ検査とスキル読み込み
活用シーン
- 01セッションをまたぐエージェント記憶の保存
- 02異なるエージェント間の作業引き継ぎ
- 03意思決定と根拠の追跡
- 04研究資料を関連知識として整理
MCP 対応
plexusはMCP経由で15のツールを公開し、エージェントによるエンティティの検索、読み取り、作成、更新、関係管理を可能にします。アクセス範囲はトークン権限で制御されます。
設定
リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。
https://{plexus_host}/mcpclaude mcp add --transport http pcas-io-plexus https://{plexus_host}/mcp{"mcpServers":{"pcas-io-plexus":{"type":"http","url":"https://{plexus_host}/mcp"}}}Cursor で開く{"servers":{"pcas-io-plexus":{"type":"http","url":"https://{plexus_host}/mcp"}}}VS Code で開く{"mcpServers":{"pcas-io-plexus":{"type":"http","url":"https://{plexus_host}/mcp"}}}{"mcpServers":{"pcas-io-plexus":{"serverUrl":"https://{plexus_host}/mcp"}}}コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。
ツール
詳細
plexus(_graph)
AI エージェント向けの型付きオンプレミスナレッジグラフです。人間は読み取り専用、エージェントは書き込み専用で、Model Context Protocol を介してやり取りします。
pcas.io の製品ラインの一部です。

要約
ナレッジ管理は20年もの間、文書中心でした。フォルダー、メモ、Wiki、エディターをめぐる論争などです。plexus はその考え方を逆転させます。
plexus では、すべての情報が concept、decision、fact、project、task、document、skill、…といった型付きエンティティであり、その間は厳密に定義された、時間的に有効なエッジでつながれています。自由形式のテキストも、忘れられたタグも、「あとで整理しよう」もありません。書き込みとリンク作成は、エージェントからのMCP 経由でのみ行われます。ダッシュボードにはグラフが表示されますが、編集ボタンはありません。
これにより、次の3つが実現します。
- エージェントはあなたに尋ねる必要がありません。種類と関係性の規律を厳守しながら、自ら構築します。
- lint、置き換え、来歴の記録がワークフローに組み込まれているため、グラフが古くなりません。
- グラフ全体ではなく、ひとつのアイデアだけを共有できます。パスキーで保護された使い捨てリンクは、最初のクリックで無効になります。
自分のハードウェア上で Docker Compose を使って実行できます。Apache-2.0 ライセンスです。
LLM 向け Wiki と考えてください
従来の Wiki は人間の読者向けに作られています。自由形式の文章、人手で整理されたリンク、手入れをやめた途端に劣化するページツリーが特徴です。LLM をその前に置くと、LLM が最初にするのは要約を頼むことです。
plexus は対象読者を逆転させます。LLM が読み手であり書き手です。各ページは段落ではなく型付きレコードであり、各リンクはハイパーリンクではなく名前付きの関係であり、各編集は Markdown の差分ではなく、アトミックな MCP 呼び出しです。UI にはエージェントが見るものと同じグラフが表示されますが、その整理を行うのはエージェントです。
これは、Andrej Karpathy が"The LLM wiki" gist(2026年4月、5000以上のスター)で描いたパターンと同じです。クエリのたびに生の文書に対してステートレスな RAG を行うのではなく、LLM が知識を構造化され相互参照された Wiki に一度コンパイルし、最新の状態に保ちます。plexus はこのパターンをインフラストラクチャ上で実現します。
| Karpathy の LLM Wiki | plexus |
|---|---|
| 生のソース(変更なし) | エージェントが web_fetch、ファイル I/O、…で読み取る外部文書 |
| Wiki(型付き Markdown、整理済み) | エージェントが MCP 経由で書き込む、グラフ内の型付きエンティティと名前付きエッジ |
スキーマ(CLAUDE.md/AGENTS.md 内の規約) | MCP の境界で適用される list_kinds と list_relations のレジストリ |
| 取り込み → コンパイル → 振り返り → クエリ → lint | search_entities → save_entity/update_entity → link_entities → get_related → lint_graph |
Karpathy はこの gist を、ベンダーに依存しないプロンプトパターンである「アイデアファイル」と呼んでいます。plexus はそれを本番環境向けのバックエンドで実現します。型付きストレージ、楽観的ロック、時間的エッジ、マルチユーザー認証、監査ログを備えています。
これにより、次のことが可能になります。
- セッションをまたいだ単一の信頼できる情報源。 エージェントは会話のたびに開始時にコンテキストを読み込みます。あなたが自分のことを説明し直す必要はありません。
- エージェントをまたいだ単一の信頼できる情報源。 Claude web、Claude Code、Claude Desktop、Le Chat、Gemini など、どのクライアントが接続しても、同じグラフを使います。
- 人間をまたいだ単一の信頼できる情報源。 すぐに使えるマルチユーザー対応、ユーザーごとにスコープを設定したトークン、共有の
projectコンテナ、監査ログを備えています。
目次
理解する
- ユースケース — plexus が想定する具体的なシナリオを7つ紹介します
実行する
- クイックスタート — Docker Compose を2分足らずで起動します
- 管理者ユーザーを作成する(初回起動時) — 管理者トークンの場所も含め、初期設定の手順を説明します
エージェントを接続する
- エージェントの接続 — Claude web/Code/Desktop の設定例です
- エージェントのシステムプロンプトのテンプレート — LLM が plexus を正しく使えるようにする、コピーして貼り付けられるブロックです
- 例:ソースの取り込み — Karpathy 風の LLM wiki への取り込みを、plexus を使って最初から最後まで実演します
リファレンス
- MCP ツール · グラフが把握する内容 · 認証 · 共有 · 検索
運用する
ユースケース
エージェントの永続メモリ。 会話のたびに context_load を実行することで、アシスタントは、すでに決定した ADR、稼働中のプロジェクト、昨日完了したタスクを記憶できます。「ご存じかもしれませんが……」というプロンプトエンジニアリングは不要です。
エージェント間の引き継ぎ。 Claude Code が機能の実装を終え、引き継ぎ用のファクトを書き込みます。翌朝、Claude web は同じコンテキストを引き継ぎ、どのコミットがリリース済みで何が未完了かを把握します。説明し直す必要はありません。
小規模なグループのチームメモリ。 インスタンスを1つ立ち上げ、メンバーごとにスコープを限定した個人用トークンを発行し、project コンテナを共有します。全員が閲覧できるダッシュボードを用意し、エージェントが全員のために書き込みます。Notion も wiki も Slack の過去ログの掘り起こしも不要です。
意思決定の経緯をたどる。 すべての ADR は、supersedes/derived_from エッジを持つ一級の decision エンティティです。1年後には、F-1 エンジンを採用し続けた 理由、Apollo 13 の事後検証で 何が引き起こされたか、どの設計がどれに取って代わったかをたどれます。get_related(as_of: "2026-01-01") を使って、過去の時点を指定したクエリを実行できます。
インシデントから修正までのループ。 本番環境のインシデントを severity: high を持つ fact にします。緩和策を記した ADR から、そのファクトへ triggered_by でリンクします。ランブックから ADR へ documents でリンクします。6か月後に入社した人も、Slack のスレッドを12個読む代わりに、グラフをたどるだけで状況を把握できます。
文献/調査の要約。 エージェントが論文を取り込み、ソースの note を作成し、concept と fact のエンティティを抽出して、mentions エッジを付与します。トピックでグラフを検索でき、すべての主張について、derived_from で元のノートまでたどれます。
エージェントが実際に使う、プライベートな「第二の脳」。 inbox_item で情報を取り込み、週次レビューで task/concept/fact に昇格させ、supersedes でアーカイブします。フォームを操作しなくても、メモを構造化できます。
クイックスタート
# 1. Clone
git clone https://github.com/pcas-io/plexus.git
cd plexus
# 2. Configure — generates .env from the template with fresh 32-byte
# secrets for all four required fields:
# PLEXUS_ADMIN_TOKEN ← you'll use this once, to bootstrap
# PLEXUS_OAUTH_SECRET ← OAuth state signing
# PLEXUS_COOKIE_SECRET ← dashboard session cookie signing
# PLEXUS_SURREAL_PASS ← SurrealDB root password
scripts/bootstrap_env.sh
# 3. Start
docker compose up -d
# 4. Open the dashboard
open http://localhost:8787
初回ログインに必要な管理者トークンは .env にあります。
grep '^PLEXUS_ADMIN_TOKEN=' .env
この16進数の値をコピーしてください。次のセクションで、一度限りの初期設定の手順を説明します。
設定の詳細は.env.exampleを参照してください。Coolify、通常の Docker、Kubernetes へのデプロイに関する注意事項は以下に記載しています。
管理者ユーザーを作成する(初回起動時)
plexus は、ユーザーがいない空のデータベースで起動します。最初の管理者アカウントを作成するために、PLEXUS_ADMIN_TOKEN を一度だけ使用します。その後、トークンは使用されなくなります。
1. 管理者トークンを確認する
クイックスタートで実行した scripts/bootstrap_env.sh スクリプトによって、トークンは .env に書き込まれています。次のコマンドで確認してください。
grep '^PLEXUS_ADMIN_TOKEN=' .env
# PLEXUS_ADMIN_TOKEN=<64-char-hex>
= の後ろにある16進数の値をコピーしてください。ダッシュボードに一度だけ貼り付けます。初期設定後、plexus は MCP と通常のログインの両方で管理者トークンを拒否します。
2. フローを確認する
| # | 実行すること | plexus の動作 |
|---|---|---|
| 1 | http://localhost:8787 を開く | ログインフォームが表示されます |
| 2 | 管理者トークンを貼り付け、続行をクリックします | 「管理者トークンがあり、管理者ユーザーが存在しない」ことを認識し、/bootstrap にリダイレクトします |
| 3 | 管理者名(英字、数字、_、-、.)を入力し、管理者を作成します | 管理者ユーザーを作成し、新しい個人用トークン pt_… を一度だけ表示します。コピーしてください。 |
| 4 | ログインに進むをクリックします | /auth/login に戻り、pending-auth cookie が設定されます |
| 5 | pt_ トークンを貼り付けます | パスキー登録画面が表示されます |
| 6 | 今すぐパスキーを登録をクリックし、Touch ID/Face ID/Windows Hello/YubiKey で確認します | パスキーを保存した後、pt_ トークンをローテーションし、新しいトークンを一度だけ表示します。パスワードマネージャーの登録内容を上書きしてください。 |
| 7 | 新しいトークンでサインインをクリックし、新しいトークンを貼り付けて、パスキーをタップします | /home が表示されます |
以降、管理者トークンは管理プレーンのエンドポイント(バックアップ、リセット)でのみ使用します。MCP では拒否され、通常のダッシュボードへのログインにも使えません。
トークンをローテーションする理由
初期トークン(管理者トークンと最初の pt_)は、一度限りの招待コードです。メール、チャット、画面共有などで傍受された場合でも、サインインした時点でローテーションにより、その情報は無価値になります。/users から作成する追加ユーザーにも、同じ管理者からユーザーへの引き継ぎと初回登録時のローテーションが適用され、同様に保護されます。
次に進むには
ダッシュボード内の /help ページに、MCP の統合、トークンのスコープ設定、パスキーの管理、共有リンク、グラフビューなど、その他のすべての情報が掲載されています。

エージェントを接続する
plexus は POST /mcp 経由で Model Context Protocol を使用します。MCP 対応クライアントであれば、どれでも接続できます。
Claude.ai(ウェブ)
設定 → 統合 → カスタム統合を追加 → https://<your-plexus-host>/mcp。plexus が OAuth を自動で処理するため、同意画面は一度だけ表示されます。
Claude Code(CLI)
claude mcp add plexus --transport http \
--header "Authorization: Bearer pt_YOUR_TOKEN" \
https://<your-plexus-host>/mcp
Claude Desktop
~/Library/Application Support/Claude/claude_desktop_config.json に記述します。
{
"mcpServers": {
"plexus": {
"type": "http",
"url": "https://<your-plexus-host>/mcp",
"headers": { "Authorization": "Bearer pt_YOUR_TOKEN" }
}
}
}
MCP ツール
plexus は 15 個のツールを公開します。
| ツール | 目的 | スコープ |
|---|---|---|
save_entity | 新しいエンティティを作成 | 書き込み |
get_entity | ID で単一のエンティティを取得 | 読み取り |
list_entities | フィルター(kind、context、status)を指定して一覧表示 | 読み取り |
search_entities | 必要に応じてハイライト表示できる、BM25 ランキングによる全文検索 | 読み取り |
update_entity | 楽観的ロック(expected_version)を使用して更新 | 書き込み |
archive_entity | 論理削除(status → archived) | 書き込み |
link_entities | 2 つのエンティティ間に時間的なエッジを作成 | 書き込み |
unlink_entity | エッジを無効化(valid_to = now) | 書き込み |
get_related | 方向と時点フィルターを指定してエッジをたどる | 読み取り |
list_kinds | kind レジストリを照会 | 読み取り |
list_relations | relation レジストリを照会 | 読み取り |
context_load | セッションのウォームアップ — 最新のエンティティ 20 件とレジストリ | 読み取り |
lint_graph | 孤立したエンティティとタイトルの重複を確認 | 読み取り |
list_skills | すべてのスキルの簡潔なインデックス | 読み取り |
load_skill | 名前、トリガーフレーズ、または BM25 フォールバックを指定してスキルを 1 つ読み込む | 読み取り |
すべてのツールはハンドラーの境界でスコープを適用します。requireWrite()、checkContext()、checkKind()、rejectSecret() が使用されます。kind=secret は MCP 経由では到達できず、すべての読み取りレスポンスから除外されます。

エージェントのシステムプロンプトテンプレート
このブロックを、MCP 対応エージェント(Claude のウェブ版カスタム統合、Claude Code CLI、Claude Desktop、Le Chat、カスタム SDK ハーネス)に貼り付けてください。意図的に簡潔にしており、どの行にも役割があります。モデルに依存せず、MCP ツール呼び出しに対応するあらゆる LLM で動作します。
# Plexus is your long-term memory
You have access to **plexus**, a persistent on-prem knowledge graph,
over MCP. Treat it as authoritative memory across sessions. Never
re-derive what the graph already knows; never let the graph drift.
## Every session
1. **Start:** call `context_load` once. Read the 20 most recent
entities and the kinds/relations registries. No other lookups
until you know what's already there.
2. **Before writing:** call `search_entities` (BM25, 2–4 keywords) to
avoid duplicates. If you find a close match, `update_entity` with
`expected_version`; do not create a new one.
3. **Writing new knowledge:** `save_entity` with the right `kind`.
Pick deliberately — kind drives how other agents will query it.
- `concept` — pattern, idea, domain term
- `decision` — an ADR (see "ADR discipline" below)
- `fact` — atomic statement, incident, milestone
- `project` — container that other entities are `part_of`
- `task` — actionable item (`attributes.is_milestone: true` for
milestones)
- `document` — runbook, post-mortem, design doc
- `note` — provenance anchor for an external source (URL, paper)
- `config` / `secret` — URLs, env vars, credentials. Secrets are
MCP-unreadable by design; do not attempt to read them.
- `inbox_item` — GTD quick capture to triage later
4. **Linking:** pick a named relation, not `relates_to`:
`part_of`, `depends_on`, `blocks`, `supersedes`, `documents`,
`derived_from`, `triggered_by`, `implements`, `produces`,
`consumes`, `mentions`, `owned_by`, `has_version`, `variant_of`.
5. **Traversal:** prefer one `get_related` over multiple
`get_entity` calls. Use `as_of` for point-in-time queries.
6. **Graph hygiene:** at the end of a substantial session, call
`lint_graph` and fix orphans and duplicate titles.
## ADR discipline
Every `kind=decision` MUST carry at least one edge —
`derived_from`, `triggered_by`, `supersedes`, or `part_of`. A
free-floating decision is a bug; the next agent won't find it.
## Session handoff
At the end of every non-trivial session, save a handoff-fact:
- `kind: "fact"`
- `attributes.session_type: "handoff"`
- `attributes.session_date: "<ISO date>"`
- `attributes.session_id: "<short slug>"`
- `attributes.agent_id: "<model id>"`
- `attributes.git_branch: "<branch>"` if applicable
- `part_of: "<project-entity-id>"` (required for handoffs, not
optional — server enforces this)
Title format: `"<Project> Handoff <Date> — <Core topic>"`.
## Optimistic locking
Every `update_entity` requires `expected_version`. On
`version_conflict`, re-fetch with `get_entity` and retry; never
blindly overwrite.
## Versioning, not deletion
Never delete by hand. Supersede: create the new entity, link
`supersedes <old>` (1:1), then `archive_entity` the old one.
## Memory routing — when plexus, when local scratchpad
**Goes in plexus (the default):**
- Anything team-, project-, architecture- or decision-relevant.
- Incidents, milestones, post-mortems, ADRs.
- Anything an agent in another session will want to know.
**Keep in a local scratchpad instead:**
- Per-session ephemera (current file paths, interim REPL output).
- Host-local gotchas with no team value (a specific IDE's template
literal trap, one machine's firewall oddity).
**When in doubt, plexus wins.** You can always archive a noisy
entity; you cannot recover one you never wrote.
## Trigger phrases
If the user says any of these, load the matching skill via
`load_skill` before acting:
| Phrase | Skill |
|---|---|
| "ADR" / "decision" | entscheidung / adr |
| "post-mortem" | post-mortem |
| "pre-mortem" | pre-mortem |
| "5 whys" / "root cause" | 5-whys |
| "runbook" | runbook |
| "STRIDE" / "threat model" | stride |
| "weekly review" | weekly-review |
| "meeting notes" | meeting-protokoll |
Skills are `kind=skill` entities in the graph with a trigger-phrase
attribute. `list_skills` gives you the index; `load_skill` returns
the full markdown body.
エージェントの役割に応じて絞り込んでください。 何でもできるトークンを1つ使うのではなく、範囲を限定した個別のトークンを発行します。
- Writer エージェント(Claude Code CLI):
permission=write、すべてのコンテキスト、すべての種類。 - Reader ウィジェット(ダッシュボード、監視ジョブなど):
permission=read、単一のcontexts: [dev]、シークレットなし。 - ドメイン特化型エージェント(例:調査内容の要約エージェント):
permission=write、kinds: [note, concept, fact]のみ。
ダッシュボードの /tokens ページでは、これらのトークンを2回のクリックで生成できます。
例:ソースの取り込み
エージェントが実際に plexus に対して実行するときの、Karpathy 方式の取り込みループを紹介します。ユーザーが記事を渡すと、エージェントは plexus ツールだけを使って LLM wiki パターンの5つのフェーズを進め、1分足らずでグラフを充実させます。
ユーザー:「これを取り込んでください:https://example.com/engineering/feature-flags-at-scale」
1 — 分類と状況確認。 エージェントはソースを取得して article に分類し、重複を作らないよう既存の内容を確認します。
search_entities({ query: "feature flags progressive rollout", limit: 10 })
// → 2 concepts, 1 ADR, 1 fact in the dev context
2 — 抽出。 記事の場合、エージェントは重要な主張、新たな事実、決定事項、既存のエンティティと矛盾するものを抽出します。
3 — 型付きエンティティの書き込み。 意図ごとに種類を分け、重複を作成する代わりに既存のエンティティを更新します。
// New concept — the specific rollout pattern the article introduces
save_entity({
kind: "concept", context: "dev",
title: "Shadow-flag pre-rollout (Acme pattern)",
body: "# Shadow flag pre-rollout\n..."
})
// → entities:ab12...
// New atomic fact with provenance-grade attributes
save_entity({
kind: "fact", context: "dev",
title: "Acme reduced rollout-related incidents by 62% after adopting shadow flags",
attributes: { severity: "info", category: "benchmark", source_year: 2026 }
})
// → entities:cd34...
// Existing concept gets enriched, not duplicated
update_entity({
id: "entities:prev-concept-id",
expected_version: 3,
body: body + "\n\n## Update 2026-04-17\n..."
})
4 — 出典の関連付け。 取り込みごとに、出典を示すアンカーとして note をちょうど1つ作成し、抽出した各エンティティから derived_from でそのノートにリンクします。
save_entity({
kind: "note", context: "dev",
title: "Source: Acme — Feature flags at scale",
body: "## Metadata\nURL, date, author.\n\n## Summary\n2–3 sentences.\n\n## Derived entities\n- Shadow-flag pre-rollout (Acme pattern)\n- Acme reduced rollout-related incidents by 62% ..."
})
// → entities:ef56...
link_entities({ from_id: "entities:ab12...", to_id: "entities:ef56...", relation: "derived_from" })
link_entities({ from_id: "entities:cd34...", to_id: "entities:ef56...", relation: "derived_from" })
link_entities({ from_id: "entities:prev-concept-id", to_id: "entities:ef56...", relation: "mentions" })
5 — 2つの出力。 ユーザーへの返答とグラフの更新の両方を行います。後で済ませる作業にせず、同じターン内で完了します。
エージェントの返答:
Acme のフィーチャーフラグに関する記事を取り込みました。新しいエンティティが2件、既存のコンセプトが1件拡充され、新しいソースノート(
entities:ef56…)へのderived_from/mentionsエッジが3件追加されました。シャドーフラグの事前ロールアウトは既存のロールアウト ADR と矛盾せず、相互に補完しています。
6か月後、誰かがエージェントに「どうしてシャドーフラグを採用したんだっけ?」と尋ねます。エージェントは search_entities({ query: "shadow flag rollout" }) を実行し、該当項目に対して get_related を使ってソースノートと、それを引用した ADR を取得し、生の HTML を読み直す代わりに、整理済みのグラフに基づいて回答します。要約し直す必要はありません。「ご存じかもしれませんが」と前置きする必要もありません。wiki に答えがすでにあります。
グラフが把握する内容
種類
| 種類 | 意味 |
|---|---|
concept | 抽象的な知識、パターン |
decision | アーキテクチャ決定記録 |
fact | 個別の記述、インシデント、マイルストーン |
project | コンテナー |
task | 作業項目(マイルストーンの場合は属性 is_milestone を使用) |
document | 設計書、運用手順書、事後分析、プロトコル |
note | ソース、出典を示すアンカー |
template | 再利用可能なパターン |
config | URL、環境変数 |
secret | 認証情報 — MCP からはアクセス不可。ダッシュボードのみで、追加認証としてパスキーが必要 |
inbox_item | GTD のクイックキャプチャ |
user | ユーザー参照 |
tag | カテゴリー分類 |
skill | トリガーフレーズを含む Markdown スキル。load_skill で読み込み |
関係
contains / part_of · relates_to · depends_on / blocks · supersedes / superseded_by · documents / documented_by · implements · produces / produced_by · consumes · mentions · derived_from · triggered_by · has_version · variant_of · executed_by · owned_by
各エッジには valid_from、valid_to、confidence、source(manual / llm-inferred / computed / imported)が含まれます。unlink_entity は valid_to = now を設定しますが、エッジは履歴として残ります。get_related(as_of: …) を使うと、過去の任意の時点におけるグラフの状態を取得できます。

認証
人間向け:パスキー
ダッシュボードはパスキー専用です。初回ログイン時にトークンとパスキーを使用すると、トークンは自動的にローテーションされます。初期トークンは一度限りの招待コードで、登録が完了すると無効になります。ユーザーごとに最大3つのパスキーを登録できます(ノートPC、スマートフォン、ハードウェアキー)。最後の1つは削除できません。
エージェント向け:パーソナルトークンとOAuth
パーソナルトークン(pt_…)は、ダッシュボードから発行する、権限範囲を明示したファーストパーティのセルフサービス型トークンです。権限範囲は permission × contexts × kinds × expires_in_days で指定します。claude code、CLI による自動化、または長期間有効な認証情報を信頼して渡せる MCP クライアントで使用します。
OAuth 2.1 トークン(ot_…)は、サードパーティの MCP クライアント向けです。plexus は完全な認可サーバーを提供します。
- RFC 8414(認可サーバーメタデータ)および RFC 9728(保護されたリソースのメタデータ)によるディスカバリー。
- 動的クライアント登録(RFC 7591)。
S256による PKCE が必須です。plainはスキーマとコードで拒否されます。- 認可エンドポイントとトークンエンドポイントの両方で、リソースインジケーター(RFC 8707)に対応します。
- リダイレクト URI は定数時間で完全一致比較されます。
- リフレッシュ時にトークンをローテーションします。新しいトークンが発行された時点で古いリフレッシュトークンが失効し、侵害を検知できるようになります。
管理者トークンは /mcp で厳格な 403 エラーとして拒否されます。「とにかく管理者トークンを使う」という抜け道はありません。
共有
グラフ全体を渡さずに、単一のエンティティを読み取り専用の一度限りのリンクとして共有できます。
- ダッシュボードでエンティティを開きます。共有をクリックします。
- 目的に紐付けられたステップアップのパスキープロンプト(
stepup:share:<userId>)が表示されます。共有チャレンジをリセットチャレンジとして再利用することはできません。 - plexus は32バイトの
st_トークンを発行します。保存されるのは SHA-256 ハッシュのみです。URL は一度だけ表示されます。 - 受信者は
GET /share/:tokenを開きます。ログインは不要で、レート制限が適用され、読み取り専用です。 - 2回目の呼び出しでは
410 Goneが返されます。いつでも無効化できます。
共有 URL は1つで、形式は3種類です。
| URL | Content-Type | 用途 |
|---|---|---|
/share/:token | text/html | ブラウザー |
/share/:token?raw | text/markdown | LLM または glow にパイプで渡す |
/share/:token?raw=json | application/json | jq に対応した自動化 |
kind=secret のエンティティは共有できません。このチェックはトークン作成前に行われます。
検索
search_entities は、カスタムアナライザー(plexus_text)を備えた SurrealDB の BM25 インデックスで実行されます。
blankとclassのトークナイザー、lowercaseとasciiのフィルター(Unicode から ASCII への変換により、üはuに、ßはssに一致します)。- 2つのインデックス:
entities_title_searchとentities_body_search。 - ランキング:
search::score(0) * 2 + search::score(1)。タイトルの重みは2倍です。 - 一致箇所は、オプションで
<mark>タグで囲んで強調表示できます。 - 応答サイズを制限するには、オプションの
body_preview_chars(0~2000)を指定します。広範なクエリでは指定を推奨します。

アーキテクチャ
┌──────────────────────────────────────────────┐
│ agents (Claude web / CLI / Desktop / ...) │
└───────────────┬──────────────────────────────┘
│ MCP over HTTPS + Bearer token
▼
┌──────────────────────────────────────────────┐
│ plexus worker (Hono + TypeScript, Node 22) │
│ ├─ POST /mcp — MCP tool dispatcher │
│ ├─ /oauth/* — OAuth 2.1 AS + PRM │
│ ├─ /auth/* — WebAuthn passkey flow │
│ ├─ /share/:token — one-shot read links │
│ ├─ /dashboard — read-only human UI │
│ └─ /admin/backup — portable JSON export │
└───────────────┬──────────────────────────────┘
│ SurrealQL
▼
┌──────────────────────────────────────────────┐
│ SurrealDB v2 (RocksDB, persistent volume) │
│ entities · edges · registries · activity │
└──────────────────────────────────────────────┘
ソースの構成
src/
├── index.ts # HTTP bootstrap, CSP, security headers
├── auth/ # passkeys, sessions, CORS allow-list
├── db/ # SurrealDB client, repositories, util
├── mcp/ # MCP tool handlers, skill tools, dispatcher
├── routes/ # dashboard, oauth, shares, admin, well-known
├── ui/ # layout, styles, pages, markdown renderer
├── backup.ts # portable JSON export
└── version.ts # build-embedded version string
migrations/ # SurrealDB schema in migration order
scripts/ # one-off admin / ops helpers
ファイルサイズの目安となる上限は約700行です。それを超えて大きくなったファイルは、責務ごとに分割されます。
設定
すべての設定は環境変数で行います。デフォルト値を含む一覧は.env.exampleを参照してください。
| 変数 | 必須 | 説明 |
|---|---|---|
PLEXUS_ADMIN_TOKEN | ✓ | 初期管理者トークンです。本番環境ではシークレットマネージャーを使用してください。 |
PLEXUS_OAUTH_SECRET | ✓ | 認可コードと OAuth の state に署名します。 |
PLEXUS_COOKIE_SECRET | ✓ | ダッシュボードのセッションクッキーに署名します。 |
PLEXUS_SURREAL_URL | ✓ | SurrealDB のエンドポイントです。Compose ネットワーク内ではデフォルトで http://surrealdb:8000 になります。 |
PLEXUS_SURREAL_USER | ✓ | SurrealDB の root ユーザーです。 |
PLEXUS_SURREAL_PASS | ✓ | SurrealDB の root パスワードです。 |
PLEXUS_SURREAL_NS | 名前空間です(デフォルトは plexus)。 | |
PLEXUS_SURREAL_DB | データベースです(デフォルトは main)。 | |
PLEXUS_RP_ID | WebAuthn の証明書利用者 ID です。ホスト名と一致している必要があります。デフォルトは localhost です。 | |
PLEXUS_RP_NAME | パスキーのプロンプトに表示される名前です。 | |
PLEXUS_BASE_URL | OAuth メタデータで使用する正規のベース URL です。 | |
PLEXUS_PORT | HTTP の待ち受けポートです(デフォルトは 8787)。 | |
PLEXUS_LOG_LEVEL | debug / info / warn / error です。 |
十分なエントロピーを持つシークレットを生成します:
openssl rand -hex 32
セルフホスティング
Docker Compose(推奨)
同梱の docker-compose.yml は、plexus ワーカーと SurrealDB をまとめて起動します。ワーカーは 127.0.0.1:8787 のみで公開されるため、パブリック DNS レコードの入力ミスによってダッシュボードが誤って公開されることはありません。自分のマシン以外からアクセスする場合は、前段にリバースプロキシを配置してください。
docker compose up -d
docker compose logs -f plexus
Coolify ホストでは、Coolify オーバーレイを重ねて、外部の coolify Traefik ネットワークに接続し、ルーティングラベルを固定します:
docker compose -f docker-compose.yml -f docker-compose.coolify.yml up -d
リバースプロキシの背後に配置する場合
plexus の本番環境では TLS が必要です。ダッシュボードは Secure クッキーを設定するため、localhost 以外でプレーン HTTP を使用すると正常に動作しません。Caddy、Traefik、nginx などのプロキシを使用してください。最小限の Caddy 設定は Caddyfile にあります。
CSP ヘッダーでは、D3 用に cdn.jsdelivr.net を(SRI ハッシュとともに)許可しています。別の CDN 経由でダッシュボードを配信する場合は、src/index.ts の CSP を適宜更新してください。
バックアップ
curl -H "Authorization: Bearer $PLEXUS_ADMIN_TOKEN" \
https://<your-plexus-host>/admin/backup \
> plexus-backup-$(date +%Y-%m-%d).json
エクスポートには、エンティティ、エッジ、種別、リレーション、ユーザーが含まれます(トークンハッシュは含まれません)。セッション、パスキー、OAuth トークン、個人用トークン、共有トークンは含まれません。認証情報とセッション状態はバックアップされません。?include_audit=1 を渡すと、直近 1000 件のアクティビティログ行を別のファイル名で含められます(個人データを含むため、明示的なオプトインが必要です)。
開発
npm ci
docker compose up -d surrealdb
cp .env.example .env # fill in required fields
npm run dev # live reload on :8787
品質ゲート:
npm run typecheck
npm run build
npx vitest run
npm audit --omit=dev --audit-level=moderate
コミット規約、PR に関する要件、レビュー時に適用する設計原則については、CONTRIBUTING.md を参照してください。
セキュリティ
- Markdown は許可リスト方式の URL パーサーでレンダリングされます。リンクには
rel="noopener nofollow ugc"が付与されます。 - 属性ペイロードの上限は 64 KB で、Zod の境界で拒否されます。プロトタイプキー(
__proto__、constructor、prototype)は再帰的に除去されます。 - トークン生成では、剰余バイアスを避けるため、base62 に対する棄却サンプリングを使用します。
- WebAuthn チャレンジでは、機密性の高い操作に目的のバインドを適用し、追加認証を求めます。
- ログインエラー時は、(ユーザー、パスキー、レート制限の)どの要因が失敗した場合も一律に
LOGIN_FAILを返し、ユーザーの存在を特定できないようにします。 kind=secretは MCP 経由ではアクセスできず、共有もできません。ダッシュボードでは管理者のみが、追加認証用パスキーを使ってアクセスできます。
脆弱性は非公開で報告してください。詳細は SECURITY.md を参照してください。セキュリティに関する問題を公開 issue として投稿しないでください。
ライセンス
Apache License 2.0。詳細は LICENSE を参照してください。
plexus と pcas.io は pcas.io プロジェクトの商標です。Apache ライセンスはコードを対象とするものであり、商標権を付与するものではありません。
謝辞
plexus は、SurrealDB、Hono、Model Context Protocol SDK、SimpleWebAuthn、Zod、D3を基盤として構築されています。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。