@agenthippo-ai/agenthippo 0.1.1 → 1.110.9
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/out/agentpack/index.js +2 -1
- package/out/agentpack/paths.js +23 -0
- package/out/analytics/dashboard/fleetDisplayUtils.js +3 -0
- package/out/analytics/dashboard/provider.js +13 -2
- package/out/analytics/dashboard/webview/panel.js +293 -82
- package/out/analytics/identity.js +0 -0
- package/out/analytics/service.js +23 -25
- package/out/analytics/sessionEvents.js +109 -22
- package/out/auth/authBaseUrl.js +59 -0
- package/out/auth/bootstrapApply.js +9 -10
- package/out/auth/brokerClient.js +49 -15
- package/out/auth/onboardingPrompt.js +112 -0
- package/out/auth/provider.js +9 -22
- package/out/auth/sessionStore.js +3 -40
- package/out/auth/signInFlow.js +2 -1
- package/out/auth-broker/config.js +54 -4
- package/out/auth-broker/server.js +152 -20
- package/out/auth-broker/store.js +25 -24
- package/out/authority/warrant.js +206 -0
- package/out/branding.js +27 -1
- package/out/cli/commands/ask.js +3 -0
- package/out/cli/commands/chat.js +3 -0
- package/out/cli/commands/conformance.js +249 -0
- package/out/cli/commands/data-plane.js +67 -0
- package/out/cli/commands/deploy.js +50 -10
- package/out/cli/commands/gateway.js +58 -0
- package/out/cli/commands/idp.js +85 -0
- package/out/cli/commands/pack.js +182 -3
- package/out/cli/commands/sandbox.js +332 -0
- package/out/cli/commands/serve.js +1165 -82
- package/out/cli/commands/store.js +12 -4
- package/out/cli/credential-context.js +42 -80
- package/out/cli/data-plane/broker.js +78 -0
- package/out/cli/data-plane/connector-host.js +121 -0
- package/out/cli/data-plane/egress.js +189 -0
- package/out/cli/data-plane/gateway.js +218 -0
- package/out/cli/data-plane/index.js +152 -0
- package/out/cli/data-plane/registry.js +89 -0
- package/out/cli/data-plane/remote.js +106 -0
- package/out/cli/data-plane/sidecar.js +161 -0
- package/out/cli/deploy/blueprints/registry.js +84 -0
- package/out/cli/deploy/layers/identity.js +172 -0
- package/out/cli/deploy-init.js +1182 -347
- package/out/cli/deps.js +106 -2
- package/out/cli/idp-init.js +338 -0
- package/out/cli/index.js +192 -23
- package/out/cli/pack-install-ref.js +15 -0
- package/out/cli/pack-loader.js +45 -3
- package/out/cli/pack-runtime.js +61 -0
- package/out/cli/runtime.js +44 -3
- package/out/cli/serve-hardening.js +139 -3
- package/out/cli/state-sync.js +16 -15
- package/out/cli/utils.js +12 -1
- package/out/engines/base.js +32 -3
- package/out/engines/claude.js +47 -8
- package/out/engines/codex.js +125 -22
- package/out/engines/custom.js +54 -12
- package/out/evidence/sessionShip.js +610 -0
- package/out/extension.js +13 -2
- package/out/gateway/bridge.js +40 -2
- package/out/gateway/client.js +34 -1
- package/out/gateway/installSource.js +68 -0
- package/out/gateway/lifecycle.js +185 -51
- package/out/gateway/outboundPaths.js +4 -1
- package/out/gateway/outboundSend.js +6 -6
- package/out/gateway/secretsPreflight.js +90 -0
- package/out/gateway/service.js +7 -2
- package/out/headless/index.js +40 -9
- package/out/live-e2e/mcp-demo/server.mjs +32 -0
- package/out/live-probe/connector.mjs +8 -0
- package/out/live-probe/env-report.mjs +8 -0
- package/out/mcp.js +115 -67
- package/out/pack/caps.js +271 -0
- package/out/pack/governor.js +309 -0
- package/out/pack/lease-schema.js +270 -0
- package/out/pack/lease.js +640 -0
- package/out/pack/reconcile.js +217 -0
- package/out/pack/signature.js +839 -0
- package/out/remote/handleRemoteChat.js +44 -3
- package/out/remote/remoteTrust.js +277 -0
- package/out/remote/serveClient.js +52 -5
- package/out/runner/host.js +131 -0
- package/out/runner/protocol.js +108 -0
- package/out/runner/remoteEngine.js +130 -0
- package/out/runtime/interfaces/edit-tracker.js +2 -0
- package/out/runtime/vscode/vscode-edit-tracker.js +2 -0
- package/out/sandbox/containerDriver.js +570 -0
- package/out/sandbox/processDriver.js +315 -0
- package/out/sandbox/sandboxRunner.js +191 -0
- package/out/sandbox/support.js +301 -0
- package/out/sandbox/types.js +55 -0
- package/out/scripts/agentide-litellm.sh +67 -5
- package/out/scripts/agentide_litellm_hooks_v1.py +182 -21
- package/out/scripts/analytics/agentide-litellm-otel.config.yaml +94 -118
- package/out/scripts/analytics/setup-analytics.ps1 +37 -13
- package/out/scripts/analytics/setup-analytics.sh +93 -13
- package/out/scripts/analytics/start-analytics.sh +86 -14
- package/out/scripts/gateway/setup-gateway.sh +72 -32
- package/out/shared/models.js +12 -111
- package/out/shared/providers.js +2 -2
- package/out/spotlight/install.js +7 -1
- package/out/store/index.js +41 -10
- package/out/sync.js +3 -1
- package/out/test/analytics.test.js +198 -34
- package/out/test/auth-baseurl.test.js +132 -0
- package/out/test/auth-bootstrapApply.test.js +27 -6
- package/out/test/auth-broker-hardening.test.js +227 -0
- package/out/test/auth-broker.test.js +112 -9
- package/out/test/blueprint-registry.test.js +105 -0
- package/out/test/branding.test.js +13 -0
- package/out/test/caps-governor.test.js +455 -0
- package/out/test/codex-new-file-survival.test.js +119 -0
- package/out/test/configuration-scopes.test.js +131 -0
- package/out/test/credential-context.test.js +23 -28
- package/out/test/custom-env-layering.test.js +151 -0
- package/out/test/data-plane-engine-wiring.test.js +250 -0
- package/out/test/data-plane.test.js +311 -0
- package/out/test/deploy-init.test.js +295 -4
- package/out/test/egress-broker.test.js +172 -0
- package/out/test/fleetDisplayUtils.test.js +4 -0
- package/out/test/gateway-client.test.js +28 -0
- package/out/test/gateway-install-source.test.js +111 -0
- package/out/test/gateway-lifecycle.test.js +1 -1
- package/out/test/gateway-outbound.test.js +7 -0
- package/out/test/gateway-secrets-preflight.test.js +127 -0
- package/out/test/gateway-session-routing.test.js +18 -0
- package/out/test/governor-hardening.test.js +217 -0
- package/out/test/headless-sync.test.js +22 -0
- package/out/test/idp-init.test.js +151 -0
- package/out/test/lease-signature.test.js +357 -0
- package/out/test/lease-trust-root.test.js +273 -0
- package/out/test/lease-validation.test.js +169 -0
- package/out/test/lease.test.js +114 -0
- package/out/test/mcp-obo.test.js +52 -27
- package/out/test/mcp.test.js +69 -3
- package/out/test/model-routing.test.js +133 -0
- package/out/test/pack-admission.test.js +216 -0
- package/out/test/pack-digest-parity.test.js +140 -0
- package/out/test/pack-reconcile.test.js +183 -0
- package/out/test/pack-signature-artifact.test.js +103 -0
- package/out/test/pack-signature.test.js +176 -0
- package/out/test/pack-trust-root.test.js +202 -0
- package/out/test/remote-trust.test.js +242 -0
- package/out/test/runner-protocol.test.js +249 -0
- package/out/test/sandbox-driver.test.js +553 -0
- package/out/test/sandbox-hardening.test.js +395 -0
- package/out/test/sandbox-runner.test.js +343 -0
- package/out/test/serve-gateway-turn.test.js +267 -0
- package/out/test/serve-hardening.test.js +125 -6
- package/out/test/serveClient.test.js +38 -5
- package/out/test/sessionShip.test.js +560 -0
- package/out/test/spotlightPublish.test.js +28 -0
- package/out/test/state-sync-peer.test.js +18 -5
- package/out/test/store-install-safe.test.js +69 -0
- package/out/test/tls-bypass.test.js +90 -0
- package/out/test/warrant.test.js +138 -0
- package/out/utils.js +73 -20
- package/package.json +1 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*---------------------------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) AgentHippo.ai. All rights reserved.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.parseStrictJsonConfig = parseStrictJsonConfig;
|
|
7
|
+
exports.collectMissingChannelEnvSecrets = collectMissingChannelEnvSecrets;
|
|
8
|
+
exports.disableChannelsInConfigText = disableChannelsInConfigText;
|
|
9
|
+
const MAX_WALK_DEPTH = 8;
|
|
10
|
+
function isRecord(value) {
|
|
11
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Parse config content as strict JSON, or undefined when it does not parse.
|
|
15
|
+
*
|
|
16
|
+
* OpenClaw accepts JSON5 (the starter template has comments). A JSON5 file cannot be
|
|
17
|
+
* round-tripped through JSON.parse/stringify without losing comments, so callers must
|
|
18
|
+
* treat `undefined` as "hands off — detect nothing, rewrite nothing".
|
|
19
|
+
*/
|
|
20
|
+
function parseStrictJsonConfig(content) {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(content);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function collectEnvRefs(value, out, depth) {
|
|
29
|
+
if (depth > MAX_WALK_DEPTH) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(value)) {
|
|
33
|
+
for (const item of value) {
|
|
34
|
+
collectEnvRefs(item, out, depth + 1);
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (!isRecord(value)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (value.source === 'env' && typeof value.id === 'string' && value.id.trim()) {
|
|
42
|
+
out.add(value.id.trim());
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const child of Object.values(value)) {
|
|
46
|
+
collectEnvRefs(child, out, depth + 1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* List enabled channels whose env-sourced secret refs resolve to nothing in `env`.
|
|
51
|
+
*
|
|
52
|
+
* A channel counts as enabled unless it says `enabled: false` — a configured channel is
|
|
53
|
+
* intent, and the gateway will try (and fail) to start it either way. Literal token
|
|
54
|
+
* strings are not env refs and are never reported.
|
|
55
|
+
*/
|
|
56
|
+
function collectMissingChannelEnvSecrets(config, env) {
|
|
57
|
+
if (!isRecord(config) || !isRecord(config.channels)) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
const result = [];
|
|
61
|
+
for (const [name, channel] of Object.entries(config.channels)) {
|
|
62
|
+
if (!isRecord(channel) || channel.enabled === false) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const refs = new Set();
|
|
66
|
+
collectEnvRefs(channel, refs, 0);
|
|
67
|
+
const missing = [...refs].filter((id) => !env[id]?.trim()).sort();
|
|
68
|
+
if (missing.length) {
|
|
69
|
+
result.push({ channel: name, envVars: missing });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Return config text with the named channels set to `enabled: false`, or undefined when
|
|
76
|
+
* the content is not strict JSON (rewriting JSON5 would destroy comments).
|
|
77
|
+
*/
|
|
78
|
+
function disableChannelsInConfigText(content, channels) {
|
|
79
|
+
const config = parseStrictJsonConfig(content);
|
|
80
|
+
if (!isRecord(config) || !isRecord(config.channels)) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
for (const name of channels) {
|
|
84
|
+
const channel = config.channels[name];
|
|
85
|
+
if (isRecord(channel)) {
|
|
86
|
+
channel.enabled = false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return `${JSON.stringify(config, null, 2)}\n`;
|
|
90
|
+
}
|
package/out/gateway/service.js
CHANGED
|
@@ -46,10 +46,12 @@ exports.GatewayService = exports.GatewaySessionBindingError = void 0;
|
|
|
46
46
|
* - Session routing: disk-backed route index + per-session gateway-session.json
|
|
47
47
|
* - Publish: user connects an existing IDE session to the gateway
|
|
48
48
|
*/
|
|
49
|
+
const crypto_1 = require("crypto");
|
|
49
50
|
const os = __importStar(require("os"));
|
|
50
51
|
const path = __importStar(require("path"));
|
|
51
52
|
const vscode = __importStar(require("vscode"));
|
|
52
53
|
const scheduledFleetStorage_js_1 = require("../analytics/dashboard/scheduledFleetStorage.js");
|
|
54
|
+
const branding_js_1 = require("../branding.js");
|
|
53
55
|
const bridge_js_1 = require("./bridge.js");
|
|
54
56
|
const gateway_scope_errors_js_1 = require("./gateway-scope-errors.js");
|
|
55
57
|
const gatewayRouteIndex_js_1 = require("./gatewayRouteIndex.js");
|
|
@@ -769,7 +771,10 @@ class GatewayService {
|
|
|
769
771
|
*/
|
|
770
772
|
createSessionAwareChatUIHandler(agentId, packName) {
|
|
771
773
|
return async (message, sessionKey, peerInfo, emitter, runContext) => {
|
|
772
|
-
|
|
774
|
+
// Unguessable: this token is a bearer capability for the run's output stream, and
|
|
775
|
+
// `getGatewayEmitter` is a destructive first-reader-wins read. A predictable value
|
|
776
|
+
// lets any chat message beginning with `[gw:...]` steal or drop another run's stream.
|
|
777
|
+
const gatewayRequestToken = `gw-${(0, crypto_1.randomUUID)()}`;
|
|
773
778
|
GatewayService.gatewayEmitters.set(gatewayRequestToken, emitter);
|
|
774
779
|
const markedMessage = `[gw:${gatewayRequestToken}]${message}`;
|
|
775
780
|
try {
|
|
@@ -983,7 +988,7 @@ class GatewayService {
|
|
|
983
988
|
const tokenRaw = (gwLive.get("token") ?? "").trim() || (this.config.token ?? "").trim();
|
|
984
989
|
if (!url) {
|
|
985
990
|
if (!silent) {
|
|
986
|
-
vscode.window.showWarningMessage(
|
|
991
|
+
vscode.window.showWarningMessage(`Gateway URL not configured. Set ${(0, branding_js_1.toSettingsDisplayKey)("agentide.gateway.url")} in settings.`);
|
|
987
992
|
}
|
|
988
993
|
return;
|
|
989
994
|
}
|
package/out/headless/index.js
CHANGED
|
@@ -175,6 +175,8 @@ const session_1 = require("../session");
|
|
|
175
175
|
const sync_1 = require("./sync");
|
|
176
176
|
const sessionEvents_1 = require("../analytics/sessionEvents");
|
|
177
177
|
const runtime_1 = require("../cli/runtime");
|
|
178
|
+
const credential_context_1 = require("../cli/credential-context");
|
|
179
|
+
const paths_1 = require("../utils/paths");
|
|
178
180
|
function resolveHeadlessAnalyticsUserId(runOverride, configOverride) {
|
|
179
181
|
const fromRun = runOverride?.trim();
|
|
180
182
|
if (fromRun) {
|
|
@@ -223,13 +225,18 @@ function createHeadlessRunner(config) {
|
|
|
223
225
|
// the legacy single file at provider-sessions.json on first load.
|
|
224
226
|
(0, session_1.initSessionStoreWithStore)(new interfaces_1.DirectorySessionStore(path.join(config.storagePath, 'provider-sessions')));
|
|
225
227
|
}
|
|
226
|
-
//
|
|
228
|
+
// Per-destination credentials. When a caller supplies the split keys (serve), use them so
|
|
229
|
+
// each engine picks the key for the endpoint it dials. Otherwise fall back to the single
|
|
230
|
+
// `apiKey` as the LiteLLM key (back-compat: eval/CLI runners configured one key).
|
|
231
|
+
const hasSplitKeys = Boolean(config.litellmApiKey || config.openaiApiKey || config.anthropicApiKey);
|
|
227
232
|
const engineConfig = {
|
|
228
233
|
storagePath: config.storagePath,
|
|
229
234
|
codexHome: undefined,
|
|
230
235
|
claudeConfigDir: undefined,
|
|
231
|
-
litellmApiKey: config.apiKey,
|
|
236
|
+
litellmApiKey: config.litellmApiKey ?? (hasSplitKeys ? undefined : config.apiKey),
|
|
232
237
|
litellmBaseUrl: config.baseUrl,
|
|
238
|
+
openaiApiKey: config.openaiApiKey,
|
|
239
|
+
anthropicApiKey: config.anthropicApiKey,
|
|
233
240
|
claudeCodeExecutablePath: config.claudeCodeExecutablePath,
|
|
234
241
|
codexExecutablePath: config.codexExecutablePath,
|
|
235
242
|
};
|
|
@@ -283,11 +290,12 @@ function createHeadlessRunner(config) {
|
|
|
283
290
|
}
|
|
284
291
|
return customEngine;
|
|
285
292
|
};
|
|
286
|
-
// Shared session event writer for Layer 2 analytics.
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
|
|
293
|
+
// Shared session event writer for Layer 2 analytics. Defaults to getAgentideHome() — the
|
|
294
|
+
// exact directory sessionJsonlFleet.ts and query-analytics.sh already scan, so tool-call/
|
|
295
|
+
// warrant events stay discoverable — but honors an explicit override (config.analyticsSessionsDir)
|
|
296
|
+
// so callers with their own state-dir precedence (serve.ts) don't regress durability when
|
|
297
|
+
// no AGENTHIPPO_HOME-style env var is set but a durable --state-dir was given explicitly.
|
|
298
|
+
const sessionEventWriter = new sessionEvents_1.SessionEventWriter(config.analyticsSessionsDir ?? path.join((0, paths_1.getAgentideHome)(), 'analytics', 'sessions'));
|
|
291
299
|
return {
|
|
292
300
|
async run(options) {
|
|
293
301
|
const result = {
|
|
@@ -339,8 +347,21 @@ function createHeadlessRunner(config) {
|
|
|
339
347
|
const requestId = (0, base_1.generateRequestId)();
|
|
340
348
|
// Wrap emitter with observability (Layer 2 session events)
|
|
341
349
|
// Session events are written to ~/.agent-hippo/analytics/sessions/<session_id>.jsonl
|
|
350
|
+
// Capture warrant userId here (inside serve's runWithCredential continuation), not
|
|
351
|
+
// later at tool emit — custom-engine IPC callbacks can lose/stick ambient ALS.
|
|
342
352
|
const sessionId = options.sessionId ?? `headless-${Date.now()}`;
|
|
343
|
-
const
|
|
353
|
+
const provenance = config.provenance?.();
|
|
354
|
+
const observableEmitter = (0, sessionEvents_1.wrapEmitterWithObservability)(baseEmitter, sessionEventWriter, sessionId, requestId, (0, credential_context_1.currentCredential)()?.userId, provenance && {
|
|
355
|
+
policy_version: provenance.policyVersion,
|
|
356
|
+
pack_digest: provenance.packDigest,
|
|
357
|
+
engine_version: provenance.engineVersion,
|
|
358
|
+
tier: provenance.tier,
|
|
359
|
+
// Layer 2 must be joinable to an instance ON ITS OWN. Without this the session
|
|
360
|
+
// lane could only be attributed by going through Layer 1 (the OTEL span), which
|
|
361
|
+
// defeats the point of self-stamping: with a shared collector spool, the
|
|
362
|
+
// shipper's INSTANCE_ID cannot know which instance actually ran a turn.
|
|
363
|
+
instance_id: provenance.instanceId,
|
|
364
|
+
});
|
|
344
365
|
const requestOptions = {
|
|
345
366
|
modelId: options.modelId,
|
|
346
367
|
engineId: options.engineId,
|
|
@@ -348,11 +369,20 @@ function createHeadlessRunner(config) {
|
|
|
348
369
|
additionalDirectories: options.additionalDirectories,
|
|
349
370
|
sessionId,
|
|
350
371
|
analyticsUserId: resolveHeadlessAnalyticsUserId(options.analyticsUserId, config.analyticsUserId),
|
|
351
|
-
|
|
372
|
+
// With split keys (serve), leave options.apiKey UNSET so resolveApiRouting selects
|
|
373
|
+
// the key for the engine's destination — passing the collapsed key here would let
|
|
374
|
+
// it win over the correct provider key (the original bug). Back-compat single-key
|
|
375
|
+
// callers keep options.apiKey.
|
|
376
|
+
apiKey: hasSplitKeys ? undefined : config.apiKey,
|
|
352
377
|
baseUrl: config.baseUrl,
|
|
353
378
|
useLiteLLM: config.useLiteLLM,
|
|
354
379
|
agentPackId: packId,
|
|
355
380
|
agentPackVersion: config.packVersion,
|
|
381
|
+
policyVersion: provenance?.policyVersion,
|
|
382
|
+
packDigest: provenance?.packDigest,
|
|
383
|
+
engineVersion: provenance?.engineVersion,
|
|
384
|
+
tier: provenance?.tier,
|
|
385
|
+
instanceId: provenance?.instanceId,
|
|
356
386
|
permissions: options.permissions,
|
|
357
387
|
requestId, // Same requestId used by ObservableStreamEmitter for Layer 1/2 correlation
|
|
358
388
|
};
|
|
@@ -367,6 +397,7 @@ function createHeadlessRunner(config) {
|
|
|
367
397
|
chatSessionId: sessionId,
|
|
368
398
|
gatewaySessionKey: options.gatewaySessionKey,
|
|
369
399
|
}),
|
|
400
|
+
...(options.turnEnv ?? {}),
|
|
370
401
|
},
|
|
371
402
|
};
|
|
372
403
|
try {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
|
|
5
|
+
const BALANCES = {
|
|
6
|
+
'alice@corp.com': 4242,
|
|
7
|
+
'bob@corp.com': 99,
|
|
8
|
+
};
|
|
9
|
+
const TURN = 'ai.agenthippo/turn';
|
|
10
|
+
|
|
11
|
+
const server = new Server({ name: 'e2e-demo', version: '0.0.1' }, { capabilities: { tools: {} } });
|
|
12
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
13
|
+
tools: [{
|
|
14
|
+
name: 'get_my_balance',
|
|
15
|
+
description: 'Return the signed-in user balance. No args.',
|
|
16
|
+
inputSchema: { type: 'object', properties: {} },
|
|
17
|
+
}],
|
|
18
|
+
}));
|
|
19
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
20
|
+
const turn = (req.params._meta ?? {})[TURN] ?? null;
|
|
21
|
+
const userId = typeof turn?.userId === 'string' ? turn.userId.trim() : '';
|
|
22
|
+
if (!userId) {
|
|
23
|
+
return { isError: true, content: [{ type: 'text', text: 'fail-closed: no verified user' }] };
|
|
24
|
+
}
|
|
25
|
+
const bal = BALANCES[userId];
|
|
26
|
+
if (bal === undefined) {
|
|
27
|
+
return { isError: true, content: [{ type: 'text', text: `fail-closed: no balance for ${userId}` }] };
|
|
28
|
+
}
|
|
29
|
+
console.error(`[e2e-connector] ${userId} → ${bal}`);
|
|
30
|
+
return { content: [{ type: 'text', text: JSON.stringify({ userId, balance: bal }) }] };
|
|
31
|
+
});
|
|
32
|
+
await server.connect(new StdioServerTransport());
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
const server = new Server({ name: 'probe', version: '0.0.1' }, { capabilities: { tools: {} } });
|
|
6
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [{ name: 'probe_tool', description: 'x', inputSchema: { type: 'object' } }] }));
|
|
7
|
+
server.setRequestHandler(CallToolRequestSchema, async () => ({ content: [{ type: 'text', text: 'MAGIC=zebra42' }] }));
|
|
8
|
+
await server.connect(new StdioServerTransport());
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
const server = new Server({ name: 'env-report', version: '0.0.1' }, { capabilities: { tools: {} } });
|
|
6
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [{ name: 'report_env', description: 'reports a probe env var', inputSchema: { type: 'object' } }] }));
|
|
7
|
+
server.setRequestHandler(CallToolRequestSchema, async () => ({ content: [{ type: 'text', text: 'PROBE_SEEN=' + (process.env.AGENTHIPPO_PROBE_ENV || 'ABSENT') }] }));
|
|
8
|
+
await server.connect(new StdioServerTransport());
|
package/out/mcp.js
CHANGED
|
@@ -36,14 +36,15 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
36
36
|
};
|
|
37
37
|
})();
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.AGENTHIPPO_MCP_SERVERS_ENV = exports.
|
|
39
|
+
exports.AGENTHIPPO_MCP_SERVERS_ENV = exports.ALLOW_INLINE_DATA_TOKEN_ENV = void 0;
|
|
40
40
|
exports.loadMcpServers = loadMcpServers;
|
|
41
41
|
exports.resolveMcpEnvVar = resolveMcpEnvVar;
|
|
42
|
-
exports.mcpConfigCacheSignatureSuffix = mcpConfigCacheSignatureSuffix;
|
|
43
42
|
exports.resolveMcpServerEnv = resolveMcpServerEnv;
|
|
44
43
|
exports.generateCodexMcpConfig = generateCodexMcpConfig;
|
|
45
44
|
exports.getDefaultMcpConfig = getDefaultMcpConfig;
|
|
46
45
|
exports.loadBundledMcpServers = loadBundledMcpServers;
|
|
46
|
+
exports.resolveHttpMcpHeaders = resolveHttpMcpHeaders;
|
|
47
|
+
exports.referenceHttpMcpHeaders = referenceHttpMcpHeaders;
|
|
47
48
|
exports.formatInlineMcpServers = formatInlineMcpServers;
|
|
48
49
|
exports.serializeInlineMcpServers = serializeInlineMcpServers;
|
|
49
50
|
exports.parseInlineMcpServers = parseInlineMcpServers;
|
|
@@ -64,11 +65,16 @@ const MCP_CONFIG_FILE = '.mcp.json';
|
|
|
64
65
|
/** MCP servers subdirectory in base-prompts */
|
|
65
66
|
const MCP_SERVERS_DIR = 'mcp-servers';
|
|
66
67
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
68
|
+
* Explicit local-dev opt-in: allow the per-turn data credential to be resolved INLINE into
|
|
69
|
+
* MCP child env / `${credential:dataToken}` placeholders. Off by default — in any real
|
|
70
|
+
* deployment the credential must stay in the broker and reach tools only via the loopback
|
|
71
|
+
* data-plane gateway + turn nonce (secure-obo-design §2). Without this flag, a credential-
|
|
72
|
+
* backed value resolves to nothing and the pack's connector fails closed with its own error.
|
|
70
73
|
*/
|
|
71
|
-
exports.
|
|
74
|
+
exports.ALLOW_INLINE_DATA_TOKEN_ENV = 'AGENTHIPPO_ALLOW_INLINE_DATA_TOKEN';
|
|
75
|
+
function inlineDataTokenAllowed() {
|
|
76
|
+
return /^(1|true)$/i.test(process.env[exports.ALLOW_INLINE_DATA_TOKEN_ENV]?.trim() ?? '');
|
|
77
|
+
}
|
|
72
78
|
const ENV_PLACEHOLDER = /\$\{env:(\w+)\}/g;
|
|
73
79
|
const CREDENTIAL_PLACEHOLDER = /\$\{credential:(\w+)\}/g;
|
|
74
80
|
// -----------------------------------------------------------------------------
|
|
@@ -146,14 +152,23 @@ async function resolveServerEntry(name, entry, mcpDir, bundledMcpDir, envSources
|
|
|
146
152
|
}
|
|
147
153
|
// HTTP Streamable MCP server — no subprocess needed
|
|
148
154
|
if (entry.url) {
|
|
155
|
+
// `${env:VAR}` in the URL resolves against the serve process env (e.g. the
|
|
156
|
+
// boot-stable AGENTHIPPO_DATA_MCP_URL of the loopback data-plane gateway).
|
|
157
|
+
const url = entry.url.replace(ENV_PLACEHOLDER, (_, varName) => resolvePlainEnvVar(varName, envSources)).trim();
|
|
158
|
+
if (!url) {
|
|
159
|
+
// Placeholder didn't resolve (e.g. gateway-backed entry loaded outside serve):
|
|
160
|
+
// drop the server rather than hand engines a broken empty-URL entry.
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
149
163
|
return {
|
|
150
164
|
name,
|
|
151
165
|
label: entry.label ?? name,
|
|
152
166
|
description: entry.description,
|
|
153
167
|
command: '',
|
|
154
168
|
args: [],
|
|
155
|
-
url
|
|
169
|
+
url,
|
|
156
170
|
headers: resolveHeaders(entry.headers, envSources),
|
|
171
|
+
bearerTokenEnv: entry.bearerTokenEnv?.trim() || undefined,
|
|
157
172
|
};
|
|
158
173
|
}
|
|
159
174
|
// Custom stdio server - resolve relative paths from mcpDir
|
|
@@ -255,89 +270,63 @@ function resolveArgs(args, serverDir) {
|
|
|
255
270
|
/**
|
|
256
271
|
* Resolve a single ${env:VAR} reference for MCP child processes.
|
|
257
272
|
*
|
|
258
|
-
* The turn's OBO data token is exposed two ways
|
|
273
|
+
* The turn's OBO data token is exposed two ways, BOTH gated behind the explicit
|
|
274
|
+
* local-dev opt-in {@link ALLOW_INLINE_DATA_TOKEN_ENV}:
|
|
259
275
|
* - `${credential:dataToken}` placeholder (pack-author-facing; handled in resolveEnv).
|
|
260
|
-
* - A configurable set of env-var aliases
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
* vendor-neutral instead of hardcoding one provider's token name.
|
|
276
|
+
* - A configurable set of env-var aliases for packs that reference the token via
|
|
277
|
+
* `${env:VAR}` directly (`AGENTHIPPO_DATA_TOKEN_ENV_VARS`, comma-separated; empty
|
|
278
|
+
* by default — inline aliasing must be declared, never ambient).
|
|
264
279
|
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
280
|
+
* In a real deployment neither fires: the credential stays in the broker and tools reach
|
|
281
|
+
* it via the loopback data-plane gateway + turn nonce (secure-obo-design §3).
|
|
267
282
|
*/
|
|
268
283
|
function resolveMcpEnvVar(varName, envSources = {}) {
|
|
269
|
-
const cred = (0, credential_context_1.currentCredential)();
|
|
270
|
-
const tokenFile = cred?.dataTokenFile;
|
|
271
284
|
const aliases = getDataTokenEnvAliases();
|
|
272
|
-
const isDataTokenVar = varName === 'dataToken' || aliases.includes(varName);
|
|
273
|
-
if (tokenFile && isDataTokenVar) {
|
|
274
|
-
// Token lives in the per-turn file (AGENTHIPPO_DATA_TOKEN_FILE); never bake the OBO token
|
|
275
|
-
// into spawn env. The explicit ${credential:dataToken} placeholder resolves to empty
|
|
276
|
-
// (AgentHippo-aware servers read the file). A plain ${env:ALIAS} reference falls through to
|
|
277
|
-
// any operator-provided ambient value so a stock server that reads the var directly still
|
|
278
|
-
// works, instead of being clobbered to an empty string.
|
|
279
|
-
if (varName === 'dataToken') {
|
|
280
|
-
return '';
|
|
281
|
-
}
|
|
282
|
-
return process.env[varName] ?? envSources[varName] ?? '';
|
|
283
|
-
}
|
|
284
285
|
if (varName === 'dataToken') {
|
|
285
|
-
|
|
286
|
+
const tok = (0, credential_context_1.currentCredential)()?.dataToken?.trim();
|
|
287
|
+
return tok && inlineDataTokenAllowed() ? tok : '';
|
|
286
288
|
}
|
|
287
|
-
if (
|
|
288
|
-
|
|
289
|
+
if (aliases.includes(varName)) {
|
|
290
|
+
const tok = (0, credential_context_1.currentCredential)()?.dataToken?.trim();
|
|
291
|
+
if (tok && inlineDataTokenAllowed()) {
|
|
292
|
+
return tok;
|
|
293
|
+
}
|
|
289
294
|
}
|
|
290
295
|
return process.env[varName] ?? envSources[varName] ?? '';
|
|
291
296
|
}
|
|
292
297
|
/**
|
|
293
298
|
* Env-var names that resolve to the turn's OBO data token when a pack references
|
|
294
|
-
* `${env:VAR}` directly.
|
|
295
|
-
*
|
|
299
|
+
* `${env:VAR}` directly. Empty by default — set `AGENTHIPPO_DATA_TOKEN_ENV_VARS=VAR1,VAR2`
|
|
300
|
+
* (together with the inline opt-in) to enable. The old implicit `DATABRICKS_TOKEN` default
|
|
301
|
+
* was removed with the disk token path: inline credential exposure is now always explicit.
|
|
296
302
|
*/
|
|
297
303
|
function getDataTokenEnvAliases() {
|
|
298
304
|
const raw = process.env.AGENTHIPPO_DATA_TOKEN_ENV_VARS;
|
|
299
305
|
if (raw !== undefined) {
|
|
300
306
|
return raw.split(',').map((v) => v.trim()).filter(Boolean);
|
|
301
307
|
}
|
|
302
|
-
return [
|
|
308
|
+
return [];
|
|
303
309
|
}
|
|
304
310
|
function envValueUsesCredentialDataToken(value) {
|
|
305
311
|
return /\$\{credential:dataToken\}/.test(value);
|
|
306
312
|
}
|
|
307
|
-
/** Suffix for Codex config.toml cache invalidation when MCP credential scope changes. */
|
|
308
|
-
function mcpConfigCacheSignatureSuffix() {
|
|
309
|
-
const cred = (0, credential_context_1.currentCredential)();
|
|
310
|
-
const aliases = getDataTokenEnvAliases();
|
|
311
|
-
if (cred?.dataTokenFile) {
|
|
312
|
-
return `file:${cred.dataTokenFile}:aliases=${aliases.join(',')}`;
|
|
313
|
-
}
|
|
314
|
-
if (cred?.dataToken?.trim()) {
|
|
315
|
-
return `inline-token:aliases=${aliases.join(',')}`;
|
|
316
|
-
}
|
|
317
|
-
return `no-token:aliases=${aliases.join(',')}`;
|
|
318
|
-
}
|
|
319
313
|
function resolveEnv(env, envSources = {}) {
|
|
320
314
|
if (!env) {
|
|
321
315
|
return undefined;
|
|
322
316
|
}
|
|
323
317
|
const resolved = {};
|
|
324
|
-
const tokenFile = (0, credential_context_1.currentCredential)()?.dataTokenFile;
|
|
325
|
-
if (tokenFile) {
|
|
326
|
-
resolved[exports.AGENTHIPPO_DATA_TOKEN_FILE_ENV] = tokenFile;
|
|
327
|
-
}
|
|
328
318
|
for (const [key, value] of Object.entries(env)) {
|
|
329
319
|
CREDENTIAL_PLACEHOLDER.lastIndex = 0;
|
|
330
320
|
if (envValueUsesCredentialDataToken(value)) {
|
|
331
|
-
// Credential-backed secret.
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
resolved[key] = (0, credential_context_1.currentCredential)().dataToken.trim();
|
|
321
|
+
// Credential-backed secret. Baked into spawn env ONLY behind the explicit
|
|
322
|
+
// local-dev opt-in — the supported transport is the data-plane gateway, where
|
|
323
|
+
// the credential never leaves the broker process (secure-obo-design §2).
|
|
324
|
+
const tok = (0, credential_context_1.currentCredential)()?.dataToken?.trim();
|
|
325
|
+
if (tok) {
|
|
326
|
+
if (inlineDataTokenAllowed()) {
|
|
327
|
+
resolved[key] = tok;
|
|
328
|
+
}
|
|
329
|
+
// else: intentionally unset — the connector fails closed with its own error.
|
|
341
330
|
}
|
|
342
331
|
else {
|
|
343
332
|
// No OBO credential (local dev / non-serve context): fall back to env var.
|
|
@@ -436,12 +425,14 @@ function generateCodexMcpConfig(servers, options) {
|
|
|
436
425
|
if (servers.length === 0) {
|
|
437
426
|
return '';
|
|
438
427
|
}
|
|
439
|
-
// `default_tools_approval_mode = "
|
|
440
|
-
//
|
|
428
|
+
// `default_tools_approval_mode = "approve"` is the Codex per-server key that lets MCP
|
|
429
|
+
// tools run without an interactive prompt. The value enum is prompt|approve — "auto" is
|
|
430
|
+
// NOT valid: Codex ≥0.131 silently ignores it, defaults the server to `prompt`, and a
|
|
431
|
+
// headless `codex exec` turn then auto-Cancels the approval elicitation, surfacing as
|
|
432
|
+
// "user cancelled MCP tool call" on every tool call (verified live against 0.131.0).
|
|
441
433
|
// Only emit it in headless/serve auto-approve contexts — the interactive IDE path leaves
|
|
442
|
-
// it unset so users get Codex's normal approval prompts.
|
|
443
|
-
|
|
444
|
-
const approvalLine = options?.autoApprove ? 'default_tools_approval_mode = "auto"' : undefined;
|
|
434
|
+
// it unset so users get Codex's normal approval prompts.
|
|
435
|
+
const approvalLine = options?.autoApprove ? 'default_tools_approval_mode = "approve"' : undefined;
|
|
445
436
|
const sections = servers.map(s => {
|
|
446
437
|
// HTTP Streamable MCP server — codex connects via URL, no subprocess
|
|
447
438
|
if (s.url) {
|
|
@@ -449,6 +440,13 @@ function generateCodexMcpConfig(servers, options) {
|
|
|
449
440
|
`[mcp_servers.${s.name}]`,
|
|
450
441
|
`url = ${JSON.stringify(s.url)}`,
|
|
451
442
|
];
|
|
443
|
+
if (s.bearerTokenEnv) {
|
|
444
|
+
// The auth token is read from the codex PROCESS env at request time
|
|
445
|
+
// (per-spawn codexEnv carries the turn nonce) — the config file itself
|
|
446
|
+
// stays byte-identical across turns, which is what removes the
|
|
447
|
+
// cross-user config.toml race (secure-obo-design §3.2).
|
|
448
|
+
urlLines.push(`bearer_token_env_var = ${JSON.stringify(s.bearerTokenEnv)}`);
|
|
449
|
+
}
|
|
452
450
|
if (s.headers && Object.keys(s.headers).length > 0) {
|
|
453
451
|
const headerEntries = Object.entries(s.headers)
|
|
454
452
|
.map(([k, v]) => `${JSON.stringify(k)} = ${JSON.stringify(v)}`)
|
|
@@ -541,6 +539,52 @@ async function loadBundledMcpServers(basePromptsDir) {
|
|
|
541
539
|
}
|
|
542
540
|
/** Env var carrying JSON-serialized {@link InlineMcpServers} into custom engine workers. */
|
|
543
541
|
exports.AGENTHIPPO_MCP_SERVERS_ENV = 'AGENTHIPPO_MCP_SERVERS';
|
|
542
|
+
/**
|
|
543
|
+
* Resolve the final HTTP headers for a URL-based MCP server, applying `bearerTokenEnv`
|
|
544
|
+
* against the ENGINE'S per-turn env (process.env + per-spawn patch). Engines that build
|
|
545
|
+
* their MCP config in memory per turn (claude, custom) call this with the same env they
|
|
546
|
+
* hand the engine subprocess, so the turn nonce lands in the request header without ever
|
|
547
|
+
* touching a shared file.
|
|
548
|
+
*/
|
|
549
|
+
function resolveHttpMcpHeaders(server, turnEnv) {
|
|
550
|
+
const headers = { ...(server.headers ?? {}) };
|
|
551
|
+
if (server.bearerTokenEnv) {
|
|
552
|
+
const token = turnEnv[server.bearerTokenEnv]?.trim();
|
|
553
|
+
if (token) {
|
|
554
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* The variable names the Claude CLI's `${NAME}` config expansion accepts. A `bearerTokenEnv`
|
|
561
|
+
* outside this shape would ship a header the CLI sends verbatim, so it falls back to in-memory
|
|
562
|
+
* resolution instead.
|
|
563
|
+
*/
|
|
564
|
+
const CLI_EXPANDABLE_ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
565
|
+
/**
|
|
566
|
+
* Claude-engine variant of {@link resolveHttpMcpHeaders}: emit a `${NAME}` REFERENCE and let the
|
|
567
|
+
* CLI expand it from its own environment at connect time, instead of resolving the secret here.
|
|
568
|
+
*
|
|
569
|
+
* The Agent SDK serializes the whole mcpServers object into `--mcp-config <json>` argv, and
|
|
570
|
+
* /proc/<pid>/cmdline is world-readable — no ptrace gate, unlike environ — so a resolved
|
|
571
|
+
* Authorization header there is visible to every uid on the host and to execve-argv audit
|
|
572
|
+
* tooling. The reference keeps the turn nonce at environ-level exposure, which the engine child
|
|
573
|
+
* already carries for the egress CLI. Expansion verified live on CLI 2.1.220, including the
|
|
574
|
+
* failure mode this relies on: an UNSET name passes through literally and the data gateway
|
|
575
|
+
* rejects it — fail closed, never fail open.
|
|
576
|
+
*/
|
|
577
|
+
function referenceHttpMcpHeaders(server, turnEnv) {
|
|
578
|
+
const name = server.bearerTokenEnv;
|
|
579
|
+
if (name && !CLI_EXPANDABLE_ENV_NAME.test(name)) {
|
|
580
|
+
return resolveHttpMcpHeaders(server, turnEnv);
|
|
581
|
+
}
|
|
582
|
+
const headers = { ...(server.headers ?? {}) };
|
|
583
|
+
if (name) {
|
|
584
|
+
headers['Authorization'] = `Bearer \${${name}}`;
|
|
585
|
+
}
|
|
586
|
+
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
587
|
+
}
|
|
544
588
|
/**
|
|
545
589
|
* Convert resolved pack MCP servers to the inline record shape engines consume.
|
|
546
590
|
*
|
|
@@ -548,15 +592,19 @@ exports.AGENTHIPPO_MCP_SERVERS_ENV = 'AGENTHIPPO_MCP_SERVERS';
|
|
|
548
592
|
* `{ type: 'http', url, headers? }` — the Cursor SDK (`@cursor/sdk` McpServerConfig) accepts
|
|
549
593
|
* this shape inline, so remote MCP servers are available to cursor/custom engines, not just
|
|
550
594
|
* to the Codex engine (which gets them via config.toml).
|
|
595
|
+
*
|
|
596
|
+
* `turnEnv` (when provided) resolves each URL server's `bearerTokenEnv` into a concrete
|
|
597
|
+
* Authorization header — pass the same env the engine subprocess will receive.
|
|
551
598
|
*/
|
|
552
|
-
function formatInlineMcpServers(servers) {
|
|
599
|
+
function formatInlineMcpServers(servers, turnEnv) {
|
|
553
600
|
const mcpServers = {};
|
|
554
601
|
for (const server of servers) {
|
|
555
602
|
if (server.url) {
|
|
603
|
+
const headers = resolveHttpMcpHeaders(server, turnEnv ?? process.env);
|
|
556
604
|
mcpServers[server.name] = {
|
|
557
605
|
type: 'http',
|
|
558
606
|
url: server.url,
|
|
559
|
-
...(
|
|
607
|
+
...(headers ? { headers } : {}),
|
|
560
608
|
};
|
|
561
609
|
continue;
|
|
562
610
|
}
|