@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.
- package/CHANGELOG.md +36 -0
- package/README.md +4 -0
- package/capabilities/host-integration/external-mcp/GUIDE.md +29 -0
- package/capabilities/lifecycle-hooks/GUIDE.md +13 -3
- package/dist/activity/runtime/mcp-query-tools.js +16 -10
- package/dist/cli/mcp/external-mcp.js +481 -0
- package/dist/cli/mcp/status.js +295 -0
- package/dist/cli.js +14 -1
- package/dist/lsp/test-support/server-fixture.js +1 -2
- package/dist/mcp/filesystem/filesystem-tools.js +7 -3
- package/dist/mcp/hooks/command-runner.js +12 -7
- package/dist/mcp/hooks/external-mcp-transform.js +224 -0
- package/dist/mcp/hooks/hook-cli.js +3 -0
- package/dist/mcp/hooks/hooks.js +49 -3
- package/dist/mcp/oauth/auth-protocol.js +375 -0
- package/dist/mcp/oauth/oauth-provider.js +11 -12
- package/dist/mcp/oauth/oauth-store.js +2 -2
- package/dist/mcp/oauth/router.js +2 -6
- package/dist/mcp/operations/batch/executor.js +1 -1
- package/dist/mcp/operations/bulk-read.js +7 -6
- package/dist/mcp/operations/external-mcp/external-mcp-oauth.js +387 -0
- package/dist/mcp/operations/external-mcp/external-mcp-runtime.js +59 -0
- package/dist/mcp/operations/external-mcp/external-mcp.js +382 -0
- package/dist/mcp/operations/media-content.js +28 -0
- package/dist/mcp/operations/native-bulk-mutations.js +1 -1
- package/dist/mcp/process/tools.js +19 -15
- package/dist/mcp/request-context.js +18 -0
- package/dist/mcp/request-meta.js +28 -3
- package/dist/mcp/server/core/activity-support.js +2 -2
- package/dist/mcp/server/core/capabilities/external-mcp.js +31 -0
- package/dist/mcp/server/core/capabilities.js +7 -0
- package/dist/mcp/server/core/capability-registry.js +2 -0
- package/dist/mcp/server/core/tool-support.js +29 -0
- package/dist/mcp/server/operations/runtime/filesystem-tools.js +91 -48
- package/dist/mcp/server/operations/runtime/operation-runtime.js +18 -11
- package/dist/mcp/server/transport/http-server.js +42 -21
- package/dist/mcp/server/workspace/runtime/workspace-open.js +13 -9
- package/dist/mcp/server/workspace/runtime/workspace-tools.js +29 -25
- package/dist/runtime/config/config.js +4 -0
- package/dist/runtime/config/external-mcp-auth-store.js +225 -0
- package/dist/runtime/config/external-mcp-config.js +146 -0
- package/dist/runtime/config/external-mcp-registry.js +165 -0
- package/dist/runtime/testing/server-fixture.js +10 -5
- package/dist/server.js +19 -10
- package/dist/subagents/sessions/mcp/audit.js +81 -0
- package/dist/ui/.vite/manifest.json +297 -298
- package/dist/ui/activity-panel-app.html +1 -1
- package/dist/ui/assets/activity-panel-app-Bw7ZFX2g.js +114 -0
- package/dist/ui/assets/angular-html-DeUNP12X.js +1 -0
- package/dist/ui/assets/{angular-ts-BearSZDb.js → angular-ts-B3v8FKi_.js} +1 -1
- package/dist/ui/assets/{apl-CYQtr-y6.js → apl-Bs3iQm4P.js} +1 -1
- package/dist/ui/assets/{astro-DDzqKIjK.js → astro-D6GHecBY.js} +1 -1
- package/dist/ui/assets/{blade-DkwEyHvQ.js → blade-BMNbVOHv.js} +1 -1
- package/dist/ui/assets/c-BquuA6bv.js +1 -0
- package/dist/ui/assets/{cobol-xBn5JxHE.js → cobol-BuZ6kfps.js} +1 -1
- package/dist/ui/assets/{coffee-BaXXvoJw.js → coffee-DgC5uBzL.js} +1 -1
- package/dist/ui/assets/cpp-BdvWqCaB.js +1 -0
- package/dist/ui/assets/{crystal-DjOpXVU1.js → crystal-0Q5qBvNI.js} +1 -1
- package/dist/ui/assets/css-Be_T8idh.js +1 -0
- package/dist/ui/assets/{edge-DNaRizoR.js → edge-DaGLdgh_.js} +1 -1
- package/dist/ui/assets/{elixir-CwBMwZmy.js → elixir-CMgiFIzB.js} +1 -1
- package/dist/ui/assets/{elm-gOUD2CW3.js → elm-B-ufMlMC.js} +1 -1
- package/dist/ui/assets/{erb-BcR-fjUp.js → erb-CaGiOZM-.js} +1 -1
- package/dist/ui/assets/{git-rebase-BAQPKHF6.js → git-rebase-CpwUWCzK.js} +1 -1
- package/dist/ui/assets/{glimmer-js-P_wOfSz_.js → glimmer-js-BRxccvnE.js} +1 -1
- package/dist/ui/assets/{glimmer-ts-B44aY1fZ.js → glimmer-ts-CgQbPzlT.js} +1 -1
- package/dist/ui/assets/glsl-D7JmHFr0.js +1 -0
- package/dist/ui/assets/graphql-C8n0eUBK.js +1 -0
- package/dist/ui/assets/{hack-ddU2k0nt.js → hack-lVN7KVQv.js} +1 -1
- package/dist/ui/assets/haml-h18lG0pS.js +1 -0
- package/dist/ui/assets/{handlebars-BpzaUpmz.js → handlebars-lRFjRPfj.js} +1 -1
- package/dist/ui/assets/heavy-payload-Cqx9MOkH.js +290 -0
- package/dist/ui/assets/html-DtILqTE1.js +1 -0
- package/dist/ui/assets/{html-derivative-B2beVdmA.js → html-derivative-CSTSmPup.js} +1 -1
- package/dist/ui/assets/{http-iEskZy8U.js → http-DNI8fsbY.js} +1 -1
- package/dist/ui/assets/{hurl-BuUsLSKv.js → hurl-DgRi60JG.js} +1 -1
- package/dist/ui/assets/java-CW9pZ5bh.js +1 -0
- package/dist/ui/assets/javascript-Cvg2UrWL.js +1 -0
- package/dist/ui/assets/{jinja-CRpkWwrH.js → jinja-CZw_S1FB.js} +1 -1
- package/dist/ui/assets/{jison-BYfSnuHj.js → jison-C56u_FZv.js} +1 -1
- package/dist/ui/assets/json-DW2UTv1q.js +1 -0
- package/dist/ui/assets/jsx-Zj5gobdD.js +1 -0
- package/dist/ui/assets/{julia-B8Rxy7vD.js → julia-Cvcrre-L.js} +1 -1
- package/dist/ui/assets/{just-DzhIWpFP.js → just-8CZ2u6Tj.js} +1 -1
- package/dist/ui/assets/{latex-CUNjUUL-.js → latex-D289AXWG.js} +1 -1
- package/dist/ui/assets/{liquid-hUg7nC61.js → liquid-CFEX3SkZ.js} +1 -1
- package/dist/ui/assets/lua-BKpJ0ybO.js +1 -0
- package/dist/ui/assets/{marko-CQmIkwMF.js → marko-D9maZ0a6.js} +1 -1
- package/dist/ui/assets/{mdc-CB-6dHYa.js → mdc-rM9l0qWR.js} +1 -1
- package/dist/ui/assets/{nginx-CQORo_Bt.js → nginx-C9w6p2C9.js} +1 -1
- package/dist/ui/assets/{nim-BNwBNzdn.js → nim-BRePHk0J.js} +1 -1
- package/dist/ui/assets/{perl-Cnz14fAJ.js → perl-CskTmiPQ.js} +1 -1
- package/dist/ui/assets/{php-YywDo63b.js → php-DCU_FvNI.js} +1 -1
- package/dist/ui/assets/{pug-BwbJp4cM.js → pug-XLaVxTvv.js} +1 -1
- package/dist/ui/assets/{qml-D2DY6dMH.js → qml-CaONKSl-.js} +1 -1
- package/dist/ui/assets/r-BCVR0ZE3.js +1 -0
- package/dist/ui/assets/{razor-pMTG7k41.js → razor-DDnGXxnU.js} +1 -1
- package/dist/ui/assets/regexp-DWGTYYVx.js +1 -0
- package/dist/ui/assets/{rst-CO_ndDm4.js → rst-Dz3GWvQm.js} +1 -1
- package/dist/ui/assets/{ruby-xzRn9gTh.js → ruby-DC3jGtMY.js} +1 -1
- package/dist/ui/assets/{sas-CBdtFKLA.js → sas-7mNpvsjZ.js} +1 -1
- package/dist/ui/assets/scss-uBsoCJVy.js +1 -0
- package/dist/ui/assets/shellscript-BCN9N_Jf.js +1 -0
- package/dist/ui/assets/{shellsession-BaZnm5rZ.js → shellsession-Hsw46VXV.js} +1 -1
- package/dist/ui/assets/{soy-IouG3xdB.js → soy-D6KV69HU.js} +1 -1
- package/dist/ui/assets/sql-CVzVQkJh.js +1 -0
- package/dist/ui/assets/{stata-dgbkIZHP.js → stata-BwUjXNhI.js} +1 -1
- package/dist/ui/assets/{surrealql-CBROnw_p.js → surrealql-CgloWqRa.js} +1 -1
- package/dist/ui/assets/{svelte-6r6BpX4-.js → svelte-DcC3n6IC.js} +1 -1
- package/dist/ui/assets/{templ-BKD_PsbL.js → templ-DOFfnWTJ.js} +1 -1
- package/dist/ui/assets/{tex-CKY1bwHb.js → tex-ln89VPaX.js} +1 -1
- package/dist/ui/assets/{ts-tags-BiYP5N_C.js → ts-tags-CWMzCstH.js} +1 -1
- package/dist/ui/assets/tsx-DgzfiGeK.js +1 -0
- package/dist/ui/assets/{twig-BTe1oPFW.js → twig-Bi2itZL9.js} +1 -1
- package/dist/ui/assets/typescript-CQ24f4zW.js +1 -0
- package/dist/ui/assets/{vue-CT0IiySh.js → vue-BS8jE5u6.js} +1 -1
- package/dist/ui/assets/{vue-html-B-tXcbat.js → vue-html-o38xqhyB.js} +1 -1
- package/dist/ui/assets/{vue-vine-C5ZyDCrs.js → vue-vine-D2NvKv50.js} +1 -1
- package/dist/ui/assets/xml-_44rx9_2.js +1 -0
- package/dist/ui/assets/{xsl-DvDSndNI.js → xsl-B5QWQPoi.js} +1 -1
- package/dist/ui/assets/yaml-CKL2pEWe.js +1 -0
- package/dist/workspaces/relay/auth/remote-auth.js +3 -5
- package/dist/workspaces/relay/result-support.js +19 -0
- package/dist/workspaces/relay/tests/test-support.js +10 -4
- package/dist/workspaces/relay/workspace-relay.js +20 -12
- package/docs/configuration.md +200 -2
- package/docs/debugging.md +8 -5
- package/docs/roadmap.md +64 -3
- package/docs/security.md +18 -0
- package/package.json +12 -4
- package/scripts/debug/accept/bootstrap.mjs +5 -0
- package/scripts/debug/accept/harness.mjs +10 -3
- package/scripts/debug/accept/media.mjs +335 -0
- package/scripts/debug/accept/modern-http.mjs +169 -0
- package/scripts/debug/accept/support.mjs +10 -6
- package/scripts/debug/accept.mjs +12 -1
- package/scripts/debug/relay-accept/support.mjs +40 -0
- package/scripts/debug/relay-accept.mjs +8 -9
- package/scripts/debug/runtime.mjs +4 -1
- package/scripts/debug/runtime.test.mjs +9 -0
- package/scripts/release/parity-sandbox.mjs +35 -0
- package/scripts/release/release-gate.test.mjs +42 -1
- package/scripts/release-parity.mjs +3 -22
- package/scripts/release-proof.mjs +24 -4
- package/scripts/release-proof.test.mjs +12 -4
- package/dist/ui/assets/activity-panel-app-XlqSanXT.js +0 -115
- package/dist/ui/assets/angular-html-tB9EchbM.js +0 -1
- package/dist/ui/assets/c-Dv-N5hjn.js +0 -1
- package/dist/ui/assets/cpp-C3unXX-3.js +0 -1
- package/dist/ui/assets/css-8Xr4kNNH.js +0 -1
- package/dist/ui/assets/glsl-BvCQg9MI.js +0 -1
- package/dist/ui/assets/graphql-C9S90gn_.js +0 -1
- package/dist/ui/assets/haml-Bb1w9rMG.js +0 -1
- package/dist/ui/assets/heavy-payload-CJKqi8Ac.js +0 -290
- package/dist/ui/assets/html-dUOs5pA9.js +0 -1
- package/dist/ui/assets/java-BFZCrORY.js +0 -1
- package/dist/ui/assets/javascript-DaIQ0Zqj.js +0 -1
- package/dist/ui/assets/json-BQC_Yp48.js +0 -1
- package/dist/ui/assets/jsx-Dd-CJRf9.js +0 -1
- package/dist/ui/assets/lua-BoZTiL2I.js +0 -1
- package/dist/ui/assets/r-B5nYsy-M.js +0 -1
- package/dist/ui/assets/regexp-oP0lbks_.js +0 -1
- package/dist/ui/assets/scss-CEqgKQNf.js +0 -1
- package/dist/ui/assets/shellscript-DzJJiWQC.js +0 -1
- package/dist/ui/assets/sql-DC2rdTzV.js +0 -1
- package/dist/ui/assets/tsx-D2OMiPNF.js +0 -1
- package/dist/ui/assets/typescript-BGIABksB.js +0 -1
- package/dist/ui/assets/xml-CfeBNlMP.js +0 -1
- package/dist/ui/assets/yaml-umbj2r1g.js +0 -1
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { parseExternalMcpStandaloneConfig, } from "./external-mcp-config.js";
|
|
5
|
+
const MISSING_FINGERPRINT = "missing";
|
|
6
|
+
const MAX_DIAGNOSTIC_LENGTH = 320;
|
|
7
|
+
export class ExternalMcpConfigRegistry {
|
|
8
|
+
configDir;
|
|
9
|
+
legacyServers;
|
|
10
|
+
onDiagnostic;
|
|
11
|
+
global;
|
|
12
|
+
projects = new Map();
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.configDir = resolve(options.configDir);
|
|
15
|
+
this.legacyServers = { ...(options.legacyServers ?? {}) };
|
|
16
|
+
this.onDiagnostic = options.onDiagnostic;
|
|
17
|
+
}
|
|
18
|
+
resolve(workspaceRoot) {
|
|
19
|
+
const projectRoot = resolve(workspaceRoot);
|
|
20
|
+
const global = this.loadGlobalSource();
|
|
21
|
+
const projectPath = join(projectRoot, ".forgerelay", "mcp.json");
|
|
22
|
+
const project = this.loadDynamicSource("project", projectPath, this.projects.get(projectRoot));
|
|
23
|
+
this.projects.set(projectRoot, project);
|
|
24
|
+
return this.composeSnapshot(global, project);
|
|
25
|
+
}
|
|
26
|
+
resolveGlobal() {
|
|
27
|
+
return this.composeSnapshot(this.loadGlobalSource());
|
|
28
|
+
}
|
|
29
|
+
loadGlobalSource() {
|
|
30
|
+
const globalPath = join(this.configDir, "mcp.json");
|
|
31
|
+
this.global = this.loadDynamicSource("global", globalPath, this.global);
|
|
32
|
+
return this.global;
|
|
33
|
+
}
|
|
34
|
+
composeSnapshot(global, project) {
|
|
35
|
+
const servers = { ...this.legacyServers };
|
|
36
|
+
const origins = Object.fromEntries(Object.keys(this.legacyServers).map((name) => [name, "legacy"]));
|
|
37
|
+
const masked = {};
|
|
38
|
+
applySource(servers, origins, masked, global.entries, "global");
|
|
39
|
+
if (project)
|
|
40
|
+
applySource(servers, origins, masked, project.entries, "project");
|
|
41
|
+
const diagnostics = [global.diagnostic, project?.diagnostic]
|
|
42
|
+
.filter((entry) => entry !== undefined);
|
|
43
|
+
return {
|
|
44
|
+
servers,
|
|
45
|
+
origins,
|
|
46
|
+
masked,
|
|
47
|
+
sources: [
|
|
48
|
+
{
|
|
49
|
+
source: "legacy",
|
|
50
|
+
path: join(this.configDir, "config.json"),
|
|
51
|
+
state: "valid",
|
|
52
|
+
usingLastKnownGood: false,
|
|
53
|
+
},
|
|
54
|
+
global.status,
|
|
55
|
+
...(project ? [project.status] : []),
|
|
56
|
+
],
|
|
57
|
+
diagnostics,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
loadDynamicSource(source, path, previous) {
|
|
61
|
+
let content;
|
|
62
|
+
try {
|
|
63
|
+
content = readFileSync(path, "utf8");
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (isMissingFileError(error)) {
|
|
67
|
+
if (previous?.observedFingerprint === MISSING_FINGERPRINT)
|
|
68
|
+
return previous;
|
|
69
|
+
return {
|
|
70
|
+
observedFingerprint: MISSING_FINGERPRINT,
|
|
71
|
+
entries: {},
|
|
72
|
+
hasLastKnownGood: true,
|
|
73
|
+
status: {
|
|
74
|
+
source,
|
|
75
|
+
path,
|
|
76
|
+
state: "missing",
|
|
77
|
+
usingLastKnownGood: false,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const fingerprint = `read-error:${errorCode(error)}`;
|
|
82
|
+
if (previous?.observedFingerprint === fingerprint)
|
|
83
|
+
return previous;
|
|
84
|
+
return this.invalidSource(source, path, fingerprint, previous, error);
|
|
85
|
+
}
|
|
86
|
+
const fingerprint = createHash("sha256").update(content).digest("base64url");
|
|
87
|
+
if (previous?.observedFingerprint === fingerprint)
|
|
88
|
+
return previous;
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = JSON.parse(content);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return this.invalidSource(source, path, fingerprint, previous, new Error("Invalid JSON syntax."));
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const entries = parseExternalMcpStandaloneConfig(parsed);
|
|
98
|
+
return {
|
|
99
|
+
observedFingerprint: fingerprint,
|
|
100
|
+
entries,
|
|
101
|
+
hasLastKnownGood: true,
|
|
102
|
+
status: {
|
|
103
|
+
source,
|
|
104
|
+
path,
|
|
105
|
+
state: "valid",
|
|
106
|
+
usingLastKnownGood: false,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
return this.invalidSource(source, path, fingerprint, previous, error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
invalidSource(source, path, observedFingerprint, previous, error) {
|
|
115
|
+
const diagnostic = {
|
|
116
|
+
source,
|
|
117
|
+
path,
|
|
118
|
+
message: boundedDiagnosticMessage(error),
|
|
119
|
+
};
|
|
120
|
+
this.onDiagnostic?.(diagnostic);
|
|
121
|
+
return {
|
|
122
|
+
observedFingerprint,
|
|
123
|
+
entries: previous?.entries ?? {},
|
|
124
|
+
hasLastKnownGood: previous?.hasLastKnownGood ?? false,
|
|
125
|
+
status: {
|
|
126
|
+
source,
|
|
127
|
+
path,
|
|
128
|
+
state: "invalid",
|
|
129
|
+
usingLastKnownGood: previous?.hasLastKnownGood === true,
|
|
130
|
+
},
|
|
131
|
+
diagnostic,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function applySource(servers, origins, masked, entries, source) {
|
|
136
|
+
for (const [name, entry] of Object.entries(entries)) {
|
|
137
|
+
if ("disabled" in entry && entry.disabled === true) {
|
|
138
|
+
delete servers[name];
|
|
139
|
+
delete origins[name];
|
|
140
|
+
masked[name] = source;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
servers[name] = entry;
|
|
144
|
+
origins[name] = source;
|
|
145
|
+
delete masked[name];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function boundedDiagnosticMessage(error) {
|
|
149
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
150
|
+
const normalized = raw.replace(/\s+/g, " ").trim() || "Invalid External MCP configuration.";
|
|
151
|
+
return normalized.length <= MAX_DIAGNOSTIC_LENGTH
|
|
152
|
+
? normalized
|
|
153
|
+
: `${normalized.slice(0, MAX_DIAGNOSTIC_LENGTH - 3)}...`;
|
|
154
|
+
}
|
|
155
|
+
function isMissingFileError(error) {
|
|
156
|
+
return errorCode(error) === "ENOENT";
|
|
157
|
+
}
|
|
158
|
+
function errorCode(error) {
|
|
159
|
+
if (typeof error === "object" && error !== null && "code" in error) {
|
|
160
|
+
const code = error.code;
|
|
161
|
+
if (typeof code === "string" && code)
|
|
162
|
+
return code;
|
|
163
|
+
}
|
|
164
|
+
return "UNKNOWN";
|
|
165
|
+
}
|
|
@@ -4,8 +4,7 @@ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
|
-
import { Client } from "@modelcontextprotocol/
|
|
8
|
-
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
7
|
+
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
|
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";
|
|
@@ -30,7 +29,12 @@ export async function fixture(t, options = {}) {
|
|
|
30
29
|
const project = join(root, "project");
|
|
31
30
|
const agentDir = join(root, "agent");
|
|
32
31
|
const stateDir = join(root, ".state");
|
|
32
|
+
const configDir = join(root, ".config");
|
|
33
33
|
await mkdir(join(project, ".forgerelay", "agents"), { recursive: true });
|
|
34
|
+
await mkdir(configDir, { recursive: true });
|
|
35
|
+
if (options.userConfig) {
|
|
36
|
+
await writeFile(join(configDir, "config.json"), JSON.stringify(options.userConfig, null, 2) + "\n");
|
|
37
|
+
}
|
|
34
38
|
await mkdir(agentDir, { recursive: true });
|
|
35
39
|
await writeFile(join(agentDir, "AGENTS.md"), "global instructions\n");
|
|
36
40
|
await writeFile(join(project, "AGENTS.md"), "project instructions\n");
|
|
@@ -51,7 +55,7 @@ export async function fixture(t, options = {}) {
|
|
|
51
55
|
await git(project, ["commit", "-m", "Initial commit"]);
|
|
52
56
|
}
|
|
53
57
|
const loadedConfig = loadConfig({
|
|
54
|
-
FORGERELAY_CONFIG_DIR:
|
|
58
|
+
FORGERELAY_CONFIG_DIR: configDir,
|
|
55
59
|
FORGERELAY_STATE_DIR: stateDir,
|
|
56
60
|
FORGERELAY_ALLOWED_ROOTS: root,
|
|
57
61
|
FORGERELAY_WORKTREE_ROOT: join(root, ".worktrees"),
|
|
@@ -143,8 +147,9 @@ export async function callOpen(client, path, conversationScopeId, mode, newWorkt
|
|
|
143
147
|
return client.callTool(params);
|
|
144
148
|
}
|
|
145
149
|
export function structuredContent(result) {
|
|
146
|
-
|
|
147
|
-
|
|
150
|
+
const structured = result.structuredContent;
|
|
151
|
+
assert.ok(structured);
|
|
152
|
+
return structured;
|
|
148
153
|
}
|
|
149
154
|
export function responseText(result) {
|
|
150
155
|
const content = result.content;
|
package/dist/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { realpath } from "node:fs/promises";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
5
5
|
import { registerAppResource } from "@modelcontextprotocol/ext-apps/server";
|
|
6
6
|
import { registerActivityQueryTools } from "./activity/runtime/mcp-query-tools.js";
|
|
7
7
|
import { buildCapabilityFingerprint, loadCapabilityGuides } from "./mcp/server/core/capabilities.js";
|
|
@@ -15,12 +15,14 @@ import { HookRunner } from "./mcp/hooks/hooks.js";
|
|
|
15
15
|
import { checkHookConfiguration } from "./mcp/hooks/hook-cli.js";
|
|
16
16
|
import { buildExecutionShellContext, buildServerInstructions, buildToolDescriptions, toolNames } from "./mcp/server-instructions.js";
|
|
17
17
|
import { IncomingArtifactAdapterRegistry } from "./mcp/artifacts/incoming-artifacts.js";
|
|
18
|
+
import { createExternalMcpCapabilityRuntime } from "./mcp/operations/external-mcp/external-mcp-runtime.js";
|
|
18
19
|
import { registerProcessTools } from "./mcp/process/tools.js";
|
|
19
20
|
import { attachCompletedProcessNotices, recordBashCompletion } from "./mcp/process/runtime.js";
|
|
20
21
|
import { CompositeActivityCoordinator } from "./workspaces/composite/composite-activity.js";
|
|
21
22
|
import { CompositeWorkspaceRegistry } from "./workspaces/composite/composite-workspaces.js";
|
|
22
23
|
import { RemoteWorkspaceRelay } from "./workspaces/relay/workspace-relay.js";
|
|
23
|
-
import { hostConversationScopeId } from "./mcp/request-meta.js";
|
|
24
|
+
import { hostContextDeliveryScopeId, hostConversationScopeId } from "./mcp/request-meta.js";
|
|
25
|
+
import { mcpHandlerRequestContext } from "./mcp/request-context.js";
|
|
24
26
|
import { createActivityPanelApp } from "./mcp/panel/app.js";
|
|
25
27
|
import { shutdownHttpServer } from "./mcp/server/transport/server-shutdown.js";
|
|
26
28
|
import { formatPathForPrompt } from "./workspaces/resources/skills.js";
|
|
@@ -46,7 +48,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
46
48
|
const activityPanelApp = createActivityPanelApp(config, FORGERELAY_VERSION);
|
|
47
49
|
const ownsRemoteWorkspaces = options.remoteWorkspaces === undefined;
|
|
48
50
|
const remoteWorkspaces = options.remoteWorkspaces
|
|
49
|
-
?? new RemoteWorkspaceRelay(config.configDir, config.stateDir);
|
|
51
|
+
?? new RemoteWorkspaceRelay(config.configDir, config.stateDir, config.mediaMaxBytes);
|
|
50
52
|
const compositeWorkspaces = options.compositeWorkspaces
|
|
51
53
|
?? new CompositeWorkspaceRegistry(config.stateDir);
|
|
52
54
|
const workspaceTasks = new WorkspaceTaskStore(config.stateDir);
|
|
@@ -109,16 +111,20 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
109
111
|
const reminderWorkspaceId = taskReminderWorkspaceIdFor(target);
|
|
110
112
|
return attachWorkspaceTaskReminder(presented, reminderWorkspaceId ? taskReminders.recordWork(reminderWorkspaceId) : undefined);
|
|
111
113
|
};
|
|
112
|
-
const hostScopeIdFor = (
|
|
113
|
-
const
|
|
114
|
-
|
|
114
|
+
const hostScopeIdFor = (requestContext) => hostConversationScopeId(requestContext, connectionScopeId);
|
|
115
|
+
const contextDeliveryScopeIdFor = (requestContext) => hostContextDeliveryScopeId(requestContext, connectionScopeId);
|
|
116
|
+
const prepareExecutionContext = async (target, requestContext) => {
|
|
117
|
+
const conversationScopeId = hostScopeIdFor(requestContext);
|
|
115
118
|
const turnId = target.compositeWorkspaceId && target.memberName
|
|
116
119
|
? await compositeActivity.prepareMember(target.compositeWorkspaceId, target.memberName, target.executionWorkspaceId, conversationScopeId)
|
|
117
120
|
: undefined;
|
|
118
121
|
return {
|
|
119
|
-
requestMeta,
|
|
120
|
-
signal,
|
|
121
|
-
sessionId,
|
|
122
|
+
requestMeta: requestContext.requestMeta,
|
|
123
|
+
signal: requestContext.signal,
|
|
124
|
+
sessionId: requestContext.transportSessionId,
|
|
125
|
+
requestId: requestContext.requestId,
|
|
126
|
+
protocolEra: requestContext.protocolEra,
|
|
127
|
+
conversationScopeId,
|
|
122
128
|
...(turnId ? { turnId } : {}),
|
|
123
129
|
...(target.memberName ? { activityMember: target.memberName } : {}),
|
|
124
130
|
};
|
|
@@ -128,11 +134,13 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
128
134
|
const incomingArtifactRegistry = new IncomingArtifactAdapterRegistry(incomingArtifactAdapters);
|
|
129
135
|
const artifactDownloadAvailable = config.artifactsEnabled && isArtifactDownloadSupportedPlatform();
|
|
130
136
|
const reviewChangesAvailable = config.widgets === "changes";
|
|
137
|
+
const externalMcpCapability = createExternalMcpCapabilityRuntime(config);
|
|
131
138
|
let batchExecutor;
|
|
132
139
|
const batchExecuteAvailable = config.toolMode !== "codex";
|
|
133
140
|
const capabilityRegistry = createCapabilityRegistry({
|
|
134
141
|
inspectHooks: (workspaceRoot) => checkHookConfiguration(workspaceRoot, config.hooks),
|
|
135
142
|
...subagentMcp.registryDependencies,
|
|
143
|
+
externalMcp: externalMcpCapability,
|
|
136
144
|
workspaceRecovery: {
|
|
137
145
|
available: true,
|
|
138
146
|
run: async (input, context) => ({
|
|
@@ -457,10 +465,11 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
|
|
|
457
465
|
...compact,
|
|
458
466
|
});
|
|
459
467
|
};
|
|
460
|
-
registerAppResource(server, "ForgeRelay Activity Panel", activityPanelApp.uri, activityPanelApp.resourceMetadata, async (uri, extra) => activityPanelApp.readResource(uri.toString(), extra.
|
|
468
|
+
registerAppResource(server, "ForgeRelay Activity Panel", activityPanelApp.uri, activityPanelApp.resourceMetadata, async (uri, extra) => activityPanelApp.readResource(uri.toString(), mcpHandlerRequestContext(extra).transportSessionId));
|
|
461
469
|
registerOpenWorkspaceTool({
|
|
462
470
|
server, config, workspaces, remoteWorkspaces, compositeWorkspaces, workspaceTasks, processSessions,
|
|
463
471
|
capabilityRegistry, compositeTaskGuides, loadCompositeMemberContext, rememberWorkspacePanelState, hostScopeIdFor,
|
|
472
|
+
contextDeliveryScopeIdFor,
|
|
464
473
|
presentation: {
|
|
465
474
|
config, forgerelayVersion: FORGERELAY_VERSION, workspaces, workspaceTasks, reviewCheckpoints,
|
|
466
475
|
capabilityRegistry, subagentProviders, hooks, rememberWorkspacePanelState,
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
export function capabilityActivityAuditRequest(input) {
|
|
2
|
+
if (input.name === "mcp.external") {
|
|
3
|
+
const argumentsValue = isAuditRecord(input.arguments) ? input.arguments : {};
|
|
4
|
+
const callArguments = isAuditRecord(argumentsValue.arguments) ? argumentsValue.arguments : undefined;
|
|
5
|
+
return {
|
|
6
|
+
workspaceId: input.workspaceId,
|
|
7
|
+
name: input.name,
|
|
8
|
+
action: "run",
|
|
9
|
+
arguments: {
|
|
10
|
+
operation: argumentsValue.operation,
|
|
11
|
+
...(typeof argumentsValue.server === "string" ? { server: argumentsValue.server } : {}),
|
|
12
|
+
...(typeof argumentsValue.tool === "string" ? { tool: argumentsValue.tool } : {}),
|
|
13
|
+
...(callArguments ? { argumentKeys: Object.keys(callArguments).sort() } : {}),
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
2
17
|
if (input.name !== "subagent.session") {
|
|
3
18
|
return {
|
|
4
19
|
workspaceId: input.workspaceId,
|
|
@@ -24,6 +39,23 @@ export function capabilityActivityAuditRequest(input) {
|
|
|
24
39
|
};
|
|
25
40
|
}
|
|
26
41
|
export function capabilityActivityAuditResult(name, result) {
|
|
42
|
+
if (name === "mcp.external" && isAuditRecord(result)) {
|
|
43
|
+
const structuredContent = isAuditRecord(result.structuredContent) ? result.structuredContent : undefined;
|
|
44
|
+
const capabilityResult = structuredContent && isAuditRecord(structuredContent.result)
|
|
45
|
+
? structuredContent.result
|
|
46
|
+
: undefined;
|
|
47
|
+
const error = structuredContent && isAuditRecord(structuredContent.error)
|
|
48
|
+
? structuredContent.error
|
|
49
|
+
: undefined;
|
|
50
|
+
return {
|
|
51
|
+
name,
|
|
52
|
+
action: "run",
|
|
53
|
+
...(capabilityResult ? { result: summarizeExternalMcpCapabilityResult(capabilityResult) } : {}),
|
|
54
|
+
...(error
|
|
55
|
+
? { error: { code: error.code } }
|
|
56
|
+
: {}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
27
59
|
if (name !== "subagent.session" || !isAuditRecord(result))
|
|
28
60
|
return result;
|
|
29
61
|
const structuredContent = isAuditRecord(result.structuredContent) ? result.structuredContent : undefined;
|
|
@@ -50,6 +82,55 @@ export function capabilityActivityAuditResult(name, result) {
|
|
|
50
82
|
function isAuditRecord(value) {
|
|
51
83
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
52
84
|
}
|
|
85
|
+
function summarizeExternalMcpCapabilityResult(result) {
|
|
86
|
+
const summary = {
|
|
87
|
+
operation: result.operation,
|
|
88
|
+
...(typeof result.server === "string" ? { server: result.server } : {}),
|
|
89
|
+
...(typeof result.tool === "string" ? { tool: result.tool } : {}),
|
|
90
|
+
};
|
|
91
|
+
if (Array.isArray(result.servers)) {
|
|
92
|
+
summary.servers = result.servers.flatMap((entry) => {
|
|
93
|
+
if (!isAuditRecord(entry) || typeof entry.name !== "string")
|
|
94
|
+
return [];
|
|
95
|
+
return [{ name: entry.name, transport: entry.transport }];
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (Array.isArray(result.tools)) {
|
|
99
|
+
summary.tools = result.tools.flatMap((entry) => isAuditRecord(entry) && typeof entry.name === "string" ? [entry.name] : []);
|
|
100
|
+
}
|
|
101
|
+
if (Array.isArray(result.content)) {
|
|
102
|
+
summary.contentTypes = result.content.flatMap((entry) => isAuditRecord(entry) && typeof entry.type === "string" ? [entry.type] : []);
|
|
103
|
+
const media = result.content.flatMap((entry, index) => {
|
|
104
|
+
if (!isAuditRecord(entry)
|
|
105
|
+
|| entry.type !== "image"
|
|
106
|
+
|| typeof entry.mimeType !== "string"
|
|
107
|
+
|| typeof entry.bytes !== "number")
|
|
108
|
+
return [];
|
|
109
|
+
return [{ index, mimeType: entry.mimeType, bytes: entry.bytes }];
|
|
110
|
+
});
|
|
111
|
+
if (media.length > 0)
|
|
112
|
+
summary.media = media;
|
|
113
|
+
}
|
|
114
|
+
if (Array.isArray(result.transforms)) {
|
|
115
|
+
const transforms = result.transforms.flatMap((entry) => {
|
|
116
|
+
if (!isAuditRecord(entry)
|
|
117
|
+
|| (entry.phase !== "request" && entry.phase !== "result")
|
|
118
|
+
|| typeof entry.name !== "string"
|
|
119
|
+
|| (entry.scope !== "global" && entry.scope !== "project")
|
|
120
|
+
|| entry.status !== "passed")
|
|
121
|
+
return [];
|
|
122
|
+
return [{
|
|
123
|
+
phase: entry.phase,
|
|
124
|
+
name: entry.name.slice(0, 200),
|
|
125
|
+
scope: entry.scope,
|
|
126
|
+
status: "passed",
|
|
127
|
+
}];
|
|
128
|
+
});
|
|
129
|
+
if (transforms.length > 0)
|
|
130
|
+
summary.transforms = transforms;
|
|
131
|
+
}
|
|
132
|
+
return summary;
|
|
133
|
+
}
|
|
53
134
|
function summarizeSubagentCapabilityResult(result) {
|
|
54
135
|
const summary = { operation: result.operation };
|
|
55
136
|
const session = isAuditRecord(result.session) ? result.session : undefined;
|