@rubytech/create-maxy-code 0.1.51 → 0.1.57

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 (304) hide show
  1. package/dist/__tests__/platform-port-stamp.test.js +24 -0
  2. package/dist/index.js +5 -2
  3. package/dist/port-resolution.js +2 -0
  4. package/package.json +1 -1
  5. package/payload/platform/lib/require-port-env/dist/index.d.ts +31 -0
  6. package/payload/platform/lib/require-port-env/dist/index.d.ts.map +1 -0
  7. package/payload/platform/lib/require-port-env/dist/index.js +52 -0
  8. package/payload/platform/lib/require-port-env/dist/index.js.map +1 -0
  9. package/payload/platform/lib/require-port-env/src/index.ts +56 -0
  10. package/payload/platform/lib/require-port-env/tsconfig.json +8 -0
  11. package/payload/platform/package-lock.json +0 -1
  12. package/payload/platform/package.json +2 -3
  13. package/payload/platform/plugins/admin/PLUGIN.md +16 -3
  14. package/payload/platform/plugins/admin/hooks/__tests__/turn-completed-graph-write.test.sh +183 -144
  15. package/payload/platform/plugins/admin/hooks/turn-completed-graph-write.sh +180 -99
  16. package/payload/platform/plugins/admin/mcp/dist/index.js +20 -12
  17. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  18. package/payload/platform/plugins/brochures/PLUGIN.md +2 -0
  19. package/payload/platform/plugins/brochures/skills/property-market-report/SKILL.md +100 -0
  20. package/payload/platform/plugins/brochures/skills/property-market-report/references/render.py +267 -0
  21. package/payload/platform/plugins/brochures/skills/property-market-report/references/template.html +266 -0
  22. package/payload/platform/plugins/brochures/skills/property-socials/SKILL.md +141 -0
  23. package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.d.ts.map +1 -1
  24. package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js +2 -1
  25. package/payload/platform/plugins/cloudflare/mcp/dist/lib/setup-orchestrator.js.map +1 -1
  26. package/payload/platform/plugins/docs/references/admin-session.md +33 -3
  27. package/payload/platform/plugins/docs/references/deployment.md +25 -0
  28. package/payload/platform/plugins/docs/references/internals.md +26 -0
  29. package/payload/platform/plugins/docs/references/platform.md +3 -1
  30. package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
  31. package/payload/platform/plugins/docs/references/troubleshooting.md +1 -1
  32. package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.d.ts.map +1 -1
  33. package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.js +2 -1
  34. package/payload/platform/plugins/email/mcp/dist/lib/claude-bridge.js.map +1 -1
  35. package/payload/platform/plugins/memory/.claude-plugin/plugin.json +1 -1
  36. package/payload/platform/plugins/memory/PLUGIN.md +1 -1
  37. package/payload/platform/plugins/memory/mcp/dist/index.js +6 -1
  38. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  39. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-fields.test.d.ts +2 -0
  40. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-fields.test.d.ts.map +1 -0
  41. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-fields.test.js +107 -0
  42. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-fields.test.js.map +1 -0
  43. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts +4 -2
  44. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts.map +1 -1
  45. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js +22 -13
  46. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -1
  47. package/payload/platform/plugins/memory/references/graph-primitives.md +42 -0
  48. package/payload/platform/plugins/memory/skills/concept-synthesis/SKILL.md +1 -1
  49. package/payload/platform/plugins/memory/skills/conversation-archive-enrich/SKILL.md +2 -2
  50. package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +1 -1
  51. package/payload/platform/plugins/property-data/.claude-plugin/plugin.json +17 -0
  52. package/payload/platform/plugins/property-data/PLUGIN.md +100 -0
  53. package/payload/platform/plugins/property-data/mcp/dist/index.d.ts +2 -0
  54. package/payload/platform/plugins/property-data/mcp/dist/index.d.ts.map +1 -0
  55. package/payload/platform/plugins/property-data/mcp/dist/index.js +153 -0
  56. package/payload/platform/plugins/property-data/mcp/dist/index.js.map +1 -0
  57. package/payload/platform/plugins/property-data/mcp/dist/lib/crypto.d.ts +3 -0
  58. package/payload/platform/plugins/property-data/mcp/dist/lib/crypto.d.ts.map +1 -0
  59. package/payload/platform/plugins/property-data/mcp/dist/lib/crypto.js +72 -0
  60. package/payload/platform/plugins/property-data/mcp/dist/lib/crypto.js.map +1 -0
  61. package/payload/platform/plugins/property-data/mcp/dist/lib/neo4j.d.ts +5 -0
  62. package/payload/platform/plugins/property-data/mcp/dist/lib/neo4j.d.ts.map +1 -0
  63. package/payload/platform/plugins/property-data/mcp/dist/lib/neo4j.js +38 -0
  64. package/payload/platform/plugins/property-data/mcp/dist/lib/neo4j.js.map +1 -0
  65. package/payload/platform/plugins/property-data/mcp/dist/lib/propertydata-api.d.ts +29 -0
  66. package/payload/platform/plugins/property-data/mcp/dist/lib/propertydata-api.d.ts.map +1 -0
  67. package/payload/platform/plugins/property-data/mcp/dist/lib/propertydata-api.js +112 -0
  68. package/payload/platform/plugins/property-data/mcp/dist/lib/propertydata-api.js.map +1 -0
  69. package/payload/platform/plugins/property-data/mcp/dist/tools/endpoints.d.ts +14 -0
  70. package/payload/platform/plugins/property-data/mcp/dist/tools/endpoints.d.ts.map +1 -0
  71. package/payload/platform/plugins/property-data/mcp/dist/tools/endpoints.js +20 -0
  72. package/payload/platform/plugins/property-data/mcp/dist/tools/endpoints.js.map +1 -0
  73. package/payload/platform/plugins/property-data/mcp/dist/tools/key-deregister.d.ts +4 -0
  74. package/payload/platform/plugins/property-data/mcp/dist/tools/key-deregister.d.ts.map +1 -0
  75. package/payload/platform/plugins/property-data/mcp/dist/tools/key-deregister.js +19 -0
  76. package/payload/platform/plugins/property-data/mcp/dist/tools/key-deregister.js.map +1 -0
  77. package/payload/platform/plugins/property-data/mcp/dist/tools/key-list.d.ts +4 -0
  78. package/payload/platform/plugins/property-data/mcp/dist/tools/key-list.d.ts.map +1 -0
  79. package/payload/platform/plugins/property-data/mcp/dist/tools/key-list.js +10 -0
  80. package/payload/platform/plugins/property-data/mcp/dist/tools/key-list.js.map +1 -0
  81. package/payload/platform/plugins/property-data/mcp/dist/tools/key-register.d.ts +5 -0
  82. package/payload/platform/plugins/property-data/mcp/dist/tools/key-register.d.ts.map +1 -0
  83. package/payload/platform/plugins/property-data/mcp/dist/tools/key-register.js +43 -0
  84. package/payload/platform/plugins/property-data/mcp/dist/tools/key-register.js.map +1 -0
  85. package/payload/platform/plugins/property-data/mcp/package-lock.json +2566 -0
  86. package/payload/platform/plugins/property-data/mcp/package.json +21 -0
  87. package/payload/platform/plugins/property-data/mcp/src/__tests__/key-roundtrip.test.ts +107 -0
  88. package/payload/platform/plugins/property-data/mcp/src/__tests__/propertydata-api.test.ts +184 -0
  89. package/payload/platform/plugins/property-data/mcp/src/__tests__/schema-parity.test.ts +48 -0
  90. package/payload/platform/plugins/property-data/mcp/src/index.ts +249 -0
  91. package/payload/platform/plugins/property-data/mcp/src/lib/crypto.ts +79 -0
  92. package/payload/platform/plugins/property-data/mcp/src/lib/neo4j.ts +47 -0
  93. package/payload/platform/plugins/property-data/mcp/src/lib/propertydata-api.ts +135 -0
  94. package/payload/platform/plugins/property-data/mcp/src/tools/endpoints.ts +27 -0
  95. package/payload/platform/plugins/property-data/mcp/src/tools/key-deregister.ts +21 -0
  96. package/payload/platform/plugins/property-data/mcp/src/tools/key-list.ts +10 -0
  97. package/payload/platform/plugins/property-data/mcp/src/tools/key-register.ts +58 -0
  98. package/payload/platform/plugins/property-data/mcp/tsconfig.json +20 -0
  99. package/payload/platform/plugins/property-data/mcp/vitest.config.ts +8 -0
  100. package/payload/platform/plugins/property-data/skills/property-data/SKILL.md +58 -0
  101. package/payload/platform/plugins/waitlist/PLUGIN.md +6 -8
  102. package/payload/platform/plugins/waitlist/mcp/dist/index.js +33 -0
  103. package/payload/platform/plugins/waitlist/mcp/dist/index.js.map +1 -1
  104. package/payload/platform/plugins/waitlist/mcp/dist/lib/admin-model.d.ts +16 -0
  105. package/payload/platform/plugins/waitlist/mcp/dist/lib/admin-model.d.ts.map +1 -0
  106. package/payload/platform/plugins/waitlist/mcp/dist/lib/admin-model.js +58 -0
  107. package/payload/platform/plugins/waitlist/mcp/dist/lib/admin-model.js.map +1 -0
  108. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-extract.d.ts +34 -0
  109. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-extract.d.ts.map +1 -0
  110. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-extract.js +129 -0
  111. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-extract.js.map +1 -0
  112. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-heal.d.ts +12 -18
  113. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-heal.d.ts.map +1 -1
  114. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-heal.js +89 -86
  115. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-heal.js.map +1 -1
  116. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-setup.d.ts.map +1 -1
  117. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-setup.js +15 -53
  118. package/payload/platform/plugins/waitlist/mcp/dist/tools/waitlist-setup.js.map +1 -1
  119. package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -0
  120. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +5 -5
  121. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
  122. package/payload/platform/plugins/workflows/PLUGIN.md +10 -176
  123. package/payload/platform/plugins/workflows/mcp/dist/index.js +14 -142
  124. package/payload/platform/plugins/workflows/mcp/dist/index.js.map +1 -1
  125. package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.d.ts +1 -88
  126. package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.d.ts.map +1 -1
  127. package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.js +0 -86
  128. package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.js.map +1 -1
  129. package/payload/platform/plugins/workflows/mcp/dist/lib/validate-capabilities.d.ts +5 -12
  130. package/payload/platform/plugins/workflows/mcp/dist/lib/validate-capabilities.d.ts.map +1 -1
  131. package/payload/platform/plugins/workflows/mcp/dist/lib/validate-capabilities.js +5 -46
  132. package/payload/platform/plugins/workflows/mcp/dist/lib/validate-capabilities.js.map +1 -1
  133. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.d.ts +1 -16
  134. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.d.ts.map +1 -1
  135. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.js +39 -75
  136. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.js.map +1 -1
  137. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.d.ts +8 -21
  138. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.d.ts.map +1 -1
  139. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js +39 -266
  140. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js.map +1 -1
  141. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-get.d.ts +3 -8
  142. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-get.d.ts.map +1 -1
  143. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-get.js +3 -10
  144. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-get.js.map +1 -1
  145. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.d.ts.map +1 -1
  146. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.js +38 -86
  147. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.js.map +1 -1
  148. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-validate.d.ts.map +1 -1
  149. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-validate.js +8 -26
  150. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-validate.js.map +1 -1
  151. package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts.map +1 -1
  152. package/payload/platform/services/claude-session-manager/dist/fs-watcher.js +1 -2
  153. package/payload/platform/services/claude-session-manager/dist/fs-watcher.js.map +1 -1
  154. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  155. package/payload/platform/services/claude-session-manager/dist/http-server.js +204 -23
  156. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  157. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +39 -1
  158. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  159. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +109 -10
  160. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  161. package/payload/platform/services/claude-session-manager/dist/reaper.d.ts.map +1 -1
  162. package/payload/platform/services/claude-session-manager/dist/reaper.js +11 -0
  163. package/payload/platform/services/claude-session-manager/dist/reaper.js.map +1 -1
  164. package/payload/platform/services/claude-session-manager/dist/session-store.d.ts +21 -0
  165. package/payload/platform/services/claude-session-manager/dist/session-store.d.ts.map +1 -1
  166. package/payload/platform/services/claude-session-manager/dist/session-store.js.map +1 -1
  167. package/payload/platform/services/claude-session-manager/vitest.config.ts +14 -0
  168. package/payload/platform/templates/agents/admin/IDENTITY.md +2 -0
  169. package/payload/platform/templates/agents/admin/SOUL.md +3 -1
  170. package/payload/premium-plugins/real-agent/BUNDLE.md +5 -3
  171. package/payload/premium-plugins/real-agent/agents/negotiator.md +1 -1
  172. package/payload/premium-plugins/real-agent/agents/valuer.md +1 -1
  173. package/payload/premium-plugins/real-agent/plugins/.claude-plugin/marketplace.json +5 -0
  174. package/payload/premium-plugins/real-agent/plugins/brochures/PLUGIN.md +2 -0
  175. package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-market-report/SKILL.md +100 -0
  176. package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-market-report/references/render.py +267 -0
  177. package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-market-report/references/template.html +266 -0
  178. package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-socials/SKILL.md +141 -0
  179. package/payload/premium-plugins/real-agent/plugins/property-data/.claude-plugin/plugin.json +17 -0
  180. package/payload/premium-plugins/real-agent/plugins/property-data/PLUGIN.md +100 -0
  181. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/index.d.ts +2 -0
  182. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/index.d.ts.map +1 -0
  183. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/index.js +153 -0
  184. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/index.js.map +1 -0
  185. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/crypto.d.ts +3 -0
  186. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/crypto.d.ts.map +1 -0
  187. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/crypto.js +72 -0
  188. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/crypto.js.map +1 -0
  189. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/neo4j.d.ts +5 -0
  190. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/neo4j.d.ts.map +1 -0
  191. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/neo4j.js +38 -0
  192. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/neo4j.js.map +1 -0
  193. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/propertydata-api.d.ts +29 -0
  194. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/propertydata-api.d.ts.map +1 -0
  195. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/propertydata-api.js +112 -0
  196. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/lib/propertydata-api.js.map +1 -0
  197. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/endpoints.d.ts +14 -0
  198. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/endpoints.d.ts.map +1 -0
  199. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/endpoints.js +20 -0
  200. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/endpoints.js.map +1 -0
  201. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-deregister.d.ts +4 -0
  202. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-deregister.d.ts.map +1 -0
  203. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-deregister.js +19 -0
  204. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-deregister.js.map +1 -0
  205. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-list.d.ts +4 -0
  206. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-list.d.ts.map +1 -0
  207. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-list.js +10 -0
  208. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-list.js.map +1 -0
  209. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-register.d.ts +5 -0
  210. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-register.d.ts.map +1 -0
  211. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-register.js +43 -0
  212. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/dist/tools/key-register.js.map +1 -0
  213. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/package-lock.json +2566 -0
  214. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/package.json +21 -0
  215. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/__tests__/key-roundtrip.test.ts +107 -0
  216. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/__tests__/propertydata-api.test.ts +184 -0
  217. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/__tests__/schema-parity.test.ts +48 -0
  218. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/index.ts +249 -0
  219. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/lib/crypto.ts +79 -0
  220. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/lib/neo4j.ts +47 -0
  221. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/lib/propertydata-api.ts +135 -0
  222. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/tools/endpoints.ts +27 -0
  223. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/tools/key-deregister.ts +21 -0
  224. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/tools/key-list.ts +10 -0
  225. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/src/tools/key-register.ts +58 -0
  226. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/tsconfig.json +20 -0
  227. package/payload/premium-plugins/real-agent/plugins/property-data/mcp/vitest.config.ts +8 -0
  228. package/payload/premium-plugins/real-agent/plugins/property-data/skills/property-data/SKILL.md +58 -0
  229. package/payload/server/{chunk-CWI7NOWK.js → chunk-2IKQERHN.js} +32 -4
  230. package/payload/server/maxy-edge.js +53 -25
  231. package/payload/server/public/assets/admin-2ZJmzJ_g.js +216 -0
  232. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-DGHlapUl.js → architectureDiagram-Q4EWVU46-DgfgldJT.js} +1 -1
  233. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-Dgnf9J14.js → blockDiagram-DXYQGD6D-agu8GDzA.js} +1 -1
  234. package/payload/server/public/assets/{brand-CtilcX3W.css → brand-BDASZqMG.css} +1 -1
  235. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-GBVmghhJ.js → c4Diagram-AHTNJAMY-CmNsUmAe.js} +1 -1
  236. package/payload/server/public/assets/channel-DXGOX55q.js +1 -0
  237. package/payload/server/public/assets/{chunk-336JU56O-CSVC_J3A.js → chunk-336JU56O-D9Uts7k9.js} +2 -2
  238. package/payload/server/public/assets/{chunk-426QAEUC-CK1Z0942.js → chunk-426QAEUC-D4ABqXMn.js} +1 -1
  239. package/payload/server/public/assets/{chunk-4TB4RGXK-CaqJYLxL.js → chunk-4TB4RGXK-D1KE4OAX.js} +1 -1
  240. package/payload/server/public/assets/{chunk-5FUZZQ4R-57BmPy_B.js → chunk-5FUZZQ4R-CAh0GXe2.js} +1 -1
  241. package/payload/server/public/assets/{chunk-5PVQY5BW-Ud03uYx_.js → chunk-5PVQY5BW-DBHa6HOv.js} +1 -1
  242. package/payload/server/public/assets/{chunk-EDXVE4YY-p5KO0PoG.js → chunk-EDXVE4YY-BDC-6mL3.js} +1 -1
  243. package/payload/server/public/assets/{chunk-ENJZ2VHE-CkxgLujt.js → chunk-ENJZ2VHE-CH3uROkW.js} +1 -1
  244. package/payload/server/public/assets/{chunk-ICPOFSXX-DwMkY8p7.js → chunk-ICPOFSXX-BzZf0nAw.js} +1 -1
  245. package/payload/server/public/assets/{chunk-OYMX7WX6-CyYOUKBw.js → chunk-OYMX7WX6-BrLHrI6O.js} +1 -1
  246. package/payload/server/public/assets/{chunk-U2HBQHQK-Dk5rfJM6.js → chunk-U2HBQHQK-Bw4UkLe_.js} +1 -1
  247. package/payload/server/public/assets/{chunk-X2U36JSP-D0qCg9Nq.js → chunk-X2U36JSP-23xgdk05.js} +1 -1
  248. package/payload/server/public/assets/{chunk-YZCP3GAM-C0uFHrdT.js → chunk-YZCP3GAM-BA8iGgRa.js} +1 -1
  249. package/payload/server/public/assets/{chunk-ZZ45TVLE-C7lSyGRs.js → chunk-ZZ45TVLE-B-ol4EgR.js} +1 -1
  250. package/payload/server/public/assets/classDiagram-6PBFFD2Q-DTuuDxpK.js +1 -0
  251. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-CIw8RU0q.js +1 -0
  252. package/payload/server/public/assets/clone-O4ZyuvYl.js +1 -0
  253. package/payload/server/public/assets/{dagre-BWZ4wr8y.js → dagre--kXszEIb.js} +1 -1
  254. package/payload/server/public/assets/{dagre-KV5264BT-C7mpqRpw.js → dagre-KV5264BT-amN1uU2d.js} +1 -1
  255. package/payload/server/public/assets/data-DlmoKxmN.js +1 -0
  256. package/payload/server/public/assets/{device-url-actions-347cMDY4.js → device-url-actions-CPoz5fqz.js} +1 -1
  257. package/payload/server/public/assets/{diagram-5BDNPKRD-BcQRB7_3.js → diagram-5BDNPKRD-Cwizs9tP.js} +1 -1
  258. package/payload/server/public/assets/{diagram-G4DWMVQ6-BLSGUKbc.js → diagram-G4DWMVQ6-BpSu5Uao.js} +1 -1
  259. package/payload/server/public/assets/{diagram-MMDJMWI5-Ra1VvGJh.js → diagram-MMDJMWI5-DiArtkto.js} +1 -1
  260. package/payload/server/public/assets/{diagram-TYMM5635-Bx_r1USn.js → diagram-TYMM5635-_tygK58Y.js} +1 -1
  261. package/payload/server/public/assets/{erDiagram-SMLLAGMA-CJhoN8dG.js → erDiagram-SMLLAGMA-CBV6LWYA.js} +1 -1
  262. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-Bd5CZIoq.js → flowDiagram-DWJPFMVM-CHZFrtOU.js} +1 -1
  263. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-BNQJqJFh.js → ganttDiagram-T4ZO3ILL-B3vKq5ov.js} +1 -1
  264. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-DoE1AFmA.js → gitGraphDiagram-UUTBAWPF-dnJPjbtj.js} +1 -1
  265. package/payload/server/public/assets/graph-BVvcFSEQ.js +1 -0
  266. package/payload/server/public/assets/graph-labels-cCvgu38Y.js +1 -0
  267. package/payload/server/public/assets/{graphlib-D7JFfLSw.js → graphlib-CNXtnega.js} +1 -1
  268. package/payload/server/public/assets/{infoDiagram-42DDH7IO-BtsKRO-J.js → infoDiagram-42DDH7IO-D8DYwVAF.js} +1 -1
  269. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-DapnDR0V.js → ishikawaDiagram-UXIWVN3A-CfEVY2Ud.js} +1 -1
  270. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-BLqfwMMu.js → journeyDiagram-VCZTEJTY-FmoBq7Kh.js} +1 -1
  271. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-DqiSbvUW.js → kanban-definition-6JOO6SKY-B-Q9qk99.js} +1 -1
  272. package/payload/server/public/assets/{line-DcwBbbhp.js → line-DZwbJENP.js} +1 -1
  273. package/payload/server/public/assets/{mermaid-parser.core-BZwNRInp.js → mermaid-parser.core-C3ahKykw.js} +1 -1
  274. package/payload/server/public/assets/{mermaid.core-DnsyVInc.js → mermaid.core-B8OFZIE8.js} +3 -3
  275. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-MPPO8aDx.js → mindmap-definition-QFDTVHPH-CqhkfbWa.js} +1 -1
  276. package/payload/server/public/assets/{page-Cz_pVOv_.js → page-BnAxPkmg.js} +1 -1
  277. package/payload/server/public/assets/{page-pXt2BdOS.js → page-BnaN3u86.js} +1 -1
  278. package/payload/server/public/assets/{pieDiagram-DEJITSTG-BKzbjD_5.js → pieDiagram-DEJITSTG-CJqQ6u3-.js} +1 -1
  279. package/payload/server/public/assets/{public-C-0V5aC8.js → public-rWe9ADCS.js} +3 -3
  280. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-CvvvRvae.js → quadrantDiagram-34T5L4WZ-BtEqOXQM.js} +1 -1
  281. package/payload/server/public/assets/{requirementDiagram-MS252O5E-BxXZA7z2.js → requirementDiagram-MS252O5E-DPHWUtE6.js} +1 -1
  282. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-BRRFjjao.js → sankeyDiagram-XADWPNL6-FBmc3wHT.js} +1 -1
  283. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-DOy_E8xe.js → sequenceDiagram-FGHM5R23-BMSd2-1M.js} +1 -1
  284. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-CDqat01p.js → stateDiagram-FHFEXIEX-DqFbU-LT.js} +1 -1
  285. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CXTnsHGq.js +1 -0
  286. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-AK1yuR8Z.js → timeline-definition-GMOUNBTQ-DsfvHVK8.js} +1 -1
  287. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-CCKJ7H16.js → vennDiagram-DHZGUBPP-kHU05mqC.js} +1 -1
  288. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-CKHKJnG_.js → wardleyDiagram-NUSXRM2D-C696Mez_.js} +1 -1
  289. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-BBD35k9e.js → xychartDiagram-5P7HB3ND-yht-JyXJ.js} +1 -1
  290. package/payload/server/public/data.html +5 -5
  291. package/payload/server/public/graph.html +5 -5
  292. package/payload/server/public/index.html +7 -7
  293. package/payload/server/public/public.html +4 -4
  294. package/payload/server/server.js +209 -178
  295. package/payload/server/public/assets/admin-BbcN8otj.js +0 -216
  296. package/payload/server/public/assets/channel-p5ZCfK_I.js +0 -1
  297. package/payload/server/public/assets/classDiagram-6PBFFD2Q-DqDM1D4-.js +0 -1
  298. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DeYvPD8H.js +0 -1
  299. package/payload/server/public/assets/clone-60k82qeh.js +0 -1
  300. package/payload/server/public/assets/data-oXs84UW6.js +0 -1
  301. package/payload/server/public/assets/graph-DcpqWbSs.js +0 -1
  302. package/payload/server/public/assets/graph-labels-DJvuUODB.js +0 -1
  303. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-DHckg-aj.js +0 -1
  304. /package/payload/server/public/assets/{brand-B6pMzF6h.js → brand-BWCtpG7_.js} +0 -0
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@maxy/property-data",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "start": "node dist/index.js",
10
+ "test": "vitest run"
11
+ },
12
+ "dependencies": {
13
+ "@modelcontextprotocol/sdk": "^1.12.1",
14
+ "neo4j-driver": "^5.28.1"
15
+ },
16
+ "devDependencies": {
17
+ "typescript": "^5.7.0",
18
+ "@types/node": "^22.0.0",
19
+ "vitest": "^4.1.2"
20
+ }
21
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Key register / list / deregister round-trip against a mocked Neo4j
3
+ * session + mocked crypto + mocked fetch (for key validation against
4
+ * PropertyData's /national-hpi endpoint).
5
+ */
6
+
7
+ import { afterEach, describe, expect, it, vi } from "vitest";
8
+
9
+ vi.mock("../lib/neo4j.js", () => {
10
+ let stored: { encryptedKey: string; createdAt: string; updatedAt: string } | null = null;
11
+ return {
12
+ getSession: () => ({
13
+ run: vi.fn(async (cypher: string, params: Record<string, unknown>) => {
14
+ if (/CREATE INDEX/.test(cypher)) return { records: [] };
15
+ if (/MERGE \(k:PropertyDataKey/.test(cypher)) {
16
+ const now = String(params.now ?? new Date().toISOString());
17
+ stored = {
18
+ encryptedKey: String(params.encryptedKey),
19
+ createdAt: stored?.createdAt ?? now,
20
+ updatedAt: now,
21
+ };
22
+ return { records: [] };
23
+ }
24
+ if (/MATCH \(k:PropertyDataKey/.test(cypher) && /DELETE/.test(cypher)) {
25
+ const count = stored ? 1 : 0;
26
+ stored = null;
27
+ return {
28
+ records: [{ get: (k: string) => (k === "deleted" ? { toNumber: () => count } : null) }],
29
+ };
30
+ }
31
+ if (/MATCH \(k:PropertyDataKey/.test(cypher) && /RETURN/.test(cypher)) {
32
+ return {
33
+ records: stored
34
+ ? [
35
+ {
36
+ get: (k: string) => {
37
+ if (k === "encryptedKey") return stored!.encryptedKey;
38
+ if (k === "createdAt") return stored!.createdAt;
39
+ if (k === "updatedAt") return stored!.updatedAt;
40
+ return null;
41
+ },
42
+ },
43
+ ]
44
+ : [],
45
+ };
46
+ }
47
+ return { records: [] };
48
+ }),
49
+ close: vi.fn(async () => {}),
50
+ }),
51
+ closeDriver: vi.fn(async () => {}),
52
+ };
53
+ });
54
+
55
+ vi.mock("../lib/crypto.js", () => ({
56
+ encrypt: (s: string) => `enc:${s}`,
57
+ decrypt: (s: string) => s.replace(/^enc:/, ""),
58
+ }));
59
+
60
+ import { keyRegister } from "../tools/key-register.js";
61
+ import { keyDeregister } from "../tools/key-deregister.js";
62
+ import { keyList } from "../tools/key-list.js";
63
+
64
+ const originalFetch = globalThis.fetch;
65
+
66
+ afterEach(() => {
67
+ globalThis.fetch = originalFetch;
68
+ vi.restoreAllMocks();
69
+ });
70
+
71
+ describe("key round-trip", () => {
72
+ it("rejects an unregistered account on list", async () => {
73
+ const out = await keyList({ accountId: "acc-empty" });
74
+ expect(out).toMatch(/No PropertyData API key registered/);
75
+ });
76
+
77
+ it("rejects deregister when no key is stored", async () => {
78
+ await expect(keyDeregister({ accountId: "acc-empty-2" })).rejects.toThrow(
79
+ /No PropertyData API key/,
80
+ );
81
+ });
82
+
83
+ it("rejects registration when PropertyData returns 401", async () => {
84
+ globalThis.fetch = vi.fn(
85
+ async () => new Response("unauthorized", { status: 401 }),
86
+ ) as unknown as typeof fetch;
87
+ await expect(keyRegister({ apiKey: "bad-key", accountId: "acc-x" })).rejects.toThrow(
88
+ /rejected the API key/,
89
+ );
90
+ });
91
+
92
+ it("registers, lists, then deregisters successfully", async () => {
93
+ globalThis.fetch = vi.fn(
94
+ async () =>
95
+ new Response(JSON.stringify({ status: "success", uk_hpi: 100 }), { status: 200 }),
96
+ ) as unknown as typeof fetch;
97
+ await keyRegister({ apiKey: "good-key-12345", accountId: "acc-r" });
98
+
99
+ const listed = await keyList({ accountId: "acc-r" });
100
+ expect(listed).toMatch(/PropertyData API key registered/);
101
+ expect(listed).not.toContain("good-key-12345");
102
+
103
+ await keyDeregister({ accountId: "acc-r" });
104
+ const afterDelete = await keyList({ accountId: "acc-r" });
105
+ expect(afterDelete).toMatch(/No PropertyData API key/);
106
+ });
107
+ });
@@ -0,0 +1,184 @@
1
+ /**
2
+ * propertydata-api.ts behaviour with mocked fetch + mocked neo4j store.
3
+ * Covers:
4
+ * - missing-key path throws KeyNotRegisteredError
5
+ * - 4xx upstream maps to PropertyDataError with the status preserved
6
+ * - 5xx upstream maps to PropertyDataError with the status preserved
7
+ * - PropertyData JSON envelope with status=error maps to PropertyDataError
8
+ * - non-JSON body maps to BadResponseError
9
+ * - successful call returns the payload as a JSON string
10
+ */
11
+
12
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
13
+
14
+ vi.mock("../lib/neo4j.js", () => {
15
+ let stored: { encryptedKey: string } | null = null;
16
+ return {
17
+ getSession: () => ({
18
+ run: vi.fn(async (cypher: string, params: Record<string, unknown>) => {
19
+ if (/MATCH \(k:PropertyDataKey/.test(cypher) && /RETURN/.test(cypher)) {
20
+ return {
21
+ records: stored
22
+ ? [
23
+ {
24
+ get: (k: string) => {
25
+ if (k === "encryptedKey") return stored!.encryptedKey;
26
+ if (k === "createdAt") return "2026-01-01T00:00:00Z";
27
+ if (k === "updatedAt") return "2026-01-01T00:00:00Z";
28
+ return null;
29
+ },
30
+ },
31
+ ]
32
+ : [],
33
+ };
34
+ }
35
+ if (/MERGE \(k:PropertyDataKey/.test(cypher)) {
36
+ stored = { encryptedKey: String(params.encryptedKey) };
37
+ return { records: [] };
38
+ }
39
+ if (/DELETE k/.test(cypher)) {
40
+ const count = stored ? 1 : 0;
41
+ stored = null;
42
+ return {
43
+ records: [{ get: (k: string) => (k === "deleted" ? { toNumber: () => count } : null) }],
44
+ };
45
+ }
46
+ return { records: [] };
47
+ }),
48
+ close: vi.fn(async () => {}),
49
+ }),
50
+ closeDriver: vi.fn(async () => {}),
51
+ };
52
+ });
53
+
54
+ vi.mock("../lib/crypto.js", () => ({
55
+ encrypt: (s: string) => `enc:${s}`,
56
+ decrypt: (s: string) => s.replace(/^enc:/, ""),
57
+ }));
58
+
59
+ import {
60
+ propertyDataCall,
61
+ KeyNotRegisteredError,
62
+ PropertyDataError,
63
+ BadResponseError,
64
+ } from "../lib/propertydata-api.js";
65
+ import { keyRegister } from "../tools/key-register.js";
66
+
67
+ const originalFetch = globalThis.fetch;
68
+
69
+ function mockFetch(impl: (url: string) => Response | Promise<Response>) {
70
+ globalThis.fetch = vi.fn(async (url: string | URL | Request) =>
71
+ impl(String(url)),
72
+ ) as unknown as typeof fetch;
73
+ }
74
+
75
+ beforeEach(() => {
76
+ // no-op
77
+ });
78
+
79
+ afterEach(() => {
80
+ globalThis.fetch = originalFetch;
81
+ vi.restoreAllMocks();
82
+ });
83
+
84
+ async function registerTestKey() {
85
+ mockFetch(() => new Response(JSON.stringify({ status: "success", uk_hpi: 100 }), { status: 200 }));
86
+ await keyRegister({ apiKey: "test-key-12345", accountId: "acc-1" });
87
+ }
88
+
89
+ describe("propertyDataCall", () => {
90
+ it("throws KeyNotRegisteredError when no key is stored", async () => {
91
+ await expect(
92
+ propertyDataCall({
93
+ endpoint: "demand",
94
+ toolName: "property-data-demand",
95
+ accountId: "ghost-account",
96
+ params: { postcode: "SN7" },
97
+ }),
98
+ ).rejects.toBeInstanceOf(KeyNotRegisteredError);
99
+ });
100
+
101
+ it("returns the JSON payload as a string on success", async () => {
102
+ await registerTestKey();
103
+ mockFetch(
104
+ () => new Response(JSON.stringify({ status: "success", point_estimate: 350000 }), { status: 200 }),
105
+ );
106
+ const result = await propertyDataCall({
107
+ endpoint: "valuation-sale",
108
+ toolName: "property-data-valuation-sale",
109
+ accountId: "acc-1",
110
+ params: { postcode: "SN7", type: "detached", bedrooms: 3 },
111
+ });
112
+ expect(result).toContain("point_estimate");
113
+ expect(JSON.parse(result).point_estimate).toBe(350000);
114
+ });
115
+
116
+ it("maps HTTP 4xx to PropertyDataError preserving the status", async () => {
117
+ await registerTestKey();
118
+ mockFetch(() => new Response("bad postcode", { status: 400 }));
119
+ try {
120
+ await propertyDataCall({
121
+ endpoint: "demand",
122
+ toolName: "property-data-demand",
123
+ accountId: "acc-1",
124
+ params: { postcode: "INVALID" },
125
+ });
126
+ throw new Error("expected PropertyDataError");
127
+ } catch (err) {
128
+ expect(err).toBeInstanceOf(PropertyDataError);
129
+ expect((err as PropertyDataError).status).toBe(400);
130
+ }
131
+ });
132
+
133
+ it("maps HTTP 5xx to PropertyDataError preserving the status", async () => {
134
+ await registerTestKey();
135
+ mockFetch(() => new Response("upstream down", { status: 503 }));
136
+ try {
137
+ await propertyDataCall({
138
+ endpoint: "demand",
139
+ toolName: "property-data-demand",
140
+ accountId: "acc-1",
141
+ params: { postcode: "SN7" },
142
+ });
143
+ throw new Error("expected PropertyDataError");
144
+ } catch (err) {
145
+ expect(err).toBeInstanceOf(PropertyDataError);
146
+ expect((err as PropertyDataError).status).toBe(503);
147
+ }
148
+ });
149
+
150
+ it("maps PropertyData status=error envelope to PropertyDataError", async () => {
151
+ await registerTestKey();
152
+ mockFetch(
153
+ () =>
154
+ new Response(JSON.stringify({ status: "error", message: "Insufficient comparables" }), {
155
+ status: 200,
156
+ }),
157
+ );
158
+ try {
159
+ await propertyDataCall({
160
+ endpoint: "valuation-sale",
161
+ toolName: "property-data-valuation-sale",
162
+ accountId: "acc-1",
163
+ params: { postcode: "SN7" },
164
+ });
165
+ throw new Error("expected PropertyDataError");
166
+ } catch (err) {
167
+ expect(err).toBeInstanceOf(PropertyDataError);
168
+ expect((err as PropertyDataError).upstreamMessage).toMatch(/Insufficient/);
169
+ }
170
+ });
171
+
172
+ it("maps non-JSON body to BadResponseError", async () => {
173
+ await registerTestKey();
174
+ mockFetch(() => new Response("<html>service maintenance</html>", { status: 200 }));
175
+ await expect(
176
+ propertyDataCall({
177
+ endpoint: "demand",
178
+ toolName: "property-data-demand",
179
+ accountId: "acc-1",
180
+ params: { postcode: "SN7" },
181
+ }),
182
+ ).rejects.toBeInstanceOf(BadResponseError);
183
+ });
184
+ });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Schema parity: every tool declared in PLUGIN.md frontmatter must have a
3
+ * handler registered in index.ts, and every handler in index.ts must be
4
+ * declared in PLUGIN.md. Blocks silent drift between the manifest the
5
+ * platform reads and the tool surface the server exposes.
6
+ */
7
+
8
+ import { describe, expect, it } from "vitest";
9
+ import { readFileSync } from "node:fs";
10
+ import { resolve, dirname } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+
13
+ const here = dirname(fileURLToPath(import.meta.url));
14
+ const PLUGIN_ROOT = resolve(here, "../../..");
15
+
16
+ function readPluginMdTools(): string[] {
17
+ const md = readFileSync(resolve(PLUGIN_ROOT, "PLUGIN.md"), "utf-8");
18
+ const fmMatch = md.match(/^---\n([\s\S]*?)\n---/);
19
+ if (!fmMatch) throw new Error("PLUGIN.md frontmatter not found");
20
+ const tools: string[] = [];
21
+ for (const line of fmMatch[1].split("\n")) {
22
+ const m = line.match(/^\s+-\s+name:\s+(\S+)/);
23
+ if (m) tools.push(m[1]);
24
+ }
25
+ return tools;
26
+ }
27
+
28
+ function readIndexTools(): string[] {
29
+ const src = readFileSync(resolve(PLUGIN_ROOT, "mcp/src/index.ts"), "utf-8");
30
+ const matches = src.matchAll(/server\.tool\(\s*"([^"]+)"/g);
31
+ return Array.from(matches, (m) => m[1]);
32
+ }
33
+
34
+ describe("schema parity", () => {
35
+ it("PLUGIN.md frontmatter and index.ts register the same tool set", () => {
36
+ const declared = readPluginMdTools().sort();
37
+ const registered = readIndexTools().sort();
38
+ expect(registered).toEqual(declared);
39
+ });
40
+
41
+ it("declares all 12 tools (3 key-mgmt + 9 endpoints)", () => {
42
+ const declared = readPluginMdTools();
43
+ expect(declared).toHaveLength(12);
44
+ expect(declared).toContain("property-data-key-register");
45
+ expect(declared).toContain("property-data-valuation-sale");
46
+ expect(declared).toContain("property-data-national-hpi");
47
+ });
48
+ });
@@ -0,0 +1,249 @@
1
+ import { initStderrTee } from "../../../../../../platform/lib/mcp-stderr-tee/dist/index.js";
2
+ initStderrTee("property-data");
3
+
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { z } from "zod";
7
+ import { keyRegister } from "./tools/key-register.js";
8
+ import { keyDeregister } from "./tools/key-deregister.js";
9
+ import { keyList } from "./tools/key-list.js";
10
+ import {
11
+ valuationSale,
12
+ valuationRent,
13
+ prices,
14
+ pricesRent,
15
+ soldPrices,
16
+ demand,
17
+ yields,
18
+ growth,
19
+ nationalHpi,
20
+ } from "./tools/endpoints.js";
21
+ import { closeDriver } from "./lib/neo4j.js";
22
+ import {
23
+ KeyNotRegisteredError,
24
+ PropertyDataError,
25
+ BadResponseError,
26
+ } from "./lib/propertydata-api.js";
27
+
28
+ const accountId = process.env.ACCOUNT_ID;
29
+ if (!accountId) {
30
+ throw new Error("ACCOUNT_ID environment variable is required");
31
+ }
32
+
33
+ const server = new McpServer({
34
+ name: "maxy-property-data",
35
+ version: "0.1.0",
36
+ });
37
+
38
+ console.error(`[property-data] server started, account=${accountId}`);
39
+
40
+ function errorEnvelope(err: unknown): { content: { type: "text"; text: string }[]; isError: true } {
41
+ let text: string;
42
+ if (err instanceof KeyNotRegisteredError) {
43
+ text =
44
+ "Error: key-not-registered. Register a PropertyData API key with property-data-key-register before calling this tool.";
45
+ } else if (err instanceof PropertyDataError) {
46
+ text = `Error: http-${err.status} from PropertyData. ${err.upstreamMessage}`;
47
+ } else if (err instanceof BadResponseError) {
48
+ text = `Error: bad-response from PropertyData (non-JSON, length=${err.length}).`;
49
+ } else {
50
+ text = `Error: ${err instanceof Error ? err.message : String(err)}`;
51
+ }
52
+ return { content: [{ type: "text" as const, text }], isError: true };
53
+ }
54
+
55
+ function endpointHandler<P extends Record<string, unknown>>(
56
+ fn: (args: P & { accountId: string }) => Promise<string>,
57
+ ) {
58
+ return async (params: P) => {
59
+ try {
60
+ const text = await fn({ ...params, accountId: accountId as string });
61
+ return { content: [{ type: "text" as const, text }] };
62
+ } catch (err) {
63
+ return errorEnvelope(err);
64
+ }
65
+ };
66
+ }
67
+
68
+ // ─────────────────────────────────────────────────────────────────
69
+ // Key Management
70
+ // ─────────────────────────────────────────────────────────────────
71
+
72
+ server.tool(
73
+ "property-data-key-register",
74
+ "Register the account's PropertyData API key. Validates the key against PropertyData's /national-hpi endpoint, encrypts it with AES-256-GCM, and stores it in Neo4j. One key per account; re-registering overwrites the existing key.",
75
+ {
76
+ apiKey: z.string().min(8).describe("PropertyData API key from propertydata.co.uk dashboard"),
77
+ },
78
+ async ({ apiKey }) => {
79
+ try {
80
+ await keyRegister({ apiKey, accountId: accountId as string });
81
+ return {
82
+ content: [
83
+ {
84
+ type: "text" as const,
85
+ text: "PropertyData API key registered successfully.",
86
+ },
87
+ ],
88
+ };
89
+ } catch (err) {
90
+ return errorEnvelope(err);
91
+ }
92
+ },
93
+ );
94
+
95
+ server.tool(
96
+ "property-data-key-deregister",
97
+ "Remove the account's PropertyData API key. The encrypted key is permanently deleted from Neo4j.",
98
+ {},
99
+ async () => {
100
+ try {
101
+ await keyDeregister({ accountId: accountId as string });
102
+ return {
103
+ content: [{ type: "text" as const, text: "PropertyData API key deregistered." }],
104
+ };
105
+ } catch (err) {
106
+ return errorEnvelope(err);
107
+ }
108
+ },
109
+ );
110
+
111
+ server.tool(
112
+ "property-data-key-list",
113
+ "Show whether a PropertyData API key is registered for this account. Never reveals the key value.",
114
+ {},
115
+ async () => {
116
+ try {
117
+ const text = await keyList({ accountId: accountId as string });
118
+ return { content: [{ type: "text" as const, text }] };
119
+ } catch (err) {
120
+ return errorEnvelope(err);
121
+ }
122
+ },
123
+ );
124
+
125
+ // ─────────────────────────────────────────────────────────────────
126
+ // Valuation endpoints
127
+ // ─────────────────────────────────────────────────────────────────
128
+
129
+ const valuationSchema = {
130
+ postcode: z.string().min(2).describe("UK postcode (outcode or full)"),
131
+ type: z
132
+ .enum(["detached", "semi-detached", "terraced", "flat"])
133
+ .describe("Property type"),
134
+ bedrooms: z.number().int().min(0).describe("Bedroom count"),
135
+ bathrooms: z.number().int().min(0).optional().describe("Bathroom count"),
136
+ internal_area: z.number().optional().describe("Internal area (sq ft)"),
137
+ construction_date: z.string().optional().describe("Construction date band (e.g. 'pre1900', '1900-1929')"),
138
+ finish_quality: z
139
+ .enum(["luxury", "above_average", "average", "below_average", "needs_modernisation"])
140
+ .optional()
141
+ .describe("Finish quality"),
142
+ };
143
+
144
+ server.tool(
145
+ "property-data-valuation-sale",
146
+ "Point sale-price estimate for a property given postcode + attributes. Returns point estimate plus comparables.",
147
+ valuationSchema,
148
+ endpointHandler(valuationSale),
149
+ );
150
+
151
+ server.tool(
152
+ "property-data-valuation-rent",
153
+ "Point rent estimate (PCM) for a property given postcode + attributes. Returns point estimate plus comparables.",
154
+ valuationSchema,
155
+ endpointHandler(valuationRent),
156
+ );
157
+
158
+ // ─────────────────────────────────────────────────────────────────
159
+ // Asking-price distributions
160
+ // ─────────────────────────────────────────────────────────────────
161
+
162
+ const pricesSchema = {
163
+ postcode: z.string().min(2).describe("UK postcode (outcode or full)"),
164
+ type: z
165
+ .enum(["detached", "semi-detached", "terraced", "flat"])
166
+ .optional()
167
+ .describe("Filter by property type"),
168
+ bedrooms: z.number().int().min(0).optional().describe("Filter by bedroom count"),
169
+ };
170
+
171
+ server.tool(
172
+ "property-data-prices",
173
+ "Asking-price distribution for sale listings in a postcode — mean, median, 80th percentile.",
174
+ pricesSchema,
175
+ endpointHandler(prices),
176
+ );
177
+
178
+ server.tool(
179
+ "property-data-prices-rent",
180
+ "Asking-price distribution for rental listings in a postcode — mean, median, 80th percentile (PCM).",
181
+ pricesSchema,
182
+ endpointHandler(pricesRent),
183
+ );
184
+
185
+ // ─────────────────────────────────────────────────────────────────
186
+ // Sold prices (Land Registry)
187
+ // ─────────────────────────────────────────────────────────────────
188
+
189
+ server.tool(
190
+ "property-data-sold-prices",
191
+ "Land Registry sold transactions for a postcode, optionally filtered by age, type, and bedrooms.",
192
+ {
193
+ postcode: z.string().min(2).describe("UK postcode (outcode or full)"),
194
+ max_age: z.number().int().min(1).optional().describe("Max transaction age in months"),
195
+ type: z
196
+ .enum(["detached", "semi-detached", "terraced", "flat"])
197
+ .optional()
198
+ .describe("Filter by property type"),
199
+ bedrooms: z.number().int().min(0).optional().describe("Filter by bedroom count"),
200
+ },
201
+ endpointHandler(soldPrices),
202
+ );
203
+
204
+ // ─────────────────────────────────────────────────────────────────
205
+ // Demand / yields / growth / national HPI
206
+ // ─────────────────────────────────────────────────────────────────
207
+
208
+ server.tool(
209
+ "property-data-demand",
210
+ "Demand signals for a postcode — count of properties for sale and average days to sell-STC.",
211
+ { postcode: z.string().min(2).describe("UK postcode (outcode or full)") },
212
+ endpointHandler(demand),
213
+ );
214
+
215
+ server.tool(
216
+ "property-data-yields",
217
+ "Gross rental yield for a postcode and bedroom count.",
218
+ {
219
+ postcode: z.string().min(2).describe("UK postcode (outcode or full)"),
220
+ bedrooms: z.number().int().min(0).describe("Bedroom count"),
221
+ },
222
+ endpointHandler(yields),
223
+ );
224
+
225
+ server.tool(
226
+ "property-data-growth",
227
+ "Short-term and long-term price growth signals for a postcode.",
228
+ { postcode: z.string().min(2).describe("UK postcode (outcode or full)") },
229
+ endpointHandler(growth),
230
+ );
231
+
232
+ server.tool(
233
+ "property-data-national-hpi",
234
+ "UK national house-price index series. No postcode required.",
235
+ {},
236
+ endpointHandler(nationalHpi),
237
+ );
238
+
239
+ // ─────────────────────────────────────────────────────────────────
240
+ // Lifecycle
241
+ // ─────────────────────────────────────────────────────────────────
242
+
243
+ process.on("SIGINT", async () => {
244
+ await closeDriver();
245
+ process.exit(0);
246
+ });
247
+
248
+ const transport = new StdioServerTransport();
249
+ await server.connect(transport);
@@ -0,0 +1,79 @@
1
+ import { randomBytes, createCipheriv, createDecipheriv } from "node:crypto";
2
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { homedir } from "node:os";
5
+
6
+ const ALGORITHM = "aes-256-gcm";
7
+ const KEY_LENGTH = 32;
8
+ const IV_LENGTH = 12;
9
+ const AUTH_TAG_LENGTH = 16;
10
+ const KEY_FILE_NAME = ".property-data-encryption-key";
11
+
12
+ function resolveConfigDir(): string {
13
+ try {
14
+ const platformRoot = process.env.PLATFORM_ROOT;
15
+ if (platformRoot) {
16
+ const brandPath = resolve(platformRoot, "config/brand.json");
17
+ const brand = JSON.parse(readFileSync(brandPath, "utf-8"));
18
+ if (brand.configDir) return brand.configDir;
19
+ }
20
+ } catch {
21
+ // Fall back to default
22
+ }
23
+ return ".maxy";
24
+ }
25
+
26
+ function keyFilePath(): string {
27
+ return resolve(homedir(), resolveConfigDir(), KEY_FILE_NAME);
28
+ }
29
+
30
+ function loadOrGenerateKey(): Buffer {
31
+ const path = keyFilePath();
32
+ if (existsSync(path)) {
33
+ const hex = readFileSync(path, "utf-8").trim();
34
+ const buf = Buffer.from(hex, "hex");
35
+ if (buf.length !== KEY_LENGTH) {
36
+ throw new Error(
37
+ `Encryption key at ${path} is ${buf.length} bytes, expected ${KEY_LENGTH}. ` +
38
+ `Delete the file and re-register the PropertyData API key.`,
39
+ );
40
+ }
41
+ return buf;
42
+ }
43
+ const key = randomBytes(KEY_LENGTH);
44
+ writeFileSync(path, key.toString("hex"), { mode: 0o600 });
45
+ console.error(`[property-data] encryption key generated at ${path}`);
46
+ return key;
47
+ }
48
+
49
+ export function encrypt(plaintext: string): string {
50
+ const key = loadOrGenerateKey();
51
+ const iv = randomBytes(IV_LENGTH);
52
+ const cipher = createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
53
+ let encrypted = cipher.update(plaintext, "utf-8", "hex");
54
+ encrypted += cipher.final("hex");
55
+ const authTag = cipher.getAuthTag().toString("hex");
56
+ return `${iv.toString("hex")}:${encrypted}:${authTag}`;
57
+ }
58
+
59
+ export function decrypt(encryptedValue: string): string {
60
+ const parts = encryptedValue.split(":");
61
+ if (parts.length !== 3) {
62
+ throw new Error("Malformed encrypted value — expected iv:ciphertext:authTag");
63
+ }
64
+ const [ivHex, ciphertextHex, authTagHex] = parts;
65
+ const path = keyFilePath();
66
+ if (!existsSync(path)) {
67
+ throw new Error(
68
+ `Encryption key not found at ${path}. The stored PropertyData API key is unrecoverable. Re-register.`,
69
+ );
70
+ }
71
+ const key = loadOrGenerateKey();
72
+ const iv = Buffer.from(ivHex, "hex");
73
+ const authTag = Buffer.from(authTagHex, "hex");
74
+ const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
75
+ decipher.setAuthTag(authTag);
76
+ let decrypted = decipher.update(ciphertextHex, "hex", "utf-8");
77
+ decrypted += decipher.final("utf-8");
78
+ return decrypted;
79
+ }