Nodiom Cloud について
Nodiom Cloudは、AIエージェントが作業を提出し、人がレビューできるMarkdownベースの共有ボードとドキュメントを提供します。Nodiomと同じ構造セレクターを使い、エージェントと人の間で状態を共有し、作業者の記録と承認ステップを備えています。
主な機能
- 01Markdownドキュメントの共有
- 02見出しとチェックリスト項目によるボード
- 03タスクのアトミックな取得
- 04人による承認または差し戻し
- 05エージェントの作業者記録とレビュー通知
活用シーン
- 01複数エージェント間のタスク引き継ぎ
- 02エージェントの作業完了後に内容をレビュー
- 03異なるマシンや人の間でドキュメント状態を共有
MCP 対応
Nodiom Cloudは、Claude、ChatGPT、Cursor、VS Codeなどのクライアントが接続するMCPエンドポイントを提供します。MCPツールは、エージェント間の引き継ぎや人によるレビューを含む、共有ドキュメントとボードのワークフローに対応します。
設定
リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。
https://api.nodiom.md/mcpclaude mcp add --transport http synexiom-labs-nodiom-cloud https://api.nodiom.md/mcp{"mcpServers":{"synexiom-labs-nodiom-cloud":{"type":"http","url":"https://api.nodiom.md/mcp"}}}Cursor で開く{"servers":{"synexiom-labs-nodiom-cloud":{"type":"http","url":"https://api.nodiom.md/mcp"}}}VS Code で開く{"mcpServers":{"synexiom-labs-nodiom-cloud":{"type":"http","url":"https://api.nodiom.md/mcp"}}}{"mcpServers":{"synexiom-labs-nodiom-cloud":{"serverUrl":"https://api.nodiom.md/mcp"}}}コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。
ツール
詳細
nodiom
Markdown の構造化された読み取りと書き込み。
壊れやすい文字列操作を行わずに、AI エージェントがドキュメントの構造を確実に扱えるようにします。
Nodiom は Markdown をツリーとして読み取り、編集します。見出しはブランチ、リスト、段落、表、コードブロックはノードです。DOM をクエリするようにパス形式のセレクターでノードを指定し、ファイルの残りを書き換えたり壊したりすることなく変更できます。
3 つの利用方法
| 内容 | 場所 | |
|---|---|---|
| ライブラリ(このリポジトリ) | ファイルまたは文字列を対象とした、Node.js 向け Markdown の構造化された読み取り/書き込み | npm install @synexiom-labs/nodiom |
| nodiom-mcp | ローカルの Markdown ファイルに対するこれらの操作を AI エージェントに提供する MCP サーバー | Synexiom-Labs/nodiom-mcp |
| Nodiom Cloud | ホスト型サービス。エージェントと人が共有する状態を管理します。AI エージェントが完了した作業を提出し、監督する人が承認または差し戻しを行う、ひとつのボードです | nodiom.md |
Markdown 内の構造化された状態はここで、エージェントと人が共有する状態は Cloud で扱います。3 つすべてが同じエンジンと同じセレクターを使用します。
問題
AI エージェントやその他のプログラムが Markdown ドキュメントを変更する場合、通常はファイル全体を文字列として読み込み、正規表現で該当するセクションを見つけ、新しいテキストを挿入して、書式が維持されていることを期待します。2 つのライターが同時に書き込むと、一方の変更が気づかれないまま失われます。
Nodiom は Markdown をその本来の姿、つまりツリーとして扱います。場所(# Project > ## Tasks > li[contains="c_7f3a"])と操作(読み取り、書き込み、追記、削除)を指定すると、そのノード以外は変更されません。
簡単な例
import { Nodiom } from '@synexiom-labs/nodiom';
const doc = await Nodiom.fromFile('./project.md', { lock: true });
// Read everything under a heading (the heading line itself isn't included)
const summary = doc.read('# Project > ## Summary');
// Add a task
doc.append('# Project > ## Tasks', '- [ ] Deploy to staging');
// Tick it, addressed by its text rather than its position
doc.write('# Project > ## Tasks > li[contains="Deploy to staging"]', '- [x] Deploy to staging');
// Remove the first finished item
doc.delete('# Project > ## Done > li[0]');
// Write back to disk and release the lock
await doc.save();
インストール
npm install @synexiom-labs/nodiom
要件: Node.js 18 以降
セレクター
セレクターは " > " で区切られたセグメントのパスで、各セグメントはドキュメントの構造内のノードに対応します。
見出しセグメント
"# Project Alpha" → The H1 "Project Alpha" and all its content
"## Tasks" → A top-level H2 named "Tasks"
"# Project Alpha > ## Tasks" → The H2 "Tasks" nested under H1 "Project Alpha"
# の数は、ドキュメント内における見出しの深さと一致する必要があります。
スコープ。 「## Tasks 配下のコンテンツ」とは、その見出しの後から、同じレベルまたはより上位の次の見出しまでのすべてを指します。
重複する見出し。 同じ見出しがスコープ内に複数回現れる場合、修飾のないセグメントは曖昧となり、AmbiguousSelectorError がスローされます。nodiom が最初の見出しを黙って選ぶことはありません。親見出しで区別するか、li[n] のようにゼロ始まりで負の値も使えるインデックスを指定します。
"# Doc > ## Notes[0]" → The first "## Notes" under "# Doc"
"# Doc > ## Notes[1]" → The second
"# Doc > ## Notes[-1]" → The last
インデックスはテキストに直接続けて記述する必要があります。スペースを含む "## References [1]" は文字どおりの見出しとして照合され、実際に Tasks[1] と記述された見出しは必ず最初に文字どおりの見出しとして照合されます。
フロントマター。 ドキュメントの先頭にある YAML(---)または TOML(+++)ブロックは、フロントマターとして認識されます。tree() には現れず、セレクターで指定することもできません。また、どの編集操作でもバイト単位で変更されません。
要素セグメント
"## Tasks > li[0]" → First list item under ## Tasks
"## Tasks > li[-1]" → Last list item (negative indexing)
"## Notes > p[0]" → First paragraph under ## Notes
"## Arch > table[0]" → First table under ## Arch
"## Arch > code[0]" → First code block under ## Arch
要素の種類:p、li、code、blockquote、table、hr。
コンテンツセグメント
インデックスは位置を示すものであり、識別子ではありません。li[2] を計算してから書き込むまでの間に、別のライターが同じセクションに追記すると、そのインデックスが別のノードを指すようになり、書き込み先が気づかれないまま誤ったノードになります。
複数のライターがドキュメントを扱う場合は、コンテンツで指定してください。
'## In Progress > li[contains="c_7f3a"]' → The list item whose text contains "c_7f3a"
"## Notes > p[contains='TODO']" → The paragraph containing "TODO"
部分文字列はノード自身のソーステキストと照合され、スコープ内で厳密に 1 つのノードに一致する必要があります。複数のノードに一致すると、最初のものを選ばずに AmbiguousSelectorError がスローされます。複数の候補のうち 1 つを黙って編集するという問題を防ぐための仕様です。
API
読み込み
// From a file
const doc = await Nodiom.fromFile('./wiki.md');
// From a file, with an advisory lock (see Concurrency)
const doc = await Nodiom.fromFile('./wiki.md', { lock: true, lockTimeout: 5000 });
// From a string: no filesystem, safe for serverless
const doc = Nodiom.fromString(markdown);
読み取り
// The Markdown under a selector. For a heading, its body without the heading
// line, so write(sel, read(sel)) changes nothing.
const content: string = doc.read('# Project > ## Summary');
// Each list item's source under a selector, marker and nested lines included:
// ['- [ ] Task A', '- [x] Task B\n - [x] sub-step']
const items: string[] = doc.readList('# Project > ## Tasks');
// Whether a location exists, and what it is
const info = doc.query('# Project > ## Tasks');
// { exists: true, type: 'heading', depth: 2, childCount: 7, index: 3 }
// The heading outline
const outline = doc.tree();
// [{ heading: 'Project', depth: 1, children: [{ heading: 'Tasks', depth: 2, children: [] }] }]
変更
変更操作は this を返すため、チェーンできます。
// Replace the body of a section; the heading is kept
doc.write('# Project > ## Summary', 'On track for Q3.');
// Replace one list item. Text without its own bullet keeps the item's marker
// and checkbox: this stays "- [ ] Final review (Friday)"
doc.write('## Tasks > li[contains="Final review"]', 'Final review (Friday)');
// Add after the last node in a section, separated by a blank line
doc.append('# Project > ## Tasks', '- [ ] Task C');
// Remove a node, or a heading together with its whole section
doc.delete('# Project > ## Done > li[0]');
doc
.append('# Project > ## Tasks', '- [ ] Task D')
.delete('# Project > ## Done > li[-1]');
コンテンツは記述どおりに挿入され(トリミングのみ)、再シリアライズされることはありません。また、変更対象にしていない部分はすべてバイト単位でそのまま保持されます。
保存
const markdown: string = doc.toString();
await doc.save(); // write back to the original file, release any lock
await doc.saveAs('./out.md'); // write to a new file (keeps the original lock)
await doc.unlock(); // release the lock explicitly
エラー
import {
NodiomError, // base class of the errors below
SelectorNotFoundError, // nothing matched; .suggestions has near misses
SelectorParseError, // the selector itself is malformed
AmbiguousSelectorError, // more than one node matched
LockError, // couldn't get the file lock in time
} from '@synexiom-labs/nodiom';
try {
doc.read('# Project > ## Taks'); // typo
} catch (e) {
if (e instanceof SelectorNotFoundError) {
console.log(e.selector); // "# Project > ## Taks"
console.log(e.suggestions); // ["Tasks"]
}
}
忠実性
構造エディターは、変更対象以外を損なわない場合にのみ役立ちます。Nodiom はドキュメントを再出力するのではなく、ノードの正確な位置でソーステキストを編集するため、編集したノード以外の書式、スペース、リストマーカー、エスケープはそのまま保持されます。実際の Markdown ファイル 2,369 件(初期ユーザー自身のメモ)をサンプルとして検証したところ、すべてのファイルがバイト単位で完全に同一の状態で往復しました。
AI エージェントからの利用
自分のマシンで利用する場合:nodiom-mcp
nodiom-mcp は、ディスク上の Markdown ファイルに対するこれらの操作を、Claude Code、Claude Desktop、Cursor、または任意の MCP クライアント向けの MCP ツール(nodiom_tree、nodiom_read、nodiom_read_list、nodiom_query、nodiom_write、nodiom_append、nodiom_delete)として提供します。
claude mcp add -s user nodiom -- npx -y @synexiom-labs/nodiom-mcp
マシン、エージェント、人をまたいで利用する場合:Nodiom Cloud
エージェントが異なるマシンで動作していたり、異なるベンダーのものであったりする場合、状態を共有するための場所が必要です。Nodiom Cloud は、このエンジンを基盤として構築されたそのための場所です。
- 共有ドキュメント:複数のエージェントが同時に編集しても、互いの変更を上書きしません。
- ボード:列は見出し、カードはチェックリスト項目で構成されるため、ボードはどこでも読める Markdown ドキュメントです。エージェントはカードを 1 回のアトミックな操作で確保するため、同じタスクを 2 つのエージェントが取ることはありません。
- 人によるレビュー手順。 エージェントは完了した作業をレビュー列に渡して停止します。人が承認するか、エージェントが読むメモを添えて差し戻します。エージェントがこの手順を飛ばすことはできません。
- 作成者の記録と通知。 すべての書き込みには実行したエージェントが記録され、レビュー通知はメール、Slack、または Discord で届きます。
Claude、ChatGPT、Cursor、または VS Code から、アドレスを 1 つ追加してサインインするだけで接続できます。コピーするキーはありません。
https://api.nodiom.md/mcp
無料プランがあります。各アプリのセットアップ方法は nodiom.md に、ツールの完全なリファレンスは ドキュメント に記載されています。
並行処理
1 台のマシン上では、nodiom は proper-lockfile を使ってアドバイザリーファイルロックを取得します。fromFile は読み取り前にロックを取得するため、2 つのプロセスが古いバージョンを同時に読み取って互いの変更を上書きすることはありません。
const doc = await Nodiom.fromFile('./shared-wiki.md', { lock: true });
// Other nodiom processes locking this file wait, or throw LockError after lockTimeout
doc.append('## Log', `- ${new Date().toISOString()} Agent B finished the task`);
await doc.save(); // writes and releases the lock
このロックが対象とするのは、同じマシン上の Node.js プロセスです。マシン間またはサーバーレス関数間で利用する場合は、ドキュメントへのすべての書き込みがデータベースの行ロックのもとで実行される Nodiom Cloud をお使いください。
比較
| アプローチ | 構造化された読み取り? | 構造的な書き込み? | 往復時の忠実性 | サーバーレス | 並行処理 |
|---|---|---|---|---|---|
fs.readFile + 正規表現 | いいえ | 不安定 | 正規表現による | いいえ | いいえ |
gray-matter | フロントマターのみ | フロントマターのみ | はい | はい | いいえ |
| データベース | いいえ | いいえ | いいえ(Markdown が失われる) | はい | はい |
| ベクトル DB | セマンティックのみ | いいえ | いいえ | はい | はい |
| nodiom | はい | はい | はい | はい(fromString) | 同じマシン |
| Nodiom Cloud | はい | はい | はい | はい | マシン間 |
ロードマップ
予定されている項目です。順序は未定です。
insert()に位置指定機能を追加:兄弟ノードの前または後に挿入します。diff():2 つのドキュメントバージョン間の構造的な差分を表示します。- 見出しのグロブセレクター(
## *Tasks*)。 - 1.0:安定したセレクター構文と API。
リリース情報は CHANGELOG に記載されています。
貢献
貢献を歓迎します。大規模な変更の PR を作成する前に、Issue を作成してください。
git clone https://github.com/Synexiom-Labs/nodiom
cd nodiom
npm install
npm test
ライセンス
MIT — Synexiom Labs Inc.
Cortexiom の推論アーキテクチャを支える状態レイヤーから生まれました。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。