mmm-mcp-server 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md ADDED
@@ -0,0 +1,228 @@
1
+ # mmm-mcp-server
2
+
3
+ [日本語](README.md) | English
4
+
5
+ A tool that lets you read and write your own maps in Matryoshka Mind Map (a mind map / TODO app) directly from conversations with AI agents. Because it is built on the Model Context Protocol (MCP) open standard, it works with any MCP-compatible AI agent, not just Claude Desktop and Claude Code (**tested on Claude Desktop and Claude Code**; for other clients, see their documentation on how to register an MCP server).
6
+
7
+ - Say "Show me my current maps" and it lists your maps.
8
+ - Turn ideas from your conversation directly into a hierarchical mind map.
9
+ - Just say "I finished task X" to check it off your TODO list.
10
+ - Import long notes or Markdown straight into a map.
11
+
12
+ This tool runs entirely on your computer, with no always-on server. It starts only when needed and accesses your own maps with your own login credentials (the developer cannot see your data).
13
+
14
+ > **For users who installed via `git clone` before September 30, 2026**: Due to a data format migration in the app, older versions will fail when writing to maps. Please replace the registration in Step 5 below with `npx -y mmm-mcp-server` (you do not need to log in again). If you want to keep using your cloned copy, run `git pull && npm install && npm run build` in the `mmm_mcp` folder and restart your AI agent.
15
+
16
+ > This guide walks you through every step, starting with how to open the terminal (the window where you type commands), so that you can follow along even if you are not used to working with a computer.
17
+ >
18
+ > **Supported OS**: It should work on both macOS and Windows. It runs purely on Node.js and does not depend on OS-specific features. However, **it has only been tested on real machines running macOS.** If you notice anything while trying it on Windows, please let us know via an Issue in this repository. Steps that differ by OS (opening the terminal and installing Node.js) are described separately for macOS and Windows. All other commands are the same on both.
19
+
20
+ ## 1. Prerequisites
21
+
22
+ - A Matryoshka Mind Map account (**anonymous-only accounts cannot be used.** Make sure you can log in with Google, Apple, or an email address. You can also link one later with the "Link email address" feature in the app).
23
+ - An MCP-compatible AI agent (tested with Claude Desktop / Claude Code; other MCP-compatible clients should generally work too).
24
+ - Node.js (the runtime that runs the tool; you will install it in Step 3).
25
+
26
+ ## 2. Open the Terminal
27
+
28
+ ### On macOS
29
+
30
+ 1. Press `Command` + `Space` on your keyboard (opens Spotlight search).
31
+ 2. Type "Terminal".
32
+ 3. Click "Terminal" in the results, or press Enter.
33
+
34
+ ### On Windows
35
+
36
+ 1. Press the `Windows` key on your keyboard (opens Start menu search).
37
+ 2. Type "PowerShell".
38
+ 3. Click "Windows PowerShell" in the results, or press Enter.
39
+
40
+ Once a dark (or light) window opens, you are ready. For the rest of the steps, paste the commands into this window one line at a time and press Enter (the commands themselves are the same on macOS and Windows).
41
+
42
+ ## 3. Install Node.js
43
+
44
+ Check whether Node.js is already installed with this command:
45
+
46
+ ```bash
47
+ node --version
48
+ ```
49
+
50
+ If a version number of `v18` or higher is displayed (e.g., `v20.11.0`), it is already installed. If you see an error like "command not found", download the installer from:
51
+
52
+ https://nodejs.org/
53
+
54
+ The site shows two large buttons. Click the one labeled **"LTS"** to download (the `.pkg` installer for macOS or the `.msi` installer for Windows is chosen automatically for your OS). Double-click the downloaded file and follow the on-screen instructions (on macOS: "Continue" → "Agree" → "Install"; on Windows: "Next" → "I accept..." → "Install").
55
+
56
+ When the installation is finished, go back to the terminal and check again:
57
+
58
+ ```bash
59
+ node --version
60
+ ```
61
+
62
+ ## 4. Log In
63
+
64
+ Choose one of the following three options based on how you usually log in to the Matryoshka Mind Map app. **None of these methods require any setup on your part beforehand.** The first run downloads the tool, so it may take a moment.
65
+
66
+ ### If you log in with Google
67
+
68
+ ```bash
69
+ npx -y -p mmm-mcp-server mmm-login login --method google
70
+ ```
71
+
72
+ Your browser opens automatically; log in with the Google account you usually use.
73
+
74
+ ### If you log in with Apple
75
+
76
+ ```bash
77
+ npx -y -p mmm-mcp-server mmm-login login --method apple
78
+ ```
79
+
80
+ Your browser opens automatically; log in with your Apple ID.
81
+
82
+ ### If you log in with an email address and password
83
+
84
+ ```bash
85
+ npx -y -p mmm-mcp-server mmm-login login --method email
86
+ ```
87
+
88
+ You will be asked for your email address and password (the password is not shown on screen).
89
+
90
+ > **If you get a "wrong password" error**, it does not necessarily mean you mistyped your password. If your account signs in to the app with Google or Apple and has no email/password credentials at all, you get the same error. If that may be the case, try the "Google" or "Apple" steps above.
91
+
92
+ Once you have logged in, you do not need to log in again each time (your credentials are stored only on your computer, in a form that only you can read).
93
+
94
+ ```bash
95
+ npx -y -p mmm-mcp-server mmm-login status
96
+ ```
97
+
98
+ shows your login status. To log out:
99
+
100
+ ```bash
101
+ npx -y -p mmm-mcp-server mmm-login logout
102
+ ```
103
+
104
+ ## 5. Register It with Your AI Agent
105
+
106
+ Below are the instructions for Claude Desktop and Claude Code, which have been tested. If you use another MCP-compatible client (e.g., Codex), in most cases you only need to register the server launch command (`npx -y mmm-mcp-server`) in that client's MCP server settings. See your client's documentation for the exact steps.
107
+
108
+ ### Claude Code
109
+
110
+ ```bash
111
+ claude mcp add mmm -- npx -y mmm-mcp-server
112
+ ```
113
+
114
+ ### Claude Desktop
115
+
116
+ Open the Claude Desktop configuration file (`claude_desktop_config.json`) and add the following under `mcpServers`:
117
+
118
+ ```json
119
+ {
120
+ "mcpServers": {
121
+ "mmm": {
122
+ "command": "npx",
123
+ "args": ["-y", "mmm-mcp-server"]
124
+ }
125
+ }
126
+ }
127
+ ```
128
+
129
+ Save the file and restart Claude Desktop.
130
+
131
+ ## 6. Try It Out
132
+
133
+ Try talking to your AI agent like this:
134
+
135
+ - "Show me my Matryoshka maps."
136
+ - "Add a task called [task] to the [map name] map."
137
+ - "I finished the [task] task in the [map name] map."
138
+ - "I'll paste my notes in Markdown; import them into the [map name] map."
139
+
140
+ **Only the first time the contents of a map (the element text) are read**, the tool asks for your permission before returning the actual contents. This is out of consideration for personal information that the contents may contain.
141
+
142
+ Instead of the contents, the tool returns only an instruction: "confirm directly with the user, then call again with `confirmed: true`." This is best-effort: the server has no way to verify whether the agent actually asked you, so whether the confirmation happens properly depends on the AI agent you use. Once confirmed, the same map is not confirmed again while that server is running (roughly one conversation).
143
+
144
+ (Previously, MCP's standard confirmation dialog, "elicitation", was also used to enforce this. However, some clients advertised support without actually showing a dialog, so contents became unreadable without the user ever seeing a confirmation screen. It was removed on 2026-09-03 in favor of the approach above.)
145
+
146
+ ## Keeping Your Maps Tidy
147
+
148
+ Letting an AI agent write your maps is convenient, but left alone the maps tend to become hard to read. Paste the following into the instruction file your AI agent loads every time (for Claude Code, `~/.claude/CLAUDE.md`) to keep the writing style consistent:
149
+
150
+ ```markdown
151
+ ## How to write in mmm
152
+
153
+ - Keep each element short enough to read in one line. Do not pack in explanations.
154
+ If you want to keep reasons or notes, put them one level down as child elements.
155
+ - Put a URL in its own element. Do not mix it with text like "Docs here: https://...".
156
+ Create an element named "Docs" and put only the URL as its child.
157
+ - Order reminder items chronologically. Name them by when to do them, such as
158
+ "Right after release", "In 2 weeks", or "When X happens".
159
+ ```
160
+
161
+ The reasons:
162
+
163
+ - **One line per element**: Matryoshka shows elements nested inside each other, so long elements hurt readability and lose the benefit of drilling down through the hierarchy.
164
+ - **Do not mix URLs with text**: A URL in the same element as other text is not treated as a link in the app, so tapping it does nothing.
165
+ - **Chronological order**: Compared with ordering by task name, checking items off in order makes it easier to notice anything you missed.
166
+
167
+ ## Troubleshooting
168
+
169
+ | Symptom | Cause / Solution |
170
+ |---|---|
171
+ | `node --version` says "command not found" | Node.js from Step 3 is not installed yet. After installing, close the terminal and open it again. |
172
+ | "Wrong password" when logging in | See the note in Step 4. Try logging in with Google or Apple instead. |
173
+ | The agent does not operate on your maps when asked | Check that the registration in Step 5 is correct. Claude Desktop needs a restart after registration. |
174
+ | The agent will not show map contents / stops at the confirmation | Check that you answered "yes" when asked for permission to read. |
175
+ | The AI cannot load a large map | With several hundred elements or more, the `list_elements` response gets large and may not fit in one go. Some AI agents handle this by saving it to a file and reading only the parts they need. If it happens often, split the map (swiping left on a top-level element turns it and everything under it into a separate map). |
176
+
177
+ ## Advanced Example: Recording Tasks per Folder Automatically
178
+
179
+ What follows is **just one example of how it can be used**. It is not a standard workflow recommended for everyone, but shows what becomes possible with this kind of setup. Feel free to ignore it if it does not suit you.
180
+
181
+ **Goal**: Pair each folder (project) you work on in Claude Code with a map of the same name, and have the AI agent record and update tasks that come up during work in that map in real time, without asking.
182
+
183
+ > Setting (2) below uses Claude Code's "global CLAUDE.md", an instruction file that is always loaded. If your AI agent has a similar feature for instructions loaded every time, the same idea should apply.
184
+
185
+ ### Setup
186
+
187
+ **(1) Re-register the mmm server so it is available in every folder**
188
+
189
+ When registering with Claude Code in Step 5, the `mmm` tool is only available in the current folder by default. To use it no matter which folder you start `claude` in, register it with `--scope user`:
190
+
191
+ ```bash
192
+ claude mcp add mmm --scope user -- npx -y mmm-mcp-server
193
+ ```
194
+
195
+ **(2) Write the workflow rules in your global CLAUDE.md (`~/.claude/CLAUDE.md`)**
196
+
197
+ For example, add something like this:
198
+
199
+ ```markdown
200
+ ## Task management with mmm
201
+
202
+ Record tasks that come up in the working folder in mmm (the Matryoshka Mind Map
203
+ integration) and manage them in real time.
204
+
205
+ - Use the working folder name as the map title.
206
+ - Check with list_maps whether a map with the same name exists. If not, create it
207
+ right away with create_map(title=folder name, isTodo=true) without confirming
208
+ the title (confirmation is not required for this workflow only).
209
+ - Calling list_elements to see existing tasks asks for confirmation only the first
210
+ time the map is read in that process. After that, add and update tasks in real
211
+ time with auto_structure_thought / update_task_status.
212
+ ```
213
+
214
+ ### Why this works
215
+
216
+ - `create_map` takes the title directly as an argument; "confirm in the conversation before calling" is only a recommended behavior for AI agents written in the tool description. A more specific instruction in CLAUDE.md takes priority, so maps can be created automatically without confirmation.
217
+ - Only `list_elements`, which reads map contents, has confirmation enforced on the server side out of consideration for confidential information (see "Try It Out" above). This does not change with the per-folder workflow: confirmation happens once, the first time a map is read in that process.
218
+
219
+ ### Caveats
220
+
221
+ - Maps are created automatically for every folder, so work-log style maps will mix in with your carefully curated mind maps. Some people may find this cluttered. Adjust it to suit you, for example by using a separate account or limiting it to certain folders.
222
+ - Skipping confirmation for `create_map` applies only when you spell out this workflow in your own CLAUDE.md. Without any setup, the agent confirms in the conversation before creating a map, as usual.
223
+
224
+ ## About This Tool
225
+
226
+ - Your login credentials (refresh token) are stored only on your computer (macOS: `~/.config/mmm-mcp/credentials.json`, Windows: `C:\Users\<username>\.config\mmm-mcp\credentials.json`) and are never sent anywhere else.
227
+ - Your map contents are read from and written to your Firebase account directly, without going through any third-party server, including the developer's (only when logging in with Apple, the last step of login goes through a signing-only relay server, but your map contents never pass through it).
228
+ - There is no always-running process; it runs only while you are in a conversation with your AI agent.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # mmm-mcp-server
2
2
 
3
+ 日本語 | [English](README.en.md)
4
+
3
5
  Matryoshka Mind Map(マトリョシカ、マインドマップ/TODOアプリ)の自分のマップを、
4
6
  AIエージェントとの会話から直接読み書きできるようにするツールです。MCP
5
7
  (Model Context Protocol)という共通規格に対応したツールなので、Claude
@@ -8,7 +8,9 @@
8
8
  // そのまま兄弟順。子を持つ要素だけドキュメントが存在する(無ければ葉)。
9
9
  //
10
10
  // MCPは「新形式のみ対応」(移行ロジックはDart側だけに持たせる)。levels/root
11
- // が無いマップに対する操作は、アプリで一度開いてもらうよう案内して止める。
11
+ // が無いマップに対する操作は、切替前(bodiesとlevelsの両方へ書く間)だけ、
12
+ // アプリで一度開いてもらうよう案内して止める。切替後は空のマップとして扱う
13
+ // (levelsMode.tsのreadLevelsSnapshot)。
12
14
  export class LevelsNotInitializedError extends Error {
13
15
  constructor(title) {
14
16
  super(`「${title}」はまだ新形式(levels)のデータがありません。` +
@@ -9,16 +9,17 @@
9
9
  // アプリで足した要素が見つからず、bodiesを読み取り専用にするルールの適用後は
10
10
  // コミットごと失敗する。呼び出しのたびに読み直すので、MCPサーバーを
11
11
  // 再起動しなくても切替に追従する。
12
- import { LevelsNotInitializedError } from "./levels.js";
13
12
  import { levelsSnapshotFromDocs } from "./levelsOps.js";
14
13
  export async function isLevelsOnly(client) {
15
14
  const config = await client.getDocument("config/app");
16
15
  return config?.data.migrationEnabled !== false;
17
16
  }
18
- /** levels全件を読む。levels/rootが無ければ、アプリで開いてもらうよう案内して止める。 */
19
- export async function readLevelsSnapshot(client, levelsPath, mapTitle) {
20
- const snapshot = levelsSnapshotFromDocs(await client.listDocuments(levelsPath));
21
- if (!snapshot.has("root"))
22
- throw new LevelsNotInitializedError(mapTitle);
23
- return snapshot;
17
+ /**
18
+ * levels全件を読む。levels/rootが無いマップは空のマップとして扱う
19
+ * (rootへの最初の書き込みは、存在しないことを前提条件にした新規作成になる)。
20
+ * 切替後はアプリでマップを開いてもrootが作られないので、「アプリで開いて」と
21
+ * 止めると中身の無い古いマップへ永久に書き込めなくなる。
22
+ */
23
+ export async function readLevelsSnapshot(client, levelsPath) {
24
+ return levelsSnapshotFromDocs(await client.listDocuments(levelsPath));
24
25
  }
@@ -12,7 +12,7 @@ import { registerDeleteElements } from "./tools/deleteElements.js";
12
12
  import { registerMoveElement } from "./tools/moveElement.js";
13
13
  const server = new McpServer({
14
14
  name: "matryoshka-mindmap",
15
- version: "0.1.1",
15
+ version: "0.1.2",
16
16
  }, {
17
17
  instructions: "このサーバーはリアルタイム同期を行わない。各ツールは呼び出された瞬間の" +
18
18
  "Firestoreの状態を返すだけで、アプリ側でユーザーが直接行った変更は" +
@@ -35,7 +35,7 @@ export function registerAutoStructureThought(server) {
35
35
  if (await isLevelsOnly(client)) {
36
36
  let levelsPathResult = "";
37
37
  await client.runOptimistic(async () => {
38
- const snapshot = await readLevelsSnapshot(client, levelsPath, map.header.title);
38
+ const snapshot = await readLevelsSnapshot(client, levelsPath);
39
39
  if (targetParentId !== null && !locateInLevels(snapshot, targetParentId)) {
40
40
  throw new Error(`parentElementId(${targetParentId})がこのマップに見つかりません。list_elementsで確認してください。`);
41
41
  }
@@ -23,10 +23,14 @@ export function registerDeleteElements(server) {
23
23
  assertCanEditMap(map, session);
24
24
  const levelsPath = levelsPathOf(map);
25
25
  const bodiesPath = bodiesPathOf(map);
26
- assertLevelsRootExists(await levelsDocExists(client, levelsPath, "root"), map.header.title);
27
- if (await isLevelsOnly(client)) {
26
+ // 切替後はrootが無いマップを空のマップとして扱うので、この確認は切替前だけ。
27
+ const levelsOnly = await isLevelsOnly(client);
28
+ if (!levelsOnly) {
29
+ assertLevelsRootExists(await levelsDocExists(client, levelsPath, "root"), map.header.title);
30
+ }
31
+ if (levelsOnly) {
28
32
  if (confirmed !== true) {
29
- const snapshot = await readLevelsSnapshot(client, levelsPath, map.header.title);
33
+ const snapshot = await readLevelsSnapshot(client, levelsPath);
30
34
  const allTargets = new Set();
31
35
  const targets = [];
32
36
  for (const elementId of elementIds) {
@@ -61,7 +65,7 @@ export function registerDeleteElements(server) {
61
65
  let levelsTotal = 0;
62
66
  await client.runOptimistic(async () => {
63
67
  levelsDeleted.length = 0;
64
- const snapshot = await readLevelsSnapshot(client, levelsPath, map.header.title);
68
+ const snapshot = await readLevelsSnapshot(client, levelsPath);
65
69
  for (const elementId of elementIds) {
66
70
  if (!locateInLevels(snapshot, elementId))
67
71
  continue; // 既に削除済み(リトライ時など)
@@ -21,7 +21,7 @@ export function registerEditElement(server) {
21
21
  if (await isLevelsOnly(client)) {
22
22
  let levelsPathResult = null;
23
23
  await client.runOptimistic(async () => {
24
- const snapshot = await readLevelsSnapshot(client, levelsPath, map.header.title);
24
+ const snapshot = await readLevelsSnapshot(client, levelsPath);
25
25
  const { writes, found } = buildLevelsPatchWrites(levelsPath, snapshot, [
26
26
  { elementId, patch: { detail } },
27
27
  ]);
@@ -35,7 +35,7 @@ export function registerImportMarkdownContext(server) {
35
35
  if (await isLevelsOnly(client)) {
36
36
  let levelsPathResult = "";
37
37
  await client.runOptimistic(async () => {
38
- const snapshot = await readLevelsSnapshot(client, levelsPath, map.header.title);
38
+ const snapshot = await readLevelsSnapshot(client, levelsPath);
39
39
  if (targetParentId !== null && !locateInLevels(snapshot, targetParentId)) {
40
40
  throw new Error(`parentElementId(${targetParentId})がこのマップに見つかりません。list_elementsで確認してください。`);
41
41
  }
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { getContext } from "../mcpContext.js";
3
3
  import { resolveMap, levelsPathOf } from "../domain/mapResolver.js";
4
+ import { isLevelsOnly } from "../domain/levelsMode.js";
4
5
  import { assertLevelsInitialized, buildTreeFromLevels, readAllLevelDocs, } from "../domain/levels.js";
5
6
  // ElementNodeの`done`を`checked`という名前で公開する(仕様書の用語に合わせる)。
6
7
  function toChecked(nodes) {
@@ -78,7 +79,11 @@ export function registerListElements(server) {
78
79
  }
79
80
  }
80
81
  const levelsById = await readAllLevelDocs(client, levelsPathOf(map));
81
- assertLevelsInitialized(levelsById, map.header.title);
82
+ // 切替前はrootが無ければ未移行(bodiesに中身があり得る)なので止める。
83
+ // 切替後はアプリで開いてもrootが作られないので、空のマップとして返す。
84
+ if (!(await isLevelsOnly(client))) {
85
+ assertLevelsInitialized(levelsById, map.header.title);
86
+ }
82
87
  const tree = buildTreeFromLevels(levelsById);
83
88
  return {
84
89
  content: [
@@ -25,17 +25,21 @@ export function registerMoveElement(server) {
25
25
  assertCanEditMap(map, session);
26
26
  const levelsPath = levelsPathOf(map);
27
27
  const bodiesPath = bodiesPathOf(map);
28
- assertLevelsRootExists(await levelsDocExists(client, levelsPath, "root"), map.header.title);
28
+ // 切替後はrootが無いマップを空のマップとして扱うので、この確認は切替前だけ。
29
+ const levelsOnly = await isLevelsOnly(client);
30
+ if (!levelsOnly) {
31
+ assertLevelsRootExists(await levelsDocExists(client, levelsPath, "root"), map.header.title);
32
+ }
29
33
  if (elementId === newParentElementId) {
30
34
  throw new Error("要素を自分自身の子にすることはできません。");
31
35
  }
32
36
  if (elementId === afterElementId) {
33
37
  throw new Error("afterElementIdにelementId自身は指定できません。");
34
38
  }
35
- if (await isLevelsOnly(client)) {
39
+ if (levelsOnly) {
36
40
  let levelsPathResult = null;
37
41
  await client.runOptimistic(async () => {
38
- const snapshot = await readLevelsSnapshot(client, levelsPath, map.header.title);
42
+ const snapshot = await readLevelsSnapshot(client, levelsPath);
39
43
  if (afterElementId !== null && !locateInLevels(snapshot, afterElementId)) {
40
44
  throw new Error(`afterElementId(${afterElementId})がこのマップに見つかりません。`);
41
45
  }
@@ -30,7 +30,7 @@ export function registerUpdateTaskStatus(server) {
30
30
  if (await isLevelsOnly(client)) {
31
31
  let levelsResults = [];
32
32
  await client.runOptimistic(async () => {
33
- const snapshot = await readLevelsSnapshot(client, levelsPath, map.header.title);
33
+ const snapshot = await readLevelsSnapshot(client, levelsPath);
34
34
  const { writes, found } = buildLevelsPatchWrites(levelsPath, snapshot, updates.map((u) => ({ elementId: u.elementId, patch: { done: u.checked } })));
35
35
  levelsResults = updates.map((u) => ({
36
36
  elementId: u.elementId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mmm-mcp-server",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "mcpName": "io.github.nhorie-canal/mmm-mcp-server",
5
5
  "description": "Matryoshka Mind Map の自分のデータをClaudeから直接読み書きするMCPサーバー",
6
6
  "license": "MIT",