Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions src/lib/actions/sandbox/mcp-bridge-add-restart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@ import {
detachProvider,
ensureMcpBridgeProviderProfile,
inspectMcpProvider,
isProviderPolicyReceiptBoundaryRefusalError,
type McpCredentialRevisionObservation,
observeMcpCredentialRevision,
providerMatchesCredential,
providerShapeDetail,
reconcileIncompleteAddProviderAttachment,
refreshMcpProviderEnvironment,
upsertMcpProvider,
waitForAttachedMcpCredential,
Expand Down Expand Up @@ -314,7 +316,11 @@ async function addMcpBridgeUnlocked(
// persisting ownership or mutating a provider, policy, or adapter.
assertMcpCredentialBoundaryRuntimeVersion();
await ensureSandboxGatewaySelected(sandboxName);
const addPolicyOperation = `add MCP server '${options.server}'`;
let addPolicyAuthority: policies.PolicyMutationAuthority | undefined;
if (!existingEntry) {
addPolicyAuthority = policies.inspectPolicyMutationAuthority(sandboxName, addPolicyOperation);
policies.assertNemoClawManagedPolicy(addPolicyAuthority, addPolicyOperation);
await withMcpCredentialOwnershipLock(() => {
// Publish the durable MCP reservation under the same cross-command lock
// used by credentials add. Neither command can pass its collision check
Expand Down Expand Up @@ -373,6 +379,13 @@ async function addMcpBridgeUnlocked(
// may therefore reuse only missing or exact resources, never drift.
writeBridgeEntry(sandboxName, entry);
}
if (resumingPreflightedAdd) {
reconcileIncompleteAddProviderAttachment(sandboxName, entry);
}
if (!addPolicyAuthority) {
addPolicyAuthority = policies.inspectPolicyMutationAuthority(sandboxName, addPolicyOperation);
policies.assertNemoClawManagedPolicy(addPolicyAuthority, addPolicyOperation);
}
const adapterInspection = inspectAgentAdapterRegistration(sandboxName, adapter, entry);
if (
adapterInspection.state !== "absent" &&
Expand Down Expand Up @@ -495,9 +508,11 @@ async function addMcpBridgeUnlocked(
credentialRevision,
});
if (adapter === "hermes-config") assertHermesMcpRuntimeIntent(sandboxName);
policies.recheckPolicyMutationAuthority(sandboxName, addPolicyOperation, addPolicyAuthority);
const { addState: _completedAddState, ...committedEntry } = entry;
writeBridgeEntry(sandboxName, committedEntry);
} catch (error) {
const preserveProviderAttachment = isProviderPolicyReceiptBoundaryRefusalError(error);
const rollbackProviderInspection =
(providerAttachAttempted || providerCreated) && entry.providerId
? inspectMcpProvider(providerName)
Expand All @@ -515,11 +530,14 @@ async function addMcpBridgeUnlocked(
if (policyApplied) {
removeGeneratedPolicy(sandboxName, entry, { bestEffort: true });
}
const detachOutcome = providerAttachAttempted
? detachProvider(sandboxName, entry, { bestEffort: true })
: "absent";
let reservationCleanupProved = !providerAttachAttempted;
if (providerAttachAttempted && detachOutcome !== "unknown") {
const detachOutcome =
providerAttachAttempted && !preserveProviderAttachment
? detachProvider(sandboxName, entry, { bestEffort: true })
: preserveProviderAttachment
? "unknown"
: "absent";
let reservationCleanupProved = !providerAttachAttempted && !preserveProviderAttachment;
if (providerAttachAttempted && !preserveProviderAttachment && detachOutcome !== "unknown") {
try {
waitForDetachedMcpCredential(sandboxName, entry);
reservationCleanupProved = true;
Expand Down
3 changes: 2 additions & 1 deletion src/lib/actions/sandbox/mcp-bridge-input-targets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import path from "node:path";

import { describe, expect, it, vi } from "vitest";

import { buildManagedMcpPolicyReceiptFixture } from "../../../../test/helpers/mcp-policy-receipt-process-fixture";
import { isTrustedPrivateEndpointCapability } from "../../security/trusted-private-endpoint";
import { addMcpBridge, normalizeMcpServerUrl } from "./mcp-bridge";
import {
Expand Down Expand Up @@ -166,7 +167,7 @@ const replace = (module, name, value) => Object.defineProperty(module, name, {
configurable: true, enumerable: true, value, writable: true,
});
const registry = require("./src/lib/state/registry.js");
const policies = require("./src/lib/policy/index.js");
${buildManagedMcpPolicyReceiptFixture()}
const adapters = require("./src/lib/actions/sandbox/mcp-bridge-adapters.js");
const policy = require("./src/lib/actions/sandbox/mcp-bridge-policy.js");
const provider = require("./src/lib/actions/sandbox/mcp-bridge-provider.js");
Expand Down
Loading
Loading