@geminilight/mindos 0.7.2 → 0.7.4

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.
Files changed (254) hide show
  1. package/_standalone/.mindos-build-version +1 -1
  2. package/_standalone/.next/BUILD_ID +1 -1
  3. package/_standalone/.next/app-path-routes-manifest.json +19 -19
  4. package/_standalone/.next/build-manifest.json +3 -3
  5. package/_standalone/.next/cache/.previewinfo +1 -1
  6. package/_standalone/.next/cache/.rscinfo +1 -1
  7. package/_standalone/.next/cache/config.json +3 -3
  8. package/_standalone/.next/prerender-manifest.json +3 -3
  9. package/_standalone/.next/react-loadable-manifest.json +4 -4
  10. package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
  11. package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  12. package/_standalone/.next/server/app/_global-error.html +2 -2
  13. package/_standalone/.next/server/app/_global-error.rsc +1 -1
  14. package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  15. package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  16. package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  17. package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  18. package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  19. package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  20. package/_standalone/.next/server/app/_not-found/page.js +1 -1
  21. package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
  24. package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
  25. package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
  26. package/_standalone/.next/server/app/agents/page.js +1 -1
  27. package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
  28. package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
  29. package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
  30. package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
  31. package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
  32. package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
  33. package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
  34. package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
  35. package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
  36. package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
  37. package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
  38. package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
  39. package/_standalone/.next/server/app/api/agents/copy-skill/route_client-reference-manifest.js +1 -1
  40. package/_standalone/.next/server/app/api/agents/custom/detect/route_client-reference-manifest.js +1 -1
  41. package/_standalone/.next/server/app/api/agents/custom/route_client-reference-manifest.js +1 -1
  42. package/_standalone/.next/server/app/api/ask/route.js +4 -4
  43. package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
  44. package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
  45. package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
  46. package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
  47. package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
  48. package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
  49. package/_standalone/.next/server/app/api/channels/verify/route_client-reference-manifest.js +1 -1
  50. package/_standalone/.next/server/app/api/connect/route_client-reference-manifest.js +1 -1
  51. package/_standalone/.next/server/app/api/embedding/route_client-reference-manifest.js +1 -1
  52. package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
  53. package/_standalone/.next/server/app/api/extract-docx/route_client-reference-manifest.js +1 -1
  54. package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
  55. package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
  56. package/_standalone/.next/server/app/api/file/raw/route_client-reference-manifest.js +1 -1
  57. package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
  58. package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  59. package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  60. package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
  61. package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  62. package/_standalone/.next/server/app/api/im/activity/route_client-reference-manifest.js +1 -1
  63. package/_standalone/.next/server/app/api/im/config/route_client-reference-manifest.js +1 -1
  64. package/_standalone/.next/server/app/api/im/feishu/long-connection/event/route_client-reference-manifest.js +1 -1
  65. package/_standalone/.next/server/app/api/im/feishu/long-connection/route_client-reference-manifest.js +1 -1
  66. package/_standalone/.next/server/app/api/im/status/route_client-reference-manifest.js +1 -1
  67. package/_standalone/.next/server/app/api/im/test/route_client-reference-manifest.js +1 -1
  68. package/_standalone/.next/server/app/api/im/webhook/feishu/route_client-reference-manifest.js +1 -1
  69. package/_standalone/.next/server/app/api/im/webhook-status/route_client-reference-manifest.js +1 -1
  70. package/_standalone/.next/server/app/api/inbox/clip/route_client-reference-manifest.js +1 -1
  71. package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
  72. package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
  73. package/_standalone/.next/server/app/api/lint/route_client-reference-manifest.js +1 -1
  74. package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
  75. package/_standalone/.next/server/app/api/mcp/direct-tools/route_client-reference-manifest.js +1 -1
  76. package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
  77. package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
  78. package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
  79. package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
  80. package/_standalone/.next/server/app/api/mcp/tools/route_client-reference-manifest.js +1 -1
  81. package/_standalone/.next/server/app/api/mcp/uninstall/route_client-reference-manifest.js +1 -1
  82. package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
  83. package/_standalone/.next/server/app/api/obsidian/compat-report/route_client-reference-manifest.js +1 -1
  84. package/_standalone/.next/server/app/api/obsidian/import/route_client-reference-manifest.js +1 -1
  85. package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
  86. package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
  87. package/_standalone/.next/server/app/api/search/prewarm/route_client-reference-manifest.js +1 -1
  88. package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  89. package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
  90. package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
  91. package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
  92. package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
  93. package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
  94. package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
  95. package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
  96. package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
  97. package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
  98. package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  99. package/_standalone/.next/server/app/api/space-overview/route_client-reference-manifest.js +1 -1
  100. package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
  101. package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
  102. package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
  103. package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  104. package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
  105. package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
  106. package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
  107. package/_standalone/.next/server/app/capture/history/page.js +1 -1
  108. package/_standalone/.next/server/app/capture/history/page.js.nft.json +1 -1
  109. package/_standalone/.next/server/app/capture/history/page_client-reference-manifest.js +1 -1
  110. package/_standalone/.next/server/app/capture/page.js +1 -1
  111. package/_standalone/.next/server/app/capture/page.js.nft.json +1 -1
  112. package/_standalone/.next/server/app/capture/page_client-reference-manifest.js +1 -1
  113. package/_standalone/.next/server/app/changelog/page.js +1 -1
  114. package/_standalone/.next/server/app/changelog/page.js.nft.json +1 -1
  115. package/_standalone/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
  116. package/_standalone/.next/server/app/changes/page.js +1 -1
  117. package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
  118. package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
  119. package/_standalone/.next/server/app/echo/[segment]/page.js +1 -1
  120. package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
  121. package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
  122. package/_standalone/.next/server/app/echo/page.js +1 -1
  123. package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
  124. package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
  125. package/_standalone/.next/server/app/explore/page.js +1 -1
  126. package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
  127. package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
  128. package/_standalone/.next/server/app/help/page.js +1 -1
  129. package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
  130. package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
  131. package/_standalone/.next/server/app/inbox/history/page.js +1 -1
  132. package/_standalone/.next/server/app/inbox/history/page.js.nft.json +1 -1
  133. package/_standalone/.next/server/app/inbox/history/page_client-reference-manifest.js +1 -1
  134. package/_standalone/.next/server/app/login/page.js +1 -1
  135. package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
  136. package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  137. package/_standalone/.next/server/app/page.js +1 -1
  138. package/_standalone/.next/server/app/page.js.nft.json +1 -1
  139. package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  140. package/_standalone/.next/server/app/setup/page.js +1 -1
  141. package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
  142. package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  143. package/_standalone/.next/server/app/todo/page.js +1 -1
  144. package/_standalone/.next/server/app/todo/page.js.nft.json +1 -1
  145. package/_standalone/.next/server/app/todo/page_client-reference-manifest.js +1 -1
  146. package/_standalone/.next/server/app/trash/page.js +2 -2
  147. package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
  148. package/_standalone/.next/server/app/view/[...path]/page.js +2 -2
  149. package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
  150. package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
  151. package/_standalone/.next/server/app/wiki/page.js +2 -0
  152. package/_standalone/.next/server/app/wiki/page.js.nft.json +1 -0
  153. package/_standalone/.next/server/app/wiki/page_client-reference-manifest.js +1 -0
  154. package/_standalone/.next/server/app-paths-manifest.json +19 -19
  155. package/_standalone/.next/server/chunks/2250.js +1 -1
  156. package/_standalone/.next/server/chunks/{1057.js → 3861.js} +2 -2
  157. package/_standalone/.next/server/chunks/4802.js +30 -30
  158. package/_standalone/.next/server/chunks/8388.js +1 -1
  159. package/_standalone/.next/server/middleware-build-manifest.js +1 -1
  160. package/_standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  161. package/_standalone/.next/server/pages/500.html +2 -2
  162. package/_standalone/.next/server/server-reference-manifest.js +1 -1
  163. package/_standalone/.next/server/server-reference-manifest.json +1 -1
  164. package/_standalone/.next/static/chunks/{5581-bae715e40d227b5f.js → 5581-beecbc4ca5625aa9.js} +2 -2
  165. package/_standalone/.next/static/chunks/6762.c871d0bf3f45ba87.js +1 -0
  166. package/_standalone/.next/static/chunks/{8064-e65acd2762132099.js → 8064-acac37daf946082b.js} +1 -1
  167. package/_standalone/.next/static/chunks/{3985.695651f6b5cd768c.js → 8984.91fb8cde1983c564.js} +2 -2
  168. package/_standalone/.next/static/chunks/app/{layout-0cf6f2a65f605a0d.js → layout-20faba3bc0af7cd2.js} +19 -19
  169. package/_standalone/.next/static/chunks/app/trash/page-0c2c67929b71ef71.js +1 -0
  170. package/_standalone/.next/static/chunks/app/view/[...path]/not-found-fd06cc989103ebe7.js +1 -0
  171. package/_standalone/.next/static/chunks/app/view/[...path]/page-dba70888697ba910.js +12 -0
  172. package/_standalone/.next/static/chunks/app/wiki/page-9bc1fec84d343290.js +14 -0
  173. package/_standalone/.next/static/chunks/webpack-3c1d0331f1da64b8.js +1 -0
  174. package/_standalone/.next/trace +97 -97
  175. package/_standalone/MINDOS_ARCHITECTURE_DIAGRAM.md +488 -0
  176. package/_standalone/MINDOS_EXPLORATION_SUMMARY.md +229 -0
  177. package/_standalone/MINDOS_INFRASTRUCTURE_ANALYSIS.md +732 -0
  178. package/_standalone/__tests__/api/mcp-install.test.ts +8 -2
  179. package/_standalone/__tests__/core/embedding-provider.test.ts +78 -0
  180. package/_standalone/__tests__/skills/mindos-skill-copy-alignment.test.ts +10 -4
  181. package/_standalone/components/ask/AskHeader.tsx +25 -18
  182. package/_standalone/components/ask/SessionHistoryPanel.tsx +21 -12
  183. package/_standalone/components/settings/AiTab.tsx +3 -0
  184. package/_standalone/data/skills/mindos/SKILL.md +269 -0
  185. package/_standalone/data/skills/mindos/references/write-supplement.md +119 -0
  186. package/_standalone/data/skills/mindos-zh/SKILL.md +227 -0
  187. package/_standalone/data/skills/mindos-zh/references/write-supplement.md +119 -0
  188. package/app/__tests__/api/mcp-install.test.ts +8 -2
  189. package/app/__tests__/core/embedding-provider.test.ts +78 -0
  190. package/app/__tests__/skills/mindos-skill-copy-alignment.test.ts +10 -4
  191. package/app/app/api/ask/route.ts +14 -9
  192. package/app/app/view/[...path]/ViewPageClient.tsx +15 -12
  193. package/app/app/view/[...path]/not-found.tsx +9 -5
  194. package/app/components/ask/AskHeader.tsx +25 -18
  195. package/app/components/ask/SessionHistoryPanel.tsx +21 -12
  196. package/app/components/settings/AiTab.tsx +3 -0
  197. package/app/eslint.config.mjs +18 -0
  198. package/app/lib/core/embedding-provider.ts +12 -4
  199. package/app/lib/i18n/modules/settings.ts +2 -0
  200. package/app/package-lock.json +20214 -0
  201. package/app/tsconfig.tsbuildinfo +1 -0
  202. package/app/vitest.config.ts +14 -0
  203. package/assets/demo-flow-zh.html +622 -0
  204. package/assets/images/demo-flow-dark.png +0 -0
  205. package/assets/images/demo-flow-dark.webp +0 -0
  206. package/assets/images/demo-flow-light.png +0 -0
  207. package/assets/images/demo-flow-light.webp +0 -0
  208. package/assets/images/demo-flow-zh-dark.png +0 -0
  209. package/assets/images/demo-flow-zh-dark.webp +0 -0
  210. package/assets/images/demo-flow-zh-light.png +0 -0
  211. package/assets/images/demo-flow-zh-light.webp +0 -0
  212. package/assets/images/gui-sync-cv.png +0 -0
  213. package/assets/images/gui-sync-cv.webp +0 -0
  214. package/assets/images/mindos-chat.png +0 -0
  215. package/assets/images/mindos-chat.webp +0 -0
  216. package/assets/images/mindos-dashboard.png +0 -0
  217. package/assets/images/mindos-dashboard.webp +0 -0
  218. package/assets/images/mindos-echo.png +0 -0
  219. package/assets/images/mindos-echo.webp +0 -0
  220. package/assets/images/mindos-home.png +0 -0
  221. package/assets/images/mindos-home.webp +0 -0
  222. package/assets/images/wechat-qr.png +0 -0
  223. package/bin/lib/mcp-build.js +8 -0
  224. package/mcp/package-lock.json +2202 -0
  225. package/mcp/src/index.ts +783 -0
  226. package/package.json +13 -1
  227. package/.env.local.example +0 -38
  228. package/.playwright-cli/page-2026-04-12T12-26-53-393Z.yml +0 -6
  229. package/.playwright-cli/page-2026-04-12T12-27-20-256Z.yml +0 -120
  230. package/.syncinclude +0 -105
  231. package/MINDOS_SEARCH_DIAGRAM.txt +0 -243
  232. package/_standalone/.next/static/chunks/576.3cae31209383ddbd.js +0 -1
  233. package/_standalone/.next/static/chunks/app/trash/page-085f121c0815d542.js +0 -1
  234. package/_standalone/.next/static/chunks/app/view/[...path]/not-found-2a6eec67e91eaaf9.js +0 -1
  235. package/_standalone/.next/static/chunks/app/view/[...path]/page-faeaf8c09c1c6d7c.js +0 -12
  236. package/_standalone/.next/static/chunks/webpack-a1bb35f2d540e463.js +0 -1
  237. package/scripts/build-runtime-archive.sh +0 -151
  238. package/scripts/fix-postcss-deps.cjs +0 -75
  239. package/scripts/gen-renderer-index.js +0 -64
  240. package/scripts/hooks/block-public-merge.sh +0 -42
  241. package/scripts/hooks/pre-merge-commit +0 -4
  242. package/scripts/hooks/pre-push +0 -37
  243. package/scripts/hooks/prepare-commit-msg +0 -12
  244. package/scripts/migrate-agent-audit-log.js +0 -170
  245. package/scripts/migrate-agent-diff.js +0 -146
  246. package/scripts/parse-syncinclude.sh +0 -92
  247. package/scripts/prepare-standalone.mjs +0 -83
  248. package/scripts/release.sh +0 -145
  249. package/scripts/setup.js +0 -1427
  250. package/scripts/test-oss-upload.sh +0 -100
  251. package/scripts/verify-standalone.mjs +0 -129
  252. package/scripts/write-build-stamp.js +0 -40
  253. /package/_standalone/.next/static/{q5RP_Mx8BrCfvVDnLpRRc → 0UlbV2rA2i4B-8YYg41wQ}/_buildManifest.js +0 -0
  254. /package/_standalone/.next/static/{q5RP_Mx8BrCfvVDnLpRRc → 0UlbV2rA2i4B-8YYg41wQ}/_ssgManifest.js +0 -0
@@ -0,0 +1,783 @@
1
+ /**
2
+ * MindOS MCP Server — HTTP client wrapper
3
+ *
4
+ * Pure protocol adapter: maps MCP tools to App REST API calls via fetch.
5
+ * Zero business logic — all operations delegated to the App.
6
+ *
7
+ * Transport modes:
8
+ * Streamable HTTP (default):
9
+ * mindos mcp
10
+ *
11
+ * stdio:
12
+ * MCP_TRANSPORT=stdio mindos mcp
13
+ */
14
+
15
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
16
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
17
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
18
+ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
19
+ import { randomUUID } from "node:crypto";
20
+ import { createServer } from "node:http";
21
+ import { z } from "zod";
22
+
23
+ // ─── Config ──────────────────────────────────────────────────────────────────
24
+
25
+ const BASE_URL = process.env.MINDOS_URL ?? "http://localhost:3456";
26
+ const AUTH_TOKEN = process.env.AUTH_TOKEN;
27
+ const MCP_TRANSPORT = process.env.MCP_TRANSPORT ?? "http"; // "http" | "stdio"
28
+ const MCP_HOST = process.env.MCP_HOST ?? "0.0.0.0";
29
+ const MCP_PORT = parseInt(process.env.MCP_PORT ?? "8781", 10);
30
+ const MCP_ENDPOINT = process.env.MCP_ENDPOINT ?? "/mcp";
31
+ const CHARACTER_LIMIT = 25_000;
32
+
33
+ function headers(agentName?: string): Record<string, string> {
34
+ const h: Record<string, string> = { "Content-Type": "application/json" };
35
+ if (AUTH_TOKEN) h["Authorization"] = `Bearer ${AUTH_TOKEN}`;
36
+ // Sanitize: strip control chars, limit to 100 chars
37
+ if (agentName) h["x-mindos-agent"] = agentName.replace(/[\x00-\x1f]/g, '').slice(0, 100);
38
+ return h;
39
+ }
40
+
41
+ // ─── HTTP helpers ────────────────────────────────────────────────────────────
42
+
43
+ async function get(path: string, params?: Record<string, string>, agentName?: string): Promise<Record<string, unknown>> {
44
+ const url = new URL(path, BASE_URL);
45
+ if (params) {
46
+ for (const [k, v] of Object.entries(params)) {
47
+ if (v !== undefined && v !== null) url.searchParams.set(k, v);
48
+ }
49
+ }
50
+ const res = await fetch(url.toString(), { headers: headers(agentName) });
51
+ const json = await res.json() as Record<string, unknown>;
52
+ if (!res.ok) throw new Error((json.error as string) ?? `HTTP ${res.status}`);
53
+ return json;
54
+ }
55
+
56
+ async function post(path: string, body: Record<string, unknown>, agentName?: string): Promise<Record<string, unknown>> {
57
+ const res = await fetch(new URL(path, BASE_URL).toString(), {
58
+ method: "POST",
59
+ headers: headers(agentName),
60
+ body: JSON.stringify(body),
61
+ });
62
+ const json = await res.json() as Record<string, unknown>;
63
+ if (!res.ok) throw new Error((json.error as string) ?? `HTTP ${res.status}`);
64
+ return json;
65
+ }
66
+
67
+ function ok(text: string) {
68
+ return { content: [{ type: "text" as const, text }] };
69
+ }
70
+
71
+ function error(msg: string) {
72
+ return { isError: true, content: [{ type: "text" as const, text: `Error: ${msg}` }] };
73
+ }
74
+
75
+ function truncate(text: string, limit = CHARACTER_LIMIT): string {
76
+ if (text.length <= limit) return text;
77
+ return text.slice(0, limit) + `\n\n[... truncated at ${limit} characters. Use offset/limit params for paginated access.]`;
78
+ }
79
+
80
+ // ─── Agent operation logging ────────────────────────────────────────────────
81
+
82
+ async function logOp(tool: string, params: Record<string, unknown>, result: 'ok' | 'error', message: string, agentName?: string) {
83
+ try {
84
+ const entry = { ts: new Date().toISOString(), tool, params, result, message: message.slice(0, 200), agentName: agentName || undefined };
85
+ const line = JSON.stringify(entry) + '\n';
86
+ // Append to .agent-log.json via the app API
87
+ await fetch(new URL("/api/file", BASE_URL).toString(), {
88
+ method: "POST",
89
+ headers: headers(agentName),
90
+ body: JSON.stringify({ op: "append_to_file", path: ".agent-log.json", content: line }),
91
+ }).catch(() => {});
92
+ } catch {
93
+ // Logging should never break tool execution
94
+ }
95
+ }
96
+
97
+ // ─── Tool Registration ───────────────────────────────────────────────────────
98
+
99
+ function registerTools(server: McpServer) {
100
+
101
+ /** Get the MCP client name for this server session (e.g. "claude-code", "cursor"). */
102
+ function clientName(): string | undefined {
103
+ return server.server.getClientVersion()?.name || undefined;
104
+ }
105
+
106
+ // Session-aware wrappers that auto-inject client identity into every API call
107
+ const _get = (path: string, params?: Record<string, string>) => get(path, params, clientName());
108
+ const _post = (path: string, body: Record<string, unknown>) => post(path, body, clientName());
109
+ const _logOp = (tool: string, params: Record<string, unknown>, result: 'ok' | 'error', msg: string) =>
110
+ logOp(tool, params, result, msg, clientName());
111
+
112
+ // ── mindos_list_files ───────────────────────────────────────────────────────
113
+
114
+ server.registerTool("mindos_list_files", {
115
+ title: "List Knowledge Base Files",
116
+ description: "Return the full file tree of the MindOS knowledge base as a directory tree.",
117
+ inputSchema: z.object({
118
+ response_format: z.enum(["markdown", "json"]).default("markdown"),
119
+ }),
120
+ annotations: { readOnlyHint: true },
121
+ }, async ({ response_format }) => {
122
+ try {
123
+ const json = await _get("/api/files", { format: response_format });
124
+ const result = typeof json.tree === "string" ? json.tree : JSON.stringify(json.tree ?? json, null, 2);
125
+ _logOp("mindos_list_files", { response_format }, "ok", `${result.length} chars`);
126
+ return ok(result);
127
+ } catch (e) { _logOp("mindos_list_files", { response_format }, "error", String(e)); return error(String(e)); }
128
+ });
129
+
130
+ // ── mindos_list_spaces ──────────────────────────────────────────────────────
131
+
132
+ server.registerTool("mindos_list_spaces", {
133
+ title: "List Mind Spaces",
134
+ description:
135
+ "List top-level Mind Spaces (same as home Spaces grid): name, path, file count, and README blurb. Only spaces that appear in the file tree (at least one .md/.csv under the folder) are included.",
136
+ inputSchema: z.object({
137
+ response_format: z.enum(["markdown", "json"]).default("json"),
138
+ }),
139
+ annotations: { readOnlyHint: true },
140
+ }, async ({ response_format }) => {
141
+ try {
142
+ const json = await _get("/api/file", { op: "list_spaces" });
143
+ const spaces = json.spaces as Array<{ name: string; path: string; fileCount: number; description: string }>;
144
+ if (response_format === "json") {
145
+ _logOp("mindos_list_spaces", { response_format }, "ok", `${spaces?.length ?? 0} spaces`);
146
+ return ok(JSON.stringify({ spaces: spaces ?? [] }, null, 2));
147
+ }
148
+ const lines = (spaces ?? []).map(
149
+ (s) => `- **${s.name}** (\`${s.path}/\`) — ${s.fileCount} file(s)${s.description ? ` — ${s.description}` : ""}`,
150
+ );
151
+ const text = lines.length ? lines.join("\n") : "(no top-level spaces in tree)";
152
+ _logOp("mindos_list_spaces", { response_format }, "ok", `${spaces?.length ?? 0} spaces`);
153
+ return ok(text);
154
+ } catch (e) {
155
+ _logOp("mindos_list_spaces", { response_format }, "error", String(e));
156
+ return error(String(e));
157
+ }
158
+ });
159
+
160
+ // ── mindos_read_file ────────────────────────────────────────────────────────
161
+
162
+ server.registerTool("mindos_read_file", {
163
+ title: "Read File Content",
164
+ description: "Read the full content of a file in the MindOS knowledge base.",
165
+ inputSchema: z.object({
166
+ path: z.string().min(1),
167
+ offset: z.number().int().min(0).default(0),
168
+ limit: z.number().int().min(1).max(CHARACTER_LIMIT).default(CHARACTER_LIMIT),
169
+ }),
170
+ annotations: { readOnlyHint: true },
171
+ }, async ({ path, offset, limit }) => {
172
+ try {
173
+ const json = await _get("/api/file", { path, op: "read_file" });
174
+ const content = json.content as string;
175
+ const slice = content.slice(offset, offset + limit);
176
+ const hasMore = offset + limit < content.length;
177
+ const header = hasMore
178
+ ? `[Showing characters ${offset}–${offset + slice.length} of ${content.length}. Use offset=${offset + limit} for next page.]\n\n`
179
+ : offset > 0 ? `[Showing characters ${offset}–${offset + slice.length} of ${content.length}]\n\n` : "";
180
+ _logOp("mindos_read_file", { path }, "ok", `${content.length} chars`);
181
+ return ok(header + slice);
182
+ } catch (e) { _logOp("mindos_read_file", { path }, "error", String(e)); return error(String(e)); }
183
+ });
184
+
185
+ // ── mindos_write_file ───────────────────────────────────────────────────────
186
+
187
+ server.registerTool("mindos_write_file", {
188
+ title: "Write File Content",
189
+ description: "Overwrite the entire content of an existing file.",
190
+ inputSchema: z.object({
191
+ path: z.string().min(1),
192
+ content: z.string(),
193
+ }),
194
+ }, async ({ path, content }) => {
195
+ try {
196
+ await _post("/api/file", { op: "save_file", path, content });
197
+ _logOp("mindos_write_file", { path }, "ok", `Wrote ${content.length} chars`);
198
+ return ok(`Successfully wrote ${content.length} characters to "${path}"`);
199
+ } catch (e) { _logOp("mindos_write_file", { path }, "error", String(e)); return error(String(e)); }
200
+ });
201
+
202
+ // ── mindos_create_file ──────────────────────────────────────────────────────
203
+
204
+ server.registerTool("mindos_create_file", {
205
+ title: "Create New File",
206
+ description: "Create a new file in the knowledge base. Only .md and .csv files allowed. Creates parent directories but does NOT create Space scaffolding. Use mindos_create_space to create a Space.",
207
+ inputSchema: z.object({
208
+ path: z.string().min(1).regex(/\.(md|csv)$/),
209
+ content: z.string().default(""),
210
+ }),
211
+ }, async ({ path, content }) => {
212
+ try {
213
+ await _post("/api/file", { op: "create_file", path, content });
214
+ _logOp("mindos_create_file", { path }, "ok", `Created ${content.length} chars`);
215
+ return ok(`Created "${path}" (${content.length} characters)`);
216
+ } catch (e) { _logOp("mindos_create_file", { path }, "error", String(e)); return error(String(e)); }
217
+ });
218
+
219
+ // ── mindos_batch_create_files ────────────────────────────────────────────────
220
+
221
+ server.registerTool("mindos_batch_create_files", {
222
+ title: "Batch Create Files",
223
+ description:
224
+ "Create multiple new files in a single operation. Only .md and .csv files allowed. Returns a summary of created files and any errors.",
225
+ inputSchema: z.object({
226
+ files: z.array(z.object({
227
+ path: z.string().min(1).regex(/\.(md|csv)$/).describe("Relative file path (must end in .md or .csv)"),
228
+ content: z.string().default("").describe("Initial file content"),
229
+ })).min(1).max(50).describe("List of files to create (max 50 per call)"),
230
+ }),
231
+ }, async ({ files }) => {
232
+ const created: string[] = [];
233
+ const errors: string[] = [];
234
+ for (const file of files) {
235
+ try {
236
+ await _post("/api/file", { op: "create_file", path: file.path, content: file.content });
237
+ created.push(file.path);
238
+ } catch (e) {
239
+ errors.push(`${file.path}: ${String(e)}`);
240
+ }
241
+ }
242
+ let msg = `Batch creation complete.\nCreated ${created.length} file(s): ${created.join(", ")}`;
243
+ if (errors.length > 0) msg += `\n\nFailed to create ${errors.length} file(s):\n${errors.join("\n")}`;
244
+ _logOp("mindos_batch_create_files", { count: files.length }, created.length === files.length ? "ok" : "error", msg.slice(0, 200));
245
+ return created.length === files.length ? ok(msg) : error(msg);
246
+ });
247
+
248
+ // ── mindos_create_space ─────────────────────────────────────────────────────
249
+
250
+ server.registerTool("mindos_create_space", {
251
+ title: "Create Mind Space",
252
+ description:
253
+ "Create a new Mind Space (top-level or under parent_path): directory + README.md + INSTRUCTION.md scaffold. Use this instead of create_file when adding a new cognitive zone to the knowledge base.",
254
+ inputSchema: z.object({
255
+ name: z.string().min(1).describe("Space directory name (no path separators)"),
256
+ description: z.string().default("").describe("Short purpose text stored in README.md"),
257
+ parent_path: z.string().default("").describe("Optional parent directory under MIND_ROOT (empty = top-level Space)"),
258
+ }),
259
+ }, async ({ name, description, parent_path }) => {
260
+ try {
261
+ const json = await _post("/api/file", {
262
+ op: "create_space",
263
+ path: "_",
264
+ name,
265
+ description,
266
+ parent_path,
267
+ });
268
+ const p = json.path as string;
269
+ _logOp("mindos_create_space", { name, parent_path }, "ok", p);
270
+ return ok(`Created Mind Space at "${p}"`);
271
+ } catch (e) {
272
+ _logOp("mindos_create_space", { name, parent_path }, "error", String(e));
273
+ return error(String(e));
274
+ }
275
+ });
276
+
277
+ // ── mindos_rename_space ─────────────────────────────────────────────────────
278
+
279
+ server.registerTool("mindos_rename_space", {
280
+ title: "Rename Mind Space",
281
+ description:
282
+ "Rename a Space directory (relative path to the folder, e.g. Notes or Work/Notes). Only the final folder name changes; new_name must be a single segment. Does not rewrite links inside files.",
283
+ inputSchema: z.object({
284
+ path: z.string().min(1).describe("Relative path to the space directory to rename"),
285
+ new_name: z.string().min(1).describe("New folder name only (no slashes)"),
286
+ }),
287
+ }, async ({ path: spacePath, new_name }) => {
288
+ try {
289
+ const json = await _post("/api/file", { op: "rename_space", path: spacePath, new_name });
290
+ _logOp("mindos_rename_space", { path: spacePath, new_name }, "ok", String(json.newPath));
291
+ return ok(`Renamed space "${spacePath}" → "${json.newPath}"`);
292
+ } catch (e) {
293
+ _logOp("mindos_rename_space", { path: spacePath, new_name }, "error", String(e));
294
+ return error(String(e));
295
+ }
296
+ });
297
+
298
+ // ── mindos_delete_file ──────────────────────────────────────────────────────
299
+
300
+ server.registerTool("mindos_delete_file", {
301
+ title: "Delete File",
302
+ description: "Delete a file from the knowledge base. The file is moved to trash and can be recovered within 30 days.",
303
+ inputSchema: z.object({
304
+ path: z.string().min(1),
305
+ }),
306
+ annotations: { destructiveHint: true },
307
+ }, async ({ path }) => {
308
+ try {
309
+ const res = await _post("/api/file", { op: "delete_file", path });
310
+ const trashId = res?.trashId ? ` (trashId: ${res.trashId})` : '';
311
+ _logOp("mindos_delete_file", { path }, "ok", `Moved to trash: "${path}"${trashId}`);
312
+ return ok(`Moved to trash: "${path}"${trashId}`);
313
+ } catch (e) { _logOp("mindos_delete_file", { path }, "error", String(e)); return error(String(e)); }
314
+ });
315
+
316
+ // ── mindos_rename_file ──────────────────────────────────────────────────────
317
+
318
+ server.registerTool("mindos_rename_file", {
319
+ title: "Rename File",
320
+ description: "Rename a file within its current directory.",
321
+ inputSchema: z.object({
322
+ path: z.string().min(1),
323
+ new_name: z.string().min(1),
324
+ }),
325
+ }, async ({ path, new_name }) => {
326
+ try {
327
+ const json = await _post("/api/file", { op: "rename_file", path, new_name });
328
+ return ok(`Renamed "${path}" → "${json.newPath}"`);
329
+ } catch (e) { return error(String(e)); }
330
+ });
331
+
332
+ // ── mindos_move_file ────────────────────────────────────────────────────────
333
+
334
+ server.registerTool("mindos_move_file", {
335
+ title: "Move File",
336
+ description: "Move a file to a new path. Returns affected backlinks.",
337
+ inputSchema: z.object({
338
+ from_path: z.string().min(1),
339
+ to_path: z.string().min(1),
340
+ }),
341
+ }, async ({ from_path, to_path }) => {
342
+ try {
343
+ const json = await _post("/api/file", { op: "move_file", path: from_path, to_path });
344
+ const affected = json.affectedFiles as string[] ?? [];
345
+ const lines = [`Moved "${from_path}" → "${json.newPath}"`];
346
+ if (affected.length > 0) {
347
+ lines.push("", `${affected.length} file(s) reference the old path:`);
348
+ for (const f of affected) lines.push(` - ${f}`);
349
+ }
350
+ return ok(lines.join("\n"));
351
+ } catch (e) { return error(String(e)); }
352
+ });
353
+
354
+ // ── mindos_search_notes ─────────────────────────────────────────────────────
355
+
356
+ server.registerTool("mindos_search_notes", {
357
+ title: "Search Knowledge Base",
358
+ description: "Full-text search across all .md and .csv files.",
359
+ inputSchema: z.object({
360
+ query: z.string().min(1).max(200),
361
+ limit: z.number().int().min(1).max(50).default(20),
362
+ scope: z.string().optional(),
363
+ file_type: z.enum(["md", "csv", "all"]).default("all"),
364
+ modified_after: z.string().optional(),
365
+ response_format: z.enum(["markdown", "json"]).default("markdown"),
366
+ }),
367
+ annotations: { readOnlyHint: true },
368
+ }, async ({ query, limit, scope, file_type, modified_after, response_format }) => {
369
+ try {
370
+ const params: Record<string, string> = { q: query, limit: String(limit) };
371
+ if (scope) params.scope = scope;
372
+ if (file_type !== "all") params.file_type = file_type;
373
+ if (modified_after) params.modified_after = modified_after;
374
+ if (response_format) params.format = response_format;
375
+ const json = await _get("/api/search", params);
376
+ _logOp("mindos_search_notes", { query, limit }, "ok", `Search completed`);
377
+ return ok(truncate(JSON.stringify(json, null, 2)));
378
+ } catch (e) { _logOp("mindos_search_notes", { query }, "error", String(e)); return error(String(e)); }
379
+ });
380
+
381
+ // ── mindos_get_recent ───────────────────────────────────────────────────────
382
+
383
+ server.registerTool("mindos_get_recent", {
384
+ title: "Get Recently Modified Files",
385
+ description: "Return recently modified files sorted by modification time.",
386
+ inputSchema: z.object({
387
+ limit: z.number().int().min(1).max(50).default(10),
388
+ response_format: z.enum(["markdown", "json"]).default("markdown"),
389
+ }),
390
+ annotations: { readOnlyHint: true },
391
+ }, async ({ limit, response_format }) => {
392
+ try {
393
+ const json = await _get("/api/recent-files", { limit: String(limit), format: response_format });
394
+ return ok(JSON.stringify(json, null, 2));
395
+ } catch (e) { return error(String(e)); }
396
+ });
397
+
398
+ // ── mindos_read_lines ───────────────────────────────────────────────────────
399
+
400
+ server.registerTool("mindos_read_lines", {
401
+ title: "Read File as Lines",
402
+ description: "Read file content as a numbered array of lines (0-indexed).",
403
+ inputSchema: z.object({
404
+ path: z.string().min(1),
405
+ }),
406
+ annotations: { readOnlyHint: true },
407
+ }, async ({ path }) => {
408
+ try {
409
+ const json = await _get("/api/file", { path, op: "read_lines" });
410
+ const lines = json.lines as string[];
411
+ const numbered = lines.map((l, i) => `${i}: ${l}`).join("\n");
412
+ return ok(`${lines.length} lines total:\n\n${numbered}`);
413
+ } catch (e) { return error(String(e)); }
414
+ });
415
+
416
+ // ── mindos_insert_lines ─────────────────────────────────────────────────────
417
+
418
+ server.registerTool("mindos_insert_lines", {
419
+ title: "Insert Lines into File",
420
+ description: "Insert lines at a specific position (0-based, -1 to prepend).",
421
+ inputSchema: z.object({
422
+ path: z.string().min(1),
423
+ after_index: z.number().int(),
424
+ lines: z.array(z.string()).min(1),
425
+ }),
426
+ }, async ({ path, after_index, lines }) => {
427
+ try {
428
+ await _post("/api/file", { op: "insert_lines", path, after_index, lines });
429
+ return ok(`Inserted ${lines.length} line(s) after index ${after_index} in "${path}"`);
430
+ } catch (e) { return error(String(e)); }
431
+ });
432
+
433
+ // ── mindos_update_lines ─────────────────────────────────────────────────────
434
+
435
+ server.registerTool("mindos_update_lines", {
436
+ title: "Replace Lines in File",
437
+ description: "Replace a range of lines (inclusive, 0-based).",
438
+ inputSchema: z.object({
439
+ path: z.string().min(1),
440
+ start: z.number().int().min(0),
441
+ end: z.number().int().min(0),
442
+ lines: z.array(z.string()).min(1),
443
+ }),
444
+ }, async ({ path, start, end, lines }) => {
445
+ try {
446
+ await _post("/api/file", { op: "update_lines", path, start, end, lines });
447
+ return ok(`Replaced lines ${start}–${end} in "${path}" with ${lines.length} new line(s)`);
448
+ } catch (e) { return error(String(e)); }
449
+ });
450
+
451
+ // ── mindos_append_to_file ───────────────────────────────────────────────────
452
+
453
+ server.registerTool("mindos_append_to_file", {
454
+ title: "Append Content to File",
455
+ description: "Append text to the end of an existing file.",
456
+ inputSchema: z.object({
457
+ path: z.string().min(1),
458
+ content: z.string().min(1),
459
+ }),
460
+ }, async ({ path, content }) => {
461
+ try {
462
+ await _post("/api/file", { op: "append_to_file", path, content });
463
+ _logOp("mindos_append_to_file", { path }, "ok", `Appended ${content.length} chars`);
464
+ return ok(`Appended ${content.length} character(s) to "${path}"`);
465
+ } catch (e) { _logOp("mindos_append_to_file", { path }, "error", String(e)); return error(String(e)); }
466
+ });
467
+
468
+ // ── mindos_insert_after_heading ─────────────────────────────────────────────
469
+
470
+ server.registerTool("mindos_insert_after_heading", {
471
+ title: "Insert Content After Heading",
472
+ description: "Insert content after a Markdown heading.",
473
+ inputSchema: z.object({
474
+ path: z.string().min(1),
475
+ heading: z.string().min(1),
476
+ content: z.string().min(1),
477
+ }),
478
+ }, async ({ path, heading, content }) => {
479
+ try {
480
+ await _post("/api/file", { op: "insert_after_heading", path, heading, content });
481
+ _logOp("mindos_insert_after_heading", { path, heading }, "ok", `Inserted after "${heading}"`);
482
+ return ok(`Inserted content after heading "${heading}" in "${path}"`);
483
+ } catch (e) { _logOp("mindos_insert_after_heading", { path, heading }, "error", String(e)); return error(String(e)); }
484
+ });
485
+
486
+ // ── mindos_update_section ───────────────────────────────────────────────────
487
+
488
+ server.registerTool("mindos_update_section", {
489
+ title: "Replace Markdown Section Content",
490
+ description: "Replace the content of a Markdown section identified by heading.",
491
+ inputSchema: z.object({
492
+ path: z.string().min(1),
493
+ heading: z.string().min(1),
494
+ content: z.string(),
495
+ }),
496
+ }, async ({ path, heading, content }) => {
497
+ try {
498
+ await _post("/api/file", { op: "update_section", path, heading, content });
499
+ _logOp("mindos_update_section", { path, heading }, "ok", `Updated section "${heading}"`);
500
+ return ok(`Updated section "${heading}" in "${path}"`);
501
+ } catch (e) { _logOp("mindos_update_section", { path, heading }, "error", String(e)); return error(String(e)); }
502
+ });
503
+
504
+ // ── mindos_append_csv ───────────────────────────────────────────────────────
505
+
506
+ server.registerTool("mindos_append_csv", {
507
+ title: "Append Row to CSV",
508
+ description: "Append a row to a CSV file with RFC 4180 escaping.",
509
+ inputSchema: z.object({
510
+ path: z.string().min(1).regex(/\.csv$/),
511
+ row: z.array(z.string()).min(1),
512
+ }),
513
+ }, async ({ path, row }) => {
514
+ try {
515
+ const json = await _post("/api/file", { op: "append_csv", path, row });
516
+ return ok(`Appended row to "${path}". File now has ${json.newRowCount} rows.`);
517
+ } catch (e) { return error(String(e)); }
518
+ });
519
+
520
+ // ── mindos_get_backlinks ────────────────────────────────────────────────────
521
+
522
+ server.registerTool("mindos_get_backlinks", {
523
+ title: "Find Backlinks to File",
524
+ description: "Find all files that reference a given file path.",
525
+ inputSchema: z.object({
526
+ path: z.string().min(1),
527
+ }),
528
+ annotations: { readOnlyHint: true },
529
+ }, async ({ path }) => {
530
+ try {
531
+ const json = await _get("/api/backlinks", { path });
532
+ return ok(JSON.stringify(json, null, 2));
533
+ } catch (e) { return error(String(e)); }
534
+ });
535
+
536
+ // ── mindos_bootstrap ────────────────────────────────────────────────────────
537
+
538
+ server.registerTool("mindos_bootstrap", {
539
+ title: "Bootstrap Agent Context",
540
+ description: "Load MindOS startup context: INSTRUCTION.md, README.md, CONFIG files, and optional target directory context.",
541
+ inputSchema: z.object({
542
+ target_dir: z.string().optional(),
543
+ }),
544
+ annotations: { readOnlyHint: true },
545
+ }, async ({ target_dir }) => {
546
+ try {
547
+ const params: Record<string, string> = {};
548
+ if (target_dir) params.target_dir = target_dir;
549
+ const json = await _get("/api/bootstrap", params);
550
+ const sections = Object.entries(json)
551
+ .filter(([, v]) => v !== undefined && v !== null)
552
+ .map(([key, val]) => `--- ${key} ---\n\n${val}`)
553
+ .join("\n\n");
554
+ return ok(truncate(sections));
555
+ } catch (e) { return error(String(e)); }
556
+ });
557
+
558
+ // ── mindos_get_history ──────────────────────────────────────────────────────
559
+
560
+ server.registerTool("mindos_get_history", {
561
+ title: "Get File Git History",
562
+ description: "Get git commit history for a file.",
563
+ inputSchema: z.object({
564
+ path: z.string().min(1),
565
+ limit: z.number().int().min(1).max(50).default(10),
566
+ }),
567
+ annotations: { readOnlyHint: true },
568
+ }, async ({ path, limit }) => {
569
+ try {
570
+ const json = await _get("/api/git", { op: "history", path, limit: String(limit) });
571
+ const entries = json.entries as Array<{ hash: string; date: string; message: string; author: string }>;
572
+ if (entries.length === 0) return ok(`No git history found for "${path}"`);
573
+ const lines = [`# Git History: ${path}`, "", `${entries.length} commit(s):`, ""];
574
+ for (const h of entries) {
575
+ lines.push(`- **${h.date}** \`${h.hash.slice(0, 8)}\` — ${h.message} (${h.author})`);
576
+ }
577
+ return ok(lines.join("\n"));
578
+ } catch (e) { return error(String(e)); }
579
+ });
580
+
581
+ // ── mindos_get_file_at_version ──────────────────────────────────────────────
582
+
583
+ server.registerTool("mindos_get_file_at_version", {
584
+ title: "Read File at Git Version",
585
+ description: "Read file content at a specific git commit.",
586
+ inputSchema: z.object({
587
+ path: z.string().min(1),
588
+ commit: z.string().min(4),
589
+ }),
590
+ annotations: { readOnlyHint: true },
591
+ }, async ({ path, commit }) => {
592
+ try {
593
+ const json = await _get("/api/git", { op: "show", path, commit });
594
+ const content = json.content as string;
595
+ return ok(truncate(`# ${path} @ ${commit.slice(0, 8)}\n\n${content}`));
596
+ } catch (e) { return error(String(e)); }
597
+ });
598
+
599
+ // ── mindos_lint ─────────────────────────────────────────────────────────────
600
+
601
+ server.registerTool("mindos_lint", {
602
+ title: "Knowledge Base Health Check",
603
+ description: "Run a health check on the knowledge base. Detects orphan files (no inbound links), stale files (not modified in 90+ days), broken links, and empty/stub files. Returns a health score (0-100) and detailed issue lists.",
604
+ inputSchema: z.object({
605
+ space: z.string().optional().describe("Optional space name to scope the analysis (e.g. 'Projects'). Omit for full KB scan."),
606
+ }),
607
+ annotations: { readOnlyHint: true },
608
+ }, async ({ space }) => {
609
+ try {
610
+ const params: Record<string, string> = {};
611
+ if (space) params.space = space;
612
+ const report = await _get("/api/lint", params) as Record<string, unknown>;
613
+ const stats = report.stats as Record<string, number>;
614
+ const score = report.healthScore as number;
615
+ const orphans = report.orphans as Array<Record<string, unknown>>;
616
+ const brokenLinks = report.brokenLinks as Array<Record<string, unknown>>;
617
+ const stale = report.stale as Array<Record<string, unknown>>;
618
+ const empty = report.empty as string[];
619
+
620
+ const lines: string[] = [
621
+ `## KB Health Check — Score: ${score}/100`,
622
+ `Scope: ${report.scope} | Files: ${stats.totalFiles}`,
623
+ '',
624
+ ];
625
+ if (orphans?.length > 0) {
626
+ lines.push(`### Orphan Files (${orphans.length})`);
627
+ for (const o of orphans.slice(0, 20)) lines.push(`- ${o.path}`);
628
+ if (orphans.length > 20) lines.push(`... and ${orphans.length - 20} more`);
629
+ lines.push('');
630
+ }
631
+ if (brokenLinks?.length > 0) {
632
+ lines.push(`### Broken Links (${brokenLinks.length})`);
633
+ for (const b of brokenLinks.slice(0, 20)) lines.push(`- ${b.source}:${b.line} → [[${b.target}]]`);
634
+ if (brokenLinks.length > 20) lines.push(`... and ${brokenLinks.length - 20} more`);
635
+ lines.push('');
636
+ }
637
+ if (stale?.length > 0) {
638
+ lines.push(`### Stale Files (${stale.length})`);
639
+ for (const s of stale.slice(0, 20)) lines.push(`- ${s.path} (${s.daysSinceUpdate}d ago)`);
640
+ if (stale.length > 20) lines.push(`... and ${stale.length - 20} more`);
641
+ lines.push('');
642
+ }
643
+ if (empty?.length > 0) {
644
+ lines.push(`### Empty Files (${empty.length})`);
645
+ for (const e of empty.slice(0, 20)) lines.push(`- ${e}`);
646
+ if (empty.length > 20) lines.push(`... and ${empty.length - 20} more`);
647
+ lines.push('');
648
+ }
649
+ if (score === 100) lines.push('All clear — your knowledge base is in great shape!');
650
+
651
+ _logOp("mindos_lint", { space }, "ok", `score=${score}, files=${stats.totalFiles}`);
652
+ return ok(lines.join('\n'));
653
+ } catch (e) { _logOp("mindos_lint", { space }, "error", String(e)); return error(String(e)); }
654
+ });
655
+
656
+ // ── mindos_compile ──────────────────────────────────────────────────────────
657
+
658
+ server.registerTool("mindos_compile", {
659
+ title: "Compile Space Overview",
660
+ description: "Generate or regenerate a Space overview README using AI. Reads all files in the Space, analyzes their content, and produces a structured summary saved as README.md in the Space.",
661
+ inputSchema: z.object({
662
+ space: z.string().min(1).describe("Space path to compile (e.g. 'Research')"),
663
+ }),
664
+ }, async ({ space }) => {
665
+ try {
666
+ const result = await _post("/api/space-overview", { space }) as Record<string, unknown>;
667
+ if (result.error) {
668
+ _logOp("mindos_compile", { space }, "error", String(result.error));
669
+ return error(String(result.error));
670
+ }
671
+ const stats = result.stats as Record<string, unknown>;
672
+ const msg = `Overview generated for "${stats.spaceName}" (${stats.fileCount} files analyzed). Saved to ${space}/README.md`;
673
+ _logOp("mindos_compile", { space }, "ok", msg);
674
+ return ok(msg);
675
+ } catch (e) { _logOp("mindos_compile", { space }, "error", String(e)); return error(String(e)); }
676
+ });
677
+
678
+ } // end registerTools
679
+
680
+ // ─── Server Factory ──────────────────────────────────────────────────────────
681
+
682
+ function createMcpServer(): McpServer {
683
+ const server = new McpServer({ name: "mindos-mcp-server", version: "1.0.0" });
684
+ registerTools(server);
685
+ return server;
686
+ }
687
+
688
+ // ─── Start ───────────────────────────────────────────────────────────────────
689
+
690
+ async function main() {
691
+ if (MCP_TRANSPORT === "http") {
692
+ // ── Streamable HTTP mode (per-session transport) ─────────────────────
693
+ const sessions = new Map<string, { transport: StreamableHTTPServerTransport; server: McpServer }>();
694
+
695
+ const expressApp = createMcpExpressApp({ host: MCP_HOST });
696
+
697
+ expressApp.get("/api/health", (_req, res) => {
698
+ res.json({ ok: true, service: "mindos" });
699
+ });
700
+
701
+ if (AUTH_TOKEN) {
702
+ expressApp.use(MCP_ENDPOINT, (req, res, next) => {
703
+ const bearer = req.headers.authorization?.replace("Bearer ", "");
704
+ if (bearer !== AUTH_TOKEN) {
705
+ res.status(401).json({ error: "Unauthorized" });
706
+ return;
707
+ }
708
+ next();
709
+ });
710
+ }
711
+
712
+ expressApp.all(MCP_ENDPOINT, async (req, res) => {
713
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
714
+
715
+ if (sessionId && sessions.has(sessionId)) {
716
+ const session = sessions.get(sessionId)!;
717
+ await session.transport.handleRequest(req, res, req.body);
718
+ return;
719
+ }
720
+
721
+ if (sessionId && !sessions.has(sessionId)) {
722
+ res.status(404).json({ jsonrpc: "2.0", error: { code: -32000, message: "Session not found" } });
723
+ return;
724
+ }
725
+
726
+ // New session: create dedicated transport + server
727
+ const transport = new StreamableHTTPServerTransport({
728
+ sessionIdGenerator: () => randomUUID(),
729
+ });
730
+ const server = createMcpServer();
731
+
732
+ transport.onclose = () => {
733
+ const sid = transport.sessionId;
734
+ if (sid) sessions.delete(sid);
735
+ console.error(`[MCP] Session ${sid?.slice(0, 8)} closed (${sessions.size} active)`);
736
+ };
737
+
738
+ await server.connect(transport);
739
+ await transport.handleRequest(req, res, req.body);
740
+
741
+ const sid = transport.sessionId;
742
+ if (sid) {
743
+ sessions.set(sid, { transport, server });
744
+ const client = server.server.getClientVersion();
745
+ const clientLabel = client?.name ? ` (${client.name})` : '';
746
+ console.error(`[MCP] New session ${sid.slice(0, 8)}${clientLabel} (${sessions.size} active)`);
747
+ }
748
+ });
749
+
750
+ const httpServer = createServer(expressApp as Parameters<typeof createServer>[1]);
751
+ httpServer.listen(MCP_PORT, MCP_HOST, () => {
752
+ const displayHost = MCP_HOST === '0.0.0.0' ? '127.0.0.1' : MCP_HOST;
753
+ console.error(`MindOS MCP server (HTTP) listening on http://${displayHost}:${MCP_PORT}${MCP_ENDPOINT}`);
754
+ console.error(`API backend: ${BASE_URL}`);
755
+ });
756
+
757
+ // Detect parent-exit via stdin EOF — but only when stdin is a real pipe
758
+ // from a parent process (e.g. Desktop/Electron). Under launchd/systemd,
759
+ // stdin is /dev/null which emits EOF immediately and would kill the server.
760
+ const launchedByDaemon =
761
+ process.env.LAUNCHED_BY_LAUNCHD === '1' || !!process.env.INVOCATION_ID;
762
+ if (!process.stdin.isTTY && !launchedByDaemon) {
763
+ process.stdin.resume();
764
+ process.stdin.on('end', () => {
765
+ console.error('[MindOS MCP] Parent process exited (stdin closed), shutting down');
766
+ httpServer.close();
767
+ setTimeout(() => process.exit(0), 1000);
768
+ });
769
+ process.stdin.on('error', () => {});
770
+ }
771
+ } else {
772
+ // ── stdio mode ───────────────────────────────────────────────────────
773
+ const server = createMcpServer();
774
+ const transport = new StdioServerTransport();
775
+ await server.connect(transport);
776
+ console.error(`MindOS MCP server started (stdio, API: ${BASE_URL})`);
777
+ }
778
+ }
779
+
780
+ main().catch((e) => {
781
+ console.error("Fatal:", e);
782
+ process.exit(1);
783
+ });