@akira-tl/forgerelay 1.0.1 → 1.1.1

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 (169) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +4 -0
  3. package/capabilities/host-integration/external-mcp/GUIDE.md +29 -0
  4. package/capabilities/lifecycle-hooks/GUIDE.md +13 -3
  5. package/dist/activity/runtime/mcp-query-tools.js +16 -10
  6. package/dist/cli/mcp/external-mcp.js +481 -0
  7. package/dist/cli/mcp/status.js +295 -0
  8. package/dist/cli.js +14 -1
  9. package/dist/lsp/test-support/server-fixture.js +1 -2
  10. package/dist/mcp/filesystem/filesystem-tools.js +7 -3
  11. package/dist/mcp/hooks/command-runner.js +12 -7
  12. package/dist/mcp/hooks/external-mcp-transform.js +224 -0
  13. package/dist/mcp/hooks/hook-cli.js +3 -0
  14. package/dist/mcp/hooks/hooks.js +49 -3
  15. package/dist/mcp/oauth/auth-protocol.js +375 -0
  16. package/dist/mcp/oauth/oauth-provider.js +11 -12
  17. package/dist/mcp/oauth/oauth-store.js +2 -2
  18. package/dist/mcp/oauth/router.js +2 -6
  19. package/dist/mcp/operations/batch/executor.js +1 -1
  20. package/dist/mcp/operations/bulk-read.js +7 -6
  21. package/dist/mcp/operations/external-mcp/external-mcp-oauth.js +387 -0
  22. package/dist/mcp/operations/external-mcp/external-mcp-runtime.js +59 -0
  23. package/dist/mcp/operations/external-mcp/external-mcp.js +382 -0
  24. package/dist/mcp/operations/media-content.js +28 -0
  25. package/dist/mcp/operations/native-bulk-mutations.js +1 -1
  26. package/dist/mcp/process/tools.js +19 -15
  27. package/dist/mcp/request-context.js +18 -0
  28. package/dist/mcp/request-meta.js +28 -3
  29. package/dist/mcp/server/core/activity-support.js +2 -2
  30. package/dist/mcp/server/core/capabilities/external-mcp.js +31 -0
  31. package/dist/mcp/server/core/capabilities.js +7 -0
  32. package/dist/mcp/server/core/capability-registry.js +2 -0
  33. package/dist/mcp/server/core/tool-support.js +29 -0
  34. package/dist/mcp/server/operations/runtime/filesystem-tools.js +91 -48
  35. package/dist/mcp/server/operations/runtime/operation-runtime.js +18 -11
  36. package/dist/mcp/server/transport/http-server.js +42 -21
  37. package/dist/mcp/server/workspace/runtime/workspace-open.js +13 -9
  38. package/dist/mcp/server/workspace/runtime/workspace-tools.js +29 -25
  39. package/dist/runtime/config/config.js +4 -0
  40. package/dist/runtime/config/external-mcp-auth-store.js +225 -0
  41. package/dist/runtime/config/external-mcp-config.js +146 -0
  42. package/dist/runtime/config/external-mcp-registry.js +165 -0
  43. package/dist/runtime/testing/server-fixture.js +10 -5
  44. package/dist/server.js +19 -10
  45. package/dist/subagents/sessions/mcp/audit.js +81 -0
  46. package/dist/ui/.vite/manifest.json +297 -298
  47. package/dist/ui/activity-panel-app.html +1 -1
  48. package/dist/ui/assets/activity-panel-app-Bw7ZFX2g.js +114 -0
  49. package/dist/ui/assets/angular-html-DeUNP12X.js +1 -0
  50. package/dist/ui/assets/{angular-ts-BearSZDb.js → angular-ts-B3v8FKi_.js} +1 -1
  51. package/dist/ui/assets/{apl-CYQtr-y6.js → apl-Bs3iQm4P.js} +1 -1
  52. package/dist/ui/assets/{astro-DDzqKIjK.js → astro-D6GHecBY.js} +1 -1
  53. package/dist/ui/assets/{blade-DkwEyHvQ.js → blade-BMNbVOHv.js} +1 -1
  54. package/dist/ui/assets/c-BquuA6bv.js +1 -0
  55. package/dist/ui/assets/{cobol-xBn5JxHE.js → cobol-BuZ6kfps.js} +1 -1
  56. package/dist/ui/assets/{coffee-BaXXvoJw.js → coffee-DgC5uBzL.js} +1 -1
  57. package/dist/ui/assets/cpp-BdvWqCaB.js +1 -0
  58. package/dist/ui/assets/{crystal-DjOpXVU1.js → crystal-0Q5qBvNI.js} +1 -1
  59. package/dist/ui/assets/css-Be_T8idh.js +1 -0
  60. package/dist/ui/assets/{edge-DNaRizoR.js → edge-DaGLdgh_.js} +1 -1
  61. package/dist/ui/assets/{elixir-CwBMwZmy.js → elixir-CMgiFIzB.js} +1 -1
  62. package/dist/ui/assets/{elm-gOUD2CW3.js → elm-B-ufMlMC.js} +1 -1
  63. package/dist/ui/assets/{erb-BcR-fjUp.js → erb-CaGiOZM-.js} +1 -1
  64. package/dist/ui/assets/{git-rebase-BAQPKHF6.js → git-rebase-CpwUWCzK.js} +1 -1
  65. package/dist/ui/assets/{glimmer-js-P_wOfSz_.js → glimmer-js-BRxccvnE.js} +1 -1
  66. package/dist/ui/assets/{glimmer-ts-B44aY1fZ.js → glimmer-ts-CgQbPzlT.js} +1 -1
  67. package/dist/ui/assets/glsl-D7JmHFr0.js +1 -0
  68. package/dist/ui/assets/graphql-C8n0eUBK.js +1 -0
  69. package/dist/ui/assets/{hack-ddU2k0nt.js → hack-lVN7KVQv.js} +1 -1
  70. package/dist/ui/assets/haml-h18lG0pS.js +1 -0
  71. package/dist/ui/assets/{handlebars-BpzaUpmz.js → handlebars-lRFjRPfj.js} +1 -1
  72. package/dist/ui/assets/heavy-payload-Cqx9MOkH.js +290 -0
  73. package/dist/ui/assets/html-DtILqTE1.js +1 -0
  74. package/dist/ui/assets/{html-derivative-B2beVdmA.js → html-derivative-CSTSmPup.js} +1 -1
  75. package/dist/ui/assets/{http-iEskZy8U.js → http-DNI8fsbY.js} +1 -1
  76. package/dist/ui/assets/{hurl-BuUsLSKv.js → hurl-DgRi60JG.js} +1 -1
  77. package/dist/ui/assets/java-CW9pZ5bh.js +1 -0
  78. package/dist/ui/assets/javascript-Cvg2UrWL.js +1 -0
  79. package/dist/ui/assets/{jinja-CRpkWwrH.js → jinja-CZw_S1FB.js} +1 -1
  80. package/dist/ui/assets/{jison-BYfSnuHj.js → jison-C56u_FZv.js} +1 -1
  81. package/dist/ui/assets/json-DW2UTv1q.js +1 -0
  82. package/dist/ui/assets/jsx-Zj5gobdD.js +1 -0
  83. package/dist/ui/assets/{julia-B8Rxy7vD.js → julia-Cvcrre-L.js} +1 -1
  84. package/dist/ui/assets/{just-DzhIWpFP.js → just-8CZ2u6Tj.js} +1 -1
  85. package/dist/ui/assets/{latex-CUNjUUL-.js → latex-D289AXWG.js} +1 -1
  86. package/dist/ui/assets/{liquid-hUg7nC61.js → liquid-CFEX3SkZ.js} +1 -1
  87. package/dist/ui/assets/lua-BKpJ0ybO.js +1 -0
  88. package/dist/ui/assets/{marko-CQmIkwMF.js → marko-D9maZ0a6.js} +1 -1
  89. package/dist/ui/assets/{mdc-CB-6dHYa.js → mdc-rM9l0qWR.js} +1 -1
  90. package/dist/ui/assets/{nginx-CQORo_Bt.js → nginx-C9w6p2C9.js} +1 -1
  91. package/dist/ui/assets/{nim-BNwBNzdn.js → nim-BRePHk0J.js} +1 -1
  92. package/dist/ui/assets/{perl-Cnz14fAJ.js → perl-CskTmiPQ.js} +1 -1
  93. package/dist/ui/assets/{php-YywDo63b.js → php-DCU_FvNI.js} +1 -1
  94. package/dist/ui/assets/{pug-BwbJp4cM.js → pug-XLaVxTvv.js} +1 -1
  95. package/dist/ui/assets/{qml-D2DY6dMH.js → qml-CaONKSl-.js} +1 -1
  96. package/dist/ui/assets/r-BCVR0ZE3.js +1 -0
  97. package/dist/ui/assets/{razor-pMTG7k41.js → razor-DDnGXxnU.js} +1 -1
  98. package/dist/ui/assets/regexp-DWGTYYVx.js +1 -0
  99. package/dist/ui/assets/{rst-CO_ndDm4.js → rst-Dz3GWvQm.js} +1 -1
  100. package/dist/ui/assets/{ruby-xzRn9gTh.js → ruby-DC3jGtMY.js} +1 -1
  101. package/dist/ui/assets/{sas-CBdtFKLA.js → sas-7mNpvsjZ.js} +1 -1
  102. package/dist/ui/assets/scss-uBsoCJVy.js +1 -0
  103. package/dist/ui/assets/shellscript-BCN9N_Jf.js +1 -0
  104. package/dist/ui/assets/{shellsession-BaZnm5rZ.js → shellsession-Hsw46VXV.js} +1 -1
  105. package/dist/ui/assets/{soy-IouG3xdB.js → soy-D6KV69HU.js} +1 -1
  106. package/dist/ui/assets/sql-CVzVQkJh.js +1 -0
  107. package/dist/ui/assets/{stata-dgbkIZHP.js → stata-BwUjXNhI.js} +1 -1
  108. package/dist/ui/assets/{surrealql-CBROnw_p.js → surrealql-CgloWqRa.js} +1 -1
  109. package/dist/ui/assets/{svelte-6r6BpX4-.js → svelte-DcC3n6IC.js} +1 -1
  110. package/dist/ui/assets/{templ-BKD_PsbL.js → templ-DOFfnWTJ.js} +1 -1
  111. package/dist/ui/assets/{tex-CKY1bwHb.js → tex-ln89VPaX.js} +1 -1
  112. package/dist/ui/assets/{ts-tags-BiYP5N_C.js → ts-tags-CWMzCstH.js} +1 -1
  113. package/dist/ui/assets/tsx-DgzfiGeK.js +1 -0
  114. package/dist/ui/assets/{twig-BTe1oPFW.js → twig-Bi2itZL9.js} +1 -1
  115. package/dist/ui/assets/typescript-CQ24f4zW.js +1 -0
  116. package/dist/ui/assets/{vue-CT0IiySh.js → vue-BS8jE5u6.js} +1 -1
  117. package/dist/ui/assets/{vue-html-B-tXcbat.js → vue-html-o38xqhyB.js} +1 -1
  118. package/dist/ui/assets/{vue-vine-C5ZyDCrs.js → vue-vine-D2NvKv50.js} +1 -1
  119. package/dist/ui/assets/xml-_44rx9_2.js +1 -0
  120. package/dist/ui/assets/{xsl-DvDSndNI.js → xsl-B5QWQPoi.js} +1 -1
  121. package/dist/ui/assets/yaml-CKL2pEWe.js +1 -0
  122. package/dist/workspaces/relay/auth/remote-auth.js +3 -5
  123. package/dist/workspaces/relay/result-support.js +19 -0
  124. package/dist/workspaces/relay/tests/test-support.js +10 -4
  125. package/dist/workspaces/relay/workspace-relay.js +20 -12
  126. package/docs/configuration.md +200 -2
  127. package/docs/debugging.md +8 -5
  128. package/docs/roadmap.md +64 -3
  129. package/docs/security.md +18 -0
  130. package/package.json +12 -4
  131. package/scripts/debug/accept/bootstrap.mjs +5 -0
  132. package/scripts/debug/accept/harness.mjs +10 -3
  133. package/scripts/debug/accept/media.mjs +335 -0
  134. package/scripts/debug/accept/modern-http.mjs +169 -0
  135. package/scripts/debug/accept/support.mjs +10 -6
  136. package/scripts/debug/accept.mjs +12 -1
  137. package/scripts/debug/relay-accept/support.mjs +40 -0
  138. package/scripts/debug/relay-accept.mjs +8 -9
  139. package/scripts/debug/runtime.mjs +4 -1
  140. package/scripts/debug/runtime.test.mjs +9 -0
  141. package/scripts/release/parity-sandbox.mjs +35 -0
  142. package/scripts/release/release-gate.test.mjs +42 -1
  143. package/scripts/release-parity.mjs +3 -22
  144. package/scripts/release-proof.mjs +24 -4
  145. package/scripts/release-proof.test.mjs +12 -4
  146. package/dist/ui/assets/activity-panel-app-XlqSanXT.js +0 -115
  147. package/dist/ui/assets/angular-html-tB9EchbM.js +0 -1
  148. package/dist/ui/assets/c-Dv-N5hjn.js +0 -1
  149. package/dist/ui/assets/cpp-C3unXX-3.js +0 -1
  150. package/dist/ui/assets/css-8Xr4kNNH.js +0 -1
  151. package/dist/ui/assets/glsl-BvCQg9MI.js +0 -1
  152. package/dist/ui/assets/graphql-C9S90gn_.js +0 -1
  153. package/dist/ui/assets/haml-Bb1w9rMG.js +0 -1
  154. package/dist/ui/assets/heavy-payload-CJKqi8Ac.js +0 -290
  155. package/dist/ui/assets/html-dUOs5pA9.js +0 -1
  156. package/dist/ui/assets/java-BFZCrORY.js +0 -1
  157. package/dist/ui/assets/javascript-DaIQ0Zqj.js +0 -1
  158. package/dist/ui/assets/json-BQC_Yp48.js +0 -1
  159. package/dist/ui/assets/jsx-Dd-CJRf9.js +0 -1
  160. package/dist/ui/assets/lua-BoZTiL2I.js +0 -1
  161. package/dist/ui/assets/r-B5nYsy-M.js +0 -1
  162. package/dist/ui/assets/regexp-oP0lbks_.js +0 -1
  163. package/dist/ui/assets/scss-CEqgKQNf.js +0 -1
  164. package/dist/ui/assets/shellscript-DzJJiWQC.js +0 -1
  165. package/dist/ui/assets/sql-DC2rdTzV.js +0 -1
  166. package/dist/ui/assets/tsx-D2OMiPNF.js +0 -1
  167. package/dist/ui/assets/typescript-BGIABksB.js +0 -1
  168. package/dist/ui/assets/xml-CfeBNlMP.js +0 -1
  169. package/dist/ui/assets/yaml-umbj2r1g.js +0 -1
@@ -0,0 +1 @@
1
+ import{t as e}from"./java-CW9pZ5bh.js";import{t}from"./heavy-payload-Cqx9MOkH.js";var n=t({default:()=>i}),r=Object.freeze(JSON.parse(`{"displayName":"XML","name":"xml","patterns":[{"begin":"(<\\\\?)\\\\s*([-0-9A-Z_a-z]+)","captures":{"1":{"name":"punctuation.definition.tag.xml"},"2":{"name":"entity.name.tag.xml"}},"end":"(\\\\?>)","name":"meta.tag.preprocessor.xml","patterns":[{"match":" ([-A-Za-z]+)","name":"entity.other.attribute-name.xml"},{"include":"#doublequotedString"},{"include":"#singlequotedString"}]},{"begin":"(<!)(DOCTYPE)\\\\s+([:A-Z_a-z][-.0-:A-Z_a-z]*)","captures":{"1":{"name":"punctuation.definition.tag.xml"},"2":{"name":"keyword.other.doctype.xml"},"3":{"name":"variable.language.documentroot.xml"}},"end":"\\\\s*(>)","name":"meta.tag.sgml.doctype.xml","patterns":[{"include":"#internalSubset"}]},{"include":"#comments"},{"begin":"(<)((?:([-0-9A-Z_a-z]+)(:))?([-0-:A-Z_a-z]+))(?=(\\\\s[^>]*)?></\\\\2>)","beginCaptures":{"1":{"name":"punctuation.definition.tag.xml"},"2":{"name":"entity.name.tag.xml"},"3":{"name":"entity.name.tag.namespace.xml"},"4":{"name":"punctuation.separator.namespace.xml"},"5":{"name":"entity.name.tag.localname.xml"}},"end":"(>)(</)((?:([-0-9A-Z_a-z]+)(:))?([-0-:A-Z_a-z]+))(>)","endCaptures":{"1":{"name":"punctuation.definition.tag.xml"},"2":{"name":"punctuation.definition.tag.xml"},"3":{"name":"entity.name.tag.xml"},"4":{"name":"entity.name.tag.namespace.xml"},"5":{"name":"punctuation.separator.namespace.xml"},"6":{"name":"entity.name.tag.localname.xml"},"7":{"name":"punctuation.definition.tag.xml"}},"name":"meta.tag.no-content.xml","patterns":[{"include":"#tagStuff"}]},{"begin":"(</?)(?:([-.\\\\w]+)((:)))?([-.:\\\\w]+)","captures":{"1":{"name":"punctuation.definition.tag.xml"},"2":{"name":"entity.name.tag.namespace.xml"},"3":{"name":"entity.name.tag.xml"},"4":{"name":"punctuation.separator.namespace.xml"},"5":{"name":"entity.name.tag.localname.xml"}},"end":"(/?>)","name":"meta.tag.xml","patterns":[{"include":"#tagStuff"}]},{"include":"#entity"},{"include":"#bare-ampersand"},{"begin":"<%@","beginCaptures":{"0":{"name":"punctuation.section.embedded.begin.xml"}},"end":"%>","endCaptures":{"0":{"name":"punctuation.section.embedded.end.xml"}},"name":"source.java-props.embedded.xml","patterns":[{"match":"page|include|taglib","name":"keyword.other.page-props.xml"}]},{"begin":"<%[!=]?(?!--)","beginCaptures":{"0":{"name":"punctuation.section.embedded.begin.xml"}},"end":"(?!--)%>","endCaptures":{"0":{"name":"punctuation.section.embedded.end.xml"}},"name":"source.java.embedded.xml","patterns":[{"include":"source.java"}]},{"begin":"<!\\\\[CDATA\\\\[","beginCaptures":{"0":{"name":"punctuation.definition.string.begin.xml"}},"end":"]]>","endCaptures":{"0":{"name":"punctuation.definition.string.end.xml"}},"name":"string.unquoted.cdata.xml"}],"repository":{"EntityDecl":{"begin":"(<!)(ENTITY)\\\\s+(%\\\\s+)?([:A-Z_a-z][-.0-:A-Z_a-z]*)(\\\\s+(?:SYSTEM|PUBLIC)\\\\s+)?","captures":{"1":{"name":"punctuation.definition.tag.xml"},"2":{"name":"keyword.other.entity.xml"},"3":{"name":"punctuation.definition.entity.xml"},"4":{"name":"variable.language.entity.xml"},"5":{"name":"keyword.other.entitytype.xml"}},"end":"(>)","patterns":[{"include":"#doublequotedString"},{"include":"#singlequotedString"}]},"bare-ampersand":{"match":"&","name":"invalid.illegal.bad-ampersand.xml"},"comments":{"patterns":[{"begin":"<%--","captures":{"0":{"name":"punctuation.definition.comment.xml"},"end":"--%>","name":"comment.block.xml"}},{"begin":"<!--","captures":{"0":{"name":"punctuation.definition.comment.xml"}},"end":"-->","name":"comment.block.xml","patterns":[{"begin":"--(?!>)","captures":{"0":{"name":"invalid.illegal.bad-comments-or-CDATA.xml"}}}]}]},"doublequotedString":{"begin":"\\"","beginCaptures":{"0":{"name":"punctuation.definition.string.begin.xml"}},"end":"\\"","endCaptures":{"0":{"name":"punctuation.definition.string.end.xml"}},"name":"string.quoted.double.xml","patterns":[{"include":"#entity"},{"include":"#bare-ampersand"}]},"entity":{"captures":{"1":{"name":"punctuation.definition.constant.xml"},"3":{"name":"punctuation.definition.constant.xml"}},"match":"(&)([:A-Z_a-z][-.0-:A-Z_a-z]*|#[0-9]+|#x\\\\h+)(;)","name":"constant.character.entity.xml"},"internalSubset":{"begin":"(\\\\[)","captures":{"1":{"name":"punctuation.definition.constant.xml"}},"end":"(])","name":"meta.internalsubset.xml","patterns":[{"include":"#EntityDecl"},{"include":"#parameterEntity"},{"include":"#comments"}]},"parameterEntity":{"captures":{"1":{"name":"punctuation.definition.constant.xml"},"3":{"name":"punctuation.definition.constant.xml"}},"match":"(%)([:A-Z_a-z][-.0-:A-Z_a-z]*)(;)","name":"constant.character.parameter-entity.xml"},"singlequotedString":{"begin":"'","beginCaptures":{"0":{"name":"punctuation.definition.string.begin.xml"}},"end":"'","endCaptures":{"0":{"name":"punctuation.definition.string.end.xml"}},"name":"string.quoted.single.xml","patterns":[{"include":"#entity"},{"include":"#bare-ampersand"}]},"tagStuff":{"patterns":[{"captures":{"1":{"name":"entity.other.attribute-name.namespace.xml"},"2":{"name":"entity.other.attribute-name.xml"},"3":{"name":"punctuation.separator.namespace.xml"},"4":{"name":"entity.other.attribute-name.localname.xml"}},"match":"(?:^|\\\\s+)(?:([-.\\\\w]+)((:)))?([-.:\\\\w]+)\\\\s*="},{"include":"#doublequotedString"},{"include":"#singlequotedString"}]}},"scopeName":"text.xml","embeddedLangs":["java"]}`)),i=[...e,r];export{n,i as t};
@@ -1 +1 @@
1
- import{t as e}from"./xml-CfeBNlMP.js";var t=Object.freeze(JSON.parse(`{"displayName":"XSL","name":"xsl","patterns":[{"begin":"(<)(xsl)((:))(template)","captures":{"1":{"name":"punctuation.definition.tag.xml"},"2":{"name":"entity.name.tag.namespace.xml"},"3":{"name":"entity.name.tag.xml"},"4":{"name":"punctuation.separator.namespace.xml"},"5":{"name":"entity.name.tag.localname.xml"}},"end":"(>)","name":"meta.tag.xml.template","patterns":[{"captures":{"1":{"name":"entity.other.attribute-name.namespace.xml"},"2":{"name":"entity.other.attribute-name.xml"},"3":{"name":"punctuation.separator.namespace.xml"},"4":{"name":"entity.other.attribute-name.localname.xml"}},"match":" (?:([-0-9A-Z_a-z]+)((:)))?([-A-Za-z]+)"},{"include":"#doublequotedString"},{"include":"#singlequotedString"}]},{"include":"text.xml"}],"repository":{"doublequotedString":{"begin":"\\"","beginCaptures":{"0":{"name":"punctuation.definition.string.begin.xml"}},"end":"\\"","endCaptures":{"0":{"name":"punctuation.definition.string.end.xml"}},"name":"string.quoted.double.xml"},"singlequotedString":{"begin":"'","beginCaptures":{"0":{"name":"punctuation.definition.string.begin.xml"}},"end":"'","endCaptures":{"0":{"name":"punctuation.definition.string.end.xml"}},"name":"string.quoted.single.xml"}},"scopeName":"text.xml.xsl","embeddedLangs":["xml"]}`)),n=[...e,t];export{n as default};
1
+ import{t as e}from"./xml-_44rx9_2.js";var t=Object.freeze(JSON.parse(`{"displayName":"XSL","name":"xsl","patterns":[{"begin":"(<)(xsl)((:))(template)","captures":{"1":{"name":"punctuation.definition.tag.xml"},"2":{"name":"entity.name.tag.namespace.xml"},"3":{"name":"entity.name.tag.xml"},"4":{"name":"punctuation.separator.namespace.xml"},"5":{"name":"entity.name.tag.localname.xml"}},"end":"(>)","name":"meta.tag.xml.template","patterns":[{"captures":{"1":{"name":"entity.other.attribute-name.namespace.xml"},"2":{"name":"entity.other.attribute-name.xml"},"3":{"name":"punctuation.separator.namespace.xml"},"4":{"name":"entity.other.attribute-name.localname.xml"}},"match":" (?:([-0-9A-Z_a-z]+)((:)))?([-A-Za-z]+)"},{"include":"#doublequotedString"},{"include":"#singlequotedString"}]},{"include":"text.xml"}],"repository":{"doublequotedString":{"begin":"\\"","beginCaptures":{"0":{"name":"punctuation.definition.string.begin.xml"}},"end":"\\"","endCaptures":{"0":{"name":"punctuation.definition.string.end.xml"}},"name":"string.quoted.double.xml"},"singlequotedString":{"begin":"'","beginCaptures":{"0":{"name":"punctuation.definition.string.begin.xml"}},"end":"'","endCaptures":{"0":{"name":"punctuation.definition.string.end.xml"}},"name":"string.quoted.single.xml"}},"scopeName":"text.xml.xsl","embeddedLangs":["xml"]}`)),n=[...e,t];export{n as default};
@@ -0,0 +1 @@
1
+ import{t as e}from"./heavy-payload-Cqx9MOkH.js";var t=e({default:()=>n}),n=[Object.freeze(JSON.parse(`{"displayName":"YAML","fileTypes":["yaml","yml","rviz","reek","clang-format","yaml-tmlanguage","syntax","sublime-syntax"],"firstLineMatch":"^%YAML( ?1.\\\\d+)?","name":"yaml","patterns":[{"include":"#comment"},{"include":"#property"},{"include":"#directive"},{"match":"^---","name":"entity.other.document.begin.yaml"},{"match":"^\\\\.{3}","name":"entity.other.document.end.yaml"},{"include":"#node"}],"repository":{"block-collection":{"patterns":[{"include":"#block-sequence"},{"include":"#block-mapping"}]},"block-mapping":{"patterns":[{"include":"#block-pair"}]},"block-node":{"patterns":[{"include":"#prototype"},{"include":"#block-scalar"},{"include":"#block-collection"},{"include":"#flow-scalar-plain-out"},{"include":"#flow-node"}]},"block-pair":{"patterns":[{"begin":"\\\\?","beginCaptures":{"1":{"name":"punctuation.definition.key-value.begin.yaml"}},"end":"(?=\\\\?)|^ *(:)|(:)","endCaptures":{"1":{"name":"punctuation.separator.key-value.mapping.yaml"},"2":{"name":"invalid.illegal.expected-newline.yaml"}},"name":"meta.block-mapping.yaml","patterns":[{"include":"#block-node"}]},{"begin":"(?=(?:[^-\\\\]!\\"#%\\\\&'*,:>?@\\\\[\`{|}\\\\s]|[-:?]\\\\S)([^:\\\\s]|:\\\\S|\\\\s+(?![#\\\\s]))*\\\\s*:(\\\\s|$))","end":"(?=\\\\s*$|\\\\s+#|\\\\s*:(\\\\s|$))","patterns":[{"include":"#flow-scalar-plain-out-implicit-type"},{"begin":"[^-\\\\]!\\"#%\\\\&'*,:>?@\\\\[\`{|}\\\\s]|[-:?]\\\\S","beginCaptures":{"0":{"name":"entity.name.tag.yaml"}},"contentName":"entity.name.tag.yaml","end":"(?=\\\\s*$|\\\\s+#|\\\\s*:(\\\\s|$))","name":"string.unquoted.plain.out.yaml"}]},{"match":":(?=\\\\s|$)","name":"punctuation.separator.key-value.mapping.yaml"}]},"block-scalar":{"begin":"(?:(\\\\|)|(>))([1-9])?([-+])?(.*\\\\n?)","beginCaptures":{"1":{"name":"keyword.control.flow.block-scalar.literal.yaml"},"2":{"name":"keyword.control.flow.block-scalar.folded.yaml"},"3":{"name":"constant.numeric.indentation-indicator.yaml"},"4":{"name":"storage.modifier.chomping-indicator.yaml"},"5":{"patterns":[{"include":"#comment"},{"match":".+","name":"invalid.illegal.expected-comment-or-newline.yaml"}]}},"end":"^(?=\\\\S)|(?!\\\\G)","patterns":[{"begin":"^( +)(?! )","end":"^(?!\\\\1|\\\\s*$)","name":"string.unquoted.block.yaml"}]},"block-sequence":{"match":"(-)(?!\\\\S)","name":"punctuation.definition.block.sequence.item.yaml"},"comment":{"begin":"(?:^([\\\\t ]*)|[\\\\t ]+)(?=#\\\\p{print}*$)","beginCaptures":{"1":{"name":"punctuation.whitespace.comment.leading.yaml"}},"end":"(?!\\\\G)","patterns":[{"begin":"#","beginCaptures":{"0":{"name":"punctuation.definition.comment.yaml"}},"end":"\\\\n","name":"comment.line.number-sign.yaml"}]},"directive":{"begin":"^%","beginCaptures":{"0":{"name":"punctuation.definition.directive.begin.yaml"}},"end":"(?=$|[\\\\t ]+($|#))","name":"meta.directive.yaml","patterns":[{"captures":{"1":{"name":"keyword.other.directive.yaml.yaml"},"2":{"name":"constant.numeric.yaml-version.yaml"}},"match":"\\\\G(YAML)[\\\\t ]+(\\\\d+\\\\.\\\\d+)"},{"captures":{"1":{"name":"keyword.other.directive.tag.yaml"},"2":{"name":"storage.type.tag-handle.yaml"},"3":{"name":"support.type.tag-prefix.yaml"}},"match":"\\\\G(TAG)(?:[\\\\t ]+(!(?:[-0-9A-Za-z]*!)?)(?:[\\\\t ]+(!(?:%\\\\h{2}|[]!#$\\\\&-;=?-\\\\[_a-z~])*|(?![]!,\\\\[{}])(?:%\\\\h{2}|[]!#$\\\\&-;=?-\\\\[_a-z~])+))?)?"},{"captures":{"1":{"name":"support.other.directive.reserved.yaml"},"2":{"name":"string.unquoted.directive-name.yaml"},"3":{"name":"string.unquoted.directive-parameter.yaml"}},"match":"\\\\G(\\\\w+)(?:[\\\\t ]+(\\\\w+)(?:[\\\\t ]+(\\\\w+))?)?"},{"match":"\\\\S+","name":"invalid.illegal.unrecognized.yaml"}]},"flow-alias":{"captures":{"1":{"name":"keyword.control.flow.alias.yaml"},"2":{"name":"punctuation.definition.alias.yaml"},"3":{"name":"variable.other.alias.yaml"},"4":{"name":"invalid.illegal.character.anchor.yaml"}},"match":"((\\\\*))([^],/\\\\[{}\\\\s]+)([^],}\\\\s]\\\\S*)?"},"flow-collection":{"patterns":[{"include":"#flow-sequence"},{"include":"#flow-mapping"}]},"flow-mapping":{"begin":"\\\\{","beginCaptures":{"0":{"name":"punctuation.definition.mapping.begin.yaml"}},"end":"}","endCaptures":{"0":{"name":"punctuation.definition.mapping.end.yaml"}},"name":"meta.flow-mapping.yaml","patterns":[{"include":"#prototype"},{"match":",","name":"punctuation.separator.mapping.yaml"},{"include":"#flow-pair"}]},"flow-node":{"patterns":[{"include":"#prototype"},{"include":"#flow-alias"},{"include":"#flow-collection"},{"include":"#flow-scalar"}]},"flow-pair":{"patterns":[{"begin":"\\\\?","beginCaptures":{"0":{"name":"punctuation.definition.key-value.begin.yaml"}},"end":"(?=[],}])","name":"meta.flow-pair.explicit.yaml","patterns":[{"include":"#prototype"},{"include":"#flow-pair"},{"include":"#flow-node"},{"begin":":(?=\\\\s|$|[],\\\\[{}])","beginCaptures":{"0":{"name":"punctuation.separator.key-value.mapping.yaml"}},"end":"(?=[],}])","patterns":[{"include":"#flow-value"}]}]},{"begin":"(?=(?:[^-\\\\]!\\"#%\\\\&'*,:>?@\\\\[\`{|}\\\\s]|[-:?][^],\\\\[{}\\\\s])([^],:\\\\[{}\\\\s]|:[^],\\\\[{}\\\\s]|\\\\s+(?![#\\\\s]))*\\\\s*:(\\\\s|$))","end":"(?=\\\\s*$|\\\\s+#|\\\\s*:(\\\\s|$)|\\\\s*:[],\\\\[{}]|\\\\s*[],\\\\[{}])","name":"meta.flow-pair.key.yaml","patterns":[{"include":"#flow-scalar-plain-in-implicit-type"},{"begin":"[^-\\\\]!\\"#%\\\\&'*,:>?@\\\\[\`{|}\\\\s]|[-:?][^],\\\\[{}\\\\s]","beginCaptures":{"0":{"name":"entity.name.tag.yaml"}},"contentName":"entity.name.tag.yaml","end":"(?=\\\\s*$|\\\\s+#|\\\\s*:(\\\\s|$)|\\\\s*:[],\\\\[{}]|\\\\s*[],\\\\[{}])","name":"string.unquoted.plain.in.yaml"}]},{"include":"#flow-node"},{"begin":":(?=\\\\s|$|[],\\\\[{}])","captures":{"0":{"name":"punctuation.separator.key-value.mapping.yaml"}},"end":"(?=[],}])","name":"meta.flow-pair.yaml","patterns":[{"include":"#flow-value"}]}]},"flow-scalar":{"patterns":[{"include":"#flow-scalar-double-quoted"},{"include":"#flow-scalar-single-quoted"},{"include":"#flow-scalar-plain-in"}]},"flow-scalar-double-quoted":{"begin":"\\"","beginCaptures":{"0":{"name":"punctuation.definition.string.begin.yaml"}},"end":"\\"","endCaptures":{"0":{"name":"punctuation.definition.string.end.yaml"}},"name":"string.quoted.double.yaml","patterns":[{"match":"\\\\\\\\([ \\"/0LN\\\\\\\\_abefnprtv]|x\\\\d\\\\d|u\\\\d{4}|U\\\\d{8})","name":"constant.character.escape.yaml"},{"match":"\\\\\\\\\\\\n","name":"constant.character.escape.double-quoted.newline.yaml"}]},"flow-scalar-plain-in":{"patterns":[{"include":"#flow-scalar-plain-in-implicit-type"},{"begin":"[^-\\\\]!\\"#%\\\\&'*,:>?@\\\\[\`{|}\\\\s]|[-:?][^],\\\\[{}\\\\s]","end":"(?=\\\\s*$|\\\\s+#|\\\\s*:(\\\\s|$)|\\\\s*:[],\\\\[{}]|\\\\s*[],\\\\[{}])","name":"string.unquoted.plain.in.yaml"}]},"flow-scalar-plain-in-implicit-type":{"patterns":[{"captures":{"1":{"name":"constant.language.null.yaml"},"2":{"name":"constant.language.boolean.yaml"},"3":{"name":"constant.numeric.integer.yaml"},"4":{"name":"constant.numeric.float.yaml"},"5":{"name":"constant.other.timestamp.yaml"},"6":{"name":"constant.language.value.yaml"},"7":{"name":"constant.language.merge.yaml"}},"match":"(?:(null|Null|NULL|~)|([Yy]|yes|Yes|YES|[Nn]|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF)|([-+]?0b[01_]+|[-+]?0[0-7_]+|[-+]?(?:0|[1-9][0-9_]*)|[-+]?0x[_\\\\h]+|[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)|([-+]?(?:[0-9][0-9_]*)?\\\\.[.0-9]*(?:[Ee][-+][0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\\\.[0-9_]*|[-+]?\\\\.(?:inf|Inf|INF)|\\\\.(?:nan|NaN|NAN))|(\\\\d{4}-\\\\d{2}-\\\\d{2}|\\\\d{4}-\\\\d{1,2}-\\\\d{1,2}(?:[Tt]|[\\\\t ]+)\\\\d{1,2}:\\\\d{2}:\\\\d{2}(?:\\\\.\\\\d*)?(?:[\\\\t ]*Z|[-+]\\\\d{1,2}(?::\\\\d{1,2})?)?)|(=)|(<<))(?=\\\\s*$|\\\\s+#|\\\\s*:(\\\\s|$)|\\\\s*:[],\\\\[{}]|\\\\s*[],\\\\[{}])"}]},"flow-scalar-plain-out":{"patterns":[{"include":"#flow-scalar-plain-out-implicit-type"},{"begin":"[^-\\\\]!\\"#%\\\\&'*,:>?@\\\\[\`{|}\\\\s]|[-:?]\\\\S","end":"(?=\\\\s*$|\\\\s+#|\\\\s*:(\\\\s|$))","name":"string.unquoted.plain.out.yaml"}]},"flow-scalar-plain-out-implicit-type":{"patterns":[{"captures":{"1":{"name":"constant.language.null.yaml"},"2":{"name":"constant.language.boolean.yaml"},"3":{"name":"constant.numeric.integer.yaml"},"4":{"name":"constant.numeric.float.yaml"},"5":{"name":"constant.other.timestamp.yaml"},"6":{"name":"constant.language.value.yaml"},"7":{"name":"constant.language.merge.yaml"}},"match":"(?:(null|Null|NULL|~)|([Yy]|yes|Yes|YES|[Nn]|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF)|([-+]?0b[01_]+|[-+]?0[0-7_]+|[-+]?(?:0|[1-9][0-9_]*)|[-+]?0x[_\\\\h]+|[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)|([-+]?(?:[0-9][0-9_]*)?\\\\.[.0-9]*(?:[Ee][-+][0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\\\.[0-9_]*|[-+]?\\\\.(?:inf|Inf|INF)|\\\\.(?:nan|NaN|NAN))|(\\\\d{4}-\\\\d{2}-\\\\d{2}|\\\\d{4}-\\\\d{1,2}-\\\\d{1,2}(?:[Tt]|[\\\\t ]+)\\\\d{1,2}:\\\\d{2}:\\\\d{2}(?:\\\\.\\\\d*)?(?:[\\\\t ]*Z|[-+]\\\\d{1,2}(?::\\\\d{1,2})?)?)|(=)|(<<))(?=\\\\s*$|\\\\s+#|\\\\s*:(\\\\s|$))"}]},"flow-scalar-single-quoted":{"begin":"'","beginCaptures":{"0":{"name":"punctuation.definition.string.begin.yaml"}},"end":"'(?!')","endCaptures":{"0":{"name":"punctuation.definition.string.end.yaml"}},"name":"string.quoted.single.yaml","patterns":[{"match":"''","name":"constant.character.escape.single-quoted.yaml"}]},"flow-sequence":{"begin":"\\\\[","beginCaptures":{"0":{"name":"punctuation.definition.sequence.begin.yaml"}},"end":"]","endCaptures":{"0":{"name":"punctuation.definition.sequence.end.yaml"}},"name":"meta.flow-sequence.yaml","patterns":[{"include":"#prototype"},{"match":",","name":"punctuation.separator.sequence.yaml"},{"include":"#flow-pair"},{"include":"#flow-node"}]},"flow-value":{"patterns":[{"begin":"\\\\G(?![],}])","end":"(?=[],}])","name":"meta.flow-pair.value.yaml","patterns":[{"include":"#flow-node"}]}]},"node":{"patterns":[{"include":"#block-node"}]},"property":{"begin":"(?=[!\\\\&])","end":"(?!\\\\G)","name":"meta.property.yaml","patterns":[{"captures":{"1":{"name":"keyword.control.property.anchor.yaml"},"2":{"name":"punctuation.definition.anchor.yaml"},"3":{"name":"entity.name.type.anchor.yaml"},"4":{"name":"invalid.illegal.character.anchor.yaml"}},"match":"\\\\G((&))([^],/\\\\[{}\\\\s]+)(\\\\S+)?"},{"match":"\\\\G!(?:<(?:%\\\\h{2}|[]!#$\\\\&-;=?-\\\\[_a-z~])+>|(?:[-0-9A-Za-z]*!)?(?:%\\\\h{2}|[#$\\\\&-+\\\\--;=?-Z_a-z~])+|)(?=[\\\\t ]|$)","name":"storage.type.tag-handle.yaml"},{"match":"\\\\S+","name":"invalid.illegal.tag-handle.yaml"}]},"prototype":{"patterns":[{"include":"#comment"},{"include":"#property"}]}},"scopeName":"source.yaml","aliases":["yml"]}`))];export{t as n,n as t};
@@ -1,7 +1,5 @@
1
1
  import { readFileSync } from "node:fs";
2
- import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
3
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4
- import { StreamableHTTPClientTransport, StreamableHTTPError, } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
2
+ import { Client, SdkHttpError, StreamableHTTPClientTransport, UnauthorizedError, } from "@modelcontextprotocol/client";
5
3
  import { publicEndpointUrl } from "../../../mcp/oauth/public-url.js";
6
4
  const REMOTE_AUTH_TIMEOUT_MS = 15_000;
7
5
  const packageVersion = readForgeRelayVersion();
@@ -45,11 +43,11 @@ export async function refreshRemoteAuthentication(remote, endpointInput = remote
45
43
  }
46
44
  export function isRemoteMcpUnauthorized(error) {
47
45
  return error instanceof UnauthorizedError ||
48
- (error instanceof StreamableHTTPError && error.code === 401);
46
+ (error instanceof SdkHttpError && error.status === 401);
49
47
  }
50
48
  export async function connectRemoteMcpClient(remote, endpointInput) {
51
49
  const endpoint = normalizeRemoteServiceTarget(endpointInput);
52
- const client = new Client({ name: "forgerelay-cli", version: packageVersion });
50
+ const client = new Client({ name: "forgerelay-cli", version: packageVersion }, { versionNegotiation: { mode: "legacy" } });
53
51
  const transport = new StreamableHTTPClientTransport(publicEndpointUrl(endpoint, "mcp"), {
54
52
  requestInit: {
55
53
  headers: { Authorization: `Bearer ${remote.accessToken}` },
@@ -108,6 +108,25 @@ export function toolResultText(result) {
108
108
  .map((entry) => entry.text)
109
109
  .join("\n") || "remote tool returned an error";
110
110
  }
111
+ export function enforceRelayedMediaBudget(result, maxBytes) {
112
+ let remainingBytes = maxBytes;
113
+ for (const entry of result.content ?? []) {
114
+ if (entry.type !== "image")
115
+ continue;
116
+ const bytes = Buffer.byteLength(entry.data, "base64");
117
+ if (bytes > remainingBytes) {
118
+ return {
119
+ content: [{
120
+ type: "text",
121
+ text: `Relayed media content exceeds the Gateway media limit of ${maxBytes} bytes.`,
122
+ }],
123
+ isError: true,
124
+ };
125
+ }
126
+ remainingBytes -= bytes;
127
+ }
128
+ return result;
129
+ }
111
130
  export function remapToolResultWorkspaceId(result, remoteWorkspaceId, gatewayWorkspaceId) {
112
131
  return {
113
132
  ...result,
@@ -3,9 +3,8 @@ import { execFileSync } from "node:child_process";
3
3
  import { mkdir, writeFile } from "node:fs/promises";
4
4
  import { createRequire, syncBuiltinESMExports } from "node:module";
5
5
  import { dirname, join } from "node:path";
6
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
- import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
8
- import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
6
+ import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
7
+ import { CompatibilityCallToolResultSchema } from "@modelcontextprotocol/core";
9
8
  import { ActivityAuditStore } from "../../../activity/history/audit-store.js";
10
9
  import { BashOutputStore } from "../../../activity/history/bash-output-store.js";
11
10
  import { HostTurnStore } from "../../../activity/history/host-turn-store.js";
@@ -67,6 +66,8 @@ export async function startForge(t, options) {
67
66
  stateDir,
68
67
  worktreeRoot: join(options.root, "worktrees"),
69
68
  ...(options.taskReminderInterval !== undefined ? { taskReminderInterval: options.taskReminderInterval } : {}),
69
+ ...(options.mediaMaxBytes !== undefined ? { mediaMaxBytes: options.mediaMaxBytes } : {}),
70
+ ...(options.mcpServers ? { mcpServers: options.mcpServers } : {}),
70
71
  ...(options.hooks ? { hooks: options.hooks } : {}),
71
72
  }, null, 2));
72
73
  const env = {
@@ -104,6 +105,7 @@ export async function startGatewayClient(t, options) {
104
105
  await writeFile(join(options.configDir, "config.json"), JSON.stringify({
105
106
  allowedRoots: [options.allowedRoot],
106
107
  stateDir,
108
+ ...(options.mediaMaxBytes !== undefined ? { mediaMaxBytes: options.mediaMaxBytes } : {}),
107
109
  ...(options.hooks ? { hooks: options.hooks } : {}),
108
110
  }, null, 2));
109
111
  const config = loadConfig({
@@ -221,5 +223,9 @@ export function resultText(result) {
221
223
  .join("\n");
222
224
  }
223
225
  function parseToolResult(result) {
224
- return CallToolResultSchema.parse(result);
226
+ const parsed = CompatibilityCallToolResultSchema.parse(result);
227
+ if (!Array.isArray(parsed.content)) {
228
+ throw new Error("Expected a content-based compatibility tool result.");
229
+ }
230
+ return parsed;
225
231
  }
@@ -1,21 +1,23 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
3
3
  import { join } from "node:path";
4
- import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
4
+ import { CompatibilityCallToolResultSchema } from "@modelcontextprotocol/core";
5
5
  import { isRemoteMcpUnauthorized, refreshRemoteAuthentication, } from "./auth/remote-auth.js";
6
6
  import { RemoteMcpConnectionPool } from "./transport/remote-mcp-connection-pool.js";
7
7
  import { withFileLock } from "../../runtime/state/lock/file-lock.js";
8
8
  import { withRemoteServiceEndpoint } from "./transport/remote-transport.js";
9
9
  import { loadForgeRelayFiles, writeForgeRelayRemote, } from "../../runtime/config/user-config.js";
10
- import { assertRemoteToolSucceeded, copyBooleanField, copyNumberField, copyStringField, errorMessage, remapToolResultWorkspaceId, replaceExactWorkspaceId, safeManagedWorktreeRecovery, safeTaskSummary, sanitizedRemoteError, stringField, toolResultText, } from "./result-support.js";
10
+ import { assertRemoteToolSucceeded, copyBooleanField, copyNumberField, copyStringField, enforceRelayedMediaBudget, errorMessage, remapToolResultWorkspaceId, replaceExactWorkspaceId, safeManagedWorktreeRecovery, safeTaskSummary, sanitizedRemoteError, stringField, toolResultText, } from "./result-support.js";
11
11
  export class RemoteWorkspaceRelay {
12
+ mediaMaxBytes;
12
13
  routes = new Map();
13
14
  turnRoutes = new Map();
14
15
  authEnv;
15
16
  routeStateDir;
16
17
  routeStatePath;
17
18
  mcpConnections = new RemoteMcpConnectionPool();
18
- constructor(configDir, stateDir) {
19
+ constructor(configDir, stateDir, mediaMaxBytes) {
20
+ this.mediaMaxBytes = mediaMaxBytes;
19
21
  this.authEnv = { FORGERELAY_CONFIG_DIR: configDir };
20
22
  this.routeStateDir = stateDir;
21
23
  this.routeStatePath = join(stateDir, "remote-workspace-routes.json");
@@ -184,7 +186,7 @@ export class RemoteWorkspaceRelay {
184
186
  return result;
185
187
  }
186
188
  async read(gatewayWorkspaceId, input, conversationScopeId) {
187
- return this.callWorkspaceTool(gatewayWorkspaceId, "read", input, conversationScopeId);
189
+ return enforceRelayedMediaBudget(await this.callWorkspaceTool(gatewayWorkspaceId, "read", input, conversationScopeId), this.mediaMaxBytes);
188
190
  }
189
191
  async write(gatewayWorkspaceId, input, conversationScopeId) {
190
192
  return this.callWorkspaceTool(gatewayWorkspaceId, "write", input, conversationScopeId);
@@ -214,7 +216,7 @@ export class RemoteWorkspaceRelay {
214
216
  return this.callWorkspaceTool(gatewayWorkspaceId, "apply_patch", input, conversationScopeId);
215
217
  }
216
218
  async capability(gatewayWorkspaceId, input, conversationScopeId) {
217
- return this.callWorkspaceTool(gatewayWorkspaceId, "capability", input, conversationScopeId);
219
+ return enforceRelayedMediaBudget(await this.callWorkspaceTool(gatewayWorkspaceId, "capability", input, conversationScopeId), this.mediaMaxBytes);
218
220
  }
219
221
  async activityPanel(gatewayWorkspaceId, conversationScopeId) {
220
222
  const result = await this.callWorkspaceTool(gatewayWorkspaceId, "activity_panel", {}, conversationScopeId);
@@ -463,13 +465,19 @@ export class RemoteWorkspaceRelay {
463
465
  throw new Error(`Remote ForgeRelay ${alias} request failed: ${errorMessage(error)}`, { cause: error });
464
466
  }
465
467
  }
466
- const invoke = async (active) => CallToolResultSchema.parse(await active.client.callTool({
467
- name,
468
- arguments: args,
469
- ...(conversationScopeId
470
- ? { _meta: { "openai/session": conversationScopeId } }
471
- : {}),
472
- }));
468
+ const invoke = async (active) => {
469
+ const parsed = CompatibilityCallToolResultSchema.parse(await active.client.callTool({
470
+ name,
471
+ arguments: args,
472
+ ...(conversationScopeId
473
+ ? { _meta: { "openai/session": conversationScopeId } }
474
+ : {}),
475
+ }));
476
+ if (!Array.isArray(parsed.content)) {
477
+ throw new Error("Remote ForgeRelay returned a modern-only tool result on the legacy Relay protocol path.");
478
+ }
479
+ return parsed;
480
+ };
473
481
  try {
474
482
  return await invoke(connection);
475
483
  }
@@ -10,8 +10,18 @@ New installations use:
10
10
  ```text
11
11
  ~/.forgerelay/config.json
12
12
  ~/.forgerelay/auth.json
13
+ ~/.forgerelay/mcp.json # optional External MCP registry
14
+ ~/.forgerelay/mcp-auth.json # created only when External MCP OAuth state exists
13
15
  ```
14
16
 
17
+ A Workspace may also define project-scoped External MCP servers in:
18
+
19
+ ```text
20
+ <workspace>/.forgerelay/mcp.json
21
+ ```
22
+
23
+ `mcp-auth.json` is ForgeRelay-private credential state and must not be copied into a project.
24
+
15
25
  Override the directory with:
16
26
 
17
27
  ```bash
@@ -26,6 +36,10 @@ npx @akira-tl/forgerelay serve
26
36
  npx @akira-tl/forgerelay doctor
27
37
  npx @akira-tl/forgerelay config get
28
38
  npx @akira-tl/forgerelay config set publicBaseUrl https://forge.example.com/forgerelay/main,https://forge-alt.example.com/relay
39
+ npx @akira-tl/forgerelay mcp list
40
+ npx @akira-tl/forgerelay mcp test <server>
41
+ npx @akira-tl/forgerelay mcp auth <server>
42
+ npx @akira-tl/forgerelay mcp logout <server>
29
43
  npx @akira-tl/forgerelay maintenance inspect
30
44
  ```
31
45
 
@@ -146,6 +160,181 @@ A single persisted string remains fully supported, so existing configs require n
146
160
  migration. For environment configuration, use a comma-separated list in
147
161
  `FORGERELAY_PUBLIC_BASE_URL`.
148
162
 
163
+ ## Media content and external MCP
164
+
165
+ The existing `read` tool recognizes PNG, JPEG, WebP, and GIF by file signature and returns the original bytes as standard MCP `ImageContent`. Image reads do not use filename extensions for recognition, and text-only `offset` / `limit` arguments are rejected for image targets.
166
+
167
+ Inline media uses a separate decoded-byte budget:
168
+
169
+ | Variable | Default | Purpose |
170
+ | --- | --- | --- |
171
+ | `FORGERELAY_MEDIA_MAX_BYTES` | `20971520` | Maximum aggregate decoded image bytes in one Host-facing tool result (20 MiB). |
172
+
173
+ The same value may be persisted as `mediaMaxBytes` in `config.json`. The budget applies at each ForgeRelay media ingress boundary, including local Read, Workspace Relay Gateway delivery, and configured external MCP results. ForgeRelay fails an oversize result instead of truncating, resizing, recompressing, transcoding, or silently creating an Artifact.
174
+
175
+ Media content is transient. Image base64 is present only in the live MCP result delivered to the Host; Activity/Audit, structured output, UI state, Workspace state, and logs keep bounded metadata such as MIME type and decoded byte size. Reading or forwarding an image does **not** create an Artifact. Artifact download/materialization remains a separate explicit workflow.
176
+
177
+ User-configured MCP servers are exposed through the workspace-scoped `mcp.external` Capability rather than becoming new top-level MCP tools. The capability remains discoverable even when the effective server registry is empty, so servers added later by hot reload become usable without reconnecting the Host.
178
+
179
+ ### External MCP configuration files
180
+
181
+ New configuration uses a dedicated `mcp.json` file. The machine-wide registry is:
182
+
183
+ ```text
184
+ ~/.forgerelay/mcp.json
185
+ ```
186
+
187
+ or the equivalent path below `FORGERELAY_CONFIG_DIR`. A Workspace may add or override servers in:
188
+
189
+ ```text
190
+ <workspace>/.forgerelay/mcp.json
191
+ ```
192
+
193
+ Both files use this shape:
194
+
195
+ ```json
196
+ {
197
+ "servers": {
198
+ "renderer": {
199
+ "transport": "stdio",
200
+ "command": "node",
201
+ "args": ["/opt/renderer/server.mjs"]
202
+ },
203
+ "remote-renderer": {
204
+ "transport": "streamable-http",
205
+ "url": "https://renderer.example.com/mcp"
206
+ }
207
+ }
208
+ }
209
+ ```
210
+
211
+ Server names are stable lowercase identifiers matching `^[a-z][a-z0-9._-]{0,63}$`; one source may contain at most 32 servers.
212
+
213
+ Effective configuration is merged by server name in this order:
214
+
215
+ ```text
216
+ Project mcp.json > global mcp.json > legacy config.json.mcpServers
217
+ ```
218
+
219
+ A Project entry may explicitly hide an inherited server:
220
+
221
+ ```json
222
+ {
223
+ "servers": {
224
+ "remote-renderer": {
225
+ "disabled": true
226
+ }
227
+ }
228
+ }
229
+ ```
230
+
231
+ `disabled` defaults to `false` and normally does not need to be written. Removing the Project entry exposes the lower-precedence global/legacy server again. A complete server entry may also contain `"disabled": true`; ForgeRelay validates the entry but does not activate it.
232
+
233
+ The historical `config.json -> mcpServers` object remains a deprecated read-compatible source. New configuration and documentation use `mcp.json`; the legacy source is retained for at least the next two feature-release compatibility windows and should not be used for new setups.
234
+
235
+ ForgeRelay reloads global and Project `mcp.json` on demand before External MCP operations. A valid changed file becomes visible to the next `servers`, `tools`, or `call` operation without restarting ForgeRelay. Each operation uses one immutable snapshot, so an in-flight call is not switched underneath itself. If a previously valid source becomes invalid, that running ForgeRelay process keeps the whole previous valid source as last-known-good and reports a bounded diagnostic; no partially parsed subset is activated. A fresh CLI process cannot reconstruct another process's in-memory last-known-good snapshot, so it reports the invalid source while noting that an existing runtime may still be using its prior valid snapshot.
236
+
237
+ ### External MCP transports and authentication
238
+
239
+ A stdio server supports `command`, optional `args`, `env`, and `cwd`. It is launched directly, not through a shell. Its process still runs with the operating-system authority of the user running ForgeRelay. A Project `.forgerelay/mcp.json` can therefore introduce executable project configuration; opening an allowed project is the trust boundary, and ForgeRelay does not add a second per-server approval prompt.
240
+
241
+ A Streamable HTTP server supports `url` and optional static `headers`:
242
+
243
+ ```json
244
+ {
245
+ "servers": {
246
+ "private-api": {
247
+ "transport": "streamable-http",
248
+ "url": "https://mcp.example.com/mcp",
249
+ "headers": {
250
+ "Authorization": "Bearer <user-managed-secret>"
251
+ }
252
+ }
253
+ }
254
+ }
255
+ ```
256
+
257
+ ForgeRelay does not require every HTTP MCP server to use OAuth. The effective models are:
258
+
259
+ - no authentication: connect directly;
260
+ - static credentials: user-managed `headers` for HTTP or `env` for stdio;
261
+ - interactive OAuth: explicitly initiated by a human through the CLI.
262
+
263
+ Do not commit static secrets in Project `mcp.json`. ForgeRelay does not move project-defined header/env secrets into a separate secret manager in v1.1.1.
264
+
265
+ Interactive OAuth state is stored only in the machine-private:
266
+
267
+ ```text
268
+ ~/.forgerelay/mcp-auth.json
269
+ ```
270
+
271
+ (or the configured ForgeRelay config directory). Project OAuth credentials are still stored there, keyed to the canonical Project root and server; they are not written into the project. Global/legacy servers use a global credential identity. Project credentials are not reused across Projects merely because the server name or URL matches.
272
+
273
+ Authenticate explicitly:
274
+
275
+ ```bash
276
+ forgerelay mcp auth <server>
277
+ forgerelay mcp auth <server> --project /path/to/project
278
+ forgerelay mcp auth <server> --global
279
+ ```
280
+
281
+ The CLI performs OAuth discovery, PKCE and the browser/callback exchange, then atomically commits the credential. The running ForgeRelay server is not an OAuth callback broker; it simply reads the updated credential store on later External MCP calls. On a desktop the CLI opens the authorization URL and listens on a short-lived loopback callback. On SSH/headless systems it prints the authorization URL and asks for the final callback URL to be pasted; pasted input is displayed only as `*` mask characters.
282
+
283
+ Normal token refresh is non-interactive and may be performed by the runtime. If refresh fails, the resource/issuer binding changes, or the server requires additional scope, the runtime does not open a browser: it returns `mcp.auth_required` or `mcp.reauthorization_required`, and the human reruns `forgerelay mcp auth <server>`. Multiple ForgeRelay processes coordinate refresh through the shared credential store so refresh-token rotation does not turn a successful refresh in one process into a false reauthorization failure in another.
284
+
285
+ ForgeRelay uses Dynamic Client Registration when the authorization server supports the compatible flow. For CIMD-only authorization servers, configure a stable public HTTPS client metadata document and the exact loopback callback port it advertises:
286
+
287
+ ```json
288
+ {
289
+ "servers": {
290
+ "modern": {
291
+ "transport": "streamable-http",
292
+ "url": "https://mcp.example.com/mcp",
293
+ "oauth": {
294
+ "clientMetadataUrl": "https://client.example.com/forgerelay.json",
295
+ "callbackPort": 49152
296
+ }
297
+ }
298
+ }
299
+ }
300
+ ```
301
+
302
+ `clientMetadataUrl` must use HTTPS with a non-root path; `callbackPort` must be `1024..65535`. ForgeRelay does not operate a hosted CIMD service, so this advanced configuration is supplied by the operator when a server requires it.
303
+
304
+ Remove local OAuth state with:
305
+
306
+ ```bash
307
+ forgerelay mcp logout <server>
308
+ ```
309
+
310
+ ForgeRelay removes the local credential even when remote token revocation is unsupported or fails, and reports that distinction.
311
+
312
+ ### External MCP diagnostics
313
+
314
+ Use the CLI before editing runtime state by guesswork:
315
+
316
+ ```bash
317
+ forgerelay mcp list
318
+ forgerelay mcp test <server>
319
+ forgerelay doctor
320
+ ```
321
+
322
+ `mcp list` is passive: it reports the resolved Project/global/legacy sources, disabled entries, authentication state, credential-store health, and invalid/last-known-good configuration diagnostics. With no explicit scope it uses the active Workspace root when supplied by ForgeRelay, otherwise walks upward from the current directory for the nearest `.forgerelay/mcp.json`; `--project` selects a Project explicitly and `--global` excludes Project configuration.
323
+
324
+ `mcp test` is the active probe. It connects to the selected server and performs tool discovery, reporting source, transport, auth state, negotiated MCP era/version, tool count, and actionable failures such as authentication required, unreachable transport, or tool discovery failure.
325
+
326
+ `doctor` remains passive. Its External MCP section reads configuration and credential metadata only; it does not launch configured stdio servers or make HTTP MCP requests. Use `mcp test` when an active connectivity check is intended.
327
+
328
+ ### External MCP protocol compatibility and Relay
329
+
330
+ ForgeRelay's External MCP client negotiates modern MCP 2026-07-28 when available and falls back to supported legacy MCP servers. Host-facing HTTP similarly supports the modern stateless protocol while retaining the legacy sessionful path for compatible Hosts.
331
+
332
+ For a relayed Workspace, External MCP configuration, credentials, process launches, OAuth refreshes, and upstream MCP calls belong to the **Execution ForgeRelay**. The Gateway does not copy or forward External MCP credentials. Run `forgerelay mcp auth` on the machine/configuration that owns execution when a relayed Workspace needs OAuth.
333
+
334
+ The Agent explicitly selects the configured server, advertised tool, and arguments through `capability(name="mcp.external", action="run", ...)`. Direct upstream PNG/JPEG/WebP/GIF `ImageContent` is validated and forwarded without decode/re-encode. Text, paths, URLs, `resource_link` values, and other references remain exactly that by default: ForgeRelay does not automatically fetch them, call `read`, infer that they refer to an image, or create an Artifact.
335
+
336
+ A known server/tool may opt into explicit request/result adaptation with `ExternalMcpBeforeForward` or `ExternalMcpAfterForward` project/global Hooks. These transforms use the structured versioned stdin/stdout protocol described under [Lifecycle hooks](#lifecycle-hooks); ordinary Hook stdout never rewrites MCP data. For example, a project can match one renderer tool whose normal result is a path and deliberately convert that result into `ImageContent`. The Hook command performs any file/network access with its normal local-user authority, and the transformed result still passes through the same MCP shape, MIME/base64, and `mediaMaxBytes` validation before Host delivery.
337
+
149
338
  ## Native artifact download
150
339
 
151
340
  Native-file download is disabled by default. Enable it with:
@@ -553,7 +742,7 @@ Hooks v1 是自动生命周期规则。规则由用户或 Agent 主动写入;
553
742
 
554
743
  | 字段 | 含义 |
555
744
  | --- | --- |
556
- | `event` | 必填,九个 Hook event 之一。 |
745
+ | `event` | 必填,当前 Hook event 之一。 |
557
746
  | `matcher` | 可选,只在匹配当前生命周期上下文时执行。 |
558
747
  | `command` | 必填,本地 shell 命令。 |
559
748
  | `timeoutSeconds` | 默认 `30`,范围 `1` 到 `300`。 |
@@ -587,6 +776,11 @@ forgerelay hooks check --project /path/to/project
587
776
  | `pathRegex` | 对 payload 中的 `path` 或 `paths` 做正则匹配。 |
588
777
  | `provider` | 精确匹配 subagent provider。 |
589
778
  | `workspaceMode` | `checkout` 或 `worktree`。 |
779
+ | `capability` | 精确匹配 Capability 名;external MCP transform 使用 `mcp.external`。 |
780
+ | `externalServer` | 精确匹配已经配置的 external MCP server 名。 |
781
+ | `externalTool` | 精确匹配该 server 当前调用的 upstream tool 名。 |
782
+
783
+ `capability` / `externalServer` / `externalTool` 只匹配当前已选择的 Capability target,不允许 Hook 输出改写 server/tool 或提供新的连接地址、命令、凭据。
590
784
 
591
785
  Matcher 匹配 ForgeRelay 收到的那次 tool request,不会窥探该命令内部后续启动的子进程。例如 `bash` 参数本身是 `git push origin v0.2.0` 时可以命中;若参数只是 `./release.sh`,而脚本内部再执行 `git push`,ForgeRelay 不会把内部子进程重新解释成新的 Hook 事件。
592
786
 
@@ -600,13 +794,17 @@ Matcher 匹配 ForgeRelay 收到的那次 tool request,不会窥探该命令
600
794
  | `BeforeTool` | workspace-scoped MCP tool 执行前触发;失败或超时会阻断原操作。`open_workspace` 因执行前还没有 workspace,不走该事件。 |
601
795
  | `AfterTool` | tool 成功后触发。 |
602
796
  | `AfterToolFailure` | tool 失败或被 `BeforeTool` 拒绝后触发。 |
797
+ | `ExternalMcpBeforeForward` | 已选择 `mcp.external` server/tool 后、upstream 调用前触发;使用 structured transform 协议,可替换当前 request arguments,但不能改写 server/tool。失败会阻止 upstream 调用。 |
798
+ | `ExternalMcpAfterForward` | upstream external MCP 调用成功后触发;使用 structured transform 协议,可替换当前 MCP result。替换结果重新经过标准 MCP/Media 校验。失败会令 Capability 失败,但不会声称回滚 upstream 已发生的副作用。 |
603
799
  | `AfterFileChange` | `write`、`edit`、`rename`、`delete`、`apply_patch`、native artifact 等明确文件变更成功后触发;不会推断 shell 的文件副作用。 |
604
800
  | `BeforeWorktreeClose` | worktree commit、fast-forward、cleanup 前触发;失败会保留 worktree 并阻断 close。 |
605
801
  | `AfterWorktreeClose` | managed worktree 成功关闭后触发;此时从 source checkout 运行。 |
606
802
  | `SubagentStart` | 本地 subagent worker 进入执行时触发。 |
607
803
  | `SubagentStop` | subagent 完成或进入 error 状态时触发。 |
608
804
 
609
- `BeforeTool` 与 `BeforeWorktreeClose` blocking 事件。其他事件是 observational:失败会被记录并报告,但不会回滚已经完成的文件、Git、进程或网络副作用。Blocking 同样不是事务;Hook 命令自己已经产生的副作用不会因 exit code 非零而撤销。Host blocking Hook 仍运行时取消 MCP request,会终止该 Hook 并阻止原始 tool operation 开始,因此不会出现 Host 已放弃请求后 Hook 又放行后续原始副作用的情况。
805
+ `BeforeTool` 与 `BeforeWorktreeClose` 是普通 lifecycle blocking 事件。`ExternalMcpBeforeForward` 也是 external-MCP forwarding 的前置阻断点;`ExternalMcpAfterForward` 则发生在 upstream 已成功返回之后,因此它只能阻止变换后结果继续交付,不能回滚或伪装撤销 upstream 副作用。其他 after-events 是 observational:失败会被记录并报告,但不会回滚已经完成的文件、Git、进程或网络副作用。Blocking 同样不是事务;Hook 命令自己已经产生的副作用不会因 exit code 非零而撤销。Host 在前置 blocking Hook 仍运行时取消 MCP request,会终止该 Hook 并阻止对应原始 operation 开始。
806
+
807
+ External MCP transform 的可变数据不通过普通 Hook stdout 约定。ForgeRelay 把一个 `version: 1` JSON envelope 写到 transform Hook 的 stdin,并只接受 stdout 中一个合法 JSON envelope:before-forward 返回 `arguments`,after-forward 返回 `result`。这一协议只对两个 External MCP transform event 生效;现有 lifecycle Hook stdout 仍只是命令输出。
610
808
 
611
809
  ### Agent 可见报告
612
810
 
package/docs/debugging.md CHANGED
@@ -54,7 +54,7 @@ The selected debug config is authoritative for its own `host` and `port`; the
54
54
  launcher reports endpoints derived from that file instead of assuming `7677`.
55
55
  This makes parallel isolated instances straightforward: give each instance its
56
56
  own config, state, and worktree directories and configure different ports (for
57
- example `7677` for a Gateway and `6768` for an Execution ForgeRelay), then launch
57
+ example `7677` for a Gateway and `7678` for an Execution ForgeRelay), then launch
58
58
  each with its own `FORGERELAY_DEBUG_CONFIG_DIR`.
59
59
 
60
60
  ## Run a local MCP Apps Host
@@ -107,12 +107,15 @@ behavior.
107
107
 
108
108
  ```bash
109
109
  npm run debug:accept
110
+ npm run debug:accept:modern
111
+ npm run debug:accept:relay
110
112
  ```
111
113
 
112
- The acceptance runner starts its own fully isolated ForgeRelay fixture on
113
- `127.0.0.1:7677`, with its own generated Owner password and checked-in debug
114
- configuration. It sends real HTTP requests with `curl` and shuts the server down
115
- when it finishes.
114
+ `debug:accept` starts its own fully isolated legacy/sessionful ForgeRelay fixture on
115
+ `127.0.0.1:7677`. `debug:accept:modern` exercises the MCP 2026-07-28 stateless Host
116
+ path on isolated `7678`, and `debug:accept:relay` exercises an isolated
117
+ 7677 Gateway → 7678 Execution topology. Each runner owns its temporary config/state,
118
+ generates its own authentication material, and shuts down what it started.
116
119
  It refuses to start when port `7677` is already occupied so it cannot
117
120
  accidentally validate an older debug process.
118
121
 
package/docs/roadmap.md CHANGED
@@ -348,9 +348,70 @@ recovery, checkpoint, and retention facts; the Gateway only routes and presents
348
348
  results. Development acceptance remains on isolated 7677/7678 instances and never uses
349
349
  the normal 7676 installation.
350
350
 
351
- Native Windows Command Shell Runtime support was intentionally kept out of the
352
- 0.9 recovery/history line and was subsequently delivered across the 0.10.x P3
353
- work for PowerShell 7, Windows PowerShell 5.1, and `cmd.exe`.
351
+ ## 0.10 Command Shell Runtime P3
352
+
353
+ 0.10 ForgeRelay 的命令执行从“默认假设 Bash”提升为显式、可观察、跨平台的
354
+ Command Shell Runtime,同时保持公开 Core tool 名称 `bash` 不变,避免因为 Windows
355
+ 支持而扩大 Host 的常驻 MCP surface。
356
+
357
+ - **0.10.0** — 建立 server-lifetime Command Shell Runtime identity 与选择优先级;
358
+ `forgerelay init` 管理可编辑 shell Instructions;系统级高权限启动默认拒绝,只有
359
+ 显式 `--allow-elevated` 才允许继续;
360
+ - **0.10.1** — 将 PowerShell 7(`pwsh`)作为 Windows 上的一等 Agent/Hook/PTY
361
+ runtime,并补齐真实 packaged npm acceptance;
362
+ - **0.10.2** — 加入 Windows PowerShell 5.1,完成 ConPTY、Unicode、进程树清理和
363
+ packaged launcher 路径;
364
+ - **0.10.3** — 将 `cmd.exe` 提升为一等 runtime,覆盖其独立 quoting、escaping、
365
+ `%ERRORLEVEL%`、pipe/PTY 与 `.cmd` launcher contract;
366
+ - **0.10.4** — 让 shell identity 跟随实际 Execution ForgeRelay,并贯通 Workspace
367
+ Relay、Composite Workspace、doctor、UI、Instructions 与公开 tool description;
368
+ - **0.10.5** — 移除 ForgeRelay 对 legacy `disable-model-invocation` Skill frontmatter
369
+ 的过滤,保持 Skill discovery 由 Agent 侧匹配决定。
370
+
371
+ P3 的发布验收以真实 Windows shell matrix 与 packed artifact 为核心,同时继续保留
372
+ Linux/macOS 回归验证。开发与运行时验收只使用隔离的 7677/7678 实例,不触碰 7676。
373
+
374
+ ## 1.0 — Stable product baseline
375
+
376
+ 1.0 将已经完成的 Workspace、Capability Gateway、Hooks、LSP、Activity/Audit、Tasks、
377
+ Recovery/Checkpoint、Relay/Composite 与跨平台 shell contract 提升为稳定的 1.x 产品
378
+ 基线,而不是重新设计一套运行时模型。
379
+
380
+ - **1.0.0** — 固化当前 MCP Core tool surface、CLI、Workspace 生命周期、managed
381
+ worktree finalize 与 Capability Gateway,现有 0.10.5 配置和持久化状态无需迁移;
382
+ - **1.0.1** — 修复 Capability 与 Workspace lifecycle pretty logging,使日志保留
383
+ semantic operation identity,并补齐 Composite capability attribution。
384
+
385
+ 1.x 继续遵守现有发布规则:稳定版本只有在本地 release gate 与真实 7677/7678 路径
386
+ 验收通过后才创建 tag;tag-triggered Linux/macOS/Windows workflow 成功后才形成正式
387
+ npm/GitHub Release。
388
+
389
+ ## 1.1 — First-class image transport
390
+
391
+ 1.1 把 image-bearing MCP result 纳入 ForgeRelay 的一等、受预算约束的传输路径,
392
+ 同时保持“ForgeRelay 不是图片仓库,也不自动解引用外部引用”的边界。
393
+
394
+ - `read` 依据文件签名把 PNG、JPEG、WebP、GIF 返回为标准 MCP `ImageContent`,bulk
395
+ Read 保留 text/image 输入顺序;
396
+ - Workspace Relay 在 Execution ForgeRelay → Gateway ForgeRelay 路径中原样保留
397
+ image content,并在 Gateway 侧再次执行 media budget;
398
+ - 用户显式配置的外部 MCP server 通过 `mcp.external` Capability 调用,原生
399
+ `ImageContent` 可直接转发,但 path/URL/resource reference 默认仍只是 reference;
400
+ - `ExternalMcpBeforeForward` / `ExternalMcpAfterForward` Transform Hook 为特定
401
+ server/tool 提供显式结构化适配 seam,但不能改写已选择的 server/tool;
402
+ - Media content 保持瞬时 MCP payload,不自动成为 Artifact;Activity/Audit、日志、
403
+ structured output 与 Workspace state 只持久化受限 metadata,不保存 image base64;
404
+ - 每个 tool result 默认采用 20 MiB decoded-media budget,超限显式失败,不静默截断、
405
+ resize 或转码。
406
+
407
+ 1.1.0 已完成真实 7677 HTTP/OAuth/MCP、7677 → 7678 Relay、external MCP、Transform
408
+ Hook、packed artifact 和 ChatGPT Host 图像呈现验收,并作为正式稳定版本发布。
409
+
410
+ ## 1.x forward planning
411
+
412
+ 当前 roadmap 没有承诺 1.1 之后的下一条 minor feature line。新的 1.x 产品工作应从
413
+ 明确的用户目标、架构问题或外部 issue 出发,先形成可验证的 spec/tickets,再进入实现;
414
+ 不要仅根据历史版本号顺序推断或虚构 1.2 功能范围。
354
415
 
355
416
  ## Workspace Task Lists
356
417
 
package/docs/security.md CHANGED
@@ -147,6 +147,24 @@ Workspace from another conversation intentionally shares the same completion sco
147
147
  as well. Hook handlers keep their separate bounded timeout
148
148
  policy because they are lifecycle gates rather than user-command execution.
149
149
 
150
+ ## External MCP configuration and credentials
151
+
152
+ External MCP extends the same local-authority model rather than creating a sandbox. Machine-wide servers live in the ForgeRelay config directory's `mcp.json`; Project servers may live in `<workspace>/.forgerelay/mcp.json`. A Project stdio entry can launch a configured executable with the same operating-system user authority as ForgeRelay, so Project MCP configuration is executable project configuration. ForgeRelay does not add a second per-server approval prompt after the operator has allowed and opened that project root.
153
+
154
+ Static HTTP headers and stdio environment values are user-managed configuration. Do not commit secrets in Project `mcp.json`; v1.1.1 does not automatically extract project-defined secrets into a secret manager.
155
+
156
+ Interactive External MCP OAuth credentials are different. They are kept in the machine-private ForgeRelay config directory:
157
+
158
+ ```text
159
+ ~/.forgerelay/mcp-auth.json
160
+ ```
161
+
162
+ Project OAuth credentials are keyed to the Project root but still stay in this private machine file; ForgeRelay does not write access tokens, refresh tokens, authorization codes, PKCE verifiers, or client secrets into the project. Callback URLs pasted in headless mode are masked in the terminal. Runtime token refresh is non-interactive; any fresh consent/authorization requires an explicit human `forgerelay mcp auth <server>` invocation.
163
+
164
+ External MCP configuration and OAuth state belong to the Execution ForgeRelay for relayed Workspaces. The Gateway does not receive or replicate those credentials merely because it forwards the Workspace operation.
165
+
166
+ External MCP result references are not implicitly dereferenced. Paths, URLs and MCP resource references returned by an upstream server remain references unless the Agent explicitly reads them or an explicitly configured External MCP transform Hook changes the result.
167
+
150
168
  ## Lifecycle hooks
151
169
 
152
170
  Hook command 是本地代码执行,使用与 ForgeRelay 相同的操作系统用户权限并继承进程环境。