@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
@@ -40,6 +40,12 @@ const CAPABILITY_GUIDE_DEFINITIONS = [
40
40
  description: "Read-only semantic code navigation backed by external Language servers.",
41
41
  whenToRead: "Read before using code.intelligence or configuring Language servers.",
42
42
  },
43
+ {
44
+ name: "external-mcp",
45
+ directory: "host-integration/external-mcp",
46
+ description: "User-configured external MCP tool discovery and explicit forwarding through capability.",
47
+ whenToRead: "Read before discovering or calling a configured external MCP server.",
48
+ },
43
49
  {
44
50
  name: "workspace-tasks",
45
51
  directory: "workspace/workspace-tasks",
@@ -110,6 +116,7 @@ export function buildCapabilityFingerprint(config, version, context = {}) {
110
116
  if (config.subagents) {
111
117
  capabilities.push("subagent.session");
112
118
  }
119
+ capabilities.push("mcp.external");
113
120
  if (config.artifactsEnabled && context.artifactDownloadSupported) {
114
121
  capabilities.push("artifact.native-download");
115
122
  }
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
  import { MAX_CODE_INTELLIGENCE_RESULT_LIMIT, } from "../../../lsp/code-intelligence-types.js";
3
3
  import { batchExecuteInputSchema, } from "../../operations/batch/types.js";
4
4
  import { workspaceCheckpointInputSchema, } from "./capabilities/workspace-checkpoint.js";
5
+ import { externalMcpCapabilityDefinitions, } from "./capabilities/external-mcp.js";
5
6
  export class CapabilityError extends Error {
6
7
  code;
7
8
  constructor(code, message) {
@@ -332,6 +333,7 @@ export function createCapabilityRegistry(dependencies) {
332
333
  run: async (input, context, options) => dependencies.workspaceTasks.run(input, context, options),
333
334
  }]
334
335
  : []),
336
+ ...externalMcpCapabilityDefinitions(dependencies.externalMcp),
335
337
  ...(dependencies.subagentSession
336
338
  ? [{
337
339
  name: "subagent.session",
@@ -52,6 +52,35 @@ export function contentText(content) {
52
52
  .map((item) => item.text)
53
53
  .join("\n");
54
54
  }
55
+ export function imageContentMetadata(content) {
56
+ const image = content.find((item) => item.type === "image");
57
+ return image
58
+ ? {
59
+ type: "image",
60
+ mimeType: image.mimeType,
61
+ bytes: Buffer.byteLength(image.data, "base64"),
62
+ }
63
+ : undefined;
64
+ }
65
+ export function metadataSafeContent(content) {
66
+ return content.filter((item) => item.type === "text");
67
+ }
68
+ export function auditToolResultWithoutImageData(result) {
69
+ if (typeof result !== "object" || result === null)
70
+ return result;
71
+ const record = result;
72
+ const content = Array.isArray(record.content) ? record.content : undefined;
73
+ if (!content)
74
+ return result;
75
+ const media = imageContentMetadata(content);
76
+ if (!media)
77
+ return result;
78
+ return {
79
+ ...record,
80
+ content: metadataSafeContent(content),
81
+ media,
82
+ };
83
+ }
55
84
  export function toolErrorPreview(content) {
56
85
  const text = contentText(content).replace(/\s+/g, " ").trim();
57
86
  if (!text)
@@ -3,15 +3,17 @@ import { tmpdir } from "node:os";
3
3
  import { isAbsolute } from "node:path";
4
4
  import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
5
5
  import * as z from "zod/v4";
6
+ import { mcpHandlerRequestContext } from "../../../request-context.js";
6
7
  import { applyPatch } from "../../../filesystem/apply-patch.js";
7
8
  import { readFileTool } from "../../../filesystem/filesystem-tools.js";
9
+ import { createMediaBudget } from "../../../operations/media-content.js";
8
10
  import { toolNames } from "../../../server-instructions.js";
9
11
  import { executeBulkRead } from "../../../operations/bulk-read.js";
10
12
  import { formatPathForPrompt } from "../../../../workspaces/resources/skills.js";
11
13
  import { activityRelationFor, activityRequestFor, runActivityTool, runActivityToolWithHooks, } from "../../core/activity-support.js";
12
14
  import { workspaceHookInvocation } from "../../core/capability-support.js";
13
15
  import { resultOutputSchema, workspaceAgentsFileOutputSchema } from "../../core/schemas.js";
14
- import { contentText, logToolCall, textBlock, toolResultAgentsFiles, toolResultContent, toolResultIsError, toolResultText, workspaceLogContext, } from "../../core/tool-support.js";
16
+ import { contentText, imageContentMetadata, logToolCall, textBlock, toolResultAgentsFiles, toolResultContent, toolResultIsError, toolResultText, workspaceLogContext, } from "../../core/tool-support.js";
15
17
  import { appendAutomaticMutationDiagnostics } from "./mutation-diagnostics.js";
16
18
  const WRITE_TOOL_ANNOTATIONS = {
17
19
  readOnlyHint: false,
@@ -25,6 +27,11 @@ const EDIT_TOOL_ANNOTATIONS = {
25
27
  idempotentHint: false,
26
28
  openWorldHint: false,
27
29
  };
30
+ const MEDIA_METADATA_OUTPUT_SCHEMA = z.object({
31
+ type: z.literal("image"),
32
+ mimeType: z.string(),
33
+ bytes: z.number().int().nonnegative(),
34
+ });
28
35
  export function registerFilesystemTools(options) {
29
36
  const { server, config, workspaces, compositeWorkspaces, compositeTaskGuides, remoteWorkspaces, coreOperations, nativeBulkMutations, activityLifecycle, codeIntelligence, hooks, toolDescriptions, resolveExecutionTarget, prepareExecutionContext, presentSemanticWorkResult, hostScopeIdFor, } = options;
30
37
  registerAppTool(server, toolNames.read, {
@@ -66,10 +73,12 @@ export function registerFilesystemTools(options) {
66
73
  },
67
74
  outputSchema: resultOutputSchema({
68
75
  agentsFiles: z.array(workspaceAgentsFileOutputSchema).optional(),
76
+ media: MEDIA_METADATA_OUTPUT_SCHEMA.optional(),
69
77
  results: z.array(z.object({
70
78
  path: z.string(),
71
79
  status: z.enum(["done", "error"]),
72
80
  result: z.string(),
81
+ media: MEDIA_METADATA_OUTPUT_SCHEMA.optional(),
73
82
  })).optional(),
74
83
  files: z.number().int().nonnegative().optional(),
75
84
  failed: z.number().int().nonnegative().optional(),
@@ -77,6 +86,7 @@ export function registerFilesystemTools(options) {
77
86
  _meta: {},
78
87
  annotations: { readOnlyHint: true },
79
88
  }, async ({ workspaceId, member, path, paths, offset, limit }, extra) => {
89
+ const requestContext = mcpHandlerRequestContext(extra);
80
90
  if ((path === undefined) === (paths === undefined)) {
81
91
  throw new Error("read requires exactly one of path or paths.");
82
92
  }
@@ -90,23 +100,32 @@ export function registerFilesystemTools(options) {
90
100
  }
91
101
  }
92
102
  const startedAt = performance.now();
93
- const children = await Promise.all(requestedPaths.map(async (requestedPath) => {
94
- const response = await readFileTool({ path: requestedPath, offset, limit }, {
103
+ const mediaBudget = createMediaBudget(config.mediaMaxBytes);
104
+ const execution = await executeBulkRead({
105
+ paths: requestedPaths,
106
+ signal: requestContext.signal,
107
+ run: (requestedPath) => readFileTool({ path: requestedPath, offset, limit }, {
95
108
  cwd: process.cwd(),
96
109
  root: config.allowedRoots[0] ?? process.cwd(),
97
110
  readRoots: config.allowedRoots,
98
- });
99
- return {
100
- path: requestedPath,
101
- status: response.isError ? "error" : "done",
102
- response,
103
- result: contentText(response.content),
104
- };
105
- }));
106
- const failed = children.filter((child) => child.status === "error").length;
107
- const content = children.flatMap((child) => requestedPaths.length === 1
108
- ? child.response.content
109
- : [textBlock(`--- ${child.path} · ${child.status} ---`), ...child.response.content]);
111
+ mediaBudget,
112
+ }),
113
+ isError: (response) => response.isError === true,
114
+ resultText: (response) => contentText(response.content),
115
+ });
116
+ const children = execution.children;
117
+ const failed = execution.failed;
118
+ const content = children.flatMap((child) => {
119
+ const childContent = child.response
120
+ ? child.response.content
121
+ : [textBlock(child.result)];
122
+ return requestedPaths.length === 1
123
+ ? childContent
124
+ : [textBlock(`--- ${child.path} · ${child.status} ---`), ...childContent];
125
+ });
126
+ const singleMedia = requestedPaths.length === 1 && children[0]?.response
127
+ ? imageContentMetadata(children[0].response.content)
128
+ : undefined;
110
129
  logToolCall(config, {
111
130
  tool: toolNames.read,
112
131
  path: requestedPaths.length === 1 ? requestedPaths[0] : `${requestedPaths.length} unscoped files`,
@@ -118,9 +137,20 @@ export function registerFilesystemTools(options) {
118
137
  ...(failed > 0 ? { isError: true } : {}),
119
138
  structuredContent: {
120
139
  result: contentText(content),
140
+ ...(singleMedia ? { media: singleMedia } : {}),
121
141
  ...(requestedPaths.length > 1
122
142
  ? {
123
- results: children.map(({ path: childPath, status, result }) => ({ path: childPath, status, result })),
143
+ results: children.map(({ path: childPath, status, result, response }) => {
144
+ const media = response
145
+ ? imageContentMetadata(response.content)
146
+ : undefined;
147
+ return {
148
+ path: childPath,
149
+ status,
150
+ result,
151
+ ...(media ? { media } : {}),
152
+ };
153
+ }),
124
154
  files: children.length,
125
155
  failed,
126
156
  }
@@ -153,23 +183,25 @@ export function registerFilesystemTools(options) {
153
183
  }
154
184
  const target = resolveExecutionTarget(workspaceId, member);
155
185
  const executionWorkspaceId = target.executionWorkspaceId;
156
- const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
186
+ const executionContext = await prepareExecutionContext(target, requestContext);
157
187
  if (remoteWorkspaces.has(executionWorkspaceId)) {
158
- return presentSemanticWorkResult(await remoteWorkspaces.read(executionWorkspaceId, { path, paths, offset, limit }, hostScopeIdFor(extra._meta, extra.sessionId)), target, hostScopeIdFor(extra._meta, extra.sessionId));
188
+ return presentSemanticWorkResult(await remoteWorkspaces.read(executionWorkspaceId, { path, paths, offset, limit }, hostScopeIdFor(requestContext)), target, hostScopeIdFor(requestContext));
159
189
  }
160
190
  if (path !== undefined) {
161
- return presentSemanticWorkResult(await coreOperations.read({ workspaceId: executionWorkspaceId, path, offset, limit }, executionContext), target, hostScopeIdFor(extra._meta, extra.sessionId));
191
+ return presentSemanticWorkResult(await coreOperations.read({ workspaceId: executionWorkspaceId, path, offset, limit }, executionContext), target, hostScopeIdFor(requestContext));
162
192
  }
163
193
  const workspace = workspaces.getWorkspace(executionWorkspaceId);
164
194
  let response;
165
- await runActivityTool(activityLifecycle, workspace, hostScopeIdFor(extra._meta, extra.sessionId), toolNames.read, activityRequestFor({ workspaceId: executionWorkspaceId, paths, offset, limit }, executionContext), async (parentContext) => {
195
+ await runActivityTool(activityLifecycle, workspace, hostScopeIdFor(requestContext), toolNames.read, activityRequestFor({ workspaceId: executionWorkspaceId, paths, offset, limit }, executionContext), async (parentContext) => {
196
+ const mediaBudget = createMediaBudget(config.mediaMaxBytes);
166
197
  const execution = await executeBulkRead({
167
198
  paths: paths,
168
- signal: extra.signal,
199
+ signal: requestContext.signal,
169
200
  run: (childPath) => coreOperations.read({ workspaceId: executionWorkspaceId, path: childPath, offset, limit }, {
170
201
  ...executionContext,
171
202
  parentActivityId: parentContext.activityId,
172
203
  turnId: parentContext.turnId,
204
+ mediaBudget,
173
205
  }),
174
206
  isError: toolResultIsError,
175
207
  resultText: toolResultText,
@@ -191,11 +223,17 @@ export function registerFilesystemTools(options) {
191
223
  content,
192
224
  structuredContent: {
193
225
  result: contentText(content),
194
- results: execution.children.map(({ path: childPath, status, result }) => ({
195
- path: childPath,
196
- status,
197
- result,
198
- })),
226
+ results: execution.children.map(({ path: childPath, status, result, response }) => {
227
+ const media = response
228
+ ? imageContentMetadata(toolResultContent(response))
229
+ : undefined;
230
+ return {
231
+ path: childPath,
232
+ status,
233
+ result,
234
+ ...(media ? { media } : {}),
235
+ };
236
+ }),
199
237
  files: execution.children.length,
200
238
  failed: execution.failed,
201
239
  ...(agentsFiles.length > 0 ? { agentsFiles } : {}),
@@ -211,7 +249,7 @@ export function registerFilesystemTools(options) {
211
249
  : { type: "succeeded" }, activityRelationFor(executionContext));
212
250
  if (!response)
213
251
  throw new Error("Bulk Read completed without a response.");
214
- return presentSemanticWorkResult(response, target, hostScopeIdFor(extra._meta, extra.sessionId));
252
+ return presentSemanticWorkResult(response, target, hostScopeIdFor(requestContext));
215
253
  });
216
254
  if (config.toolMode !== "codex") {
217
255
  registerAppTool(server, toolNames.write, {
@@ -231,13 +269,14 @@ export function registerFilesystemTools(options) {
231
269
  _meta: {},
232
270
  annotations: WRITE_TOOL_ANNOTATIONS,
233
271
  }, async ({ workspaceId, member, ...input }, extra) => {
272
+ const requestContext = mcpHandlerRequestContext(extra);
234
273
  const target = resolveExecutionTarget(workspaceId, member);
235
274
  const executionWorkspaceId = target.executionWorkspaceId;
236
- const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
275
+ const executionContext = await prepareExecutionContext(target, requestContext);
237
276
  if (remoteWorkspaces.has(executionWorkspaceId)) {
238
- return presentSemanticWorkResult(await remoteWorkspaces.write(executionWorkspaceId, input, hostScopeIdFor(extra._meta, extra.sessionId)), target, hostScopeIdFor(extra._meta, extra.sessionId));
277
+ return presentSemanticWorkResult(await remoteWorkspaces.write(executionWorkspaceId, input, hostScopeIdFor(requestContext)), target, hostScopeIdFor(requestContext));
239
278
  }
240
- return presentSemanticWorkResult(await coreOperations.write({ workspaceId: executionWorkspaceId, ...input }, executionContext), target, hostScopeIdFor(extra._meta, extra.sessionId));
279
+ return presentSemanticWorkResult(await coreOperations.write({ workspaceId: executionWorkspaceId, ...input }, executionContext), target, hostScopeIdFor(requestContext));
241
280
  });
242
281
  registerAppTool(server, toolNames.edit, {
243
282
  title: "Edit file",
@@ -281,19 +320,20 @@ export function registerFilesystemTools(options) {
281
320
  _meta: {},
282
321
  annotations: EDIT_TOOL_ANNOTATIONS,
283
322
  }, async ({ workspaceId, member, path, paths, edits }, extra) => {
323
+ const requestContext = mcpHandlerRequestContext(extra);
284
324
  if ((path === undefined) === (paths === undefined)) {
285
325
  throw new Error("edit requires exactly one of path or paths.");
286
326
  }
287
327
  const target = resolveExecutionTarget(workspaceId, member);
288
328
  const executionWorkspaceId = target.executionWorkspaceId;
289
- const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
329
+ const executionContext = await prepareExecutionContext(target, requestContext);
290
330
  if (remoteWorkspaces.has(executionWorkspaceId)) {
291
- return presentSemanticWorkResult(await remoteWorkspaces.edit(executionWorkspaceId, { path, paths, edits }, hostScopeIdFor(extra._meta, extra.sessionId)), target, hostScopeIdFor(extra._meta, extra.sessionId));
331
+ return presentSemanticWorkResult(await remoteWorkspaces.edit(executionWorkspaceId, { path, paths, edits }, hostScopeIdFor(requestContext)), target, hostScopeIdFor(requestContext));
292
332
  }
293
333
  if (path !== undefined) {
294
- return presentSemanticWorkResult(await coreOperations.edit({ workspaceId: executionWorkspaceId, path, edits }, executionContext), target, hostScopeIdFor(extra._meta, extra.sessionId));
334
+ return presentSemanticWorkResult(await coreOperations.edit({ workspaceId: executionWorkspaceId, path, edits }, executionContext), target, hostScopeIdFor(requestContext));
295
335
  }
296
- return presentSemanticWorkResult(await nativeBulkMutations.edit({ workspaceId: executionWorkspaceId, paths: paths, edits }, executionContext), target, hostScopeIdFor(extra._meta, extra.sessionId));
336
+ return presentSemanticWorkResult(await nativeBulkMutations.edit({ workspaceId: executionWorkspaceId, paths: paths, edits }, executionContext), target, hostScopeIdFor(requestContext));
297
337
  });
298
338
  }
299
339
  registerAppTool(server, toolNames.rename, {
@@ -313,13 +353,14 @@ export function registerFilesystemTools(options) {
313
353
  _meta: {},
314
354
  annotations: EDIT_TOOL_ANNOTATIONS,
315
355
  }, async ({ workspaceId, member, path, newPath }, extra) => {
356
+ const requestContext = mcpHandlerRequestContext(extra);
316
357
  const target = resolveExecutionTarget(workspaceId, member);
317
358
  const executionWorkspaceId = target.executionWorkspaceId;
318
- const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
359
+ const executionContext = await prepareExecutionContext(target, requestContext);
319
360
  if (remoteWorkspaces.has(executionWorkspaceId)) {
320
- return presentSemanticWorkResult(await remoteWorkspaces.rename(executionWorkspaceId, { path, newPath }, hostScopeIdFor(extra._meta, extra.sessionId)), target, hostScopeIdFor(extra._meta, extra.sessionId));
361
+ return presentSemanticWorkResult(await remoteWorkspaces.rename(executionWorkspaceId, { path, newPath }, hostScopeIdFor(requestContext)), target, hostScopeIdFor(requestContext));
321
362
  }
322
- return presentSemanticWorkResult(await coreOperations.rename({ workspaceId: executionWorkspaceId, path, newPath }, executionContext), target, hostScopeIdFor(extra._meta, extra.sessionId));
363
+ return presentSemanticWorkResult(await coreOperations.rename({ workspaceId: executionWorkspaceId, path, newPath }, executionContext), target, hostScopeIdFor(requestContext));
323
364
  });
324
365
  registerAppTool(server, toolNames.delete, {
325
366
  title: "Delete path",
@@ -353,19 +394,20 @@ export function registerFilesystemTools(options) {
353
394
  _meta: {},
354
395
  annotations: EDIT_TOOL_ANNOTATIONS,
355
396
  }, async ({ workspaceId, member, path, paths, recursive }, extra) => {
397
+ const requestContext = mcpHandlerRequestContext(extra);
356
398
  if ((path === undefined) === (paths === undefined)) {
357
399
  throw new Error("delete requires exactly one of path or paths.");
358
400
  }
359
401
  const target = resolveExecutionTarget(workspaceId, member);
360
402
  const executionWorkspaceId = target.executionWorkspaceId;
361
- const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
403
+ const executionContext = await prepareExecutionContext(target, requestContext);
362
404
  if (remoteWorkspaces.has(executionWorkspaceId)) {
363
- return presentSemanticWorkResult(await remoteWorkspaces.delete(executionWorkspaceId, { path, paths, recursive }, hostScopeIdFor(extra._meta, extra.sessionId)), target, hostScopeIdFor(extra._meta, extra.sessionId));
405
+ return presentSemanticWorkResult(await remoteWorkspaces.delete(executionWorkspaceId, { path, paths, recursive }, hostScopeIdFor(requestContext)), target, hostScopeIdFor(requestContext));
364
406
  }
365
407
  if (path !== undefined) {
366
- return presentSemanticWorkResult(await coreOperations.delete({ workspaceId: executionWorkspaceId, path, recursive }, executionContext), target, hostScopeIdFor(extra._meta, extra.sessionId));
408
+ return presentSemanticWorkResult(await coreOperations.delete({ workspaceId: executionWorkspaceId, path, recursive }, executionContext), target, hostScopeIdFor(requestContext));
367
409
  }
368
- return presentSemanticWorkResult(await nativeBulkMutations.delete({ workspaceId: executionWorkspaceId, paths: paths, recursive }, executionContext), target, hostScopeIdFor(extra._meta, extra.sessionId));
410
+ return presentSemanticWorkResult(await nativeBulkMutations.delete({ workspaceId: executionWorkspaceId, paths: paths, recursive }, executionContext), target, hostScopeIdFor(requestContext));
369
411
  });
370
412
  if (config.toolMode === "codex") {
371
413
  registerAppTool(server, "apply_patch", {
@@ -392,15 +434,16 @@ export function registerFilesystemTools(options) {
392
434
  _meta: {},
393
435
  annotations: EDIT_TOOL_ANNOTATIONS,
394
436
  }, async ({ workspaceId, member, patch }, extra) => {
437
+ const requestContext = mcpHandlerRequestContext(extra);
395
438
  const target = resolveExecutionTarget(workspaceId, member);
396
439
  const executionWorkspaceId = target.executionWorkspaceId;
397
- const executionContext = await prepareExecutionContext(target, extra._meta, extra.signal, extra.sessionId);
440
+ const executionContext = await prepareExecutionContext(target, requestContext);
398
441
  if (remoteWorkspaces.has(executionWorkspaceId)) {
399
- return presentSemanticWorkResult(await remoteWorkspaces.applyPatch(executionWorkspaceId, { patch }, hostScopeIdFor(extra._meta, extra.sessionId)), target, hostScopeIdFor(extra._meta, extra.sessionId));
442
+ return presentSemanticWorkResult(await remoteWorkspaces.applyPatch(executionWorkspaceId, { patch }, hostScopeIdFor(requestContext)), target, hostScopeIdFor(requestContext));
400
443
  }
401
444
  const workspace = workspaces.getWorkspace(executionWorkspaceId);
402
- return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(extra._meta, extra.sessionId), activityRequestFor({ workspaceId: executionWorkspaceId, patch }, executionContext), {
403
- signal: extra.signal,
445
+ return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(requestContext), activityRequestFor({ workspaceId: executionWorkspaceId, patch }, executionContext), {
446
+ signal: requestContext.signal,
404
447
  tool: "apply_patch",
405
448
  invocation: workspaceHookInvocation(workspace),
406
449
  payload: { patchBytes: Buffer.byteLength(patch) },
@@ -420,7 +463,7 @@ export function registerFilesystemTools(options) {
420
463
  .map((file) => file.path);
421
464
  logToolCall(config, {
422
465
  tool: "apply_patch",
423
- ...workspaceLogContext(workspace, extra.sessionId),
466
+ ...workspaceLogContext(workspace, requestContext.transportSessionId),
424
467
  path: displayPath,
425
468
  success: true,
426
469
  durationMs: Math.round(performance.now() - startedAt),
@@ -447,9 +490,9 @@ export function registerFilesystemTools(options) {
447
490
  removals: applied.removals,
448
491
  files: applied.files,
449
492
  },
450
- }, codeIntelligence, workspace.root, diagnosticPaths, extra.signal);
493
+ }, codeIntelligence, workspace.root, diagnosticPaths, requestContext.signal);
451
494
  },
452
- }, activityRelationFor(executionContext)).then((result) => presentSemanticWorkResult(result, target, hostScopeIdFor(extra._meta, extra.sessionId)));
495
+ }, activityRelationFor(executionContext)).then((result) => presentSemanticWorkResult(result, target, hostScopeIdFor(requestContext)));
453
496
  });
454
497
  }
455
498
  }
@@ -1,6 +1,7 @@
1
1
  import { CapabilityError } from "../../core/capability-registry.js";
2
2
  import { deletePath, renamePath } from "../../../filesystem/file-mutations.js";
3
3
  import { editFileTool, readFileTool, writeFileTool } from "../../../filesystem/filesystem-tools.js";
4
+ import { createMediaBudget } from "../../../operations/media-content.js";
4
5
  import { runToolWithHooks } from "../../../hooks/hooks.js";
5
6
  import { toolNames } from "../../../server-instructions.js";
6
7
  import { BatchExecutor } from "../../../operations/batch/executor.js";
@@ -11,7 +12,7 @@ import { markReturnedOutput, processActivityOutcome, processToolResponse, } from
11
12
  import { capabilityActivityAuditRequest, capabilityActivityAuditResult } from "../../../../subagents/sessions/mcp/audit.js";
12
13
  import { activityRelationFor, activityRequestFor, runActivityTool, runActivityToolWithHooks, standardActivityOutcome, } from "../../core/activity-support.js";
13
14
  import { capabilityContextFor, managedWorktreeRecoveryCapabilityContext, workspaceHookInvocation, } from "../../core/capability-support.js";
14
- import { assertWorkspaceInstructionsLoadedBeforeSideEffect, contentLineCount, contentText, countDiffStats, formatDiscoveredWorkspaceInstructions, logFailedToolResponse, logToolCall, newFilePatch, textBlock, textSummary, toolResultContent, toolResultIsError, toolResultText, workspaceLogContext, } from "../../core/tool-support.js";
15
+ import { assertWorkspaceInstructionsLoadedBeforeSideEffect, auditToolResultWithoutImageData, contentLineCount, contentText, countDiffStats, formatDiscoveredWorkspaceInstructions, imageContentMetadata, logFailedToolResponse, logToolCall, metadataSafeContent, newFilePatch, textBlock, textSummary, toolResultContent, toolResultIsError, toolResultText, workspaceLogContext, } from "../../core/tool-support.js";
15
16
  import { appendAutomaticMutationDiagnostics } from "./mutation-diagnostics.js";
16
17
  export function createOperationRuntime(options) {
17
18
  const { config, workspaces, activityLifecycle, hooks, processSessions, bashOutputStore, capabilityRegistry, codeIntelligence, hostScopeIdFor, } = options;
@@ -19,7 +20,7 @@ export function createOperationRuntime(options) {
19
20
  read: async (input, context) => {
20
21
  const { workspaceId, ...readInput } = input;
21
22
  const workspace = workspaces.getWorkspace(workspaceId);
22
- return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), activityRequestFor(input, context), {
23
+ return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context), activityRequestFor(input, context), {
23
24
  signal: context.signal,
24
25
  tool: toolNames.read,
25
26
  invocation: workspaceHookInvocation(workspace),
@@ -33,6 +34,7 @@ export function createOperationRuntime(options) {
33
34
  cwd: workspace.root,
34
35
  root: workspace.root,
35
36
  readRoots: readPath.readRoots,
37
+ mediaBudget: context.mediaBudget ?? createMediaBudget(config.mediaMaxBytes),
36
38
  });
37
39
  if (response.isError) {
38
40
  logFailedToolResponse(config, {
@@ -49,10 +51,12 @@ export function createOperationRuntime(options) {
49
51
  const content = discoveredInstructionContent
50
52
  ? [discoveredInstructionContent, ...response.content]
51
53
  : response.content;
54
+ const media = imageContentMetadata(response.content);
52
55
  const summary = {
53
56
  ...textSummary(response.content),
54
57
  offset: readInput.offset ?? 1,
55
58
  limited: readInput.limit !== undefined,
59
+ ...(media ? { media } : {}),
56
60
  };
57
61
  logToolCall(config, {
58
62
  tool: toolNames.read,
@@ -70,11 +74,12 @@ export function createOperationRuntime(options) {
70
74
  workspaceId,
71
75
  path: readInput.path,
72
76
  summary,
73
- payload: { content: response.content },
77
+ payload: { content: metadataSafeContent(response.content) },
74
78
  },
75
79
  },
76
80
  structuredContent: {
77
81
  result: contentText(content),
82
+ ...(media ? { media } : {}),
78
83
  ...(discoveredInstructions.length > 0
79
84
  ? {
80
85
  agentsFiles: discoveredInstructions.map((file) => ({
@@ -86,12 +91,12 @@ export function createOperationRuntime(options) {
86
91
  },
87
92
  };
88
93
  },
89
- }, activityRelationFor(context));
94
+ }, activityRelationFor(context), auditToolResultWithoutImageData);
90
95
  },
91
96
  write: async (input, context) => {
92
97
  const { workspaceId, ...writeInput } = input;
93
98
  const workspace = workspaces.getWorkspace(workspaceId);
94
- return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), activityRequestFor(input, context), {
99
+ return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context), activityRequestFor(input, context), {
95
100
  signal: context.signal,
96
101
  tool: toolNames.write,
97
102
  invocation: workspaceHookInvocation(workspace),
@@ -152,7 +157,7 @@ export function createOperationRuntime(options) {
152
157
  edit: async (input, context) => {
153
158
  const { workspaceId, ...editInput } = input;
154
159
  const workspace = workspaces.getWorkspace(workspaceId);
155
- return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), activityRequestFor(input, context), {
160
+ return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context), activityRequestFor(input, context), {
156
161
  signal: context.signal,
157
162
  tool: toolNames.edit,
158
163
  invocation: workspaceHookInvocation(workspace),
@@ -214,7 +219,7 @@ export function createOperationRuntime(options) {
214
219
  rename: async (input, context) => {
215
220
  const { workspaceId, path, newPath } = input;
216
221
  const workspace = workspaces.getWorkspace(workspaceId);
217
- return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), activityRequestFor(input, context), {
222
+ return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context), activityRequestFor(input, context), {
218
223
  signal: context.signal,
219
224
  tool: toolNames.rename,
220
225
  invocation: workspaceHookInvocation(workspace),
@@ -273,7 +278,7 @@ export function createOperationRuntime(options) {
273
278
  delete: async (input, context) => {
274
279
  const { workspaceId, path, recursive } = input;
275
280
  const workspace = workspaces.getWorkspace(workspaceId);
276
- return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), activityRequestFor(input, context), {
281
+ return runActivityToolWithHooks(activityLifecycle, hooks, workspace, hostScopeIdFor(context), activityRequestFor(input, context), {
277
282
  signal: context.signal,
278
283
  tool: toolNames.delete,
279
284
  invocation: workspaceHookInvocation(workspace),
@@ -357,7 +362,7 @@ export function createOperationRuntime(options) {
357
362
  maxOutputTokens,
358
363
  };
359
364
  let undeliveredProcessId;
360
- const activityResult = await runActivityTool(activityLifecycle, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), surface, activityRequestFor(activityRequest, context), async (activityContext) => {
365
+ const activityResult = await runActivityTool(activityLifecycle, workspace, hostScopeIdFor(context), surface, activityRequestFor(activityRequest, context), async (activityContext) => {
361
366
  try {
362
367
  const result = await runToolWithHooks(hooks, {
363
368
  signal: context.signal,
@@ -483,7 +488,7 @@ export function createOperationRuntime(options) {
483
488
  }
484
489
  const workspace = workspaces.getWorkspace(workspaceId);
485
490
  let changedPaths = [];
486
- return runActivityTool(activityLifecycle, workspace, hostScopeIdFor(context.requestMeta, context.sessionId), toolNames.capability, activityRequestFor(capabilityActivityAuditRequest(input), context), (activityContext) => runToolWithHooks(hooks, {
491
+ return runActivityTool(activityLifecycle, workspace, hostScopeIdFor(context), toolNames.capability, activityRequestFor(capabilityActivityAuditRequest(input), context), (activityContext) => runToolWithHooks(hooks, {
487
492
  signal: context.signal,
488
493
  tool: toolNames.capability,
489
494
  invocation: workspaceHookInvocation(workspace),
@@ -503,8 +508,10 @@ export function createOperationRuntime(options) {
503
508
  activityId: activityContext.activityId,
504
509
  });
505
510
  changedPaths = execution.changedPaths ?? [];
511
+ const transientContent = execution.content;
506
512
  const result = {
507
- content: [textBlock(`Capability ${name} completed.\n${JSON.stringify(execution.value, null, 2)}`)],
513
+ content: transientContent
514
+ ?? [textBlock(`Capability ${name} completed.\n${JSON.stringify(execution.value, null, 2)}`)],
508
515
  ...(execution.card
509
516
  ? {
510
517
  _meta: {
@@ -1,10 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
3
- import { getOAuthProtectedResourceMetadataUrl } from "@modelcontextprotocol/sdk/server/auth/router.js";
4
- import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
5
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
6
- import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
7
- import { checkResourceAllowed, resourceUrlFromServerUrl } from "@modelcontextprotocol/sdk/shared/auth-utils.js";
2
+ import { checkResourceAllowed, createMcpHandler, isInitializeRequest, isLegacyRequest, resourceUrlFromServerUrl, } from "@modelcontextprotocol/server";
3
+ import { createMcpExpressApp, getOAuthProtectedResourceMetadataUrl, requireBearerAuth, } from "@modelcontextprotocol/express";
4
+ import { NodeStreamableHTTPServerTransport, toNodeHandler, toWebRequest, } from "@modelcontextprotocol/node";
8
5
  import express from "express";
9
6
  import { ActivityAuditStore } from "../../../activity/history/audit-store.js";
10
7
  import { BashOutputStore } from "../../../activity/history/bash-output-store.js";
@@ -59,7 +56,7 @@ export function createHttpServer(config, options, createMcpServer) {
59
56
  });
60
57
  const workspaceStore = createWorkspaceStore(config.stateDir);
61
58
  const workspaces = new WorkspaceRegistry(config, workspaceStore);
62
- const sharedRemoteWorkspaces = new RemoteWorkspaceRelay(config.configDir, config.stateDir);
59
+ const sharedRemoteWorkspaces = new RemoteWorkspaceRelay(config.configDir, config.stateDir, config.mediaMaxBytes);
63
60
  const sharedCompositeWorkspaces = new CompositeWorkspaceRegistry(config.stateDir);
64
61
  const sharedWorkspaceTasks = new WorkspaceTaskStore(config.stateDir);
65
62
  const sharedTaskReminders = new WorkspaceTaskReminderTracker(config.taskReminderInterval, sharedWorkspaceTasks);
@@ -79,6 +76,26 @@ export function createHttpServer(config, options, createMcpServer) {
79
76
  const subagentProviders = config.subagents
80
77
  ? getSubagentProviderAvailabilitySnapshot()
81
78
  : [];
79
+ const buildMcpServer = () => createMcpServer(config, workspaces, reviewCheckpoints, processSessions, subagentProviders, incomingArtifactAdapters, codeIntelligence, activityLifecycle, bashOutputStore, activityQueries, {
80
+ taskReminders: sharedTaskReminders,
81
+ remoteWorkspaces: sharedRemoteWorkspaces,
82
+ compositeWorkspaces: sharedCompositeWorkspaces,
83
+ });
84
+ const modernMcp = createMcpHandler(() => buildMcpServer(), {
85
+ legacy: "reject",
86
+ onerror: (error) => {
87
+ logEvent(config.logging, "error", "mcp_modern_handler_error", {
88
+ error: error.message,
89
+ });
90
+ },
91
+ });
92
+ const modernNodeHandler = toNodeHandler(modernMcp, {
93
+ onerror: (error) => {
94
+ logEvent(config.logging, "error", "mcp_modern_adapter_error", {
95
+ error: error.message,
96
+ });
97
+ },
98
+ });
82
99
  const logTransportCloseResults = (reason, results) => {
83
100
  let closedCount = 0;
84
101
  for (const result of results) {
@@ -219,15 +236,22 @@ export function createHttpServer(config, options, createMcpServer) {
219
236
  sendJsonRpcError(res, 401, -32001, "Unauthorized");
220
237
  return;
221
238
  }
222
- logEvent(config.logging, "debug", "mcp_request", {
223
- requestId,
224
- httpMethod: req.method,
225
- transportSessionIdPresent: Boolean(transportSessionId),
226
- transportSessionIdPrefix: transportSessionIdPrefix(transportSessionId),
227
- isInitialize: initializeRequest,
228
- ...mcpRequestDebugFields(req.body),
229
- });
230
239
  try {
240
+ const webRequest = await toWebRequest(req, req.body);
241
+ const legacyRequest = await isLegacyRequest(webRequest, req.body);
242
+ logEvent(config.logging, "debug", "mcp_request", {
243
+ requestId,
244
+ httpMethod: req.method,
245
+ protocolEra: legacyRequest ? "legacy" : "modern",
246
+ transportSessionIdPresent: Boolean(transportSessionId),
247
+ transportSessionIdPrefix: transportSessionIdPrefix(transportSessionId),
248
+ isInitialize: initializeRequest,
249
+ ...mcpRequestDebugFields(req.body),
250
+ });
251
+ if (!legacyRequest) {
252
+ await modernNodeHandler(req, res, req.body);
253
+ return;
254
+ }
231
255
  let transport;
232
256
  if (transportSessionId) {
233
257
  transport = transports.get(transportSessionId);
@@ -237,7 +261,7 @@ export function createHttpServer(config, options, createMcpServer) {
237
261
  }
238
262
  }
239
263
  else if (initializeRequest) {
240
- transport = new StreamableHTTPServerTransport({
264
+ transport = new NodeStreamableHTTPServerTransport({
241
265
  sessionIdGenerator: () => randomUUID(),
242
266
  onsessioninitialized: (newTransportSessionId) => {
243
267
  if (transport) {
@@ -261,11 +285,7 @@ export function createHttpServer(config, options, createMcpServer) {
261
285
  });
262
286
  }
263
287
  };
264
- const server = createMcpServer(config, workspaces, reviewCheckpoints, processSessions, subagentProviders, incomingArtifactAdapters, codeIntelligence, activityLifecycle, bashOutputStore, activityQueries, {
265
- taskReminders: sharedTaskReminders,
266
- remoteWorkspaces: sharedRemoteWorkspaces,
267
- compositeWorkspaces: sharedCompositeWorkspaces,
268
- });
288
+ const server = buildMcpServer();
269
289
  await server.connect(transport);
270
290
  }
271
291
  else {
@@ -294,6 +314,7 @@ export function createHttpServer(config, options, createMcpServer) {
294
314
  clearInterval(transportCleanupTimer);
295
315
  const results = await transports.closeAll();
296
316
  logTransportCloseResults("server_shutdown", results);
317
+ await modernMcp.close();
297
318
  await sharedRemoteWorkspaces.shutdown();
298
319
  processSessions.shutdown();
299
320
  await codeIntelligence.shutdown();