@opengeni/core 0.2.2 → 0.4.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;
@@ -553,7 +554,10 @@ declare function createAndStartSession(input: {
553
554
  name: string;
554
555
  } | null;
555
556
  goal?: GoalSpec | null;
557
+ instructions?: string | null;
556
558
  firstPartyMcpPermissions?: Permission[] | null;
559
+ mcpServers?: CreateSessionMcpServerInput[];
560
+ sessionMcpServers?: SessionMcpServerMetadata[];
557
561
  parentSessionId?: string | null;
558
562
  createIdempotencyKey?: string | null;
559
563
  sandboxGroupId?: string | null;
@@ -571,6 +575,7 @@ declare function createAndStartSession(input: {
571
575
  initialMessage: string;
572
576
  title: string | null;
573
577
  titleSource: "user" | "agent" | null;
578
+ instructions: string | null;
574
579
  resources: ({
575
580
  kind: "repository";
576
581
  uri: string;
@@ -597,7 +602,14 @@ declare function createAndStartSession(input: {
597
602
  activeSandboxId: string | null;
598
603
  activeEpoch: number;
599
604
  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;
605
+ 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;
606
+ mcpServers: {
607
+ id: string;
608
+ name: string | null;
609
+ url: string;
610
+ headerNames: string[];
611
+ credentialVersion: number;
612
+ }[];
601
613
  parentSessionId: string | null;
602
614
  createIdempotencyKey: string | null;
603
615
  temporalWorkflowId: string | null;
@@ -650,6 +662,7 @@ declare function postUserMessageTurn(input: {
650
662
  model?: string | null;
651
663
  reasoningEffort?: Settings["openaiReasoningEffort"] | null;
652
664
  clientEventId?: string;
665
+ mcpCredentialUpdates?: UpdateSessionMcpServerCredentialsInput[];
653
666
  }): Promise<{
654
667
  accepted: SessionEvent;
655
668
  turn: SessionTurn;
@@ -677,6 +690,7 @@ declare function acceptSessionUserMessage(deps: ApiRouteDeps, grant: AccessGrant
677
690
  model?: string | null;
678
691
  reasoningEffort?: ReasoningEffort | null;
679
692
  clientEventId?: string;
693
+ mcpCredentialUpdates?: SessionMcpCredentialUpdateInput[];
680
694
  }): Promise<{
681
695
  accepted: SessionEvent;
682
696
  turn: SessionTurn;
@@ -732,4 +746,4 @@ declare function assertWorkspaceDeletable(input: {
732
746
  activeSessionCount: number;
733
747
  }): void;
734
748
 
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 };
749
+ 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,
@@ -1899,10 +2034,12 @@ async function createAndStartSession(input) {
1899
2034
  sandboxBackend: input.sandboxBackend,
1900
2035
  environmentId: input.environment?.id ?? null,
1901
2036
  firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
2037
+ instructions: input.instructions ?? null,
1902
2038
  parentSessionId: input.parentSessionId ?? null,
1903
2039
  createIdempotencyKey: input.createIdempotencyKey,
1904
2040
  sandboxGroupId: input.sandboxGroupId ?? null,
1905
- ...input.sandboxOs ? { sandboxOs: input.sandboxOs } : {}
2041
+ ...input.sandboxOs ? { sandboxOs: input.sandboxOs } : {},
2042
+ mcpServers: input.mcpServers ?? []
1906
2043
  });
1907
2044
  if (!created) {
1908
2045
  return keyed;
@@ -1920,9 +2057,11 @@ async function createAndStartSession(input) {
1920
2057
  sandboxBackend: input.sandboxBackend,
1921
2058
  environmentId: input.environment?.id ?? null,
1922
2059
  firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
2060
+ instructions: input.instructions ?? null,
1923
2061
  parentSessionId: input.parentSessionId ?? null,
1924
2062
  sandboxGroupId: input.sandboxGroupId ?? null,
1925
- ...input.sandboxOs ? { sandboxOs: input.sandboxOs } : {}
2063
+ ...input.sandboxOs ? { sandboxOs: input.sandboxOs } : {},
2064
+ mcpServers: input.mcpServers ?? []
1926
2065
  });
1927
2066
  return await finishStartSession(input, session);
1928
2067
  }
@@ -1946,7 +2085,8 @@ async function finishStartSession(input, session) {
1946
2085
  type: "session.created",
1947
2086
  payload: {
1948
2087
  status: "queued",
1949
- ...input.environment ? { environmentId: input.environment.id, environmentName: input.environment.name } : {}
2088
+ ...input.environment ? { environmentId: input.environment.id, environmentName: input.environment.name } : {},
2089
+ ...input.sessionMcpServers?.length ? { mcpServers: input.sessionMcpServers } : {}
1950
2090
  }
1951
2091
  },
1952
2092
  ...goal ? [{
@@ -2056,10 +2196,14 @@ async function postUserMessageTurn(input) {
2056
2196
  const requestedModel = input.model ?? null;
2057
2197
  const requestedReasoningEffort = input.reasoningEffort ?? null;
2058
2198
  assertConfiguredModel(settings, requestedModel);
2059
- const appended = await appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessionId, (lockedSession) => {
2199
+ const appended = await appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessionId, async (lockedSession, lockedUpdate) => {
2060
2200
  if (lockedSession.status === "cancelled") {
2061
2201
  throw new HTTPException8(409, { message: `session is ${lockedSession.status}; cannot accept a new user message` });
2062
2202
  }
2203
+ const mcpCredentialUpdates = input.mcpCredentialUpdates?.length ? await lockedUpdate.updateSessionMcpServerCredentials(input.mcpCredentialUpdates) : { servers: [], missingIds: [] };
2204
+ if (mcpCredentialUpdates.missingIds.length > 0) {
2205
+ throw new HTTPException8(422, { message: `unknown session MCP server id: ${mcpCredentialUpdates.missingIds[0]}` });
2206
+ }
2063
2207
  const nextResources = mergeResourceRefs(lockedSession.resources, input.resources);
2064
2208
  const nextTools = mergeToolRefs(lockedSession.tools, input.tools);
2065
2209
  const shouldQueueSession = lockedSession.status === "idle" || lockedSession.status === "failed";
@@ -2072,7 +2216,8 @@ async function postUserMessageTurn(input) {
2072
2216
  ...input.resources.length ? { resources: input.resources } : {},
2073
2217
  ...input.tools.length ? { tools: input.tools } : {},
2074
2218
  ...requestedModel ? { model: requestedModel } : {},
2075
- ...requestedReasoningEffort ? { reasoningEffort: requestedReasoningEffort } : {}
2219
+ ...requestedReasoningEffort ? { reasoningEffort: requestedReasoningEffort } : {},
2220
+ ...mcpCredentialUpdates.servers.length ? { mcpCredentialUpdates: mcpCredentialUpdates.servers } : {}
2076
2221
  },
2077
2222
  ...input.clientEventId ? { clientEventId: input.clientEventId } : {}
2078
2223
  },
@@ -2120,10 +2265,12 @@ async function postUserMessageTurn(input) {
2120
2265
  async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
2121
2266
  const { settings, db, bus, workflowClient, objectStorage } = deps;
2122
2267
  const payload = CreateSessionRequest.parse(rawPayload);
2123
- const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
2268
+ const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
2269
+ const sessionMcpServers = validateSessionMcpServersForCreate(capabilityRuntimeSettings, grant, payload.mcpServers);
2270
+ const runtimeSettings = settingsWithSessionMcpServerConfigs(capabilityRuntimeSettings, sessionMcpServers.runtimeServers);
2124
2271
  const resources = normalizeResources(payload.resources);
2125
2272
  const requestedTools = validateToolRefs(payload.tools, runtimeSettings);
2126
- const defaultedTools = hasOwnProperty(rawPayload, "tools") ? requestedTools : withDefaultEnabledCapabilityMcpTools(requestedTools, settings, runtimeSettings);
2273
+ const defaultedTools = hasOwnProperty(rawPayload, "tools") ? requestedTools : withDefaultEnabledCapabilityMcpTools(requestedTools, settings, capabilityRuntimeSettings);
2127
2274
  const tools = withFirstPartyTools(defaultedTools, runtimeSettings);
2128
2275
  await validateGitHubRepositorySelection(db, workspaceId, resources);
2129
2276
  if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
@@ -2226,7 +2373,13 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
2226
2373
  metadata: payload.metadata,
2227
2374
  environment: environment ? { id: environment.id, name: environment.name } : null,
2228
2375
  goal: payload.goal ?? null,
2376
+ // Per-session persona instructions (already trimmed/validated by the
2377
+ // contracts schema). Persisted on the row; composed system-level at turn
2378
+ // time. Not surfaced as an event.
2379
+ instructions: payload.instructions ?? null,
2229
2380
  firstPartyMcpPermissions,
2381
+ mcpServers: sessionMcpServers.dbServers,
2382
+ sessionMcpServers: sessionMcpServers.metadata,
2230
2383
  parentSessionId,
2231
2384
  createIdempotencyKey: payload.idempotencyKey ?? null,
2232
2385
  // Create-time machine targeting (A-2a): when a target sandbox is named, the
@@ -2250,11 +2403,12 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
2250
2403
  }
2251
2404
  async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, input) {
2252
2405
  const { settings, db, bus, workflowClient, objectStorage } = deps;
2253
- const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
2406
+ const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
2407
+ const existingSession = await requireSession2(db, workspaceId, sessionId);
2408
+ const runtimeSettings = settingsWithSessionMcpServerMetadata(capabilityRuntimeSettings, existingSession.mcpServers);
2254
2409
  const requestedResources = normalizeResources(input.resources ?? []);
2255
2410
  const validatedTools = validateToolRefs(input.tools ?? [], runtimeSettings);
2256
- const requestedTools = input.toolsProvided ? validatedTools : withDefaultEnabledCapabilityMcpTools(validatedTools, settings, runtimeSettings);
2257
- const existingSession = await requireSession2(db, workspaceId, sessionId);
2411
+ const requestedTools = input.toolsProvided ? validatedTools : withDefaultEnabledCapabilityMcpTools(validatedTools, settings, capabilityRuntimeSettings);
2258
2412
  await requireLimit(deps, {
2259
2413
  accountId: grant.accountId,
2260
2414
  workspaceId,
@@ -2267,6 +2421,12 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
2267
2421
  }
2268
2422
  await validateFileResources(db, workspaceId, requestedResources);
2269
2423
  await validateGitHubRepositorySelection(db, workspaceId, [...existingSession.resources, ...requestedResources]);
2424
+ const mcpCredentialUpdates = validateSessionMcpCredentialUpdates({
2425
+ settings,
2426
+ grant,
2427
+ session: existingSession,
2428
+ updates: input.mcpCredentialUpdates ?? []
2429
+ });
2270
2430
  const { accepted, turn } = await postUserMessageTurn({
2271
2431
  db,
2272
2432
  bus,
@@ -2280,6 +2440,7 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
2280
2440
  tools: requestedTools,
2281
2441
  model: input.model ?? null,
2282
2442
  reasoningEffort: input.reasoningEffort ?? null,
2443
+ mcpCredentialUpdates,
2283
2444
  ...input.clientEventId ? { clientEventId: input.clientEventId } : {}
2284
2445
  });
2285
2446
  await recordWorkspaceUsage(deps, {
@@ -2607,6 +2768,7 @@ export {
2607
2768
  scheduledTaskTriggerToken,
2608
2769
  settingsWithEnabledCapabilityMcpServers,
2609
2770
  settingsWithMcpCapabilityServers,
2771
+ settingsWithSessionMcpServerMetadata,
2610
2772
  stableJson,
2611
2773
  swapActiveSandbox,
2612
2774
  syncCreatedScheduledTask,