Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/20260904140000-inline-mcp-and-skills.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@truefoundry/trueforge": patch
---

Accept `x-tfg-mcp` and `x-tfg-skills` in TrueFoundry mode: MCP servers and skills a request defines by name, taking precedence over the tenant registry for that request only. Both resolve for spec validation and turn execution; an unfiltered list still shows only configured resources, so request-scoped ones never appear in settings. Inline MCP credentials come from the manifest's own `auth.headers`, which lets a rotating token ride each turn.
2 changes: 1 addition & 1 deletion packages/trueforge/scripts/write-openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const app = createServerApp({
}),
tokenStore,
skillCatalog: SkillCatalog.load(),
skillStore: new SqliteSkillStore(db),
resolveSkillStore: () => new SqliteSkillStore(db),
sandboxCatalog: SandboxCatalog.load(),
sandboxProviderStore: new SqliteSandboxProviderStore(db),
resolveAgentStore: () => agentStore,
Expand Down
8 changes: 6 additions & 2 deletions packages/trueforge/src/apis/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface AgentsRouterDeps<TTransaction> {
resolveAgentStore: (c: Context) => IAgentStore<TTransaction>;
resolveModelProviderStore: (c: Context) => IModelProviderStore<TTransaction>;
resolveMcpServerStore: (c: Context) => IMcpServerStore<TTransaction>;
skillStore: ISkillStore<TTransaction>;
resolveSkillStore: (c: Context) => ISkillStore<TTransaction>;
sandboxProviderStore: ISandboxProviderStore<TTransaction>;
withTransaction: WithTransaction<TTransaction>;
resolveRequestContext: ResolveRequestContext;
Expand All @@ -53,20 +53,22 @@ async function validateManifest<TTransaction>({
deps,
modelProviderStore,
mcpServerStore,
skillStore,
tenant_id,
}: {
spec: AgentSpec;
deps: AgentsRouterDeps<TTransaction>;
modelProviderStore: IModelProviderStore<TTransaction>;
mcpServerStore: IMcpServerStore<TTransaction>;
skillStore: ISkillStore<TTransaction>;
tenant_id: string;
}): Promise<AgentSpec> {
await validateAgentSpec({
spec,
tenant_id,
modelProviderStore,
mcpServerStore,
skillStore: deps.skillStore,
skillStore,
sandboxProviderStore: deps.sandboxProviderStore,
});
return spec;
Expand All @@ -87,6 +89,7 @@ export function createAgentsRouter<TTransaction>(deps: AgentsRouterDeps<TTransac
deps,
modelProviderStore: deps.resolveModelProviderStore(c),
mcpServerStore: deps.resolveMcpServerStore(c),
skillStore: deps.resolveSkillStore(c),
tenant_id: requestContext.tenant_id,
});
try {
Expand Down Expand Up @@ -156,6 +159,7 @@ export function createAgentsRouter<TTransaction>(deps: AgentsRouterDeps<TTransac
deps,
modelProviderStore: deps.resolveModelProviderStore(c),
mcpServerStore: deps.resolveMcpServerStore(c),
skillStore: deps.resolveSkillStore(c),
tenant_id: requestContext.tenant_id,
});
const record = await deps.resolveAgentStore(c).updateAgent({
Expand Down
10 changes: 5 additions & 5 deletions packages/trueforge/src/apis/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export interface SessionsRouterDeps {
activeTurns: ActiveTurnRegistry;
resolveModelProviderStore: (c: Context) => IModelProviderStore;
resolveMcpServerStore: (c: Context) => IMcpServerStore;
skillStore: ISkillStore;
resolveSkillStore: (c: Context) => ISkillStore;
resolveAgentStore: (c: Context) => IAgentStore;
sandboxProviderStore: ISandboxProviderStore;
redis?: RedisClientType | undefined;
Expand Down Expand Up @@ -229,7 +229,7 @@ type InternalSessionsRouterDeps = Pick<
| 'sessions'
| 'resolveModelProviderStore'
| 'resolveMcpServerStore'
| 'skillStore'
| 'resolveSkillStore'
| 'resolveAgentStore'
| 'sandboxProviderStore'
| 'resolveRequestContext'
Expand Down Expand Up @@ -274,7 +274,7 @@ function createGetOrCreateSessionByExternalIdHandler(
tenant_id: requestContext.tenant_id,
modelProviderStore: deps.resolveModelProviderStore(c),
mcpServerStore: deps.resolveMcpServerStore(c),
skillStore: deps.skillStore,
skillStore: deps.resolveSkillStore(c),
sandboxProviderStore: deps.sandboxProviderStore,
});
agent = { type: 'inline', spec: body.agent.spec };
Expand Down Expand Up @@ -337,7 +337,7 @@ export function createSessionsRouter(deps: SessionsRouterDeps) {
tenant_id: requestContext.tenant_id,
modelProviderStore: deps.resolveModelProviderStore(c),
mcpServerStore: deps.resolveMcpServerStore(c),
skillStore: deps.skillStore,
skillStore: deps.resolveSkillStore(c),
sandboxProviderStore: deps.sandboxProviderStore,
});
const session = await deps.sessions.create({
Expand Down Expand Up @@ -425,7 +425,7 @@ export function createSessionsRouter(deps: SessionsRouterDeps) {
tenant_id: requestContext.tenant_id,
modelProviderStore: deps.resolveModelProviderStore(c),
mcpServerStore: deps.resolveMcpServerStore(c),
skillStore: deps.skillStore,
skillStore: deps.resolveSkillStore(c),
sandboxProviderStore: deps.sandboxProviderStore,
});
}
Expand Down
10 changes: 6 additions & 4 deletions packages/trueforge/src/apis/turns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export interface TurnsRouterDeps {
activeTurns: ActiveTurnRegistry;
resolveModelProviderStore: (c: Context) => IModelProviderStore;
resolveMcpServerStore: (c: Context) => IMcpServerWithAuthStore;
skillStore: ISkillStore;
resolveSkillStore: (c: Context) => ISkillStore;
resolveAgentStore: (c: Context) => IAgentStore;
/** Resumable live turn-event transport: create-turn writes, subscribe polls. */
eventSubscriptions: EventSubscriptionRegistry<TurnStreamingEvent>;
Expand All @@ -116,15 +116,16 @@ export interface TurnsRouterDeps {

/**
* Deps needed to create a turn and drain events in-process (no HTTP). Unlike the HTTP path, this
* carries already-resolved `modelProviderStore` / `mcpServerStore` / `agentStore` (the scheduler has no request
* context to resolve them).
* carries already-resolved `modelProviderStore` / `mcpServerStore` / `skillStore` / `agentStore`
* (the scheduler has no request context to resolve them).
*/
export type BeginTurnExecutionDeps = Pick<
TurnsRouterDeps,
'activeTurns' | 'eventSubscriptions' | 'skillStore' | 'sandboxProviderStore' | 'logger'
'activeTurns' | 'eventSubscriptions' | 'sandboxProviderStore' | 'logger'
> & {
modelProviderStore: IModelProviderStore;
mcpServerStore: IMcpServerWithAuthStore;
skillStore: ISkillStore;
agentStore: IAgentStore;
};

Expand Down Expand Up @@ -722,6 +723,7 @@ export function createTurnsRouter(deps: TurnsRouterDeps) {
...deps,
modelProviderStore: deps.resolveModelProviderStore(c),
mcpServerStore: deps.resolveMcpServerStore(c),
skillStore: deps.resolveSkillStore(c),
agentStore: deps.resolveAgentStore(c),
},
};
Expand Down
16 changes: 8 additions & 8 deletions packages/trueforge/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export interface ServerDeps<TTransaction> {
resolveAgentStore: (c?: Context) => IAgentStore<TTransaction>;
withTransaction: WithTransaction<TTransaction>;
tokenStore: IOAuthTokenStore<TTransaction>;
skillStore: ISkillStore<TTransaction>;
resolveSkillStore: (c?: Context) => ISkillStore<TTransaction>;
sandboxProviderStore: ISandboxProviderStore<TTransaction>;
scheduleStore: IScheduleStore<TTransaction>;
sessionStore: ISessionStore;
Expand Down Expand Up @@ -284,7 +284,7 @@ export function createServerApp<TTransaction>(deps: ServerDeps<TTransaction>) {
'/api/v1/skills',
withAuth(
createAvailableSkillsRouter({
skillStore: deps.skillStore,
skillStore: deps.resolveSkillStore(),
withTransaction: deps.withTransaction,
resolveRequestContext,
}),
Expand All @@ -298,7 +298,7 @@ export function createServerApp<TTransaction>(deps: ServerDeps<TTransaction>) {
resolveAgentStore: deps.resolveAgentStore,
resolveModelProviderStore: deps.resolveModelProviderStore,
resolveMcpServerStore: deps.resolveMcpServerStore,
skillStore: deps.skillStore,
resolveSkillStore: deps.resolveSkillStore,
sandboxProviderStore: deps.sandboxProviderStore,
withTransaction: deps.withTransaction,
resolveRequestContext,
Expand All @@ -318,7 +318,7 @@ export function createServerApp<TTransaction>(deps: ServerDeps<TTransaction>) {
eventSubscriptions: deps.eventSubscriptions,
modelProviderStore: deps.resolveModelProviderStore(),
mcpServerStore: deps.resolveMcpServerStore(),
skillStore: deps.skillStore,
skillStore: deps.resolveSkillStore(),
agentStore: deps.resolveAgentStore(),
sandboxProviderStore: deps.sandboxProviderStore,
logger: deps.logger,
Expand All @@ -336,7 +336,7 @@ export function createServerApp<TTransaction>(deps: ServerDeps<TTransaction>) {
resolveModelProviderStore: deps.resolveModelProviderStore,
resolveMcpServerStore: deps.resolveMcpServerStore,
tokenStore: deps.tokenStore,
skillStore: deps.skillStore,
skillStore: deps.resolveSkillStore(),
sandboxProviderStore: deps.sandboxProviderStore,
withTransaction: deps.withTransaction,
logger: deps.logger,
Expand All @@ -352,7 +352,7 @@ export function createServerApp<TTransaction>(deps: ServerDeps<TTransaction>) {
sessions: deps.sessions,
resolveModelProviderStore: deps.resolveModelProviderStore,
resolveMcpServerStore: deps.resolveMcpServerStore,
skillStore: deps.skillStore,
resolveSkillStore: deps.resolveSkillStore,
resolveAgentStore: deps.resolveAgentStore,
sandboxProviderStore: deps.sandboxProviderStore,
resolveRequestContext,
Expand All @@ -379,7 +379,7 @@ export function createServerApp<TTransaction>(deps: ServerDeps<TTransaction>) {
activeTurns: deps.activeTurns,
resolveModelProviderStore: deps.resolveModelProviderStore,
resolveMcpServerStore: deps.resolveMcpServerStore,
skillStore: deps.skillStore,
resolveSkillStore: deps.resolveSkillStore,
resolveAgentStore: deps.resolveAgentStore,
sandboxProviderStore: deps.sandboxProviderStore,
redis: deps.redis,
Expand All @@ -399,7 +399,7 @@ export function createServerApp<TTransaction>(deps: ServerDeps<TTransaction>) {
activeTurns: deps.activeTurns,
resolveModelProviderStore: deps.resolveModelProviderStore,
resolveMcpServerStore: deps.resolveMcpServerStore,
skillStore: deps.skillStore,
resolveSkillStore: deps.resolveSkillStore,
resolveAgentStore: deps.resolveAgentStore,
eventSubscriptions: deps.eventSubscriptions,
sandboxProviderStore: deps.sandboxProviderStore,
Expand Down
47 changes: 41 additions & 6 deletions packages/trueforge/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ import { PACKAGE_VERSION } from './packageVersion';
import { ActiveTurnRegistry } from './runtime/activeTurns';
import { EventSubscriptionRegistry } from './runtime/event-subscription';
import { printStandaloneStartupBanner } from './startupBanner';
import { InlineMcpServerStore } from './truefoundry/InlineMcpServerStore';
import { parseInlineMcpServers, parseInlineSkills, X_TFG_MCP, X_TFG_SKILLS } from './truefoundry/inlineResources';
import { InlineSkillStore } from './truefoundry/InlineSkillStore';
import { parsePerServerMcpHeaders, X_TFG_MCP_HEADERS } from './truefoundry/perServerMcpHeaders';
import { TrueFoundryAgentStore } from './truefoundry/TrueFoundryAgentStore';
import { TrueFoundryMcpServerStore } from './truefoundry/TrueFoundryMcpServerStore';
Expand All @@ -99,7 +102,7 @@ interface ServerPersistence<TTransaction> {
resolveAgentStore: (c?: Context) => IAgentStore<TTransaction>;
withTransaction: WithTransaction<TTransaction>;
tokenStore: IOAuthTokenStore<TTransaction>;
skillStore: ISkillStore<TTransaction>;
resolveSkillStore: (c?: Context) => ISkillStore<TTransaction>;
sandboxProviderStore: ISandboxProviderStore<TTransaction>;
scheduleStore: IScheduleStore<TTransaction>;
destroyDb: () => Promise<void>;
Expand Down Expand Up @@ -179,12 +182,38 @@ function buildResolveMcpServerStore<TTransaction>(options: {
}
const requestContext = resolveRequestContext(c);
const rawPerServerHeaders = c.req.header(X_TFG_MCP_HEADERS);
return new TrueFoundryMcpServerStore<TTransaction>({
const store = new TrueFoundryMcpServerStore<TTransaction>({
client,
accessToken: requireRequestCredentialToken(c),
subject: requestContext.subject,
perServerHeaders: rawPerServerHeaders ? parsePerServerMcpHeaders(rawPerServerHeaders) : {},
});
const rawInline = c.req.header(X_TFG_MCP);
if (rawInline === undefined) {
return store;
}
return new InlineMcpServerStore<TTransaction>({ inner: store, inline: parseInlineMcpServers(rawInline) });
};
}

/**
* Per-request skill store resolver. Skills are always DB-backed; in TrueFoundry mode a request may
* additionally carry its own definitions, which take precedence over the tenant's registry.
*/
function buildResolveSkillStore<TTransaction>(options: {
persistenceStore: ISkillStore<TTransaction>;
trueFoundryMode: boolean;
}): (c?: Context) => ISkillStore<TTransaction> {
const { persistenceStore, trueFoundryMode } = options;
if (!trueFoundryMode) {
return () => persistenceStore;
}
return c => {
const rawInline = c?.req.header(X_TFG_SKILLS);
if (rawInline === undefined) {
return persistenceStore;
}
return new InlineSkillStore<TTransaction>({ inner: persistenceStore, inline: parseInlineSkills(rawInline) });
};
}

Expand Down Expand Up @@ -267,7 +296,10 @@ async function createStandalonePersistence(options: {
resolveAgentStore: () => agentStore,
withTransaction: callback => db.transaction().execute(callback),
tokenStore,
skillStore: new SqliteSkillStore(db),
resolveSkillStore: buildResolveSkillStore({
persistenceStore: new SqliteSkillStore(db),
trueFoundryMode: false,
}),
sandboxProviderStore: new SqliteSandboxProviderStore(db),
scheduleStore: new SqliteScheduleStore(db),
destroyDb: () => db.destroy(),
Expand Down Expand Up @@ -349,7 +381,10 @@ async function createDistributedPersistence(options: {
}),
withTransaction: callback => db.transaction().execute(callback),
tokenStore,
skillStore: new PostgresSkillStore(db),
resolveSkillStore: buildResolveSkillStore({
persistenceStore: new PostgresSkillStore(db),
trueFoundryMode: serviceFoundryClient !== undefined,
}),
sandboxProviderStore: new PostgresSandboxProviderStore(db),
scheduleStore: new PostgresScheduleStore(db),
destroyDb: () => db.destroy(),
Expand All @@ -367,7 +402,7 @@ async function createServerRuntime<TTransaction>(persistence: ServerPersistence<
resolveAgentStore,
withTransaction,
tokenStore,
skillStore,
resolveSkillStore,
sandboxProviderStore,
scheduleStore,
destroyDb,
Expand Down Expand Up @@ -428,7 +463,7 @@ async function createServerRuntime<TTransaction>(persistence: ServerPersistence<
resolveAgentStore,
withTransaction,
tokenStore,
skillStore,
resolveSkillStore,
sandboxProviderStore,
scheduleStore,
sessionStore,
Expand Down
Loading
Loading