hermes-action-bridge 0.3.0 → 0.5.0
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 +70 -0
- package/README.md +123 -22
- package/dist/adapters/hermes-cli.d.ts +14 -0
- package/dist/adapters/hermes-cli.js +80 -21
- package/dist/adapters/hermes-cli.js.map +1 -1
- package/dist/approvals.d.ts +35 -0
- package/dist/approvals.js +96 -0
- package/dist/approvals.js.map +1 -0
- package/dist/audit-log.d.ts +21 -0
- package/dist/audit-log.js +31 -0
- package/dist/audit-log.js.map +1 -0
- package/dist/capabilities.d.ts +29 -0
- package/dist/capabilities.js +24 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/cli.js +205 -20
- package/dist/cli.js.map +1 -1
- package/dist/config.js +0 -6
- package/dist/config.js.map +1 -1
- package/dist/doctor.d.ts +8 -1
- package/dist/doctor.js +76 -13
- package/dist/doctor.js.map +1 -1
- package/dist/execution-gate.d.ts +5 -0
- package/dist/execution-gate.js +20 -0
- package/dist/execution-gate.js.map +1 -0
- package/dist/http-server.d.ts +18 -0
- package/dist/http-server.js +310 -0
- package/dist/http-server.js.map +1 -0
- package/dist/install/launcher.d.ts +3 -0
- package/dist/install/launcher.js +7 -0
- package/dist/install/launcher.js.map +1 -0
- package/dist/install/mcp-config.js +12 -3
- package/dist/install/mcp-config.js.map +1 -1
- package/dist/install/mcp-service.d.ts +44 -0
- package/dist/install/mcp-service.js +172 -0
- package/dist/install/mcp-service.js.map +1 -0
- package/dist/install/templates.js +38 -32
- package/dist/install/templates.js.map +1 -1
- package/dist/install/types.d.ts +1 -0
- package/dist/jobs.d.ts +51 -0
- package/dist/jobs.js +244 -0
- package/dist/jobs.js.map +1 -0
- package/dist/mcp-server.d.ts +32 -0
- package/dist/mcp-server.js +394 -25
- package/dist/mcp-server.js.map +1 -1
- package/dist/policy.js +9 -2
- package/dist/policy.js.map +1 -1
- package/dist/run.js +0 -1
- package/dist/run.js.map +1 -1
- package/dist/types.d.ts +2 -4
- package/dist/validation.d.ts +1 -0
- package/dist/validation.js +8 -0
- package/dist/validation.js.map +1 -0
- package/docs/ARCHITECTURE.md +69 -9
- package/docs/FUNCTIONAL-TESTS.md +27 -12
- package/examples/claude-code/SKILL.md +35 -29
- package/examples/codex/SKILL.md +35 -29
- package/package.json +5 -3
- package/plugins/hermes-action/.codex-plugin/plugin.json +30 -0
- package/plugins/hermes-action/.mcp.json +8 -0
- package/plugins/hermes-action/assets/app-icon.png +0 -0
- package/plugins/hermes-action/skills/hermes-action-bridge/SKILL.md +61 -0
package/dist/config.js
CHANGED
|
@@ -36,7 +36,6 @@ const rawConfigSchema = z.object({
|
|
|
36
36
|
defaults: z
|
|
37
37
|
.object({
|
|
38
38
|
mode: modeSchema.default("plan"),
|
|
39
|
-
profile: z.string().min(1).optional(),
|
|
40
39
|
source: z.string().min(1).default("external-agent"),
|
|
41
40
|
max_turns: z.number().int().positive().default(30),
|
|
42
41
|
preset: z.string().min(1).default("default"),
|
|
@@ -49,7 +48,6 @@ const rawConfigSchema = z.object({
|
|
|
49
48
|
toolsets: z.array(z.string()).default([]),
|
|
50
49
|
provider: z.string().optional(),
|
|
51
50
|
model: z.string().optional(),
|
|
52
|
-
profile: z.string().optional(),
|
|
53
51
|
max_turns: z.number().int().positive().optional(),
|
|
54
52
|
source: z.string().optional(),
|
|
55
53
|
require_approval_for: z.array(riskSchema).optional(),
|
|
@@ -132,7 +130,6 @@ function toRaw(config) {
|
|
|
132
130
|
},
|
|
133
131
|
defaults: {
|
|
134
132
|
mode: config.defaults.mode,
|
|
135
|
-
profile: config.defaults.profile,
|
|
136
133
|
source: config.defaults.source,
|
|
137
134
|
max_turns: config.defaults.maxTurns,
|
|
138
135
|
preset: config.defaults.preset,
|
|
@@ -145,7 +142,6 @@ function toRaw(config) {
|
|
|
145
142
|
toolsets: preset.toolsets,
|
|
146
143
|
provider: preset.provider,
|
|
147
144
|
model: preset.model,
|
|
148
|
-
profile: preset.profile,
|
|
149
145
|
max_turns: preset.maxTurns,
|
|
150
146
|
source: preset.source,
|
|
151
147
|
require_approval_for: preset.requireApprovalFor,
|
|
@@ -164,7 +160,6 @@ function normalizeConfig(raw) {
|
|
|
164
160
|
},
|
|
165
161
|
defaults: {
|
|
166
162
|
mode: raw.defaults.mode,
|
|
167
|
-
profile: raw.defaults.profile,
|
|
168
163
|
source: raw.defaults.source,
|
|
169
164
|
maxTurns: raw.defaults.max_turns,
|
|
170
165
|
preset: raw.defaults.preset,
|
|
@@ -177,7 +172,6 @@ function normalizeConfig(raw) {
|
|
|
177
172
|
toolsets: preset.toolsets,
|
|
178
173
|
provider: preset.provider,
|
|
179
174
|
model: preset.model,
|
|
180
|
-
profile: preset.profile,
|
|
181
175
|
maxTurns: preset.max_turns,
|
|
182
176
|
source: preset.source,
|
|
183
177
|
requireApprovalFor: preset.require_approval_for,
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAC5E,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC;IACxB,kBAAkB;IAClB,cAAc;IACd,YAAY;IACZ,QAAQ;IACR,SAAS;IACT,UAAU;IACV,mBAAmB;CACpB,CAAC,CAAC;AAEH,MAAM,yBAAyB,GAAmB;IAChD,kBAAkB;IAClB,cAAc;IACd,YAAY;IACZ,QAAQ;IACR,SAAS;IACT,UAAU;IACV,mBAAmB;CACpB,CAAC;AAEF,8HAA8H;AAC9H,MAAM,sBAAsB,GAAG,OAAO,CAAC;AAEvC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,OAAO,EAAE,CAAC;SACP,MAAM,CAAC;QACN,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC;QACtD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC5C,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,sBAAsB,CAAC;QAC9E,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;KACxD,CAAC;SACD,OAAO,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,CAAC;IACnG,QAAQ,EAAE,CAAC;SACR,MAAM,CAAC;QACN,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC;QAChC,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAC5E,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC;IACxB,kBAAkB;IAClB,cAAc;IACd,YAAY;IACZ,QAAQ;IACR,SAAS;IACT,UAAU;IACV,mBAAmB;CACpB,CAAC,CAAC;AAEH,MAAM,yBAAyB,GAAmB;IAChD,kBAAkB;IAClB,cAAc;IACd,YAAY;IACZ,QAAQ;IACR,SAAS;IACT,UAAU;IACV,mBAAmB;CACpB,CAAC;AAEF,8HAA8H;AAC9H,MAAM,sBAAsB,GAAG,OAAO,CAAC;AAEvC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,OAAO,EAAE,CAAC;SACP,MAAM,CAAC;QACN,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC;QACtD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC5C,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,sBAAsB,CAAC;QAC9E,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;KACxD,CAAC;SACD,OAAO,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,CAAC;IACnG,QAAQ,EAAE,CAAC;SACR,MAAM,CAAC;QACN,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC;QAChC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC;QACnD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;QAClD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;KAC7C,CAAC;SACD,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IACxF,OAAO,EAAE,CAAC;SACP,MAAM,CACL,CAAC,CAAC,MAAM,EAAE,EACV,CAAC,CAAC,MAAM,CAAC;QACP,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAClC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;QACzC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC/B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC5B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;QACjD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC7B,oBAAoB,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,QAAQ,EAAE;KACrD,CAAC,CACH;SACA,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,CAAC;IACrD,MAAM,EAAE,CAAC;SACN,MAAM,CAAC;QACN,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;QAChC,oBAAoB,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,yBAAyB,CAAC;KAC7E,CAAC;SACD,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,CAAC;CAC7E,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,aAAa,GAAiB;IACzC,OAAO,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,sBAAsB,EAAE;IAC9F,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE;IACrF,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE;IAClD,MAAM,EAAE;QACN,IAAI,EAAE,KAAK;QACX,kBAAkB,EAAE,CAAC,GAAG,yBAAyB,CAAC;KACnD;CACF,CAAC;AAEF,MAAM,UAAU,oBAAoB;IAClC,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,OAAO,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,sBAAsB,EAAE;QAChG,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE;QACtF,OAAO,EAAE;YACP,OAAO,EAAE,EAAE,WAAW,EAAE,8DAA8D,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;YAClH,QAAQ,EAAE,EAAE,WAAW,EAAE,iCAAiC,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE;YAC/G,MAAM,EAAE,EAAE,WAAW,EAAE,+CAA+C,EAAE,MAAM,EAAE,CAAC,oCAAoC,EAAE,mBAAmB,CAAC,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE;SAC9K;QACD,MAAM,EAAE;YACN,IAAI,EAAE,KAAK;YACX,oBAAoB,EAAE,yBAAyB;SAChD;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,YAAqB;IACnE,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IACpE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAA0B,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,eAAe,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAChI,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7C,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,YAAqB;IACpE,IAAI,YAAY;QAAE,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IACtE,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,EAAE,aAAa,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC,CAAC;AACvF,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAY,CAAC;IAC9C,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC;IACtF,OAAO,MAAiC,CAAC;AAC3C,CAAC;AAED,SAAS,SAAS,CAAC,IAA6B,EAAE,KAA8B;IAC9E,MAAM,MAAM,GAA4B,EAAE,GAAG,IAAI,EAAE,CAAC;IACpD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,aAAa,CAAC,OAAO,CAAC,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;YACnD,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC1C,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACtB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,KAAK,CAAC,MAAoB;IACjC,OAAO;QACL,OAAO,EAAE;YACP,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO;YAC/B,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO;YAC/B,iBAAiB,EAAE,MAAM,CAAC,OAAO,CAAC,eAAe;YACjD,eAAe,EAAE,MAAM,CAAC,OAAO,CAAC,cAAc;SAC/C;QACD,QAAQ,EAAE;YACR,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI;YAC1B,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM;YAC9B,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ;YACnC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM;SAC/B;QACD,OAAO,EAAE,MAAM,CAAC,WAAW,CACzB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;YACrD,IAAI;YACJ;gBACE,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,SAAS,EAAE,MAAM,CAAC,QAAQ;gBAC1B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,oBAAoB,EAAE,MAAM,CAAC,kBAAkB;aAChD;SACF,CAAC,CACH;QACD,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,oBAAoB,EAAE,MAAM,CAAC,MAAM,CAAC,kBAAkB,EAAE;KAC7F,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,GAAc;IACrC,OAAO;QACL,OAAO,EAAE;YACP,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO;YAC5B,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO;YAC5B,eAAe,EAAE,GAAG,CAAC,OAAO,CAAC,iBAAiB;YAC9C,cAAc,EAAE,GAAG,CAAC,OAAO,CAAC,eAAe;SAC5C;QACD,QAAQ,EAAE;YACR,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,IAAI;YACvB,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,MAAM;YAC3B,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,SAAS;YAChC,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,MAAM;SAC5B;QACD,OAAO,EAAE,MAAM,CAAC,WAAW,CACzB,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;YAClD,IAAI;YACJ;gBACE,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,QAAQ,EAAE,MAAM,CAAC,SAAS;gBAC1B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,kBAAkB,EAAE,MAAM,CAAC,oBAAoB;aAChD;SACF,CAAC,CACH;QACD,MAAM,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,oBAAoB,EAAE;KACvF,CAAC;AACJ,CAAC"}
|
package/dist/doctor.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type McpCommandRunner, type McpLauncher } from "./install/mcp-service.js";
|
|
1
2
|
import type { BridgeConfig } from "./types.js";
|
|
2
3
|
import type { PathContext } from "./install/types.js";
|
|
3
4
|
export type CheckStatus = "pass" | "warn" | "fail";
|
|
@@ -10,9 +11,15 @@ export interface DoctorReport {
|
|
|
10
11
|
ok: boolean;
|
|
11
12
|
checks: DoctorCheck[];
|
|
12
13
|
}
|
|
14
|
+
export interface DoctorOptions {
|
|
15
|
+
launcher?: McpLauncher | undefined;
|
|
16
|
+
commandRunner?: McpCommandRunner | undefined;
|
|
17
|
+
}
|
|
13
18
|
/** Synchronous environment checks. The optional `--probe` check is added separately by the caller. */
|
|
14
|
-
export declare function coreChecks(config: BridgeConfig | null, ctx: PathContext, configError?: string): DoctorCheck[];
|
|
19
|
+
export declare function coreChecks(config: BridgeConfig | null, ctx: PathContext, configError?: string, options?: DoctorOptions): DoctorCheck[];
|
|
15
20
|
export declare function toReport(checks: DoctorCheck[]): DoctorReport;
|
|
16
21
|
/** Optional live check (spends provider tokens): ask Hermes to echo a sentinel through the full pipeline. */
|
|
17
22
|
export declare function probeCheck(config: BridgeConfig): Promise<DoctorCheck>;
|
|
23
|
+
/** Token-free MCP initialize/list-tools handshake against the current server implementation. */
|
|
24
|
+
export declare function mcpHandshakeCheck(config: BridgeConfig): Promise<DoctorCheck>;
|
|
18
25
|
export declare function formatDoctor(report: DoctorReport): string;
|
package/dist/doctor.js
CHANGED
|
@@ -1,19 +1,29 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
2
4
|
import { defaultConfig } from "./config.js";
|
|
3
5
|
import { checkHermesStatus, versionProbeTimeoutMs } from "./status.js";
|
|
4
6
|
import { buildEffectiveRun, defaultTimeoutSeconds } from "./run.js";
|
|
5
7
|
import { runHermesCli } from "./adapters/hermes-cli.js";
|
|
6
8
|
import { skillStates } from "./install/install-service.js";
|
|
9
|
+
import { inspectMcp } from "./install/mcp-service.js";
|
|
10
|
+
import { createBridgeMcpServer } from "./mcp-server.js";
|
|
7
11
|
const minNodeMajor = 20;
|
|
8
12
|
const agentCommand = { "claude-code": "claude", codex: "codex" };
|
|
9
13
|
/** Synchronous environment checks. The optional `--probe` check is added separately by the caller. */
|
|
10
|
-
export function coreChecks(config, ctx, configError) {
|
|
14
|
+
export function coreChecks(config, ctx, configError, options = {}) {
|
|
11
15
|
const checks = [nodeCheck(), configCheck(config, configError), hermesCheck(config ?? defaultConfig), limitsCheck(config ?? defaultConfig)];
|
|
16
|
+
const skills = new Map(skillStates(ctx, "global").map(({ agent, state }) => [agent, state]));
|
|
12
17
|
for (const agent of Object.keys(agentCommand)) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
const availability = agentAvailabilityCheck(agent, options.commandRunner);
|
|
19
|
+
const skillState = skills.get(agent) ?? "absent";
|
|
20
|
+
const registration = options.launcher
|
|
21
|
+
? inspectMcp(agent, options.launcher, options.commandRunner)
|
|
22
|
+
: undefined;
|
|
23
|
+
checks.push(availability.check);
|
|
24
|
+
checks.push(skillCheck(agent, skillState, availability.available, registration));
|
|
25
|
+
if (registration)
|
|
26
|
+
checks.push(mcpCheck(registration, skillState));
|
|
17
27
|
}
|
|
18
28
|
return checks;
|
|
19
29
|
}
|
|
@@ -34,6 +44,30 @@ export async function probeCheck(config) {
|
|
|
34
44
|
const ok = result.ok && `${result.stdout}${result.stderr}`.includes("BRIDGE_OK");
|
|
35
45
|
return { id: "probe", status: ok ? "pass" : "warn", detail: ok ? "Hermes responded" : `exit ${result.exitCode}` };
|
|
36
46
|
}
|
|
47
|
+
/** Token-free MCP initialize/list-tools handshake against the current server implementation. */
|
|
48
|
+
export async function mcpHandshakeCheck(config) {
|
|
49
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
50
|
+
const server = createBridgeMcpServer(config);
|
|
51
|
+
const client = new Client({ name: "hermes-action-doctor", version: "1.0.0" });
|
|
52
|
+
try {
|
|
53
|
+
await server.connect(serverTransport);
|
|
54
|
+
await client.connect(clientTransport);
|
|
55
|
+
const tools = await client.listTools();
|
|
56
|
+
const names = new Set(tools.tools.map((tool) => tool.name));
|
|
57
|
+
const required = ["hermes_run", "hermes_plan", "hermes_capabilities", "hermes_status"];
|
|
58
|
+
const missing = required.filter((name) => !names.has(name));
|
|
59
|
+
return missing.length === 0
|
|
60
|
+
? { id: "mcp:handshake", status: "pass", detail: `${tools.tools.length} tools available` }
|
|
61
|
+
: { id: "mcp:handshake", status: "fail", detail: `missing tools: ${missing.join(", ")}` };
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return { id: "mcp:handshake", status: "fail", detail: error instanceof Error ? error.message : String(error) };
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
await client.close().catch(() => undefined);
|
|
68
|
+
await server.close().catch(() => undefined);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
37
71
|
export function formatDoctor(report) {
|
|
38
72
|
const label = { pass: "ok", warn: "warn", fail: "FAIL" };
|
|
39
73
|
const lines = report.checks.map((check) => `[${label[check.status]}] ${check.id}${check.detail ? `: ${check.detail}` : ""}`);
|
|
@@ -62,14 +96,23 @@ function limitsCheck(config) {
|
|
|
62
96
|
: `${defaultTimeoutSeconds("plan")}s plan / ${defaultTimeoutSeconds("execute")}s execute`;
|
|
63
97
|
return { id: "limits", status: "pass", detail: `context ≤ ${config.runtime.maxContextBytes} bytes, timeout ${timeout}` };
|
|
64
98
|
}
|
|
65
|
-
function agentAvailabilityCheck(agent) {
|
|
99
|
+
function agentAvailabilityCheck(agent, runner) {
|
|
66
100
|
const command = agentCommand[agent];
|
|
67
|
-
const result =
|
|
101
|
+
const result = runner
|
|
102
|
+
? runner(command, ["--version"])
|
|
103
|
+
: (() => {
|
|
104
|
+
const process = spawnSync(command, ["--version"], { encoding: "utf8", timeout: versionProbeTimeoutMs });
|
|
105
|
+
const error = process.error
|
|
106
|
+
? { code: process.error.code, message: process.error.message }
|
|
107
|
+
: undefined;
|
|
108
|
+
return { status: process.status, stdout: process.stdout ?? "", stderr: process.stderr ?? "", ...(error ? { error } : {}) };
|
|
109
|
+
})();
|
|
68
110
|
if (result.error)
|
|
69
|
-
return { id: agent, status: "warn", detail: `${command} not found on PATH` };
|
|
70
|
-
if (result.status !== 0)
|
|
71
|
-
return { id: agent, status: "warn", detail: `${command} --version exited with ${result.status ??
|
|
72
|
-
|
|
111
|
+
return { check: { id: agent, status: "warn", detail: `${command} not found on PATH` }, available: false };
|
|
112
|
+
if (result.status !== 0) {
|
|
113
|
+
return { check: { id: agent, status: "warn", detail: `${command} --version exited with ${result.status ?? "unknown status"}` }, available: false };
|
|
114
|
+
}
|
|
115
|
+
return { check: { id: agent, status: "pass", detail: result.stdout.trim() || result.stderr.trim() }, available: true };
|
|
73
116
|
}
|
|
74
117
|
const skillStateDetail = {
|
|
75
118
|
current: "up to date",
|
|
@@ -78,7 +121,27 @@ const skillStateDetail = {
|
|
|
78
121
|
"user-modified": "modified locally",
|
|
79
122
|
foreign: "a non-managed file is present",
|
|
80
123
|
};
|
|
81
|
-
function skillCheck(agent, state) {
|
|
82
|
-
|
|
124
|
+
function skillCheck(agent, state, agentAvailable, registration) {
|
|
125
|
+
const unusedTarget = state === "absent" && !agentAvailable && (!registration || registration.state === "unavailable");
|
|
126
|
+
return {
|
|
127
|
+
id: `skill:${agent}`,
|
|
128
|
+
status: state === "current" ? "pass" : unusedTarget ? "warn" : "fail",
|
|
129
|
+
detail: skillStateDetail[state],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function mcpCheck(registration, skillState) {
|
|
133
|
+
const unusedTarget = registration.state === "unavailable" && skillState === "absent";
|
|
134
|
+
const detail = {
|
|
135
|
+
absent: "not registered (run: hermes-action install all)",
|
|
136
|
+
current: "registered with the current bridge launcher",
|
|
137
|
+
conflict: registration.detail ?? "a different hermes-action registration is present",
|
|
138
|
+
unavailable: registration.detail ?? "agent CLI is unavailable",
|
|
139
|
+
error: registration.detail ?? "registration could not be inspected",
|
|
140
|
+
};
|
|
141
|
+
return {
|
|
142
|
+
id: `mcp:${registration.agent}`,
|
|
143
|
+
status: registration.state === "current" ? "pass" : unusedTarget ? "warn" : "fail",
|
|
144
|
+
detail: detail[registration.state],
|
|
145
|
+
};
|
|
83
146
|
}
|
|
84
147
|
//# sourceMappingURL=doctor.js.map
|
package/dist/doctor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export interface ExecutionGate {
|
|
2
|
+
run<T>(operation: () => Promise<T>): Promise<T>;
|
|
3
|
+
}
|
|
4
|
+
/** Reject excess direct MCP work immediately; asynchronous jobs use their own bounded queue. */
|
|
5
|
+
export declare function createExecutionGate(maxConcurrent?: number): ExecutionGate;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { positiveInteger } from "./validation.js";
|
|
2
|
+
/** Reject excess direct MCP work immediately; asynchronous jobs use their own bounded queue. */
|
|
3
|
+
export function createExecutionGate(maxConcurrent = 4) {
|
|
4
|
+
const limit = positiveInteger(maxConcurrent, 4, "maxConcurrent");
|
|
5
|
+
let active = 0;
|
|
6
|
+
return {
|
|
7
|
+
async run(operation) {
|
|
8
|
+
if (active >= limit)
|
|
9
|
+
throw new Error(`Hermes direct execution limit reached (${limit})`);
|
|
10
|
+
active += 1;
|
|
11
|
+
try {
|
|
12
|
+
return await operation();
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
active -= 1;
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=execution-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execution-gate.js","sourceRoot":"","sources":["../src/execution-gate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAMlD,gGAAgG;AAChG,MAAM,UAAU,mBAAmB,CAAC,gBAAwB,CAAC;IAC3D,MAAM,KAAK,GAAG,eAAe,CAAC,aAAa,EAAE,CAAC,EAAE,eAAe,CAAC,CAAC;IACjE,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,OAAO;QACL,KAAK,CAAC,GAAG,CAAI,SAA2B;YACtC,IAAI,MAAM,IAAI,KAAK;gBAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,KAAK,GAAG,CAAC,CAAC;YACzF,MAAM,IAAI,CAAC,CAAC;YACZ,IAAI,CAAC;gBACH,OAAO,MAAM,SAAS,EAAE,CAAC;YAC3B,CAAC;oBAAS,CAAC;gBACT,MAAM,IAAI,CAAC,CAAC;YACd,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { BridgeConfig } from "./types.js";
|
|
2
|
+
export interface BridgeHttpOptions {
|
|
3
|
+
config: BridgeConfig;
|
|
4
|
+
listen: string;
|
|
5
|
+
port: number;
|
|
6
|
+
allowTailnet: boolean;
|
|
7
|
+
tokenEnv?: string | undefined;
|
|
8
|
+
maxBodyBytes?: number | undefined;
|
|
9
|
+
maxSessions?: number | undefined;
|
|
10
|
+
sessionTtlMs?: number | undefined;
|
|
11
|
+
}
|
|
12
|
+
export interface BridgeHttpHandle {
|
|
13
|
+
host: string;
|
|
14
|
+
port: number;
|
|
15
|
+
close(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export declare function startBridgeHttpServer(options: BridgeHttpOptions): Promise<BridgeHttpHandle>;
|
|
18
|
+
export declare function isTailscaleIpv4(address: string | undefined): boolean;
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { createBridgeMcpServer, createBridgeRuntime } from "./mcp-server.js";
|
|
6
|
+
import { positiveInteger } from "./validation.js";
|
|
7
|
+
const defaultMaxBodyBytes = 1024 * 1024;
|
|
8
|
+
const defaultMaxSessions = 64;
|
|
9
|
+
const defaultSessionTtlMs = 30 * 60 * 1000;
|
|
10
|
+
const tokenNamePattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
11
|
+
export async function startBridgeHttpServer(options) {
|
|
12
|
+
validateNetworkOptions(options);
|
|
13
|
+
const token = readBearerToken(options.tokenEnv);
|
|
14
|
+
const tailnetMode = !isLoopback(options.listen);
|
|
15
|
+
if (tailnetMode && !token)
|
|
16
|
+
throw new Error("A bearer token is required for direct tailnet listening.");
|
|
17
|
+
const principalId = token ? `token:${createHash("sha256").update(token).digest("hex").slice(0, 16)}` : "loopback";
|
|
18
|
+
const maxBodyBytes = positiveInteger(options.maxBodyBytes, defaultMaxBodyBytes, "maxBodyBytes");
|
|
19
|
+
const maxSessions = positiveInteger(options.maxSessions, defaultMaxSessions, "maxSessions");
|
|
20
|
+
const sessionTtlMs = positiveInteger(options.sessionTtlMs, defaultSessionTtlMs, "sessionTtlMs");
|
|
21
|
+
const sessions = new Map();
|
|
22
|
+
const runtime = createBridgeRuntime(options.config);
|
|
23
|
+
const context = {
|
|
24
|
+
options,
|
|
25
|
+
token,
|
|
26
|
+
tailnetMode,
|
|
27
|
+
principalId,
|
|
28
|
+
maxBodyBytes,
|
|
29
|
+
maxSessions,
|
|
30
|
+
sessionTtlMs,
|
|
31
|
+
sessions,
|
|
32
|
+
pendingInitializations: 0,
|
|
33
|
+
runtime,
|
|
34
|
+
};
|
|
35
|
+
const server = createServer((request, response) => void handleRequest(request, response, context));
|
|
36
|
+
const sessionCleanupTimer = setInterval(() => expireSessions(context), Math.min(sessionTtlMs, 60_000));
|
|
37
|
+
sessionCleanupTimer.unref();
|
|
38
|
+
await listen(server, options.listen, options.port);
|
|
39
|
+
const address = server.address();
|
|
40
|
+
if (!address || typeof address === "string")
|
|
41
|
+
throw new Error("Could not determine the HTTP listener address.");
|
|
42
|
+
return {
|
|
43
|
+
host: options.listen,
|
|
44
|
+
port: address.port,
|
|
45
|
+
close: async () => {
|
|
46
|
+
clearInterval(sessionCleanupTimer);
|
|
47
|
+
try {
|
|
48
|
+
await Promise.allSettled([...sessions.values()].map(closeSession));
|
|
49
|
+
sessions.clear();
|
|
50
|
+
await closeServer(server);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
await runtime.dispose();
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async function handleRequest(request, response, context) {
|
|
59
|
+
try {
|
|
60
|
+
if (request.url !== "/mcp")
|
|
61
|
+
return sendStatus(response, 404, "Not Found");
|
|
62
|
+
const host = requestHost(request);
|
|
63
|
+
if (!host || !validHost(host, context.options.listen))
|
|
64
|
+
return sendStatus(response, 421, "Misdirected Request");
|
|
65
|
+
if (context.tailnetMode && !isTailscaleIpv4(normalizeRemoteAddress(request.socket.remoteAddress))) {
|
|
66
|
+
return sendStatus(response, 403, "Forbidden");
|
|
67
|
+
}
|
|
68
|
+
if (isTailscaleServeHost(host) && !context.token) {
|
|
69
|
+
response.setHeader("WWW-Authenticate", "Bearer");
|
|
70
|
+
return sendStatus(response, 401, "Tailscale Serve forwarding requires bearer authentication");
|
|
71
|
+
}
|
|
72
|
+
if (context.token && !validBearer(request.headers.authorization, context.token)) {
|
|
73
|
+
response.setHeader("WWW-Authenticate", "Bearer");
|
|
74
|
+
return sendStatus(response, 401, "Unauthorized");
|
|
75
|
+
}
|
|
76
|
+
const sessionId = singleHeader(request.headers["mcp-session-id"]);
|
|
77
|
+
if (request.method === "POST") {
|
|
78
|
+
const body = await readJsonBody(request, context.maxBodyBytes);
|
|
79
|
+
if (sessionId) {
|
|
80
|
+
const session = context.sessions.get(sessionId);
|
|
81
|
+
if (!session)
|
|
82
|
+
return sendMcpError(response, 404, "Unknown MCP session");
|
|
83
|
+
await handleSessionRequest(session, () => session.transport.handleRequest(request, response, body));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!isInitializeRequest(body))
|
|
87
|
+
return sendMcpError(response, 400, "MCP initialization required");
|
|
88
|
+
expireSessions(context);
|
|
89
|
+
if (context.sessions.size + context.pendingInitializations >= context.maxSessions) {
|
|
90
|
+
return sendMcpError(response, 503, "MCP session limit reached");
|
|
91
|
+
}
|
|
92
|
+
context.pendingInitializations += 1;
|
|
93
|
+
try {
|
|
94
|
+
await initializeSession(request, response, body, context);
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
context.pendingInitializations -= 1;
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (request.method === "GET" || request.method === "DELETE") {
|
|
102
|
+
if (!sessionId)
|
|
103
|
+
return sendMcpError(response, 400, "MCP session ID required");
|
|
104
|
+
const session = context.sessions.get(sessionId);
|
|
105
|
+
if (!session)
|
|
106
|
+
return sendMcpError(response, 404, "Unknown MCP session");
|
|
107
|
+
await handleSessionRequest(session, () => session.transport.handleRequest(request, response));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
response.setHeader("Allow", "GET, POST, DELETE");
|
|
111
|
+
sendStatus(response, 405, "Method Not Allowed");
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
const status = error instanceof RequestError ? error.status : 500;
|
|
115
|
+
if (!response.headersSent)
|
|
116
|
+
sendMcpError(response, status, status === 500 ? "Internal server error" : errorMessage(error));
|
|
117
|
+
else
|
|
118
|
+
response.end();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function initializeSession(request, response, body, context) {
|
|
122
|
+
const server = createBridgeMcpServer(context.options.config, {
|
|
123
|
+
transport: "http",
|
|
124
|
+
principalId: context.principalId,
|
|
125
|
+
runtime: context.runtime,
|
|
126
|
+
});
|
|
127
|
+
const transport = new StreamableHTTPServerTransport({
|
|
128
|
+
sessionIdGenerator: randomUUID,
|
|
129
|
+
onsessioninitialized: (sessionId) => {
|
|
130
|
+
context.sessions.set(sessionId, { transport, server, lastActivityAt: Date.now(), activeRequests: 1 });
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
transport.onclose = () => {
|
|
134
|
+
const sessionId = transport.sessionId;
|
|
135
|
+
if (sessionId)
|
|
136
|
+
context.sessions.delete(sessionId);
|
|
137
|
+
};
|
|
138
|
+
// SDK 1.x exposes `onclose` as an accessor returning `undefined`, which is
|
|
139
|
+
// structurally narrower than `Transport` under exactOptionalPropertyTypes.
|
|
140
|
+
await server.connect(transport);
|
|
141
|
+
try {
|
|
142
|
+
await transport.handleRequest(request, response, body);
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
const sessionId = transport.sessionId;
|
|
146
|
+
const session = sessionId ? context.sessions.get(sessionId) : undefined;
|
|
147
|
+
if (session) {
|
|
148
|
+
session.activeRequests = Math.max(0, session.activeRequests - 1);
|
|
149
|
+
session.lastActivityAt = Date.now();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function expireSessions(context) {
|
|
154
|
+
const cutoff = Date.now() - context.sessionTtlMs;
|
|
155
|
+
for (const [sessionId, session] of context.sessions) {
|
|
156
|
+
if (session.activeRequests > 0 || session.lastActivityAt > cutoff)
|
|
157
|
+
continue;
|
|
158
|
+
context.sessions.delete(sessionId);
|
|
159
|
+
void closeSession(session).catch(() => undefined);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function handleSessionRequest(session, handle) {
|
|
163
|
+
session.activeRequests += 1;
|
|
164
|
+
session.lastActivityAt = Date.now();
|
|
165
|
+
try {
|
|
166
|
+
await handle();
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
session.activeRequests = Math.max(0, session.activeRequests - 1);
|
|
170
|
+
session.lastActivityAt = Date.now();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function closeSession(session) {
|
|
174
|
+
try {
|
|
175
|
+
await session.transport.close();
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
await session.server.close();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function validateNetworkOptions(options) {
|
|
182
|
+
if (!Number.isInteger(options.port) || options.port < 0 || options.port > 65_535) {
|
|
183
|
+
throw new Error("port must be an integer between 0 and 65535");
|
|
184
|
+
}
|
|
185
|
+
if (isLoopback(options.listen))
|
|
186
|
+
return;
|
|
187
|
+
if (!options.allowTailnet)
|
|
188
|
+
throw new Error("Non-loopback listening requires --allow-tailnet.");
|
|
189
|
+
if (!isTailscaleIpv4(options.listen)) {
|
|
190
|
+
throw new Error("Direct remote listening is restricted to a Tailscale IPv4 address in 100.64.0.0/10.");
|
|
191
|
+
}
|
|
192
|
+
if (!options.tokenEnv)
|
|
193
|
+
throw new Error("Direct tailnet listening requires --token-env.");
|
|
194
|
+
}
|
|
195
|
+
function readBearerToken(name) {
|
|
196
|
+
if (!name)
|
|
197
|
+
return undefined;
|
|
198
|
+
if (!tokenNamePattern.test(name))
|
|
199
|
+
throw new Error(`Invalid token environment variable name: ${name}`);
|
|
200
|
+
const token = process.env[name];
|
|
201
|
+
if (!token)
|
|
202
|
+
throw new Error(`Bearer token environment variable is unset: ${name}`);
|
|
203
|
+
if (Buffer.byteLength(token, "utf8") < 32)
|
|
204
|
+
throw new Error("Bearer token must contain at least 32 bytes.");
|
|
205
|
+
return token;
|
|
206
|
+
}
|
|
207
|
+
function validBearer(header, expected) {
|
|
208
|
+
if (!header?.startsWith("Bearer "))
|
|
209
|
+
return false;
|
|
210
|
+
const supplied = Buffer.from(header.slice("Bearer ".length), "utf8");
|
|
211
|
+
const target = Buffer.from(expected, "utf8");
|
|
212
|
+
return supplied.length === target.length && timingSafeEqual(supplied, target);
|
|
213
|
+
}
|
|
214
|
+
function requestHost(request) {
|
|
215
|
+
const header = singleHeader(request.headers.host);
|
|
216
|
+
if (!header)
|
|
217
|
+
return undefined;
|
|
218
|
+
if (header.startsWith("[")) {
|
|
219
|
+
const closingBracket = header.indexOf("]");
|
|
220
|
+
return closingBracket > 1 ? header.slice(1, closingBracket) : undefined;
|
|
221
|
+
}
|
|
222
|
+
return header.split(":")[0] || undefined;
|
|
223
|
+
}
|
|
224
|
+
function validHost(host, listenAddress) {
|
|
225
|
+
if (isLoopback(listenAddress))
|
|
226
|
+
return host === "localhost" || isLoopback(host) || isTailscaleServeHost(host);
|
|
227
|
+
return host === listenAddress;
|
|
228
|
+
}
|
|
229
|
+
function isTailscaleServeHost(host) {
|
|
230
|
+
return /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+ts\.net$/i.test(host);
|
|
231
|
+
}
|
|
232
|
+
function isLoopback(address) {
|
|
233
|
+
if (address === "localhost" || address === "::1")
|
|
234
|
+
return true;
|
|
235
|
+
const octets = parseIpv4(address);
|
|
236
|
+
return octets?.[0] === 127;
|
|
237
|
+
}
|
|
238
|
+
export function isTailscaleIpv4(address) {
|
|
239
|
+
const octets = address ? parseIpv4(address) : undefined;
|
|
240
|
+
if (!octets)
|
|
241
|
+
return false;
|
|
242
|
+
return octets[0] === 100 && octets[1] >= 64 && octets[1] <= 127;
|
|
243
|
+
}
|
|
244
|
+
function parseIpv4(address) {
|
|
245
|
+
const parts = address.split(".");
|
|
246
|
+
if (parts.length !== 4)
|
|
247
|
+
return undefined;
|
|
248
|
+
const octets = parts.map((part) => Number(part));
|
|
249
|
+
if (octets.some((part, index) => !Number.isInteger(part) || part < 0 || part > 255 || String(part) !== parts[index])) {
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
return octets;
|
|
253
|
+
}
|
|
254
|
+
function normalizeRemoteAddress(address) {
|
|
255
|
+
return address?.startsWith("::ffff:") ? address.slice("::ffff:".length) : address;
|
|
256
|
+
}
|
|
257
|
+
function singleHeader(value) {
|
|
258
|
+
return Array.isArray(value) ? value[0] : value;
|
|
259
|
+
}
|
|
260
|
+
async function readJsonBody(request, maxBytes) {
|
|
261
|
+
const chunks = [];
|
|
262
|
+
let bytes = 0;
|
|
263
|
+
for await (const chunk of request) {
|
|
264
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
265
|
+
bytes += buffer.length;
|
|
266
|
+
if (bytes > maxBytes)
|
|
267
|
+
throw new RequestError(413, "Request body too large");
|
|
268
|
+
chunks.push(buffer);
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
throw new RequestError(400, "Request body is not valid JSON");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function sendStatus(response, status, message) {
|
|
278
|
+
response.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
|
|
279
|
+
response.end(message);
|
|
280
|
+
}
|
|
281
|
+
function sendMcpError(response, status, message) {
|
|
282
|
+
response.writeHead(status, { "content-type": "application/json" });
|
|
283
|
+
response.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32000, message }, id: null }));
|
|
284
|
+
}
|
|
285
|
+
function listen(server, host, port) {
|
|
286
|
+
return new Promise((resolve, reject) => {
|
|
287
|
+
const onError = (error) => reject(error);
|
|
288
|
+
server.once("error", onError);
|
|
289
|
+
server.listen(port, host, () => {
|
|
290
|
+
server.off("error", onError);
|
|
291
|
+
resolve();
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
function closeServer(server) {
|
|
296
|
+
return new Promise((resolve, reject) => {
|
|
297
|
+
server.close((error) => (error ? reject(error) : resolve()));
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
function errorMessage(error) {
|
|
301
|
+
return error instanceof Error ? error.message : String(error);
|
|
302
|
+
}
|
|
303
|
+
class RequestError extends Error {
|
|
304
|
+
status;
|
|
305
|
+
constructor(status, message) {
|
|
306
|
+
super(message);
|
|
307
|
+
this.status = status;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
//# sourceMappingURL=http-server.js.map
|