@hcu-lab.me/mcp-device 1.0.10 → 1.0.12

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 (491) hide show
  1. package/PRIVACY.md +21 -0
  2. package/README.md +57 -2
  3. package/SECURITY.md +67 -0
  4. package/config/revoked-approvals.json +1 -0
  5. package/control-plane/config/mcp-servers.example.toml +52 -0
  6. package/control-plane/config/mcp-servers.toml +52 -0
  7. package/control-plane/contracts/device-public-contract-v1.json +733 -0
  8. package/control-plane/scripts/GATEWAY-LICENSE.txt +7 -0
  9. package/control-plane/scripts/account-http.mjs +220 -0
  10. package/control-plane/scripts/account-store.mjs +368 -0
  11. package/control-plane/scripts/admin.mjs +68 -0
  12. package/control-plane/scripts/auth-session.mjs +411 -0
  13. package/control-plane/scripts/authenticated-mcp-wrapper.mjs +1281 -0
  14. package/control-plane/scripts/benchmark-command.mjs +9 -0
  15. package/control-plane/scripts/benchmark-mcp-catalog.mjs +174 -0
  16. package/control-plane/scripts/catalog-budget.mjs +87 -0
  17. package/control-plane/scripts/check-device-public-contract.mjs +20 -0
  18. package/control-plane/scripts/custom-tools/index.mjs +221 -0
  19. package/control-plane/scripts/custom-tools/path-utils.mjs +117 -0
  20. package/control-plane/scripts/custom-tools/response-utils.mjs +34 -0
  21. package/control-plane/scripts/dashboard-http.mjs +598 -0
  22. package/control-plane/scripts/device-access-policy.mjs +205 -0
  23. package/control-plane/scripts/device-admin.mjs +43 -0
  24. package/control-plane/scripts/device-audit.mjs +63 -0
  25. package/control-plane/scripts/device-broker.mjs +1461 -0
  26. package/control-plane/scripts/device-execution-tool-definitions.mjs +217 -0
  27. package/control-plane/scripts/device-inventory.mjs +128 -0
  28. package/control-plane/scripts/device-pairing-http.mjs +109 -0
  29. package/control-plane/scripts/device-pairing-store.mjs +245 -0
  30. package/control-plane/scripts/device-public-contract.mjs +165 -0
  31. package/control-plane/scripts/device-release.mjs +95 -0
  32. package/control-plane/scripts/device-secure-transport.mjs +128 -0
  33. package/control-plane/scripts/device-store.mjs +283 -0
  34. package/control-plane/scripts/device-usage.mjs +657 -0
  35. package/control-plane/scripts/embedded-device.mjs +149 -0
  36. package/control-plane/scripts/enduser-ui.mjs +45 -0
  37. package/control-plane/scripts/external-tool-broker.mjs +179 -0
  38. package/control-plane/scripts/oauth-authorization-server.mjs +589 -0
  39. package/control-plane/scripts/operator-dashboard-http.mjs +81 -0
  40. package/control-plane/scripts/project-inspection.mjs +227 -0
  41. package/control-plane/scripts/projects/project-id.mjs +69 -0
  42. package/control-plane/scripts/projects/trusted-roots-projects.mjs +375 -0
  43. package/control-plane/scripts/prompts/index.mjs +60 -0
  44. package/control-plane/scripts/remote-process-sessions.mjs +78 -0
  45. package/control-plane/scripts/remote-tool-result.mjs +9 -0
  46. package/control-plane/scripts/resources/index.mjs +166 -0
  47. package/control-plane/scripts/runtime-profile.mjs +54 -0
  48. package/control-plane/scripts/shell-tool-descriptor.mjs +17 -0
  49. package/control-plane/scripts/skill-source-safety.mjs +46 -0
  50. package/control-plane/scripts/skills/README.md +70 -0
  51. package/control-plane/scripts/skills/_upstream_licenses/anthropic-THIRD_PARTY_NOTICES.md +405 -0
  52. package/control-plane/scripts/skills/_upstream_licenses/hallmark-LICENSE.txt +21 -0
  53. package/control-plane/scripts/skills/_upstream_licenses/ponytail-LICENSE.txt +21 -0
  54. package/control-plane/scripts/skills/_upstream_licenses/stitch-skills-LICENSE.txt +201 -0
  55. package/control-plane/scripts/skills/_upstream_licenses/superpowers-LICENSE.txt +21 -0
  56. package/control-plane/scripts/skills/algorithmic-art/LICENSE.txt +202 -0
  57. package/control-plane/scripts/skills/algorithmic-art/SKILL.md +405 -0
  58. package/control-plane/scripts/skills/algorithmic-art/templates/generator_template.js +223 -0
  59. package/control-plane/scripts/skills/algorithmic-art/templates/viewer.html +599 -0
  60. package/control-plane/scripts/skills/brainstorming/SKILL.md +285 -0
  61. package/control-plane/scripts/skills/brainstorming/scripts/frame-template.html +213 -0
  62. package/control-plane/scripts/skills/brainstorming/scripts/helper.js +167 -0
  63. package/control-plane/scripts/skills/brainstorming/scripts/server.cjs +723 -0
  64. package/control-plane/scripts/skills/brainstorming/scripts/start-server.sh +209 -0
  65. package/control-plane/scripts/skills/brainstorming/scripts/stop-server.sh +120 -0
  66. package/control-plane/scripts/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  67. package/control-plane/scripts/skills/brainstorming/visual-companion.md +299 -0
  68. package/control-plane/scripts/skills/brand-guidelines/LICENSE.txt +202 -0
  69. package/control-plane/scripts/skills/brand-guidelines/SKILL.md +73 -0
  70. package/control-plane/scripts/skills/claude-api/LICENSE.txt +202 -0
  71. package/control-plane/scripts/skills/claude-api/SKILL.md +570 -0
  72. package/control-plane/scripts/skills/claude-api/csharp/claude-api/README.md +362 -0
  73. package/control-plane/scripts/skills/claude-api/csharp/claude-api/batches.md +14 -0
  74. package/control-plane/scripts/skills/claude-api/csharp/claude-api/files-api.md +25 -0
  75. package/control-plane/scripts/skills/claude-api/csharp/claude-api/streaming.md +28 -0
  76. package/control-plane/scripts/skills/claude-api/csharp/claude-api/tool-use.md +164 -0
  77. package/control-plane/scripts/skills/claude-api/curl/examples.md +256 -0
  78. package/control-plane/scripts/skills/claude-api/curl/managed-agents.md +371 -0
  79. package/control-plane/scripts/skills/claude-api/go/claude-api/README.md +188 -0
  80. package/control-plane/scripts/skills/claude-api/go/claude-api/files-api.md +23 -0
  81. package/control-plane/scripts/skills/claude-api/go/claude-api/streaming.md +43 -0
  82. package/control-plane/scripts/skills/claude-api/go/claude-api/tool-use.md +220 -0
  83. package/control-plane/scripts/skills/claude-api/go/managed-agents/README.md +564 -0
  84. package/control-plane/scripts/skills/claude-api/java/claude-api/README.md +238 -0
  85. package/control-plane/scripts/skills/claude-api/java/claude-api/files-api.md +27 -0
  86. package/control-plane/scripts/skills/claude-api/java/claude-api/streaming.md +24 -0
  87. package/control-plane/scripts/skills/claude-api/java/claude-api/tool-use.md +228 -0
  88. package/control-plane/scripts/skills/claude-api/java/managed-agents/README.md +443 -0
  89. package/control-plane/scripts/skills/claude-api/php/claude-api/README.md +174 -0
  90. package/control-plane/scripts/skills/claude-api/php/claude-api/batches.md +15 -0
  91. package/control-plane/scripts/skills/claude-api/php/claude-api/files-api.md +13 -0
  92. package/control-plane/scripts/skills/claude-api/php/claude-api/streaming.md +27 -0
  93. package/control-plane/scripts/skills/claude-api/php/claude-api/tool-use.md +253 -0
  94. package/control-plane/scripts/skills/claude-api/php/managed-agents/README.md +443 -0
  95. package/control-plane/scripts/skills/claude-api/python/claude-api/README.md +565 -0
  96. package/control-plane/scripts/skills/claude-api/python/claude-api/batches.md +198 -0
  97. package/control-plane/scripts/skills/claude-api/python/claude-api/files-api.md +170 -0
  98. package/control-plane/scripts/skills/claude-api/python/claude-api/sdk-upgrade.md +286 -0
  99. package/control-plane/scripts/skills/claude-api/python/claude-api/streaming.md +179 -0
  100. package/control-plane/scripts/skills/claude-api/python/claude-api/tool-use.md +627 -0
  101. package/control-plane/scripts/skills/claude-api/python/managed-agents/README.md +335 -0
  102. package/control-plane/scripts/skills/claude-api/ruby/claude-api/README.md +134 -0
  103. package/control-plane/scripts/skills/claude-api/ruby/claude-api/streaming.md +16 -0
  104. package/control-plane/scripts/skills/claude-api/ruby/claude-api/tool-use.md +41 -0
  105. package/control-plane/scripts/skills/claude-api/ruby/managed-agents/README.md +394 -0
  106. package/control-plane/scripts/skills/claude-api/shared/admin-api.md +179 -0
  107. package/control-plane/scripts/skills/claude-api/shared/agent-design.md +101 -0
  108. package/control-plane/scripts/skills/claude-api/shared/anthropic-cli.md +269 -0
  109. package/control-plane/scripts/skills/claude-api/shared/claude-platform-on-aws.md +59 -0
  110. package/control-plane/scripts/skills/claude-api/shared/cost-optimization.md +233 -0
  111. package/control-plane/scripts/skills/claude-api/shared/error-codes.md +262 -0
  112. package/control-plane/scripts/skills/claude-api/shared/live-sources.md +166 -0
  113. package/control-plane/scripts/skills/claude-api/shared/managed-agents-api-reference.md +455 -0
  114. package/control-plane/scripts/skills/claude-api/shared/managed-agents-client-patterns.md +220 -0
  115. package/control-plane/scripts/skills/claude-api/shared/managed-agents-core.md +359 -0
  116. package/control-plane/scripts/skills/claude-api/shared/managed-agents-environments.md +226 -0
  117. package/control-plane/scripts/skills/claude-api/shared/managed-agents-events.md +290 -0
  118. package/control-plane/scripts/skills/claude-api/shared/managed-agents-memory.md +201 -0
  119. package/control-plane/scripts/skills/claude-api/shared/managed-agents-multiagent.md +260 -0
  120. package/control-plane/scripts/skills/claude-api/shared/managed-agents-onboarding.md +82 -0
  121. package/control-plane/scripts/skills/claude-api/shared/managed-agents-outcomes.md +108 -0
  122. package/control-plane/scripts/skills/claude-api/shared/managed-agents-overview.md +77 -0
  123. package/control-plane/scripts/skills/claude-api/shared/managed-agents-scheduled-deployments.md +156 -0
  124. package/control-plane/scripts/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +282 -0
  125. package/control-plane/scripts/skills/claude-api/shared/managed-agents-tools.md +504 -0
  126. package/control-plane/scripts/skills/claude-api/shared/managed-agents-webhooks.md +143 -0
  127. package/control-plane/scripts/skills/claude-api/shared/model-migration.md +1872 -0
  128. package/control-plane/scripts/skills/claude-api/shared/models.md +142 -0
  129. package/control-plane/scripts/skills/claude-api/shared/platform-availability.md +52 -0
  130. package/control-plane/scripts/skills/claude-api/shared/prompt-audit.md +223 -0
  131. package/control-plane/scripts/skills/claude-api/shared/prompt-caching.md +292 -0
  132. package/control-plane/scripts/skills/claude-api/shared/token-counting.md +56 -0
  133. package/control-plane/scripts/skills/claude-api/shared/tool-use-concepts.md +526 -0
  134. package/control-plane/scripts/skills/claude-api/typescript/claude-api/README.md +405 -0
  135. package/control-plane/scripts/skills/claude-api/typescript/claude-api/batches.md +106 -0
  136. package/control-plane/scripts/skills/claude-api/typescript/claude-api/files-api.md +98 -0
  137. package/control-plane/scripts/skills/claude-api/typescript/claude-api/streaming.md +178 -0
  138. package/control-plane/scripts/skills/claude-api/typescript/claude-api/tool-use.md +586 -0
  139. package/control-plane/scripts/skills/claude-api/typescript/managed-agents/README.md +359 -0
  140. package/control-plane/scripts/skills/dispatching-parallel-agents/SKILL.md +167 -0
  141. package/control-plane/scripts/skills/enhance-prompt/README.md +34 -0
  142. package/control-plane/scripts/skills/enhance-prompt/SKILL.md +204 -0
  143. package/control-plane/scripts/skills/enhance-prompt/references/KEYWORDS.md +114 -0
  144. package/control-plane/scripts/skills/executing-plans/SKILL.md +371 -0
  145. package/control-plane/scripts/skills/executing-plans/scripts/task-done +52 -0
  146. package/control-plane/scripts/skills/executing-plans/scripts/task-start +28 -0
  147. package/control-plane/scripts/skills/extract-design-md/SKILL.md +358 -0
  148. package/control-plane/scripts/skills/extract-design-md/examples/DESIGN.md +210 -0
  149. package/control-plane/scripts/skills/extract-design-md/references/angular.md +122 -0
  150. package/control-plane/scripts/skills/extract-design-md/references/plain-css.md +161 -0
  151. package/control-plane/scripts/skills/extract-design-md/references/react-tailwind.md +157 -0
  152. package/control-plane/scripts/skills/extract-design-md/references/svelte.md +101 -0
  153. package/control-plane/scripts/skills/extract-design-md/references/vue.md +107 -0
  154. package/control-plane/scripts/skills/finishing-a-development-branch/SKILL.md +225 -0
  155. package/control-plane/scripts/skills/frontend-design/LICENSE.txt +177 -0
  156. package/control-plane/scripts/skills/frontend-design/SKILL.md +71 -0
  157. package/control-plane/scripts/skills/hallmark/SKILL.md +558 -0
  158. package/control-plane/scripts/skills/hallmark/references/anti-patterns.md +418 -0
  159. package/control-plane/scripts/skills/hallmark/references/assets.md +406 -0
  160. package/control-plane/scripts/skills/hallmark/references/color.md +95 -0
  161. package/control-plane/scripts/skills/hallmark/references/component-cookbook.md +263 -0
  162. package/control-plane/scripts/skills/hallmark/references/components/c1-outlined-chip.md +12 -0
  163. package/control-plane/scripts/skills/hallmark/references/components/c2-inline-form-as-cta.md +16 -0
  164. package/control-plane/scripts/skills/hallmark/references/components/c3-typographic-link.md +8 -0
  165. package/control-plane/scripts/skills/hallmark/references/components/c4-sticky-bottom-bar.md +16 -0
  166. package/control-plane/scripts/skills/hallmark/references/components/f1-bento-grid.md +20 -0
  167. package/control-plane/scripts/skills/hallmark/references/components/f2-sticky-scroll-stack.md +20 -0
  168. package/control-plane/scripts/skills/hallmark/references/components/f3-tabular-spec-sheet.md +11 -0
  169. package/control-plane/scripts/skills/hallmark/references/components/f4-step-sequence.md +11 -0
  170. package/control-plane/scripts/skills/hallmark/references/components/f5-annotated-screenshot.md +11 -0
  171. package/control-plane/scripts/skills/hallmark/references/components/f6-product-card-grid.md +41 -0
  172. package/control-plane/scripts/skills/hallmark/references/components/ft1-mast-headed.md +13 -0
  173. package/control-plane/scripts/skills/hallmark/references/components/ft2-inline-rule-single-line.md +10 -0
  174. package/control-plane/scripts/skills/hallmark/references/components/ft3-index-style-category-list.md +12 -0
  175. package/control-plane/scripts/skills/hallmark/references/components/ft4-dense-typographic.md +10 -0
  176. package/control-plane/scripts/skills/hallmark/references/components/ft5-statement.md +21 -0
  177. package/control-plane/scripts/skills/hallmark/references/components/ft6-letter-close.md +19 -0
  178. package/control-plane/scripts/skills/hallmark/references/components/ft7-newsletter-first.md +27 -0
  179. package/control-plane/scripts/skills/hallmark/references/components/ft8-marquee-scroll.md +25 -0
  180. package/control-plane/scripts/skills/hallmark/references/components/h1-marquee.md +15 -0
  181. package/control-plane/scripts/skills/hallmark/references/components/h2-split-diptych.md +15 -0
  182. package/control-plane/scripts/skills/hallmark/references/components/h3-quote-led.md +11 -0
  183. package/control-plane/scripts/skills/hallmark/references/components/h4-stat-led.md +14 -0
  184. package/control-plane/scripts/skills/hallmark/references/components/h5-letter-hero.md +11 -0
  185. package/control-plane/scripts/skills/hallmark/references/components/h6-photographic-fold.md +16 -0
  186. package/control-plane/scripts/skills/hallmark/references/components/h7-demo-video-clipped-by-viewport-edge.md +27 -0
  187. package/control-plane/scripts/skills/hallmark/references/components/h8-mockup-split-browser-framed.md +23 -0
  188. package/control-plane/scripts/skills/hallmark/references/components/h9-custom-illustration-centerpiece.md +27 -0
  189. package/control-plane/scripts/skills/hallmark/references/components/n1-wordmark-2-links.md +12 -0
  190. package/control-plane/scripts/skills/hallmark/references/components/n10-floating-on-scroll-morph.md +19 -0
  191. package/control-plane/scripts/skills/hallmark/references/components/n11-mega-menu.md +40 -0
  192. package/control-plane/scripts/skills/hallmark/references/components/n12-banner-retract.md +34 -0
  193. package/control-plane/scripts/skills/hallmark/references/components/n13-inline-cmdk-pill.md +39 -0
  194. package/control-plane/scripts/skills/hallmark/references/components/n1b-saas-three-section.md +35 -0
  195. package/control-plane/scripts/skills/hallmark/references/components/n2-floating-chip.md +14 -0
  196. package/control-plane/scripts/skills/hallmark/references/components/n3-side-rail.md +14 -0
  197. package/control-plane/scripts/skills/hallmark/references/components/n4-hidden-behind-k.md +9 -0
  198. package/control-plane/scripts/skills/hallmark/references/components/n5-floating-pill.md +28 -0
  199. package/control-plane/scripts/skills/hallmark/references/components/n6-newspaper-masthead.md +24 -0
  200. package/control-plane/scripts/skills/hallmark/references/components/n7-brutal-slab.md +22 -0
  201. package/control-plane/scripts/skills/hallmark/references/components/n8-terminal-command.md +21 -0
  202. package/control-plane/scripts/skills/hallmark/references/components/n9-edge-aligned-minimal.md +17 -0
  203. package/control-plane/scripts/skills/hallmark/references/components/s1-left-margin-numbered.md +15 -0
  204. package/control-plane/scripts/skills/hallmark/references/components/s2-hanging.md +13 -0
  205. package/control-plane/scripts/skills/hallmark/references/components/s3-sticky-pinned.md +19 -0
  206. package/control-plane/scripts/skills/hallmark/references/components/s4-inline-no-break.md +11 -0
  207. package/control-plane/scripts/skills/hallmark/references/components/s5-bottom-anchored.md +13 -0
  208. package/control-plane/scripts/skills/hallmark/references/components/t1-pull-quote-with-marginalia.md +12 -0
  209. package/control-plane/scripts/skills/hallmark/references/components/t2-logo-wall-hairline.md +19 -0
  210. package/control-plane/scripts/skills/hallmark/references/components/t3-single-huge-quote.md +11 -0
  211. package/control-plane/scripts/skills/hallmark/references/components/t4-numbered-stat-strip.md +14 -0
  212. package/control-plane/scripts/skills/hallmark/references/contract.md +25 -0
  213. package/control-plane/scripts/skills/hallmark/references/copy.md +182 -0
  214. package/control-plane/scripts/skills/hallmark/references/custom-craft.md +626 -0
  215. package/control-plane/scripts/skills/hallmark/references/custom-theme.md +367 -0
  216. package/control-plane/scripts/skills/hallmark/references/design-md.md +116 -0
  217. package/control-plane/scripts/skills/hallmark/references/export-formats.md +329 -0
  218. package/control-plane/scripts/skills/hallmark/references/floating-nav.md +89 -0
  219. package/control-plane/scripts/skills/hallmark/references/genres/atmospheric.md +69 -0
  220. package/control-plane/scripts/skills/hallmark/references/genres/editorial.md +72 -0
  221. package/control-plane/scripts/skills/hallmark/references/genres/modern-minimal.md +71 -0
  222. package/control-plane/scripts/skills/hallmark/references/genres/playful.md +70 -0
  223. package/control-plane/scripts/skills/hallmark/references/hero-enrichment.md +475 -0
  224. package/control-plane/scripts/skills/hallmark/references/imagery-kit.md +170 -0
  225. package/control-plane/scripts/skills/hallmark/references/interaction-and-states.md +208 -0
  226. package/control-plane/scripts/skills/hallmark/references/layout-and-space.md +112 -0
  227. package/control-plane/scripts/skills/hallmark/references/macrostructures/01-bento-grid.md +35 -0
  228. package/control-plane/scripts/skills/hallmark/references/macrostructures/02-long-document.md +34 -0
  229. package/control-plane/scripts/skills/hallmark/references/macrostructures/03-marquee-hero.md +31 -0
  230. package/control-plane/scripts/skills/hallmark/references/macrostructures/04-stat-led.md +34 -0
  231. package/control-plane/scripts/skills/hallmark/references/macrostructures/05-workbench.md +32 -0
  232. package/control-plane/scripts/skills/hallmark/references/macrostructures/06-conversational-faq.md +33 -0
  233. package/control-plane/scripts/skills/hallmark/references/macrostructures/07-manifesto.md +32 -0
  234. package/control-plane/scripts/skills/hallmark/references/macrostructures/08-photographic.md +34 -0
  235. package/control-plane/scripts/skills/hallmark/references/macrostructures/09-quote-led.md +32 -0
  236. package/control-plane/scripts/skills/hallmark/references/macrostructures/10-specimen.md +32 -0
  237. package/control-plane/scripts/skills/hallmark/references/macrostructures/11-catalogue.md +23 -0
  238. package/control-plane/scripts/skills/hallmark/references/macrostructures/12-letter.md +23 -0
  239. package/control-plane/scripts/skills/hallmark/references/macrostructures/13-index-first.md +23 -0
  240. package/control-plane/scripts/skills/hallmark/references/macrostructures/14-narrative-workflow.md +23 -0
  241. package/control-plane/scripts/skills/hallmark/references/macrostructures/15-split-studio.md +23 -0
  242. package/control-plane/scripts/skills/hallmark/references/macrostructures/16-feature-stack.md +23 -0
  243. package/control-plane/scripts/skills/hallmark/references/macrostructures/17-type-specimen.md +23 -0
  244. package/control-plane/scripts/skills/hallmark/references/macrostructures/18-portfolio-grid.md +23 -0
  245. package/control-plane/scripts/skills/hallmark/references/macrostructures/19-map-diagram.md +23 -0
  246. package/control-plane/scripts/skills/hallmark/references/macrostructures/20-ecosystem-index.md +23 -0
  247. package/control-plane/scripts/skills/hallmark/references/macrostructures/21-component-playground.md +23 -0
  248. package/control-plane/scripts/skills/hallmark/references/macrostructures.md +89 -0
  249. package/control-plane/scripts/skills/hallmark/references/microinteractions.md +258 -0
  250. package/control-plane/scripts/skills/hallmark/references/motion.md +109 -0
  251. package/control-plane/scripts/skills/hallmark/references/preview-examples.md +49 -0
  252. package/control-plane/scripts/skills/hallmark/references/responsive.md +138 -0
  253. package/control-plane/scripts/skills/hallmark/references/slop-test.md +192 -0
  254. package/control-plane/scripts/skills/hallmark/references/structure.md +160 -0
  255. package/control-plane/scripts/skills/hallmark/references/study.md +509 -0
  256. package/control-plane/scripts/skills/hallmark/references/themes/carnival.md +301 -0
  257. package/control-plane/scripts/skills/hallmark/references/themes/cobalt.md +146 -0
  258. package/control-plane/scripts/skills/hallmark/references/themes/grid.md +161 -0
  259. package/control-plane/scripts/skills/hallmark/references/themes/hum.md +403 -0
  260. package/control-plane/scripts/skills/hallmark/references/themes/lumen.md +478 -0
  261. package/control-plane/scripts/skills/hallmark/references/typography.md +243 -0
  262. package/control-plane/scripts/skills/hallmark/references/upstream/recipes.md +186 -0
  263. package/control-plane/scripts/skills/hallmark/references/upstream/study-examples.md +176 -0
  264. package/control-plane/scripts/skills/hallmark/references/upstream/tokens.css +1221 -0
  265. package/control-plane/scripts/skills/hallmark/references/verbs/audit.md +25 -0
  266. package/control-plane/scripts/skills/hallmark/references/verbs/redesign.md +269 -0
  267. package/control-plane/scripts/skills/human-comms/SKILL.md +29 -0
  268. package/control-plane/scripts/skills/index.mjs +432 -0
  269. package/control-plane/scripts/skills/internal-comms/LICENSE.txt +202 -0
  270. package/control-plane/scripts/skills/internal-comms/SKILL.md +32 -0
  271. package/control-plane/scripts/skills/internal-comms/examples/3p-updates.md +47 -0
  272. package/control-plane/scripts/skills/internal-comms/examples/company-newsletter.md +65 -0
  273. package/control-plane/scripts/skills/internal-comms/examples/faq-answers.md +30 -0
  274. package/control-plane/scripts/skills/internal-comms/examples/general-comms.md +16 -0
  275. package/control-plane/scripts/skills/mcp-adapter.mjs +94 -0
  276. package/control-plane/scripts/skills/mcp-builder/LICENSE.txt +202 -0
  277. package/control-plane/scripts/skills/mcp-builder/SKILL.md +236 -0
  278. package/control-plane/scripts/skills/mcp-builder/reference/evaluation.md +602 -0
  279. package/control-plane/scripts/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
  280. package/control-plane/scripts/skills/mcp-builder/reference/node_mcp_server.md +970 -0
  281. package/control-plane/scripts/skills/mcp-builder/reference/python_mcp_server.md +719 -0
  282. package/control-plane/scripts/skills/mcp-builder/scripts/connections.py +151 -0
  283. package/control-plane/scripts/skills/mcp-builder/scripts/evaluation.py +373 -0
  284. package/control-plane/scripts/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  285. package/control-plane/scripts/skills/mcp-builder/scripts/requirements.txt +2 -0
  286. package/control-plane/scripts/skills/ponytail/SKILL.md +120 -0
  287. package/control-plane/scripts/skills/ponytail-audit/SKILL.md +41 -0
  288. package/control-plane/scripts/skills/ponytail-debt/SKILL.md +44 -0
  289. package/control-plane/scripts/skills/ponytail-gain/SKILL.md +50 -0
  290. package/control-plane/scripts/skills/ponytail-help/SKILL.md +71 -0
  291. package/control-plane/scripts/skills/ponytail-review/SKILL.md +57 -0
  292. package/control-plane/scripts/skills/receiving-code-review/SKILL.md +205 -0
  293. package/control-plane/scripts/skills/requesting-code-review/SKILL.md +95 -0
  294. package/control-plane/scripts/skills/requesting-code-review/code-reviewer.md +198 -0
  295. package/control-plane/scripts/skills/skill-creator/LICENSE.txt +202 -0
  296. package/control-plane/scripts/skills/skill-creator/SKILL.md +485 -0
  297. package/control-plane/scripts/skills/skill-creator/agents/analyzer.md +274 -0
  298. package/control-plane/scripts/skills/skill-creator/agents/comparator.md +202 -0
  299. package/control-plane/scripts/skills/skill-creator/agents/grader.md +223 -0
  300. package/control-plane/scripts/skills/skill-creator/assets/eval_review.html +146 -0
  301. package/control-plane/scripts/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  302. package/control-plane/scripts/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  303. package/control-plane/scripts/skills/skill-creator/references/schemas.md +430 -0
  304. package/control-plane/scripts/skills/skill-creator/scripts/__init__.py +0 -0
  305. package/control-plane/scripts/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  306. package/control-plane/scripts/skills/skill-creator/scripts/generate_report.py +326 -0
  307. package/control-plane/scripts/skills/skill-creator/scripts/improve_description.py +247 -0
  308. package/control-plane/scripts/skills/skill-creator/scripts/package_skill.py +136 -0
  309. package/control-plane/scripts/skills/skill-creator/scripts/quick_validate.py +103 -0
  310. package/control-plane/scripts/skills/skill-creator/scripts/run_eval.py +310 -0
  311. package/control-plane/scripts/skills/skill-creator/scripts/run_loop.py +328 -0
  312. package/control-plane/scripts/skills/skill-creator/scripts/utils.py +47 -0
  313. package/control-plane/scripts/skills/slack-gif-creator/LICENSE.txt +202 -0
  314. package/control-plane/scripts/skills/slack-gif-creator/SKILL.md +254 -0
  315. package/control-plane/scripts/skills/slack-gif-creator/core/easing.py +234 -0
  316. package/control-plane/scripts/skills/slack-gif-creator/core/frame_composer.py +176 -0
  317. package/control-plane/scripts/skills/slack-gif-creator/core/gif_builder.py +269 -0
  318. package/control-plane/scripts/skills/slack-gif-creator/core/validators.py +136 -0
  319. package/control-plane/scripts/skills/slack-gif-creator/requirements.txt +4 -0
  320. package/control-plane/scripts/skills/sources.json +401 -0
  321. package/control-plane/scripts/skills/sources.lock.json +416 -0
  322. package/control-plane/scripts/skills/subagent-driven-development/SKILL.md +570 -0
  323. package/control-plane/scripts/skills/subagent-driven-development/implementer-prompt.md +154 -0
  324. package/control-plane/scripts/skills/subagent-driven-development/re-review-prompt.md +115 -0
  325. package/control-plane/scripts/skills/subagent-driven-development/scripts/review-package +53 -0
  326. package/control-plane/scripts/skills/subagent-driven-development/scripts/sdd-workspace +82 -0
  327. package/control-plane/scripts/skills/subagent-driven-development/scripts/task-brief +43 -0
  328. package/control-plane/scripts/skills/subagent-driven-development/task-reviewer-prompt.md +207 -0
  329. package/control-plane/scripts/skills/systematic-debugging/CREATION-LOG.md +119 -0
  330. package/control-plane/scripts/skills/systematic-debugging/SKILL.md +283 -0
  331. package/control-plane/scripts/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  332. package/control-plane/scripts/skills/systematic-debugging/condition-based-waiting.md +115 -0
  333. package/control-plane/scripts/skills/systematic-debugging/defense-in-depth.md +122 -0
  334. package/control-plane/scripts/skills/systematic-debugging/find-polluter.sh +72 -0
  335. package/control-plane/scripts/skills/systematic-debugging/root-cause-tracing.md +169 -0
  336. package/control-plane/scripts/skills/systematic-debugging/test-academic.md +14 -0
  337. package/control-plane/scripts/skills/systematic-debugging/test-pressure-1.md +58 -0
  338. package/control-plane/scripts/skills/systematic-debugging/test-pressure-2.md +68 -0
  339. package/control-plane/scripts/skills/systematic-debugging/test-pressure-3.md +69 -0
  340. package/control-plane/scripts/skills/test-driven-development/SKILL.md +330 -0
  341. package/control-plane/scripts/skills/test-driven-development/writing-good-tests.md +198 -0
  342. package/control-plane/scripts/skills/theme-factory/LICENSE.txt +202 -0
  343. package/control-plane/scripts/skills/theme-factory/SKILL.md +59 -0
  344. package/control-plane/scripts/skills/theme-factory/theme-showcase.pdf +0 -0
  345. package/control-plane/scripts/skills/theme-factory/themes/arctic-frost.md +19 -0
  346. package/control-plane/scripts/skills/theme-factory/themes/botanical-garden.md +19 -0
  347. package/control-plane/scripts/skills/theme-factory/themes/desert-rose.md +19 -0
  348. package/control-plane/scripts/skills/theme-factory/themes/forest-canopy.md +19 -0
  349. package/control-plane/scripts/skills/theme-factory/themes/golden-hour.md +19 -0
  350. package/control-plane/scripts/skills/theme-factory/themes/midnight-galaxy.md +19 -0
  351. package/control-plane/scripts/skills/theme-factory/themes/modern-minimalist.md +19 -0
  352. package/control-plane/scripts/skills/theme-factory/themes/ocean-depths.md +19 -0
  353. package/control-plane/scripts/skills/theme-factory/themes/sunset-boulevard.md +19 -0
  354. package/control-plane/scripts/skills/theme-factory/themes/tech-innovation.md +19 -0
  355. package/control-plane/scripts/skills/using-git-worktrees/SKILL.md +167 -0
  356. package/control-plane/scripts/skills/verification-before-completion/SKILL.md +120 -0
  357. package/control-plane/scripts/skills/web-artifacts-builder/LICENSE.txt +202 -0
  358. package/control-plane/scripts/skills/web-artifacts-builder/SKILL.md +74 -0
  359. package/control-plane/scripts/skills/web-artifacts-builder/scripts/bundle-artifact.sh +54 -0
  360. package/control-plane/scripts/skills/web-artifacts-builder/scripts/init-artifact.sh +322 -0
  361. package/control-plane/scripts/skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
  362. package/control-plane/scripts/skills/webapp-testing/LICENSE.txt +202 -0
  363. package/control-plane/scripts/skills/webapp-testing/SKILL.md +96 -0
  364. package/control-plane/scripts/skills/webapp-testing/examples/console_logging.py +35 -0
  365. package/control-plane/scripts/skills/webapp-testing/examples/element_discovery.py +40 -0
  366. package/control-plane/scripts/skills/webapp-testing/examples/static_html_automation.py +33 -0
  367. package/control-plane/scripts/skills/webapp-testing/scripts/with_server.py +106 -0
  368. package/control-plane/scripts/skills/writing-plans/SKILL.md +192 -0
  369. package/control-plane/scripts/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
  370. package/control-plane/scripts/skills/writing-skills/SKILL.md +681 -0
  371. package/control-plane/scripts/skills/writing-skills/anthropic-best-practices.md +1150 -0
  372. package/control-plane/scripts/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  373. package/control-plane/scripts/skills/writing-skills/graphviz-conventions.dot +172 -0
  374. package/control-plane/scripts/skills/writing-skills/persuasion-principles.md +187 -0
  375. package/control-plane/scripts/skills/writing-skills/render-graphs.js +169 -0
  376. package/control-plane/scripts/skills/writing-skills/testing-skills-with-subagents.md +384 -0
  377. package/control-plane/scripts/smoke-mcp-schemas.mjs +30 -0
  378. package/control-plane/scripts/surface-config.mjs +45 -0
  379. package/control-plane/scripts/sync-skills.mjs +370 -0
  380. package/control-plane/scripts/tool-metrics.mjs +74 -0
  381. package/control-plane/scripts/tool-risk.mjs +156 -0
  382. package/control-plane/scripts/tool-surface-stability.mjs +38 -0
  383. package/control-plane/scripts/upstreams/catalog-cache.mjs +28 -0
  384. package/control-plane/scripts/upstreams/config.mjs +158 -0
  385. package/control-plane/scripts/upstreams/diagnostics.mjs +21 -0
  386. package/control-plane/scripts/upstreams/http-client.mjs +67 -0
  387. package/control-plane/scripts/upstreams/manager.mjs +535 -0
  388. package/control-plane/scripts/upstreams/names.mjs +55 -0
  389. package/control-plane/scripts/upstreams/presets.mjs +60 -0
  390. package/control-plane/scripts/upstreams/resource-uri.mjs +29 -0
  391. package/control-plane/scripts/upstreams/stdio-client.mjs +73 -0
  392. package/control-plane/scripts/workspace-registry.mjs +147 -0
  393. package/dist/context/authorization.d.ts +5 -0
  394. package/dist/context/authorization.js +70 -0
  395. package/dist/context/capture.d.ts +46 -0
  396. package/dist/context/capture.js +187 -0
  397. package/dist/context/cli.d.ts +47 -0
  398. package/dist/context/cli.js +287 -0
  399. package/dist/context/device-adapter.d.ts +102 -0
  400. package/dist/context/device-adapter.js +79 -0
  401. package/dist/context/gateway.d.ts +354 -0
  402. package/dist/context/gateway.js +50 -0
  403. package/dist/context/graph.d.ts +63 -0
  404. package/dist/context/graph.js +214 -0
  405. package/dist/context/importer.d.ts +8 -0
  406. package/dist/context/importer.js +69 -0
  407. package/dist/context/indexer.d.ts +14 -0
  408. package/dist/context/indexer.js +57 -0
  409. package/dist/context/migrations/index.d.ts +4 -0
  410. package/dist/context/migrations/index.js +87 -0
  411. package/dist/context/redact.d.ts +15 -0
  412. package/dist/context/redact.js +43 -0
  413. package/dist/context/repositories.d.ts +9 -0
  414. package/dist/context/repositories.js +45 -0
  415. package/dist/context/search.d.ts +63 -0
  416. package/dist/context/search.js +207 -0
  417. package/dist/context/service.d.ts +278 -0
  418. package/dist/context/service.js +160 -0
  419. package/dist/context/store.d.ts +134 -0
  420. package/dist/context/store.js +722 -0
  421. package/dist/context/text.d.ts +4 -0
  422. package/dist/context/text.js +14 -0
  423. package/dist/context/tool-contract.d.ts +534 -0
  424. package/dist/context/tool-contract.js +45 -0
  425. package/dist/device/core.d.ts +29 -0
  426. package/dist/device/core.js +66 -0
  427. package/dist/device/device.d.ts +5 -1
  428. package/dist/device/device.js +87 -39
  429. package/dist/device/gateway-channel.d.ts +8 -1
  430. package/dist/device/gateway-channel.js +51 -3
  431. package/dist/device/gateway-tool-adapter.d.ts +9 -0
  432. package/dist/device/gateway-tool-adapter.js +20 -1
  433. package/dist/device/official-trust.d.ts +12 -0
  434. package/dist/device/official-trust.js +25 -1
  435. package/dist/device/runtime-owner.js +9 -1
  436. package/dist/distribution.d.ts +4 -0
  437. package/dist/distribution.js +8 -0
  438. package/dist/log-palace/cli.d.ts +7 -0
  439. package/dist/log-palace/cli.js +89 -0
  440. package/dist/log-palace/formats.d.ts +26 -0
  441. package/dist/log-palace/formats.js +126 -0
  442. package/dist/log-palace/store.d.ts +129 -0
  443. package/dist/log-palace/store.js +856 -0
  444. package/dist/mcp-device.js +27 -5
  445. package/dist/npm-scripts/remote.js +1 -0
  446. package/dist/platform/activity-journal.d.ts +16 -0
  447. package/dist/platform/activity-journal.js +68 -0
  448. package/dist/platform/cli.d.ts +1 -0
  449. package/dist/platform/cli.js +300 -0
  450. package/dist/platform/coordination-lock.d.ts +45 -0
  451. package/dist/platform/coordination-lock.js +233 -0
  452. package/dist/platform/device-runtime.d.ts +16 -0
  453. package/dist/platform/device-runtime.js +83 -0
  454. package/dist/platform/knowledge-contract.d.ts +46 -0
  455. package/dist/platform/knowledge-contract.js +1 -0
  456. package/dist/platform/knowledge-mcp.d.ts +29 -0
  457. package/dist/platform/knowledge-mcp.js +95 -0
  458. package/dist/platform/knowledge-router.d.ts +64 -0
  459. package/dist/platform/knowledge-router.js +320 -0
  460. package/dist/platform/plugin-child.d.ts +1 -0
  461. package/dist/platform/plugin-child.js +115 -0
  462. package/dist/platform/plugin-contract.d.ts +76 -0
  463. package/dist/platform/plugin-contract.js +53 -0
  464. package/dist/platform/plugin-driver.d.ts +8 -0
  465. package/dist/platform/plugin-driver.js +443 -0
  466. package/dist/platform/plugin-host.d.ts +88 -0
  467. package/dist/platform/plugin-host.js +817 -0
  468. package/dist/platform/plugin-policy.d.ts +3 -0
  469. package/dist/platform/plugin-policy.js +29 -0
  470. package/dist/platform/plugin-services.d.ts +14 -0
  471. package/dist/platform/plugin-services.js +32 -0
  472. package/dist/platform/process-identity.d.ts +9 -0
  473. package/dist/platform/process-identity.js +68 -0
  474. package/dist/platform/runtime-lease.d.ts +4 -0
  475. package/dist/platform/runtime-lease.js +58 -0
  476. package/dist/platform/runtime-root.d.ts +9 -0
  477. package/dist/platform/runtime-root.js +61 -0
  478. package/dist/platform/state-backup.d.ts +92 -0
  479. package/dist/platform/state-backup.js +399 -0
  480. package/dist/plugins/reference/plugin.d.ts +3 -0
  481. package/dist/plugins/reference/plugin.js +13 -0
  482. package/dist/server.js +5 -1
  483. package/dist/terminal-manager.d.ts +7 -0
  484. package/dist/terminal-manager.js +10 -0
  485. package/dist/tool-dispatcher.js +5 -1
  486. package/dist/tools/improved-process-tools.js +4 -0
  487. package/dist/types.d.ts +1 -0
  488. package/dist/version.d.ts +1 -1
  489. package/dist/version.js +1 -1
  490. package/package.json +29 -6
  491. package/skills/mcp-device-context/SKILL.md +40 -0
@@ -0,0 +1,1281 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import http from 'node:http';
4
+ import { execFileSync } from 'node:child_process';
5
+ import path from 'node:path';
6
+ import { fileURLToPath, pathToFileURL } from 'node:url';
7
+ import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server/validators/ajv';
8
+ import express from 'express';
9
+ import { requireBearerAuth } from '@modelcontextprotocol/express';
10
+ import { NodeStreamableHTTPServerTransport, toNodeHandler, toWebRequest } from '@modelcontextprotocol/node';
11
+ import { createMcpHandler, getOAuthProtectedResourceMetadataUrl, isInitializeRequest, isLegacyRequest, Server } from '@modelcontextprotocol/server';
12
+ import { getRuntimeProfile } from './runtime-profile.mjs';
13
+ import { applyToolRisk, assertToolAllowedForProfile, shouldExposeToolForProfile } from './tool-risk.mjs';
14
+ import { listRepoResources, listRepoResourceTemplates, readRepoResource } from './resources/index.mjs';
15
+ import { getRepoPrompt, listRepoPrompts } from './prompts/index.mjs';
16
+ import { loadSurfaceConfig } from './surface-config.mjs';
17
+ import { LOCAL_COLLISION_TOOL_NAMES, workspaceCatalogChanges } from './tool-surface-stability.mjs';
18
+ import { createSkillRegistry } from './skills/index.mjs';
19
+ import { registerSkillsExtension } from './skills/mcp-adapter.mjs';
20
+ import { createExternalToolBroker } from './external-tool-broker.mjs';
21
+ import { createExternalMcpManager } from './upstreams/manager.mjs';
22
+ import { normalizeExternalMcpConfig } from './upstreams/config.mjs';
23
+ import { isExternalResourceUri } from './upstreams/resource-uri.mjs';
24
+ import { FILESYSTEM_TOOL_DEFINITIONS, filesystemToolMeta, listProcessTools, shellExecuteToolMeta } from './device-execution-tool-definitions.mjs';
25
+ import { callCustomTool, isLocalCustomTool, listCustomTools } from './custom-tools/index.mjs';
26
+ import {
27
+ AUTH_SUPPORTED_SCOPES,
28
+ AccountAuthProvider,
29
+ SQLiteAuthState,
30
+ installAuthorizationGateRoutes,
31
+ isStaticBearerAuthorization,
32
+ shouldCreateTransportForRequest,
33
+ shouldUseStatefulSessionTransport
34
+ } from './auth-session.mjs';
35
+ import { createOAuthAuthorizationServerRouter } from './oauth-authorization-server.mjs';
36
+ import { buildToolMetric, createToolMetricsRecorder } from './tool-metrics.mjs';
37
+ import { createRemoteProcessSessionRegistry } from './remote-process-sessions.mjs';
38
+ import { normalizeRemoteFilesystemResult } from './remote-tool-result.mjs';
39
+ import { createDeviceBroker } from './device-broker.mjs';
40
+ import { createDeviceReleaseProvider } from './device-release.mjs';
41
+ import { loadDeviceInnerTlsConfig } from './device-secure-transport.mjs';
42
+ import { listDevicesToolDefinition, paginateDeviceInventory } from './device-inventory.mjs';
43
+ import { installDevicePairingRoutes } from './device-pairing-http.mjs';
44
+ import { createDevicePairingStore } from './device-pairing-store.mjs';
45
+ import { createDeviceStore } from './device-store.mjs';
46
+ import { createDeviceUsageStore } from './device-usage.mjs';
47
+ import { callerAuditId, createDeviceAccessPolicy } from './device-access-policy.mjs';
48
+ import { createDeviceAuditRecorder } from './device-audit.mjs';
49
+ import { createAccountStore } from './account-store.mjs';
50
+ import { installAccountRoutes } from './account-http.mjs';
51
+ import { installDashboardRoutes } from './dashboard-http.mjs';
52
+ import { installOperatorDashboardRoutes } from './operator-dashboard-http.mjs';
53
+ import { createEmbeddedDeviceController } from './embedded-device.mjs';
54
+ import { findUnifiedMcpConfigPath } from './projects/trusted-roots-projects.mjs';
55
+ import {
56
+ classifyWorkspaceChange,
57
+ createWorkspaceRegistry
58
+ } from './workspace-registry.mjs';
59
+
60
+ export function resolveServerConfigPath({ configPath, assetRoot, packageRoot, env = {} }) {
61
+ return path.resolve(configPath || findUnifiedMcpConfigPath(env, packageRoot) || path.join(assetRoot, 'config', 'mcp-servers.toml'));
62
+ }
63
+
64
+ /**
65
+ * One account/control plane plus one mandatory shared Device Core.
66
+ * All operational paths and environment are explicit; importing this module has no startup side effects.
67
+ */
68
+ export async function startServerRuntime(options = {}) {
69
+ const env = { ...(options.env || {}) };
70
+ const core = options.core;
71
+ const identity = options.identity;
72
+ if (!core || typeof core.invoke !== 'function' || !Array.isArray(core.capabilities)) {
73
+ throw new Error('Server mode requires an embedded shared Device Core.');
74
+ }
75
+ if (!identity || typeof identity.loadOrCreate !== 'function' || typeof identity.signChallenge !== 'function') {
76
+ throw new Error('Server mode requires the normal Device identity and proof signer.');
77
+ }
78
+ if (!options.stateRoot) throw new Error('Server stateRoot must be explicit.');
79
+ const startupCleanups = [];
80
+ let resourcesClosed = false;
81
+ let coreClosed = false;
82
+ async function closeOwnedCore() {
83
+ if (options.closeCore === false || coreClosed) return;
84
+ await core.close();
85
+ coreClosed = true;
86
+ }
87
+ try {
88
+ const assetRoot = path.resolve(options.assetRoot || path.join(path.dirname(fileURLToPath(import.meta.url)), '..'));
89
+ const packageRoot = path.resolve(options.packageRoot || path.join(assetRoot, '..'));
90
+ const validatorProvider = new AjvJsonSchemaValidator();
91
+ const contextDefinitions = options.contextTools ?? (await import(pathToFileURL(path.join(packageRoot, 'dist', 'context', 'tool-contract.js')).href)).buildContextCatalog();
92
+ const contextTools = new Map(contextDefinitions.map(definition => {
93
+ const inputSchema = definition.name === 'local_wiki'
94
+ ? { ...definition.inputSchema, properties: { ...definition.inputSchema.properties, device_id: { type: 'string', minLength: 1 } }, required: [...(definition.inputSchema.required || []), 'device_id'] }
95
+ : definition.inputSchema;
96
+ const tool = { name: definition.name, description: definition.description, inputSchema, annotations: definition.annotations };
97
+ return [tool.name, { tool, validate: validatorProvider.getValidator(inputSchema) }];
98
+ }));
99
+ const extraTools = new Map();
100
+ for (const extension of options.extraTools || []) {
101
+ if (!/^[A-Za-z0-9_.-]{1,128}$/.test(String(extension.name || '')) || typeof extension.handler !== 'function') {
102
+ throw new Error('Extension tools require a valid name, schema and handler.');
103
+ }
104
+ if (LOCAL_COLLISION_TOOL_NAMES.has(extension.name) || contextTools.has(extension.name) || extraTools.has(extension.name)) {
105
+ throw new Error('Duplicate platform tool: ' + extension.name);
106
+ }
107
+ const { handler, ...tool } = extension;
108
+ extraTools.set(tool.name, { tool, handler, validate: validatorProvider.getValidator(tool.inputSchema || { type: 'object' }) });
109
+ }
110
+
111
+ const gatewayPackageVersion = (() => {
112
+ try { return String(JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'))?.version || 'unknown'); }
113
+ catch { return 'unknown'; }
114
+ })();
115
+ const gatewayBuildSha = String(env.MCP_GATEWAY_BUILD_SHA || '').trim() || (() => {
116
+ try { return String(execFileSync('git', ['rev-parse', '--short=7', 'HEAD'], { cwd: packageRoot, encoding: 'utf8', windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] })).trim(); }
117
+ catch { return ''; }
118
+ })();
119
+ const gatewayServerVersion = String(env.MCP_GATEWAY_SERVER_VERSION || '').trim() || [gatewayPackageVersion, gatewayBuildSha].filter(Boolean).join(' · ');
120
+ const releaseLookupEnabled = !['0', 'false', 'no', 'off'].includes(String(env.MCP_DEVICE_RELEASE_LOOKUP ?? 'true').trim().toLowerCase());
121
+ const deviceReleaseProvider = options.deviceReleaseProvider || createDeviceReleaseProvider({
122
+ fallbackVersion: env.MCP_DEVICE_LATEST_VERSION || null,
123
+ enabled: releaseLookupEnabled
124
+ });
125
+ const runtimeDirectory = path.resolve(options.stateRoot);
126
+ const repoRoot = options.repoRoot || packageRoot;
127
+ const gatewayPort = Number(options.port ?? 8104);
128
+ if (!Number.isInteger(gatewayPort) || gatewayPort < 1 || gatewayPort > 65535) throw new Error('Invalid server port.');
129
+ const loopbackPort = options.loopbackPort == null ? null : Number(options.loopbackPort);
130
+ if (loopbackPort !== null && (!Number.isInteger(loopbackPort) || loopbackPort < 1 || loopbackPort > 65535 || loopbackPort === gatewayPort))
131
+ throw new Error('Invalid loopback MCP port.');
132
+ const gatewayHost = String(options.host || '127.0.0.1').trim();
133
+ const advertisedHost = String(env.MCP_ADVERTISE_HOST || '').trim() || (gatewayHost === '0.0.0.0' ? '127.0.0.1' : gatewayHost);
134
+ const advertisedUrl = String(options.publicUrl || env.MCP_ADVERTISE_URL || '').trim();
135
+ const allowHttpLan = options.allowHttpLan === true;
136
+ if (allowHttpLan) {
137
+ const explicitUrl = normalizeBaseUrl(advertisedUrl);
138
+ if (!explicitUrl || new URL(explicitUrl).protocol !== 'http:') {
139
+ throw new Error('allowHttpLan requires an explicit http:// publicUrl.');
140
+ }
141
+ }
142
+ const fallbackBaseUrl = `http://${advertisedHost}:${gatewayPort}`;
143
+ // The operator dashboard is mounted on this same authenticated application.
144
+ const staticBearerToken = env.MCP_BEARER_TOKEN;
145
+ const runtimeProfile = getRuntimeProfile(env);
146
+ const useStatefulMcpSessions = shouldUseStatefulSessionTransport(env.MCP_STATEFUL_SESSIONS);
147
+ const enableFilesystem = String(env.ENABLE_FILESYSTEM || 'true').toLowerCase() === 'true';
148
+ const enableShell = String(env.ENABLE_SHELL || 'true').toLowerCase() === 'true';
149
+ const debugAuth = envFlag(env.MCP_DEBUG_AUTH, false);
150
+ const slowToolThresholdMs = normalizeDurationMs(env.MCP_SLOW_TOOL_MS, 5000);
151
+ const toolMetrics = createToolMetricsRecorder({
152
+ metricsPath: path.resolve(env.MCP_METRICS_PATH || path.join(runtimeDirectory, 'mcp-calls.ndjson')),
153
+ enabled: envFlag(env.MCP_METRICS_ENABLED, false)
154
+ });
155
+ startupCleanups.push(() => toolMetrics.close());
156
+ const FILESYSTEM_TOOL_NAMES = new Set(['read_text_file', 'write_file', 'edit_file']);
157
+ const PROCESS_TOOL_NAMES = new Set(['start_process', 'read_process_output', 'interact_with_process', 'terminate_process']);
158
+ const activeProxyServers = new Set();
159
+
160
+ if (!repoRoot) throw new Error('REPO_ROOT is required');
161
+ if (!enableFilesystem && !enableShell) throw new Error('At least one execution tool family must be enabled');
162
+
163
+ function envFlag(value, defaultValue = false) {
164
+ const text = String(value ?? '').trim().toLowerCase();
165
+ if (!text) return defaultValue;
166
+ if (['1', 'true', 'yes', 'y', 'on'].includes(text)) return true;
167
+ if (['0', 'false', 'no', 'n', 'off'].includes(text)) return false;
168
+ return defaultValue;
169
+ }
170
+
171
+ function normalizeDurationMs(value, defaultValue) {
172
+ const parsed = Number.parseInt(String(value ?? ''), 10);
173
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : defaultValue;
174
+ }
175
+
176
+ const configPath = resolveServerConfigPath({ configPath: options.configPath, assetRoot, packageRoot, env });
177
+ if (!configPath) throw new Error('config/mcp-servers.toml is required');
178
+ const workspaceRegistry = createWorkspaceRegistry({
179
+ configPath,
180
+ repoRoot: packageRoot,
181
+ env: env
182
+ });
183
+ startupCleanups.push(() => workspaceRegistry.close());
184
+
185
+ function workspaceSnapshot() {
186
+ return workspaceRegistry.snapshot();
187
+ }
188
+
189
+ function currentSurfaceConfig(snapshot = workspaceSnapshot()) {
190
+ return loadSurfaceConfig(snapshot.rawConfig, env);
191
+ }
192
+
193
+ const remoteProcessSessions = createRemoteProcessSessionRegistry();
194
+ const gatewayDbPath = path.join(runtimeDirectory, 'gateway.sqlite');
195
+ const accountStore = createAccountStore({ dbPath: gatewayDbPath });
196
+ startupCleanups.push(() => accountStore.close());
197
+ const deviceStore = createDeviceStore({ dbPath: gatewayDbPath });
198
+ startupCleanups.push(() => deviceStore.close());
199
+ const devicePairingStore = createDevicePairingStore({ dbPath: gatewayDbPath });
200
+ startupCleanups.push(() => devicePairingStore.close());
201
+ const deviceUsageStore = createDeviceUsageStore({ dbPath: gatewayDbPath });
202
+ startupCleanups.push(() => deviceUsageStore.close());
203
+ const deviceAudit = createDeviceAuditRecorder({
204
+ auditPath: path.join(runtimeDirectory, 'device-audit.jsonl'),
205
+ enabled: envFlag(env.MCP_DEVICE_AUDIT_ENABLED, false)
206
+ });
207
+ startupCleanups.push(() => deviceAudit.close());
208
+ const deviceBroker = createDeviceBroker({
209
+ enrollmentToken: env.MCP_DEVICE_ENROLLMENT_TOKEN,
210
+ deviceStore,
211
+ pairingStore: devicePairingStore,
212
+ usageStore: deviceUsageStore,
213
+ auditRecorder: deviceAudit,
214
+ onDeviceForgotten: ({ deviceId }) => remoteProcessSessions.removeByDevice(deviceId),
215
+ innerTls: options.innerTls || loadDeviceInnerTlsConfig(env)
216
+ });
217
+ startupCleanups.push(() => deviceBroker.shutdown());
218
+ const deviceAccessPolicy = createDeviceAccessPolicy({
219
+ raw: env.MCP_DEVICE_ACCESS_POLICY || '',
220
+ profile: runtimeProfile.name
221
+ });
222
+
223
+ const skillRegistry = createSkillRegistry({ directory: options.skillRoot || path.join(assetRoot, 'scripts', 'skills') });
224
+
225
+ async function broadcastCatalogChanges(changes = {}) {
226
+ const tasks = [];
227
+ for (const server of activeProxyServers) {
228
+ if (changes.toolsChanged) tasks.push(server.sendToolListChanged().catch(() => {}));
229
+ if (changes.resourcesChanged) tasks.push(server.sendResourceListChanged().catch(() => {}));
230
+ if (changes.promptsChanged) tasks.push(server.sendPromptListChanged().catch(() => {}));
231
+ }
232
+ await Promise.all(tasks);
233
+ }
234
+
235
+ function localToolNamesForCollisionCheck() {
236
+ return [...LOCAL_COLLISION_TOOL_NAMES, ...contextTools.keys(), ...extraTools.keys()];
237
+ }
238
+
239
+ const externalMcpManager = await createExternalMcpManager({
240
+ // Startup and hot reload must read the same explicit unified configuration.
241
+ env: { ...env, MCP_UPSTREAM_CONFIG: configPath },
242
+ repoRoot: packageRoot,
243
+ localToolNames: localToolNamesForCollisionCheck(),
244
+ localPromptNames: listRepoPrompts({ runtimeProfile }).map(prompt => prompt.name),
245
+ onCatalogChanged: async changes => {
246
+ await broadcastCatalogChanges(changes);
247
+ }
248
+ });
249
+ startupCleanups.push(() => externalMcpManager.shutdown());
250
+ const externalToolBroker = createExternalToolBroker({
251
+ getTools: () => externalMcpManager.getCachedTools(),
252
+ invokeTool: (name, args) => externalMcpManager.callTool(name, args)
253
+ });
254
+
255
+ workspaceRegistry.subscribe(async (next, previous) => {
256
+ const { rootsChanged, upstreamChanged } = classifyWorkspaceChange(next, previous);
257
+ if (rootsChanged) {
258
+ await broadcastCatalogChanges(workspaceCatalogChanges({ rootsChanged }, currentSurfaceConfig(next)));
259
+ }
260
+ if (upstreamChanged) {
261
+ const nextExternalConfig = normalizeExternalMcpConfig(next.rawConfig, {
262
+ configPath,
263
+ repoRoot: packageRoot,
264
+ env: env
265
+ });
266
+ try {
267
+ await externalMcpManager.reconcile(nextExternalConfig);
268
+ } catch (error) {
269
+ console.error(`[external-mcp] reconcile failed; keeping committed topology: ${error.message}`);
270
+ }
271
+ }
272
+ });
273
+
274
+ function customToolContext(callerContext = {}) {
275
+ const snapshot = workspaceSnapshot();
276
+ return {
277
+ resolvedRepoRoots: snapshot.roots,
278
+ resolvedRepoRoot: snapshot.roots[0],
279
+ projectRegistry: snapshot.projectRegistry,
280
+ accountId: callerContext.accountId || null,
281
+ listVisibleDevices: () => deviceAccessPolicy.filterDevices(
282
+ deviceBroker.listDevices({ accountId: callerContext.accountId || null }),
283
+ {
284
+ callerSubject: callerContext.callerSubject || callerContext.callerCategory || 'anonymous',
285
+ callerCategory: callerContext.callerCategory || 'anonymous'
286
+ }
287
+ ),
288
+ callDeviceTool: async (tool, args = {}) => {
289
+ const { deviceId, toolArguments } = requireDeviceId(args);
290
+ return await callRemoteDevice({ context: callerContext, deviceId, tool, arguments: toolArguments });
291
+ },
292
+ externalToolBroker,
293
+ skillRegistry,
294
+ runtimeProfile,
295
+ packageRoot,
296
+ env: env
297
+ };
298
+ }
299
+
300
+ async function listMergedTools() {
301
+ const tools = [];
302
+ if (enableFilesystem) {
303
+ tools.push(...FILESYSTEM_TOOL_DEFINITIONS.map(filesystemToolMeta));
304
+ }
305
+ tools.push(...listCustomTools(customToolContext()));
306
+ tools.push(applyToolRisk(listDevicesToolDefinition()));
307
+ if (enableShell) {
308
+ tools.push(shellExecuteToolMeta());
309
+ tools.push(...listProcessTools());
310
+ }
311
+ tools.push(...[...contextTools.values()].map(entry => entry.tool));
312
+ tools.push(...[...extraTools.values()].map(entry => entry.tool));
313
+ tools.push(...await externalMcpManager.listAllToolsUnfiltered());
314
+ return tools.filter(tool => shouldExposeToolForProfile(tool, runtimeProfile));
315
+ }
316
+
317
+ function persistSchemaSnapshot(tools) {
318
+ const toolSchemaBytes = Buffer.byteLength(JSON.stringify({ tools }), 'utf8');
319
+ const estimatedTokens = Math.ceil(toolSchemaBytes / 4);
320
+ const estimationMethod = 'utf8_bytes_div_4_estimate';
321
+ deviceUsageStore.setSchemaSnapshot({
322
+ toolCount: tools.length,
323
+ schemaBytes: toolSchemaBytes,
324
+ estimatedTokens,
325
+ estimationMethod
326
+ });
327
+ deviceBroker.setSchemaSnapshot({
328
+ toolCount: tools.length,
329
+ toolSchemaBytes,
330
+ toolSchemaTokenEstimate: estimatedTokens,
331
+ tokenEstimateMethod: estimationMethod
332
+ });
333
+ return { toolSchemaBytes, estimatedTokens, estimationMethod };
334
+ }
335
+
336
+ async function refreshDeviceSchemaSnapshot() {
337
+ const tools = await listMergedTools();
338
+ return persistSchemaSnapshot(tools);
339
+ }
340
+
341
+ function structuredToolText(value, { includeStructured = false } = {}) {
342
+ const result = { content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] };
343
+ return includeStructured ? { ...result, structuredContent: value } : result;
344
+ }
345
+
346
+ const SHELL_RESPONSE_BUDGET_BYTES = 128 * 1024;
347
+ const SHELL_TOOL_RESULT_BUDGET_BYTES = SHELL_RESPONSE_BUDGET_BYTES - (8 * 1024);
348
+
349
+ function boundedShellToolText(value) {
350
+ const response = structuredToolText(value, { includeStructured: true });
351
+ if (Buffer.byteLength(JSON.stringify(response)) <= SHELL_TOOL_RESULT_BUDGET_BYTES) return response;
352
+ throw new Error(`shell_execute response exceeds ${SHELL_RESPONSE_BUDGET_BYTES} byte budget`);
353
+ }
354
+
355
+ function splitDeviceArguments(args = {}) {
356
+ const { device_id: deviceId, ...toolArguments } = args;
357
+ return { deviceId: String(deviceId || '').trim(), toolArguments };
358
+ }
359
+
360
+ function requireDeviceId(args = {}) {
361
+ const routed = splitDeviceArguments(args);
362
+ if (routed.deviceId) return routed;
363
+ const error = new Error('DEVICE_ID_REQUIRED: device_id is required for execution tools.');
364
+ error.code = 'DEVICE_ID_REQUIRED';
365
+ throw error;
366
+ }
367
+
368
+ function metricDeviceId(toolName, args = {}, context = {}) {
369
+ const explicit = String(args.device_id || '').trim();
370
+ if (explicit) return explicit;
371
+ if (!PROCESS_TOOL_NAMES.has(toolName) || toolName === 'start_process' || !args.session_id) return null;
372
+ try {
373
+ return remoteProcessSessions.resolve({ sessionId: args.session_id, ownerKey: context.callerKey || 'anonymous' }).deviceId;
374
+ } catch {
375
+ return null;
376
+ }
377
+ }
378
+
379
+ function metricSkillName(toolName, args = {}) {
380
+ if (toolName !== 'load_skill') return null;
381
+ return String(args.name || '').trim() || null;
382
+ }
383
+
384
+ async function callRemoteDevice({
385
+ context = {},
386
+ deviceId,
387
+ tool,
388
+ arguments: args = {},
389
+ timeoutMs,
390
+ expectedConnectionEpoch,
391
+ expectedExecutionRuntimeGeneration,
392
+ includeDispatchContext = false
393
+ }) {
394
+ const callerSubject = context.callerSubject || context.callerCategory || 'anonymous';
395
+ const callerCategory = context.callerCategory || 'anonymous';
396
+ const requestId = randomUUID();
397
+ const startedAt = Date.now();
398
+ let grant = null;
399
+ try {
400
+ grant = deviceAccessPolicy.authorize({
401
+ callerSubject,
402
+ callerCategory,
403
+ deviceId,
404
+ tool,
405
+ arguments: args
406
+ });
407
+ const brokerResult = await deviceBroker.callDevice({
408
+ requestId,
409
+ accountId: context.accountId || null,
410
+ deviceId,
411
+ tool,
412
+ arguments: args,
413
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
414
+ ...(expectedConnectionEpoch === undefined ? {} : { expectedConnectionEpoch }),
415
+ ...(expectedExecutionRuntimeGeneration === undefined ? {} : { expectedExecutionRuntimeGeneration }),
416
+ ...(includeDispatchContext ? { includeDispatchContext: true } : {})
417
+ });
418
+ const result = includeDispatchContext ? brokerResult?.result : brokerResult;
419
+ const outputBytes = deviceAccessPolicy.assertOutput(grant, result);
420
+ deviceAudit.record({
421
+ requestId,
422
+ callerId: callerAuditId(callerSubject),
423
+ callerCategory,
424
+ deviceId,
425
+ tool,
426
+ outcome: 'success',
427
+ durationMs: Date.now() - startedAt,
428
+ inputBytes: grant.inputBytes,
429
+ outputBytes
430
+ });
431
+ return includeDispatchContext
432
+ ? { result, dispatchContext: brokerResult.dispatchContext }
433
+ : result;
434
+ } catch (error) {
435
+ const inputBytes = grant?.inputBytes ?? Buffer.byteLength(JSON.stringify(args ?? {}), 'utf8');
436
+ deviceAudit.record({
437
+ requestId,
438
+ callerId: callerAuditId(callerSubject),
439
+ callerCategory,
440
+ deviceId,
441
+ tool,
442
+ outcome: 'error',
443
+ durationMs: Date.now() - startedAt,
444
+ inputBytes,
445
+ errorCode: String(error?.code || 'REMOTE_DEVICE_ERROR').slice(0, 64)
446
+ });
447
+ throw error;
448
+ }
449
+ }
450
+
451
+ async function routeToolCall(request, context = {}) {
452
+ const { callerKey } = context;
453
+ const toolName = request.params.name;
454
+ assertToolAllowedForProfile(toolName, runtimeProfile);
455
+ const extension = extraTools.get(toolName);
456
+ if (extension) {
457
+ if (!context.accountId) throw new Error('ACCOUNT_ID_REQUIRED: an authenticated account is required.');
458
+ const args = request.params.arguments || {};
459
+ const validation = extension.validate(args);
460
+ if (!validation.valid) throw new Error('Invalid arguments for ' + toolName + ': ' + validation.errorMessage);
461
+ const trustedContext = Object.freeze({
462
+ ...context,
463
+ sessionId: context.activitySessionId || context.callerKey,
464
+ listDevices: () => deviceBroker.listDevices({ accountId: context.accountId }),
465
+ callDevice: ({ deviceId, tool, arguments: toolArguments = {}, timeoutMs }) =>
466
+ callRemoteDevice({ context, deviceId, tool, arguments: toolArguments, timeoutMs })
467
+ });
468
+ const result = await extension.handler(args, trustedContext);
469
+ return result && Array.isArray(result.content) ? result : structuredToolText(result, { includeStructured: true });
470
+ }
471
+ const contextEntry = contextTools.get(toolName);
472
+ if (contextEntry) {
473
+ const args = request.params.arguments || {};
474
+ const validation = contextEntry.validate(args);
475
+ if (!validation.valid) throw new Error('Invalid arguments for ' + toolName + ': ' + validation.errorMessage);
476
+ const { deviceId, toolArguments } = requireDeviceId(args);
477
+ // Context's published schema owns device_id; preserve it for every scoped action.
478
+ const forwarded = toolName === 'local_wiki' ? toolArguments : { ...toolArguments, device_id: deviceId };
479
+ const result = await callRemoteDevice({ context, deviceId, tool: toolName, arguments: forwarded });
480
+ return result && Array.isArray(result.content) ? result : structuredToolText(result, { includeStructured: true });
481
+ }
482
+
483
+ if (toolName === 'shell_execute' && enableShell) {
484
+ const { deviceId, toolArguments } = requireDeviceId(request.params.arguments || {});
485
+ const remoteTimeoutMs = Number(toolArguments.timeout_ms || 28000);
486
+ if (!Number.isInteger(remoteTimeoutMs) || remoteTimeoutMs < 1 || remoteTimeoutMs > 28000) {
487
+ throw new Error('Remote shell_execute timeout_ms must be between 1 and 28000; use start_process for longer commands.');
488
+ }
489
+ const result = await callRemoteDevice({
490
+ context,
491
+ deviceId,
492
+ tool: 'shell_execute',
493
+ arguments: { ...toolArguments, timeout_ms: remoteTimeoutMs },
494
+ timeoutMs: remoteTimeoutMs + 2000
495
+ });
496
+ return boundedShellToolText(result);
497
+ }
498
+
499
+ if (toolName === 'list_devices') {
500
+ const devices = deviceAccessPolicy.filterDevices(deviceBroker.listDevices({ accountId: context.accountId || null }), {
501
+ callerSubject: context.callerSubject || context.callerCategory || 'anonymous',
502
+ callerCategory: context.callerCategory || 'anonymous'
503
+ });
504
+ return structuredToolText({ ok: true, ...paginateDeviceInventory(devices, request.params.arguments || {}) }, { includeStructured: true });
505
+ }
506
+
507
+ if (PROCESS_TOOL_NAMES.has(toolName) && enableShell) {
508
+ const args = request.params.arguments || {};
509
+ const ownerKey = callerKey || 'anonymous';
510
+ if (toolName === 'start_process') {
511
+ const { deviceId, toolArguments } = requireDeviceId(args);
512
+ const dispatch = await callRemoteDevice({
513
+ context,
514
+ deviceId,
515
+ tool: 'start_process',
516
+ arguments: toolArguments,
517
+ timeoutMs: Math.min(Number(toolArguments.timeout_ms || 10000) + 2000, 30000),
518
+ includeDispatchContext: true
519
+ });
520
+ const remote = dispatch.result;
521
+ const remoteSessionId = remote?.sessionId ?? remote?.session_id;
522
+ if (!remoteSessionId) throw new Error('Remote start_process did not return a session identifier.');
523
+ const sessionId = remoteProcessSessions.register({
524
+ ownerKey,
525
+ deviceId: dispatch.dispatchContext.deviceId,
526
+ connectionEpoch: dispatch.dispatchContext.connectionEpoch,
527
+ executionRuntimeGeneration: dispatch.dispatchContext.executionRuntimeGeneration,
528
+ remoteSessionId
529
+ });
530
+ return structuredToolText({ ...remote, sessionId, session_id: sessionId }, { includeStructured: true });
531
+ }
532
+ const remoteSession = remoteProcessSessions.resolve({ sessionId: args.session_id, ownerKey });
533
+ const remoteArgs = { ...args, session_id: remoteSession.remoteSessionId };
534
+ const remoteTimeoutMs = toolName === 'read_process_output'
535
+ ? 12000
536
+ : Math.min(Number(args.timeout_ms || 10000) + 2000, 30000);
537
+ const remote = await callRemoteDevice({
538
+ context,
539
+ deviceId: remoteSession.deviceId,
540
+ tool: toolName,
541
+ arguments: remoteArgs,
542
+ timeoutMs: remoteTimeoutMs,
543
+ expectedConnectionEpoch: remoteSession.connectionEpoch,
544
+ expectedExecutionRuntimeGeneration: remoteSession.executionRuntimeGeneration
545
+ });
546
+ if (toolName === 'terminate_process') {
547
+ remoteProcessSessions.remove({ sessionId: args.session_id, ownerKey });
548
+ } else {
549
+ remoteProcessSessions.touch({ sessionId: args.session_id, ownerKey });
550
+ }
551
+ return structuredToolText({ ...remote, sessionId: args.session_id, session_id: args.session_id }, { includeStructured: true });
552
+ }
553
+
554
+ if (FILESYSTEM_TOOL_NAMES.has(toolName)) {
555
+ const { deviceId, toolArguments } = requireDeviceId(request.params.arguments || {});
556
+ const result = await callRemoteDevice({ context, deviceId, tool: toolName, arguments: toolArguments });
557
+ return result && Array.isArray(result.content)
558
+ ? normalizeRemoteFilesystemResult(result)
559
+ : structuredToolText(result, { includeStructured: true });
560
+ }
561
+
562
+ if (isLocalCustomTool(toolName)) {
563
+ return await callCustomTool(toolName, request.params.arguments || {}, customToolContext(context));
564
+ }
565
+
566
+ if (externalMcpManager.isEagerToolName(toolName)) {
567
+ return await externalMcpManager.callTool(toolName, request.params.arguments || {}, runtimeProfile);
568
+ }
569
+ if (externalMcpManager.isExternalToolName(toolName)) {
570
+ throw new Error(`External MCP tool ${toolName} is deferred; use external_tool_search and the appropriate external_tool_call_* lane.`);
571
+ }
572
+
573
+ throw new Error(`Unknown or disabled tool: ${toolName}`);
574
+ }
575
+
576
+ function metricBelongsToCurrentDeviceEnrollment(metric, startedAt) {
577
+ const deviceId = String(metric?.deviceId || '').trim();
578
+ return !deviceId || deviceStore.containsEnrollmentAt(deviceId, startedAt);
579
+ }
580
+
581
+ function recordToolUsage(metric, startedAt) {
582
+ try {
583
+ if (metricBelongsToCurrentDeviceEnrollment(metric, startedAt)) {
584
+ deviceUsageStore.recordToolCall(metric);
585
+ }
586
+ } catch (error) {
587
+ console.error(`[usage-store] tool event persist failed: ${error.message}`);
588
+ }
589
+ toolMetrics.record(metric);
590
+ }
591
+
592
+ async function routeObservedToolCall(request, context) {
593
+ const toolName = request.params?.name || 'unknown';
594
+ const args = request.params?.arguments || {};
595
+ const deviceId = metricDeviceId(toolName, args, context);
596
+ const skillName = metricSkillName(toolName, args);
597
+ const startedAt = Date.now();
598
+ console.log(`[tool-call:start] ${toolName}`);
599
+ try {
600
+ const result = await routeToolCall(request, context);
601
+ const durationMs = Date.now() - startedAt;
602
+ console.log(`[tool-call:finish] ${toolName} durationMs=${durationMs}`);
603
+ if (durationMs > slowToolThresholdMs) console.log(`[tool-call:slow] ${toolName} durationMs=${durationMs}`);
604
+ const metric = buildToolMetric({
605
+ toolName,
606
+ args,
607
+ result,
608
+ durationMs,
609
+ callerCategory: context?.callerCategory,
610
+ accountId: context?.accountId,
611
+ activitySessionId: context?.activitySessionId,
612
+ deviceId,
613
+ skillName,
614
+ upstream: externalMcpManager.isExternalToolName(toolName) ? 'external-mcp' : null
615
+ });
616
+ recordToolUsage(metric, startedAt);
617
+ return result;
618
+ } catch (error) {
619
+ const durationMs = Date.now() - startedAt;
620
+ console.log(`[tool-call:error] ${toolName} durationMs=${durationMs}`);
621
+ const metric = buildToolMetric({
622
+ toolName,
623
+ args,
624
+ durationMs,
625
+ callerCategory: context?.callerCategory,
626
+ accountId: context?.accountId,
627
+ activitySessionId: context?.activitySessionId,
628
+ deviceId,
629
+ skillName,
630
+ upstream: externalMcpManager.isExternalToolName(toolName) ? 'external-mcp' : null,
631
+ error
632
+ });
633
+ recordToolUsage(metric, startedAt);
634
+ throw error;
635
+ }
636
+ }
637
+
638
+ function currentResourceContext(callerContext = {}) {
639
+ const snapshot = workspaceSnapshot();
640
+ return {
641
+ resolvedRepoRoots: snapshot.roots,
642
+ resolvedRepoRoot: snapshot.roots[0],
643
+ projectRegistry: snapshot.projectRegistry,
644
+ surfaceConfig: currentSurfaceConfig(snapshot),
645
+ accountId: callerContext.accountId || null,
646
+ listVisibleDevices: () => deviceAccessPolicy.filterDevices(
647
+ deviceBroker.listDevices({ accountId: callerContext.accountId || null }),
648
+ {
649
+ callerSubject: callerContext.callerSubject || callerContext.callerCategory || 'anonymous',
650
+ callerCategory: callerContext.callerCategory || 'anonymous'
651
+ }
652
+ ),
653
+ callDeviceTool: async (tool, args = {}) => {
654
+ const { deviceId, toolArguments } = requireDeviceId(args);
655
+ return await callRemoteDevice({ context: callerContext, deviceId, tool, arguments: toolArguments });
656
+ },
657
+ packageRoot,
658
+ env: env,
659
+ skillRegistry,
660
+ listTools: listMergedTools
661
+ };
662
+ }
663
+
664
+ function createProxyServer({ era = 'legacy', accountId, activitySessionId, callerKey, callerCategory, callerSubject }) {
665
+ const metadata = workspaceSnapshot().server;
666
+ const server = new Server(
667
+ {
668
+ name: metadata.name,
669
+ title: metadata.title,
670
+ version: gatewayPackageVersion,
671
+ description: metadata.description
672
+ },
673
+ {
674
+ instructions: [metadata.instructions, 'Native Skills clients use the MCP Skills extension. Other clients may discover and load reusable skills with skill_catalog and load_skill.'].filter(Boolean).join(' '),
675
+ capabilities: {
676
+ tools: { listChanged: true },
677
+ resources: { subscribe: false, listChanged: true },
678
+ prompts: { listChanged: true }
679
+ }
680
+ }
681
+ );
682
+
683
+ server.setRequestHandler('tools/list', async () => {
684
+ const tools = await listMergedTools();
685
+ const { toolSchemaBytes, estimatedTokens, estimationMethod } = persistSchemaSnapshot(tools);
686
+ deviceUsageStore.recordCatalogList({
687
+ accountId,
688
+ activitySessionId,
689
+ toolCount: tools.length,
690
+ schemaBytes: toolSchemaBytes,
691
+ estimatedTokens,
692
+ estimationMethod
693
+ });
694
+ return { tools };
695
+ });
696
+ server.setRequestHandler('tools/call', request => routeObservedToolCall(request, { accountId, activitySessionId, callerKey, callerCategory, callerSubject }));
697
+ const callerContext = { accountId, activitySessionId, callerKey, callerCategory, callerSubject };
698
+ server.setRequestHandler('resources/list', async () => {
699
+ const context = currentResourceContext(callerContext);
700
+ return { resources: [...listRepoResources(context), ...await externalMcpManager.listResources()] };
701
+ });
702
+ server.setRequestHandler('resources/templates/list', async () => {
703
+ const context = currentResourceContext(callerContext);
704
+ return { resourceTemplates: [...listRepoResourceTemplates(context), ...await externalMcpManager.listResourceTemplates()] };
705
+ });
706
+ server.setRequestHandler('resources/read', async request => {
707
+ if (isExternalResourceUri(request.params.uri)) return await externalMcpManager.readResource(request.params.uri);
708
+ return await readRepoResource(request.params.uri, currentResourceContext(callerContext));
709
+ });
710
+ server.setRequestHandler('prompts/list', async () => ({
711
+ prompts: [...listRepoPrompts({ runtimeProfile }), ...await externalMcpManager.listPrompts()]
712
+ }));
713
+ server.setRequestHandler('prompts/get', async request => {
714
+ if (externalMcpManager.isExternalPromptName(request.params.name)) {
715
+ return await externalMcpManager.getPrompt(request.params.name, request.params.arguments || {});
716
+ }
717
+ return getRepoPrompt(request.params.name, request.params.arguments || {}, { runtimeProfile });
718
+ });
719
+ if (era === 'legacy') activeProxyServers.add(server);
720
+ if (era === 'modern') registerSkillsExtension(server, skillRegistry);
721
+ return server;
722
+ }
723
+
724
+ function summarizeRequestBody(body) {
725
+ if (!body || typeof body !== 'object') {
726
+ return 'no-body';
727
+ }
728
+
729
+ const method = typeof body.method === 'string' ? body.method : 'unknown';
730
+ const toolName = body.params && typeof body.params.name === 'string' ? body.params.name : '';
731
+ return toolName ? `${method}:${toolName}` : method;
732
+ }
733
+
734
+ function setIncomingHeader(req, name, value) {
735
+ req.headers[name.toLowerCase()] = value;
736
+ if (!Array.isArray(req.rawHeaders)) {
737
+ return;
738
+ }
739
+
740
+ const index = req.rawHeaders.findIndex(header => header.toLowerCase() === name.toLowerCase());
741
+ if (index >= 0) {
742
+ req.rawHeaders[index + 1] = value;
743
+ return;
744
+ }
745
+
746
+ req.rawHeaders.push(name, value);
747
+ }
748
+
749
+ function fingerprint(value) {
750
+ const text = String(value || '');
751
+ return `${text.length}:${createHash('sha256').update(text).digest('hex').slice(0, 12)}`;
752
+ }
753
+
754
+ function callerKeyFromParts({ accountId = '', oauthClientId = '', staticBearer = false, sessionId = '' } = {}) {
755
+ const identity = accountId ? `account:${accountId}` : oauthClientId ? `oauth:${oauthClientId}` : staticBearer ? 'static-bearer' : 'anonymous';
756
+ const digest = createHash('sha256')
757
+ .update(sessionId ? `${identity}\nsession:${sessionId}` : identity)
758
+ .digest('hex')
759
+ .slice(0, 24);
760
+ return `caller:${digest}`;
761
+ }
762
+
763
+ function callerKeyFromRequest(req) {
764
+ return callerKeyFromParts({
765
+ accountId: req.auth?.accountId || '',
766
+ oauthClientId: req.auth?.clientId || '',
767
+ staticBearer: isStaticBearerAuthorization(req.headers.authorization, staticBearerToken),
768
+ sessionId: useStatefulMcpSessions ? req.headers['mcp-session-id'] || '' : ''
769
+ });
770
+ }
771
+
772
+ function callerCategoryFromRequest(req) {
773
+ if (isStaticBearerAuthorization(req.headers.authorization, staticBearerToken)) return 'static-bearer';
774
+ if (req.auth?.clientId) return 'oauth';
775
+ return 'anonymous';
776
+ }
777
+
778
+ function callerSubjectFromRequest(req) {
779
+ if (isStaticBearerAuthorization(req.headers.authorization, staticBearerToken)) return 'static-bearer';
780
+ if (req.auth?.accountId) return `account:${req.auth.accountId}`;
781
+ return 'anonymous';
782
+ }
783
+
784
+ function getProvidedAuthorizationToken(authorizationHeader) {
785
+ if (typeof authorizationHeader !== 'string') {
786
+ return '';
787
+ }
788
+
789
+ const header = authorizationHeader.trim();
790
+ const match = header.match(/^Bearer\s+(.+)$/i);
791
+ return (match ? match[1] : header).trim();
792
+ }
793
+
794
+ await refreshDeviceSchemaSnapshot();
795
+ const app = express();
796
+ app.set('trust proxy', 1);
797
+ app.use(express.json({ limit: '2mb' }));
798
+ app.use(express.urlencoded({ extended: false }));
799
+ const accountHttp = installAccountRoutes(app, {
800
+ accountStore,
801
+ allowAdminLogin: true,
802
+ needInvite: () => workspaceSnapshot().rawConfig?.auth?.need_invite !== false
803
+ });
804
+ installOperatorDashboardRoutes(app, {
805
+ accountStore, usageStore: deviceUsageStore, deviceBroker,
806
+ accountFromRequest: accountHttp.sessionAccountFromRequest
807
+ });
808
+ const oauthStateStore = new SQLiteAuthState(gatewayDbPath);
809
+ startupCleanups.push(() => oauthStateStore.close());
810
+ installDashboardRoutes(app, {
811
+ accountFromRequest: accountHttp.accountFromRequest,
812
+ usageStore: deviceUsageStore,
813
+ deviceBroker,
814
+ baseUrlFromRequest: requestBaseUrl,
815
+ oauthClientLookup: clientId => oauthStateStore.getClient(clientId),
816
+ listTools: listMergedTools,
817
+ serverVersion: gatewayServerVersion,
818
+ deviceReleaseProvider
819
+ });
820
+ const provider = new AccountAuthProvider({
821
+ stateStore: oauthStateStore,
822
+ accountStore,
823
+ accountFromRequest: accountHttp.accountFromRequest,
824
+ sessionBindingFromRequest: accountHttp.sessionBindingFromRequest,
825
+ activityStore: deviceUsageStore
826
+ });
827
+ installAuthorizationGateRoutes(app, {
828
+ provider,
829
+ clearSession: accountHttp.clearSession,
830
+ loginLocation: accountHttp.loginLocation
831
+ });
832
+ app.use((req, res, next) => {
833
+ if (req.path !== '/mcp') {
834
+ next();
835
+ return;
836
+ }
837
+
838
+ const origin = req.headers.origin;
839
+ if (origin) {
840
+ res.setHeader('Access-Control-Allow-Origin', origin);
841
+ res.setHeader('Vary', 'Origin');
842
+ }
843
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
844
+ res.setHeader(
845
+ 'Access-Control-Allow-Headers',
846
+ 'authorization, content-type, accept, mcp-session-id, mcp-protocol-version'
847
+ );
848
+ res.setHeader('Access-Control-Expose-Headers', 'mcp-session-id');
849
+
850
+ if (req.method === 'OPTIONS') {
851
+ res.status(204).end();
852
+ return;
853
+ }
854
+
855
+ next();
856
+ });
857
+ app.use((req, _res, next) => {
858
+ if (req.path === '/mcp') {
859
+ const sessionId = req.headers['mcp-session-id'];
860
+ const authHeader = req.headers.authorization ? 'yes' : 'no';
861
+ console.log(`[mcp-http] ${req.method} /mcp auth=${authHeader} session=${sessionId || 'none'}`);
862
+ }
863
+ next();
864
+ });
865
+
866
+ function normalizeBaseUrl(value) {
867
+ return String(value || '').trim().replace(/\/+$/, '');
868
+ }
869
+
870
+ function requestBaseUrl(req) {
871
+ const explicitBaseUrl = normalizeBaseUrl(advertisedUrl);
872
+ if (explicitBaseUrl) {
873
+ return explicitBaseUrl;
874
+ }
875
+
876
+ const forwardedProto = String(req.headers['x-forwarded-proto'] || '').split(',')[0].trim();
877
+ const forwardedHost = String(req.headers['x-forwarded-host'] || '').split(',')[0].trim();
878
+ const host = forwardedHost || req.headers.host;
879
+ if (!host) {
880
+ return fallbackBaseUrl;
881
+ }
882
+
883
+ let hostname = String(host).toLowerCase();
884
+ try {
885
+ hostname = new URL(`http://${host}`).hostname.toLowerCase();
886
+ } catch {
887
+ hostname = hostname.split(':')[0];
888
+ }
889
+ const isLocalHost = ['localhost', '127.0.0.1', '::1'].includes(hostname);
890
+ const proto = forwardedProto || (isLocalHost ? req.protocol || (req.secure ? 'https' : 'http') : 'https');
891
+ return normalizeBaseUrl(`${proto}://${host}`);
892
+ }
893
+
894
+ installDevicePairingRoutes(app, {
895
+ pairingStore: devicePairingStore,
896
+ accountFromRequest: accountHttp.accountFromRequest,
897
+ baseUrlFromRequest: requestBaseUrl
898
+ });
899
+
900
+ function buildAuthUrls(baseUrl) {
901
+ const normalizedBaseUrl = normalizeBaseUrl(baseUrl) || fallbackBaseUrl;
902
+ const issuerUrl = new URL(normalizedBaseUrl);
903
+ const resourceServerUrl = new URL('/mcp', `${normalizedBaseUrl}/`);
904
+ return { issuerUrl, resourceServerUrl };
905
+ }
906
+
907
+ const authRouters = new Map();
908
+
909
+ function getAuthRouterForBaseUrl(baseUrl) {
910
+ const normalizedBaseUrl = normalizeBaseUrl(baseUrl) || fallbackBaseUrl;
911
+ const cached = authRouters.get(normalizedBaseUrl);
912
+ if (cached) {
913
+ return cached;
914
+ }
915
+
916
+ const { issuerUrl, resourceServerUrl } = buildAuthUrls(normalizedBaseUrl);
917
+ const router = createOAuthAuthorizationServerRouter({
918
+ provider,
919
+ issuerUrl,
920
+ resourceServerUrl,
921
+ dangerouslyAllowInsecureIssuerUrl: allowHttpLan,
922
+ authorizationOptions: {
923
+ rateLimit: {
924
+ validate: { creationStack: false }
925
+ }
926
+ },
927
+ tokenOptions: {
928
+ rateLimit: {
929
+ validate: { creationStack: false }
930
+ }
931
+ },
932
+ clientRegistrationOptions: {
933
+ rateLimit: {
934
+ validate: { creationStack: false }
935
+ }
936
+ },
937
+ revocationOptions: {
938
+ rateLimit: {
939
+ validate: { creationStack: false }
940
+ }
941
+ },
942
+ scopesSupported: AUTH_SUPPORTED_SCOPES,
943
+ resourceName: 'Local Dev MCP'
944
+ });
945
+ authRouters.set(normalizedBaseUrl, router);
946
+ return router;
947
+ }
948
+
949
+ app.use((req, res, next) => {
950
+ getAuthRouterForBaseUrl(requestBaseUrl(req))(req, res, next);
951
+ });
952
+
953
+ const oauthAuthMiddlewares = new Map();
954
+
955
+ function getOAuthAuthMiddlewareForBaseUrl(baseUrl) {
956
+ const normalizedBaseUrl = normalizeBaseUrl(baseUrl) || fallbackBaseUrl;
957
+ const cached = oauthAuthMiddlewares.get(normalizedBaseUrl);
958
+ if (cached) {
959
+ return cached;
960
+ }
961
+
962
+ const { resourceServerUrl } = buildAuthUrls(normalizedBaseUrl);
963
+ const middleware = requireBearerAuth({
964
+ verifier: provider,
965
+ requiredScopes: [],
966
+ resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(resourceServerUrl)
967
+ });
968
+ oauthAuthMiddlewares.set(normalizedBaseUrl, middleware);
969
+ return middleware;
970
+ }
971
+
972
+ function mcpAuthMiddleware(req, res, next) {
973
+ if (isStaticBearerAuthorization(req.headers.authorization, staticBearerToken)) {
974
+ console.log('[auth] static-bearer accepted');
975
+ setIncomingHeader(req, 'accept', 'application/json, text/event-stream');
976
+ next();
977
+ return;
978
+ }
979
+
980
+ if (debugAuth && req.headers.authorization && staticBearerToken) {
981
+ const providedToken = getProvidedAuthorizationToken(req.headers.authorization);
982
+ console.log(
983
+ `[auth] static-bearer not matched; trying OAuth provided=${fingerprint(providedToken)} expected=${fingerprint(staticBearerToken)}`
984
+ );
985
+ }
986
+
987
+ getOAuthAuthMiddlewareForBaseUrl(requestBaseUrl(req))(req, res, next);
988
+ }
989
+
990
+ function modernCallerContext(ctx = {}) {
991
+ const authorization = ctx.requestInfo?.headers?.get?.('authorization') || '';
992
+ const staticBearer = isStaticBearerAuthorization(authorization, staticBearerToken);
993
+ const accountId = ctx.authInfo?.accountId || null;
994
+ const oauthClientId = ctx.authInfo?.clientId || '';
995
+ const callerCategory = staticBearer ? 'static-bearer' : oauthClientId ? 'oauth' : 'anonymous';
996
+ const callerSubject = staticBearer ? 'static-bearer' : accountId ? `account:${accountId}` : 'anonymous';
997
+ return {
998
+ era: ctx.era,
999
+ accountId,
1000
+ activitySessionId: ctx.authInfo?.activitySessionId || null,
1001
+ callerKey: callerKeyFromParts({ accountId: accountId || '', oauthClientId, staticBearer }),
1002
+ callerCategory,
1003
+ callerSubject
1004
+ };
1005
+ }
1006
+
1007
+ const modernMcpHandler = createMcpHandler(
1008
+ ctx => createProxyServer(modernCallerContext(ctx)),
1009
+ {
1010
+ legacy: useStatefulMcpSessions ? 'reject' : 'stateless',
1011
+ responseMode: 'auto',
1012
+ onerror: error => console.error('[mcp-v2]', error)
1013
+ }
1014
+ );
1015
+ const modernNodeHandler = toNodeHandler(modernMcpHandler, {
1016
+ onerror: error => console.error('[mcp-v2-node]', error)
1017
+ });
1018
+
1019
+ const transports = {};
1020
+
1021
+ async function createTransport(req) {
1022
+ let transport;
1023
+ const server = createProxyServer({
1024
+ accountId: req.auth?.accountId || null,
1025
+ activitySessionId: req.auth?.activitySessionId || null,
1026
+ callerKey: callerKeyFromRequest(req),
1027
+ callerCategory: callerCategoryFromRequest(req),
1028
+ callerSubject: callerSubjectFromRequest(req)
1029
+ });
1030
+
1031
+ if (!useStatefulMcpSessions) {
1032
+ transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined });
1033
+ transport.onclose = () => activeProxyServers.delete(server);
1034
+ await server.connect(transport);
1035
+ return transport;
1036
+ }
1037
+
1038
+ transport = new NodeStreamableHTTPServerTransport({
1039
+ sessionIdGenerator: () => randomUUID(),
1040
+ onsessioninitialized: id => {
1041
+ transports[id] = transport;
1042
+ }
1043
+ });
1044
+
1045
+ transport.onclose = () => {
1046
+ activeProxyServers.delete(server);
1047
+ const sid = transport.sessionId;
1048
+ if (sid && transports[sid]) delete transports[sid];
1049
+ };
1050
+
1051
+ await server.connect(transport);
1052
+ return transport;
1053
+ }
1054
+
1055
+ const mcpPostHandler = async (req, res) => {
1056
+ const sessionId = req.headers['mcp-session-id'];
1057
+ const requestSummary = summarizeRequestBody(req.body);
1058
+ console.log(`[mcp-post:start] ${requestSummary} session=${sessionId || 'none'}`);
1059
+
1060
+ try {
1061
+ let transport;
1062
+ if (useStatefulMcpSessions && sessionId && transports[sessionId]) {
1063
+ transport = transports[sessionId];
1064
+ } else if (
1065
+ !useStatefulMcpSessions ||
1066
+ shouldCreateTransportForRequest(sessionId, req.body, transports) ||
1067
+ (!sessionId && isInitializeRequest(req.body))
1068
+ ) {
1069
+ transport = await createTransport(req);
1070
+ await transport.handleRequest(req, res, req.body);
1071
+ return;
1072
+ } else {
1073
+ console.log(`[mcp-post:reject] ${requestSummary} reason=invalid-session session=${sessionId || 'none'}`);
1074
+ res.status(400).json({
1075
+ jsonrpc: '2.0',
1076
+ error: { code: -32000, message: 'Bad Request: No valid session ID provided' },
1077
+ id: null
1078
+ });
1079
+ return;
1080
+ }
1081
+
1082
+ await transport.handleRequest(req, res, req.body);
1083
+ console.log(`[mcp-post:finish] ${requestSummary} session=${sessionId || 'none'}`);
1084
+ } catch (error) {
1085
+ console.error('Error handling MCP POST request:', error);
1086
+ if (!res.headersSent) {
1087
+ res.status(500).json({
1088
+ jsonrpc: '2.0',
1089
+ error: { code: -32603, message: 'Internal server error' },
1090
+ id: null
1091
+ });
1092
+ }
1093
+ }
1094
+ };
1095
+
1096
+ const mcpGetHandler = async (req, res) => {
1097
+ const sessionId = req.headers['mcp-session-id'];
1098
+ console.log(`[mcp-get:start] session=${sessionId || 'none'}`);
1099
+ if (!sessionId || !transports[sessionId]) {
1100
+ console.log(`[mcp-get:reject] reason=invalid-session session=${sessionId || 'none'}`);
1101
+ res.status(400).send('Invalid or missing session ID');
1102
+ return;
1103
+ }
1104
+
1105
+ await transports[sessionId].handleRequest(req, res);
1106
+ console.log(`[mcp-get:finish] session=${sessionId || 'none'}`);
1107
+ };
1108
+
1109
+ const mcpDeleteHandler = async (req, res) => {
1110
+ const sessionId = req.headers['mcp-session-id'];
1111
+ console.log(`[mcp-delete:start] session=${sessionId || 'none'}`);
1112
+ if (!sessionId || !transports[sessionId]) {
1113
+ console.log(`[mcp-delete:reject] reason=invalid-session session=${sessionId || 'none'}`);
1114
+ res.status(400).send('Invalid or missing session ID');
1115
+ return;
1116
+ }
1117
+
1118
+ await transports[sessionId].handleRequest(req, res);
1119
+ console.log(`[mcp-delete:finish] session=${sessionId || 'none'}`);
1120
+ };
1121
+
1122
+ app.get('/healthz', (_req, res) => {
1123
+ const skills = skillRegistry.health();
1124
+ res.json({
1125
+ ok: skills.status === 'healthy',
1126
+ enableFilesystem,
1127
+ enableShell,
1128
+ skills
1129
+ });
1130
+ });
1131
+
1132
+ async function unifiedMcpHandler(req, res) {
1133
+ if (!useStatefulMcpSessions) {
1134
+ await modernNodeHandler(req, res, req.body);
1135
+ return;
1136
+ }
1137
+
1138
+ const probe = await toWebRequest(req, req.body);
1139
+ if (!(await isLegacyRequest(probe))) {
1140
+ await modernNodeHandler(req, res, req.body);
1141
+ return;
1142
+ }
1143
+
1144
+ if (req.method === 'POST') {
1145
+ await mcpPostHandler(req, res);
1146
+ return;
1147
+ }
1148
+ if (req.method === 'GET') {
1149
+ await mcpGetHandler(req, res);
1150
+ return;
1151
+ }
1152
+ if (req.method === 'DELETE') {
1153
+ await mcpDeleteHandler(req, res);
1154
+ return;
1155
+ }
1156
+ res.status(405).end();
1157
+ }
1158
+
1159
+ app.all('/mcp', mcpAuthMiddleware, unifiedMcpHandler);
1160
+
1161
+ let localDevice = null;
1162
+ let shutdownPromise = null;
1163
+ const serverInstance = app.listen(gatewayPort, gatewayHost);
1164
+ let loopbackInstance = null;
1165
+
1166
+ function shutdown() {
1167
+ // Keep the same failed result for every caller. A previous failed cleanup must
1168
+ // never look like a stopped runtime and allow its state lease to be released.
1169
+ shutdownPromise ??= (async () => {
1170
+ const failures = [];
1171
+ async function attempt(resource, cleanup) {
1172
+ try { await cleanup(); }
1173
+ catch (error) { failures.push({ resource, error }); }
1174
+ }
1175
+
1176
+ await attempt('embedded-device', () => localDevice?.close());
1177
+ const listeners = [serverInstance, loopbackInstance].filter(Boolean);
1178
+ const serverClosed = Promise.all(listeners.map(listener => new Promise(resolve => {
1179
+ try {
1180
+ listener.close(error => {
1181
+ if (error && error.code !== 'ERR_SERVER_NOT_RUNNING') failures.push({ resource: 'http-server', error });
1182
+ resolve();
1183
+ });
1184
+ } catch (error) {
1185
+ failures.push({ resource: 'http-server', error });
1186
+ resolve();
1187
+ }
1188
+ })));
1189
+ for (const listener of listeners) await attempt('http-idle-connections', () => listener.closeIdleConnections?.());
1190
+ await attempt('workspace-registry', () => workspaceRegistry.close());
1191
+ await attempt('tool-metrics', () => toolMetrics.close());
1192
+ await attempt('device-audit', () => deviceAudit.close());
1193
+ await attempt('device-broker', () => deviceBroker.shutdown());
1194
+ for (const transport of Object.values(transports)) {
1195
+ await attempt('mcp-transport', () => transport.close());
1196
+ }
1197
+ for (const proxy of activeProxyServers) {
1198
+ await attempt('mcp-proxy', async () => {
1199
+ await proxy.close();
1200
+ activeProxyServers.delete(proxy);
1201
+ });
1202
+ }
1203
+ for (const listener of listeners) await attempt('http-connections', () => listener.closeAllConnections?.());
1204
+ await serverClosed;
1205
+ await attempt('oauth-state', () => oauthStateStore.close());
1206
+ await attempt('accounts', () => accountStore.close());
1207
+ await attempt('device-usage', () => deviceUsageStore.close());
1208
+ await attempt('device-pairings', () => devicePairingStore.close());
1209
+ await attempt('device-store', () => deviceStore.close());
1210
+ await attempt('upstreams', () => externalMcpManager.shutdown());
1211
+ await attempt('device-core', () => closeOwnedCore());
1212
+ resourcesClosed = failures.length === 0;
1213
+ if (failures.length === 1) throw failures[0].error;
1214
+ if (failures.length > 1) {
1215
+ const error = new AggregateError(failures.map(failure => failure.error),
1216
+ 'Server cleanup failed for: ' + failures.map(failure => failure.resource).join(', '));
1217
+ error.code = 'SERVER_CLEANUP_FAILED';
1218
+ throw error;
1219
+ }
1220
+ })();
1221
+ return shutdownPromise;
1222
+ }
1223
+
1224
+ try {
1225
+ await new Promise((resolve, reject) => {
1226
+ serverInstance.once('listening', resolve);
1227
+ serverInstance.once('error', reject);
1228
+ });
1229
+ deviceBroker.attach(serverInstance);
1230
+ if (loopbackPort !== null) {
1231
+ // The same authenticated app and database serve both ports. Never bypass account authorization.
1232
+ // Only bind loopback: a secondary port must not become an additional public listener.
1233
+ // The old 8101 Cloudflare tunnel can still reach the local TCP listener.
1234
+ // Reject routed/proxied requests: only direct same-machine HTTP clients may use this port.
1235
+ loopbackInstance = http.createServer((req, res) => {
1236
+ const hostname = String(req.headers.host || '').split(':')[0].toLowerCase();
1237
+ const loopbackHost = hostname === '127.0.0.1' || hostname === 'localhost';
1238
+ const proxied = Boolean(req.headers['cf-connecting-ip'] || req.headers['cf-ray'] || req.headers['x-forwarded-for'] || req.headers['x-forwarded-host']);
1239
+ if (!loopbackHost || proxied) {
1240
+ res.writeHead(421, { 'content-type': 'text/plain; charset=utf-8' });
1241
+ res.end('Local MCP listener requires direct loopback access');
1242
+ return;
1243
+ }
1244
+ app(req, res);
1245
+ }).listen(loopbackPort, '127.0.0.1');
1246
+ await new Promise((resolve, reject) => {
1247
+ loopbackInstance.once('listening', resolve);
1248
+ loopbackInstance.once('error', reject);
1249
+ });
1250
+ }
1251
+ localDevice = await createEmbeddedDeviceController({
1252
+ broker: deviceBroker, deviceStore, pairingStore: devicePairingStore,
1253
+ identity, core, deviceName: options.deviceName,
1254
+ baseUrl: normalizeBaseUrl(advertisedUrl) || fallbackBaseUrl,
1255
+ onPairing: options.onPairing,
1256
+ pollIntervalMs: options.pairingPollIntervalMs
1257
+ });
1258
+ } catch (error) {
1259
+ try { await shutdown(); } catch { /* preserve the startup error */ }
1260
+ throw error;
1261
+ }
1262
+ return {
1263
+ app, server: serverInstance,
1264
+ baseUrl: normalizeBaseUrl(advertisedUrl) || fallbackBaseUrl,
1265
+ loopbackUrl: loopbackPort === null ? null : `http://127.0.0.1:${loopbackPort}/mcp`,
1266
+ broker: deviceBroker, accounts: accountStore, pairings: devicePairingStore,
1267
+ localDevice, dbPath: gatewayDbPath,
1268
+ refreshCapabilities: () => localDevice.refreshCapabilities(),
1269
+ listTools: listMergedTools,
1270
+ close: shutdown
1271
+ };
1272
+ } catch (error) {
1273
+ if (!resourcesClosed) {
1274
+ for (const cleanup of startupCleanups.reverse()) {
1275
+ try { await cleanup(); } catch { /* preserve the startup error */ }
1276
+ }
1277
+ try { await closeOwnedCore(); } catch { /* preserve the startup error */ }
1278
+ }
1279
+ throw error;
1280
+ }
1281
+ }