@opengeni/core 0.2.2 → 0.3.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/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Settings } from '@opengeni/config';
2
- import { ScheduledTask, Document, Permission, AccessContext, AccessGrant, LimitAction, LimitDecision, CapabilityCatalogResponse, CreateCapabilityCatalogItemRequest, CapabilityCatalogItem, CapabilityInstallation, EnableCapabilityRequest, WorkspaceEnvironment, CapabilityPack, SocialConnection, ScheduledTaskAgentConfig, ToolRef, ResourceRef, CreateScheduledTaskRequest, UpdateScheduledTaskRequest, ReasoningEffort, SessionEvent, SessionTurn, GoalSpec, Session, WorkspaceMember } from '@opengeni/contracts';
2
+ import { ScheduledTask, Document, Permission, AccessContext, AccessGrant, LimitAction, LimitDecision, CapabilityCatalogResponse, CreateCapabilityCatalogItemRequest, CapabilityCatalogItem, CapabilityInstallation, EnableCapabilityRequest, WorkspaceEnvironment, CapabilityPack, SocialConnection, ScheduledTaskAgentConfig, ToolRef, ResourceRef, CreateScheduledTaskRequest, UpdateScheduledTaskRequest, ReasoningEffort, SessionMcpCredentialUpdateInput, SessionEvent, SessionTurn, GoalSpec, SessionMcpServerMetadata, Session, WorkspaceMember } from '@opengeni/contracts';
3
3
  export { mergeToolRefs, stableJson } from '@opengeni/contracts';
4
- import { Database, SandboxRecord, EnabledMcpCapabilityServer, UpdateScheduledTaskInput } from '@opengeni/db';
4
+ import { Database, SandboxRecord, EnabledMcpCapabilityServer, UpdateScheduledTaskInput, CreateSessionMcpServerInput, UpdateSessionMcpServerCredentialsInput } from '@opengeni/db';
5
5
  import { DocumentServices } from '@opengeni/documents';
6
6
  import { EventBus } from '@opengeni/events';
7
7
  import { Observability } from '@opengeni/observability';
@@ -534,6 +534,7 @@ declare function manualScheduledTaskTriggerWorkflowId(taskId: string, triggerTok
534
534
  */
535
535
  declare function manualScheduledTaskTriggerUsageKey(workspaceId: string, taskId: string, triggerToken: string): string;
536
536
 
537
+ declare function settingsWithSessionMcpServerMetadata(settings: Settings, servers: SessionMcpServerMetadata[]): Settings;
537
538
  declare function createAndStartSession(input: {
538
539
  db: Database;
539
540
  bus: EventBus;
@@ -554,6 +555,8 @@ declare function createAndStartSession(input: {
554
555
  } | null;
555
556
  goal?: GoalSpec | null;
556
557
  firstPartyMcpPermissions?: Permission[] | null;
558
+ mcpServers?: CreateSessionMcpServerInput[];
559
+ sessionMcpServers?: SessionMcpServerMetadata[];
557
560
  parentSessionId?: string | null;
558
561
  createIdempotencyKey?: string | null;
559
562
  sandboxGroupId?: string | null;
@@ -597,7 +600,14 @@ declare function createAndStartSession(input: {
597
600
  activeSandboxId: string | null;
598
601
  activeEpoch: number;
599
602
  environmentId: string | null;
600
- firstPartyMcpPermissions: ("account:read" | "account:admin" | "members:manage" | "workspace:create" | "billing:read" | "billing:manage" | "workspace:read" | "workspace:admin" | "sessions:create" | "sessions:read" | "sessions:control" | "stream:view" | "stream:control" | "stream:acknowledge" | "files:upload" | "files:read" | "files:write" | "terminal:attach" | "documents:manage" | "documents:search" | "scheduled_tasks:manage" | "scheduled_tasks:run" | "github:manage" | "github:use" | "api_keys:manage" | "environments:manage" | "environments:use" | "goals:manage" | "enrollments:read" | "enrollments:manage")[] | null;
603
+ firstPartyMcpPermissions: ("account:read" | "account:admin" | "members:manage" | "workspace:create" | "billing:read" | "billing:manage" | "workspace:read" | "workspace:admin" | "sessions:create" | "sessions:read" | "sessions:control" | "stream:view" | "stream:control" | "stream:acknowledge" | "files:upload" | "files:read" | "files:write" | "terminal:attach" | "documents:manage" | "documents:search" | "scheduled_tasks:manage" | "scheduled_tasks:run" | "github:manage" | "github:use" | "api_keys:manage" | "environments:manage" | "environments:use" | "mcp_servers:attach" | "goals:manage" | "enrollments:read" | "enrollments:manage")[] | null;
604
+ mcpServers: {
605
+ id: string;
606
+ name: string | null;
607
+ url: string;
608
+ headerNames: string[];
609
+ credentialVersion: number;
610
+ }[];
601
611
  parentSessionId: string | null;
602
612
  createIdempotencyKey: string | null;
603
613
  temporalWorkflowId: string | null;
@@ -650,6 +660,7 @@ declare function postUserMessageTurn(input: {
650
660
  model?: string | null;
651
661
  reasoningEffort?: Settings["openaiReasoningEffort"] | null;
652
662
  clientEventId?: string;
663
+ mcpCredentialUpdates?: UpdateSessionMcpServerCredentialsInput[];
653
664
  }): Promise<{
654
665
  accepted: SessionEvent;
655
666
  turn: SessionTurn;
@@ -677,6 +688,7 @@ declare function acceptSessionUserMessage(deps: ApiRouteDeps, grant: AccessGrant
677
688
  model?: string | null;
678
689
  reasoningEffort?: ReasoningEffort | null;
679
690
  clientEventId?: string;
691
+ mcpCredentialUpdates?: SessionMcpCredentialUpdateInput[];
680
692
  }): Promise<{
681
693
  accepted: SessionEvent;
682
694
  turn: SessionTurn;
@@ -732,4 +744,4 @@ declare function assertWorkspaceDeletable(input: {
732
744
  activeSessionCount: number;
733
745
  }): void;
734
746
 
735
- export { type AccessDeps, type ApiRouteDeps, type ApiSandboxClient, type ApiSandboxSession, type AppDependencies, type ChannelARoutingServices, type DocumentIndexClient, type FleetContext, type FleetListResult, type FleetLiveness, type FleetSandboxEntry, type FleetServices, type FleetSwapResult, type LimitCheckInput, MARKETING_SOCIAL_PACK_ID, MAX_ENVIRONMENTS_PER_WORKSPACE, MAX_VARIABLES_PER_ENVIRONMENT, type ManagedAuth, type McpCapabilityProbe, type McpCapabilityProbeInput, type McpCapabilityProbeResult, type ObjectStorageDependency, type ProvisionResult, type ResumeBoxByIdInput, type ResumedSandboxSession, type RunOnOp, type RunOnResult, type SessionWorkflowClient, acceptSessionUserMessage, assertAllowedEnvironmentVariableName, assertConfiguredModel, assertPackSandboxImageCompatible, assertWorkspaceDeletable, assertWorkspaceMemberRemovable, buildCapabilityCatalog, buildFleetContextForSession, buildMarketingDailyAnalysisAgentConfig, checkLimit, createAndStartSession, createCatalogItem, createSessionForRequest, createValidatedScheduledTask, disableCapability, discoverMcpRegistryCapabilities, enableCapability, enabledCapabilityMcpToolRefs, getCapabilityPack, hasPermission, isBuiltInCapabilityPack, isUserMember, listCapabilityPacks, listFleet, listWorkspaceCapabilityPacks, manualScheduledTaskTriggerUsageKey, manualScheduledTaskTriggerWorkflowId, memberCanAdminister, mergeResourceRefs, normalizeResources, officialMcpRegistryUrl, postUserMessageTurn, provisionSandbox, reasoningEffortForSession, recordEnvironmentAuditEvent, recordWorkspaceUsage, relayConfigFromSettings, relayDialBaseFromSettings, requireAccessContext, requireAccessGrant, requireEnvironmentEncryption, requireEnvironmentForApi, requireLimit, requirePermission, requireQueuedTurnForApi, requireScheduledTaskForApi, resolveCapabilityPack, resolveMemberSubjectId, restoreScheduledTask, routingEnabled, runOnSandbox, scheduledTaskTemporalScheduleId, scheduledTaskToolsProvided, scheduledTaskTriggerToken, settingsWithEnabledCapabilityMcpServers, settingsWithMcpCapabilityServers, swapActiveSandbox, syncCreatedScheduledTask, syncUpdatedScheduledTask, updateSessionTitle, validateEnvironmentAttachment, validateFileResources, validateGitHubRepositorySelection, validateGitHubRepositorySelectionShape, validateMcpCapabilityConnection, validateToolRefs, validatedScheduledTaskUpdate, withDefaultEnabledCapabilityMcpTools, workflowIdForSession, wrapChannelABoxWithRouting };
747
+ export { type AccessDeps, type ApiRouteDeps, type ApiSandboxClient, type ApiSandboxSession, type AppDependencies, type ChannelARoutingServices, type DocumentIndexClient, type FleetContext, type FleetListResult, type FleetLiveness, type FleetSandboxEntry, type FleetServices, type FleetSwapResult, type LimitCheckInput, MARKETING_SOCIAL_PACK_ID, MAX_ENVIRONMENTS_PER_WORKSPACE, MAX_VARIABLES_PER_ENVIRONMENT, type ManagedAuth, type McpCapabilityProbe, type McpCapabilityProbeInput, type McpCapabilityProbeResult, type ObjectStorageDependency, type ProvisionResult, type ResumeBoxByIdInput, type ResumedSandboxSession, type RunOnOp, type RunOnResult, type SessionWorkflowClient, acceptSessionUserMessage, assertAllowedEnvironmentVariableName, assertConfiguredModel, assertPackSandboxImageCompatible, assertWorkspaceDeletable, assertWorkspaceMemberRemovable, buildCapabilityCatalog, buildFleetContextForSession, buildMarketingDailyAnalysisAgentConfig, checkLimit, createAndStartSession, createCatalogItem, createSessionForRequest, createValidatedScheduledTask, disableCapability, discoverMcpRegistryCapabilities, enableCapability, enabledCapabilityMcpToolRefs, getCapabilityPack, hasPermission, isBuiltInCapabilityPack, isUserMember, listCapabilityPacks, listFleet, listWorkspaceCapabilityPacks, manualScheduledTaskTriggerUsageKey, manualScheduledTaskTriggerWorkflowId, memberCanAdminister, mergeResourceRefs, normalizeResources, officialMcpRegistryUrl, postUserMessageTurn, provisionSandbox, reasoningEffortForSession, recordEnvironmentAuditEvent, recordWorkspaceUsage, relayConfigFromSettings, relayDialBaseFromSettings, requireAccessContext, requireAccessGrant, requireEnvironmentEncryption, requireEnvironmentForApi, requireLimit, requirePermission, requireQueuedTurnForApi, requireScheduledTaskForApi, resolveCapabilityPack, resolveMemberSubjectId, restoreScheduledTask, routingEnabled, runOnSandbox, scheduledTaskTemporalScheduleId, scheduledTaskToolsProvided, scheduledTaskTriggerToken, settingsWithEnabledCapabilityMcpServers, settingsWithMcpCapabilityServers, settingsWithSessionMcpServerMetadata, swapActiveSandbox, syncCreatedScheduledTask, syncUpdatedScheduledTask, updateSessionTitle, validateEnvironmentAttachment, validateFileResources, validateGitHubRepositorySelection, validateGitHubRepositorySelectionShape, validateMcpCapabilityConnection, validateToolRefs, validatedScheduledTaskUpdate, withDefaultEnabledCapabilityMcpTools, workflowIdForSession, wrapChannelABoxWithRouting };
package/dist/index.js CHANGED
@@ -1864,6 +1864,7 @@ import {
1864
1864
  createSessionGoal,
1865
1865
  createSessionWithIdempotencyKey,
1866
1866
  enqueueSessionTurn,
1867
+ encryptEnvironmentValue as encryptEnvironmentValue2,
1867
1868
  getAnySessionInGroup,
1868
1869
  getEnrollment as getEnrollment2,
1869
1870
  listDistinctEnvironmentIdsInGroup,
@@ -1877,6 +1878,140 @@ import {
1877
1878
  } from "@opengeni/db";
1878
1879
  import { appendAndPublishEvents } from "@opengeni/events";
1879
1880
  import { HTTPException as HTTPException8 } from "hono/http-exception";
1881
+ var reservedSessionMcpServerIds = /* @__PURE__ */ new Set(["opengeni", "files", "docs", "codex_apps"]);
1882
+ var maxSessionMcpCredentialHeaders = 16;
1883
+ var maxSessionMcpCredentialHeaderValueLength = 4096;
1884
+ var sessionMcpCredentialHeaderName = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
1885
+ function normalizedSessionMcpCredentialHeaders(headers) {
1886
+ if (!headers) {
1887
+ return {};
1888
+ }
1889
+ const entries = Object.entries(headers).map(([name, value]) => [name.trim(), value]).filter(([name]) => name.length > 0);
1890
+ if (entries.length > maxSessionMcpCredentialHeaders) {
1891
+ throw new HTTPException8(422, { message: `a session MCP server supports at most ${maxSessionMcpCredentialHeaders} credential headers` });
1892
+ }
1893
+ const seen = /* @__PURE__ */ new Set();
1894
+ for (const [name, value] of entries) {
1895
+ if (!sessionMcpCredentialHeaderName.test(name)) {
1896
+ throw new HTTPException8(422, { message: `invalid credential header name: ${name}` });
1897
+ }
1898
+ const lower = name.toLowerCase();
1899
+ if (seen.has(lower)) {
1900
+ throw new HTTPException8(422, { message: `duplicate credential header name: ${name}` });
1901
+ }
1902
+ seen.add(lower);
1903
+ if (value.length === 0 || value.length > maxSessionMcpCredentialHeaderValueLength) {
1904
+ throw new HTTPException8(422, { message: `credential header ${name} must be 1-${maxSessionMcpCredentialHeaderValueLength} characters` });
1905
+ }
1906
+ if (/[\u0000-\u0008\u000A-\u001F\u007F]/.test(value)) {
1907
+ throw new HTTPException8(422, { message: `credential header ${name} contains forbidden control characters` });
1908
+ }
1909
+ }
1910
+ return Object.fromEntries(entries);
1911
+ }
1912
+ function mcpServerConfigFromInput(server) {
1913
+ return {
1914
+ id: server.id,
1915
+ ...server.name ? { name: server.name } : {},
1916
+ url: server.url,
1917
+ ...server.allowedTools ? { allowedTools: server.allowedTools } : {},
1918
+ ...server.timeoutMs ? { timeoutMs: server.timeoutMs } : {},
1919
+ cacheToolsList: server.cacheToolsList ?? false
1920
+ };
1921
+ }
1922
+ function mcpServerConfigFromMetadata(server) {
1923
+ return {
1924
+ id: server.id,
1925
+ ...server.name ? { name: server.name } : {},
1926
+ url: server.url,
1927
+ cacheToolsList: false
1928
+ };
1929
+ }
1930
+ function settingsWithSessionMcpServerConfigs(settings, servers) {
1931
+ if (servers.length === 0) {
1932
+ return settings;
1933
+ }
1934
+ const sessionIds = new Set(servers.map((server) => server.id));
1935
+ return {
1936
+ ...settings,
1937
+ mcpServers: [
1938
+ ...settings.mcpServers.filter((server) => !sessionIds.has(server.id)),
1939
+ ...servers
1940
+ ]
1941
+ };
1942
+ }
1943
+ function settingsWithSessionMcpServerMetadata(settings, servers) {
1944
+ return settingsWithSessionMcpServerConfigs(settings, servers.map(mcpServerConfigFromMetadata));
1945
+ }
1946
+ function validateSessionMcpServersForCreate(settings, grant, servers) {
1947
+ if (servers.length === 0) {
1948
+ return { runtimeServers: [], dbServers: [], metadata: [] };
1949
+ }
1950
+ requirePermission(grant, "mcp_servers:attach");
1951
+ const encryptionKey = requireEnvironmentEncryption(settings);
1952
+ const existingIds = new Set(settings.mcpServers.map((server) => server.id));
1953
+ const seenIds = /* @__PURE__ */ new Set();
1954
+ const runtimeServers = [];
1955
+ const dbServers = [];
1956
+ const metadata = [];
1957
+ for (const server of servers) {
1958
+ if (seenIds.has(server.id)) {
1959
+ throw new HTTPException8(422, { message: `duplicate session MCP server id: ${server.id}` });
1960
+ }
1961
+ seenIds.add(server.id);
1962
+ if (reservedSessionMcpServerIds.has(server.id) || existingIds.has(server.id)) {
1963
+ throw new HTTPException8(422, { message: `MCP server id already exists: ${server.id}` });
1964
+ }
1965
+ const headers = normalizedSessionMcpCredentialHeaders(server.headers);
1966
+ const headersEncrypted = Object.fromEntries(
1967
+ Object.entries(headers).map(([name, value]) => [name, encryptEnvironmentValue2(encryptionKey, value)])
1968
+ );
1969
+ runtimeServers.push(mcpServerConfigFromInput(server));
1970
+ dbServers.push({
1971
+ id: server.id,
1972
+ name: server.name ?? null,
1973
+ url: server.url,
1974
+ allowedTools: server.allowedTools ?? null,
1975
+ timeoutMs: server.timeoutMs ?? null,
1976
+ cacheToolsList: server.cacheToolsList ?? false,
1977
+ headersEncrypted
1978
+ });
1979
+ metadata.push({
1980
+ id: server.id,
1981
+ name: server.name ?? null,
1982
+ url: server.url,
1983
+ headerNames: Object.keys(headersEncrypted).sort(),
1984
+ credentialVersion: 1
1985
+ });
1986
+ }
1987
+ return { runtimeServers, dbServers, metadata };
1988
+ }
1989
+ function validateSessionMcpCredentialUpdates(input) {
1990
+ if (input.updates.length === 0) {
1991
+ return [];
1992
+ }
1993
+ requirePermission(input.grant, "mcp_servers:attach");
1994
+ const encryptionKey = requireEnvironmentEncryption(input.settings);
1995
+ const knownIds = new Set(input.session.mcpServers.map((server) => server.id));
1996
+ const seenIds = /* @__PURE__ */ new Set();
1997
+ const encryptedUpdates = input.updates.map((update) => {
1998
+ if (seenIds.has(update.id)) {
1999
+ throw new HTTPException8(422, { message: `duplicate session MCP credential update id: ${update.id}` });
2000
+ }
2001
+ seenIds.add(update.id);
2002
+ if (!knownIds.has(update.id)) {
2003
+ throw new HTTPException8(422, { message: `unknown session MCP server id: ${update.id}` });
2004
+ }
2005
+ const headers = normalizedSessionMcpCredentialHeaders(update.headers);
2006
+ return {
2007
+ id: update.id,
2008
+ headersEncrypted: Object.fromEntries(
2009
+ Object.entries(headers).map(([name, value]) => [name, encryptEnvironmentValue2(encryptionKey, value)])
2010
+ )
2011
+ };
2012
+ });
2013
+ return encryptedUpdates;
2014
+ }
1880
2015
  async function createAndStartSession(input) {
1881
2016
  const sessionMetadata = {
1882
2017
  ...input.metadata,
@@ -1902,7 +2037,8 @@ async function createAndStartSession(input) {
1902
2037
  parentSessionId: input.parentSessionId ?? null,
1903
2038
  createIdempotencyKey: input.createIdempotencyKey,
1904
2039
  sandboxGroupId: input.sandboxGroupId ?? null,
1905
- ...input.sandboxOs ? { sandboxOs: input.sandboxOs } : {}
2040
+ ...input.sandboxOs ? { sandboxOs: input.sandboxOs } : {},
2041
+ mcpServers: input.mcpServers ?? []
1906
2042
  });
1907
2043
  if (!created) {
1908
2044
  return keyed;
@@ -1922,7 +2058,8 @@ async function createAndStartSession(input) {
1922
2058
  firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
1923
2059
  parentSessionId: input.parentSessionId ?? null,
1924
2060
  sandboxGroupId: input.sandboxGroupId ?? null,
1925
- ...input.sandboxOs ? { sandboxOs: input.sandboxOs } : {}
2061
+ ...input.sandboxOs ? { sandboxOs: input.sandboxOs } : {},
2062
+ mcpServers: input.mcpServers ?? []
1926
2063
  });
1927
2064
  return await finishStartSession(input, session);
1928
2065
  }
@@ -1946,7 +2083,8 @@ async function finishStartSession(input, session) {
1946
2083
  type: "session.created",
1947
2084
  payload: {
1948
2085
  status: "queued",
1949
- ...input.environment ? { environmentId: input.environment.id, environmentName: input.environment.name } : {}
2086
+ ...input.environment ? { environmentId: input.environment.id, environmentName: input.environment.name } : {},
2087
+ ...input.sessionMcpServers?.length ? { mcpServers: input.sessionMcpServers } : {}
1950
2088
  }
1951
2089
  },
1952
2090
  ...goal ? [{
@@ -2056,10 +2194,14 @@ async function postUserMessageTurn(input) {
2056
2194
  const requestedModel = input.model ?? null;
2057
2195
  const requestedReasoningEffort = input.reasoningEffort ?? null;
2058
2196
  assertConfiguredModel(settings, requestedModel);
2059
- const appended = await appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessionId, (lockedSession) => {
2197
+ const appended = await appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessionId, async (lockedSession, lockedUpdate) => {
2060
2198
  if (lockedSession.status === "cancelled") {
2061
2199
  throw new HTTPException8(409, { message: `session is ${lockedSession.status}; cannot accept a new user message` });
2062
2200
  }
2201
+ const mcpCredentialUpdates = input.mcpCredentialUpdates?.length ? await lockedUpdate.updateSessionMcpServerCredentials(input.mcpCredentialUpdates) : { servers: [], missingIds: [] };
2202
+ if (mcpCredentialUpdates.missingIds.length > 0) {
2203
+ throw new HTTPException8(422, { message: `unknown session MCP server id: ${mcpCredentialUpdates.missingIds[0]}` });
2204
+ }
2063
2205
  const nextResources = mergeResourceRefs(lockedSession.resources, input.resources);
2064
2206
  const nextTools = mergeToolRefs(lockedSession.tools, input.tools);
2065
2207
  const shouldQueueSession = lockedSession.status === "idle" || lockedSession.status === "failed";
@@ -2072,7 +2214,8 @@ async function postUserMessageTurn(input) {
2072
2214
  ...input.resources.length ? { resources: input.resources } : {},
2073
2215
  ...input.tools.length ? { tools: input.tools } : {},
2074
2216
  ...requestedModel ? { model: requestedModel } : {},
2075
- ...requestedReasoningEffort ? { reasoningEffort: requestedReasoningEffort } : {}
2217
+ ...requestedReasoningEffort ? { reasoningEffort: requestedReasoningEffort } : {},
2218
+ ...mcpCredentialUpdates.servers.length ? { mcpCredentialUpdates: mcpCredentialUpdates.servers } : {}
2076
2219
  },
2077
2220
  ...input.clientEventId ? { clientEventId: input.clientEventId } : {}
2078
2221
  },
@@ -2120,10 +2263,12 @@ async function postUserMessageTurn(input) {
2120
2263
  async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
2121
2264
  const { settings, db, bus, workflowClient, objectStorage } = deps;
2122
2265
  const payload = CreateSessionRequest.parse(rawPayload);
2123
- const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
2266
+ const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
2267
+ const sessionMcpServers = validateSessionMcpServersForCreate(capabilityRuntimeSettings, grant, payload.mcpServers);
2268
+ const runtimeSettings = settingsWithSessionMcpServerConfigs(capabilityRuntimeSettings, sessionMcpServers.runtimeServers);
2124
2269
  const resources = normalizeResources(payload.resources);
2125
2270
  const requestedTools = validateToolRefs(payload.tools, runtimeSettings);
2126
- const defaultedTools = hasOwnProperty(rawPayload, "tools") ? requestedTools : withDefaultEnabledCapabilityMcpTools(requestedTools, settings, runtimeSettings);
2271
+ const defaultedTools = hasOwnProperty(rawPayload, "tools") ? requestedTools : withDefaultEnabledCapabilityMcpTools(requestedTools, settings, capabilityRuntimeSettings);
2127
2272
  const tools = withFirstPartyTools(defaultedTools, runtimeSettings);
2128
2273
  await validateGitHubRepositorySelection(db, workspaceId, resources);
2129
2274
  if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
@@ -2227,6 +2372,8 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
2227
2372
  environment: environment ? { id: environment.id, name: environment.name } : null,
2228
2373
  goal: payload.goal ?? null,
2229
2374
  firstPartyMcpPermissions,
2375
+ mcpServers: sessionMcpServers.dbServers,
2376
+ sessionMcpServers: sessionMcpServers.metadata,
2230
2377
  parentSessionId,
2231
2378
  createIdempotencyKey: payload.idempotencyKey ?? null,
2232
2379
  // Create-time machine targeting (A-2a): when a target sandbox is named, the
@@ -2250,11 +2397,12 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
2250
2397
  }
2251
2398
  async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, input) {
2252
2399
  const { settings, db, bus, workflowClient, objectStorage } = deps;
2253
- const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
2400
+ const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
2401
+ const existingSession = await requireSession2(db, workspaceId, sessionId);
2402
+ const runtimeSettings = settingsWithSessionMcpServerMetadata(capabilityRuntimeSettings, existingSession.mcpServers);
2254
2403
  const requestedResources = normalizeResources(input.resources ?? []);
2255
2404
  const validatedTools = validateToolRefs(input.tools ?? [], runtimeSettings);
2256
- const requestedTools = input.toolsProvided ? validatedTools : withDefaultEnabledCapabilityMcpTools(validatedTools, settings, runtimeSettings);
2257
- const existingSession = await requireSession2(db, workspaceId, sessionId);
2405
+ const requestedTools = input.toolsProvided ? validatedTools : withDefaultEnabledCapabilityMcpTools(validatedTools, settings, capabilityRuntimeSettings);
2258
2406
  await requireLimit(deps, {
2259
2407
  accountId: grant.accountId,
2260
2408
  workspaceId,
@@ -2267,6 +2415,12 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
2267
2415
  }
2268
2416
  await validateFileResources(db, workspaceId, requestedResources);
2269
2417
  await validateGitHubRepositorySelection(db, workspaceId, [...existingSession.resources, ...requestedResources]);
2418
+ const mcpCredentialUpdates = validateSessionMcpCredentialUpdates({
2419
+ settings,
2420
+ grant,
2421
+ session: existingSession,
2422
+ updates: input.mcpCredentialUpdates ?? []
2423
+ });
2270
2424
  const { accepted, turn } = await postUserMessageTurn({
2271
2425
  db,
2272
2426
  bus,
@@ -2280,6 +2434,7 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
2280
2434
  tools: requestedTools,
2281
2435
  model: input.model ?? null,
2282
2436
  reasoningEffort: input.reasoningEffort ?? null,
2437
+ mcpCredentialUpdates,
2283
2438
  ...input.clientEventId ? { clientEventId: input.clientEventId } : {}
2284
2439
  });
2285
2440
  await recordWorkspaceUsage(deps, {
@@ -2607,6 +2762,7 @@ export {
2607
2762
  scheduledTaskTriggerToken,
2608
2763
  settingsWithEnabledCapabilityMcpServers,
2609
2764
  settingsWithMcpCapabilityServers,
2765
+ settingsWithSessionMcpServerMetadata,
2610
2766
  stableJson,
2611
2767
  swapActiveSandbox,
2612
2768
  syncCreatedScheduledTask,