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
39 changes: 34 additions & 5 deletions apps/sim/executor/handlers/agent/agent-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ import type {
ResolvedSecretInputPath,
ResolvedSecretTraceRegistry,
} from '@/executor/utils/resolved-secret-trace-registry'
import { annotateDuplicateToolBindings } from '@/executor/utils/tool-binding-labels'
import { annotateToolPinnedParams } from '@/executor/utils/tool-pinned-params'
import { resolveVertexCredential } from '@/executor/utils/vertex-credential'
import { executeProviderRequest } from '@/providers'
import {
Expand All @@ -83,6 +83,7 @@ import {
getInlineHydrationMaxBytes,
} from '@/providers/file-attachments.server'
import { isAutoModel, SIM_AUTO_MODEL_ID } from '@/providers/models'
import { collectPinnedFieldsFromParams, registerToolPinnedFields } from '@/providers/tool-binding'
import {
type ProviderToolInputProvenance,
registerProviderToolInputProvenance,
Expand Down Expand Up @@ -210,6 +211,19 @@ function isTransportTimeout(error: unknown): boolean {
/**
* Handler for Agent blocks that process LLM requests with optional tools.
*/
/**
* Splits an MCP tool entry's stored params into the keys that identify the server and the values
* the user pinned on the call.
*
* Both MCP paths must strip the same control keys: they name the server rather than the request,
* and anything left in `userProvidedParams` is stated to the model as a pinned value. Keeping the
* split in one place is what stops the two paths drifting apart.
*/
function splitMcpControlParams(params: Record<string, any> | undefined) {
const { serverId, serverName, toolName, ...userProvidedParams } = params ?? {}
return { serverId, serverName, toolName, userProvidedParams }
}

export class AgentBlockHandler implements BlockHandler {
canHandle(block: SerializedBlock): boolean {
return block.metadata?.id === BlockType.AGENT
Expand Down Expand Up @@ -808,7 +822,9 @@ export class AgentBlockHandler implements BlockHandler {
const tools = allTools.filter(
(tool): tool is ProviderToolConfig => tool !== null && tool !== undefined
)
await annotateDuplicateToolBindings(ctx, tools)
// A tool whose params resolved an environment secret must not have its literal values stated;
// the provenance map already identifies exactly those tools.
await annotateToolPinnedParams(ctx, tools, (tool) => inputProvenance.has(tool))
return { tools, inputProvenance }
}

Expand Down Expand Up @@ -1127,7 +1143,9 @@ export class AgentBlockHandler implements BlockHandler {
projectedTool?: ToolInput,
toolIndex?: number
): Promise<any> {
const { serverId, toolName, serverName, ...userProvidedParams } = tool.params || {}
const { serverId, serverName, toolName, userProvidedParams } = splitMcpControlParams(
tool.params
)
const projectedSchema = projectedTool?.schema ?? tool.schema
if (projectedSchema !== undefined && !isPlainRecord(projectedSchema)) {
refuseResolvedSecretProjection({
Expand Down Expand Up @@ -1352,7 +1370,7 @@ export class AgentBlockHandler implements BlockHandler {
mcpTool: any,
serverId: string
): Promise<any> {
const { toolName, ...userProvidedParams } = tool.params || {}
const { toolName, userProvidedParams } = splitMcpControlParams(tool.params)
return this.buildMcpTool({
serverId,
toolName,
Expand All @@ -1374,13 +1392,24 @@ export class AgentBlockHandler implements BlockHandler {
const filteredSchema = filterSchemaForLLM(config.schema, config.userProvidedParams)
const toolId = createMcpToolId(config.serverId, config.toolName)

return {
const mcpTool = {
id: toolId,
description: config.description,
parameters: filteredSchema,
params: config.userProvidedParams,
usageControl: config.usageControl || 'auto',
}

const { formatParameterLabel, isPasswordParameter } = await import('@/tools/params')
registerToolPinnedFields(
mcpTool,
collectPinnedFieldsFromParams(config.userProvidedParams, {
formatParamLabel: formatParameterLabel,
isPasswordParam: isPasswordParameter,
})
)

return mcpTool
}

private async transformBlockTool(
Expand Down
21 changes: 19 additions & 2 deletions apps/sim/executor/handlers/pi/local/sim-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type { PiToolResult, PiToolSpec } from '@/executor/handlers/pi/core/backe
import type { ExecutionContext } from '@/executor/types'
import { projectResolvedSecretModelContent } from '@/executor/utils/resolved-secret-content-projection'
import type { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry'
import { annotateDuplicateToolBindings } from '@/executor/utils/tool-binding-labels'
import { annotateToolPinnedParams } from '@/executor/utils/tool-pinned-params'
import { assignProviderToolIdentities } from '@/providers/tool-identity'
import type { ProviderToolConfig } from '@/providers/types'
import { transformBlockTool } from '@/providers/utils'
Expand Down Expand Up @@ -233,7 +233,24 @@ export async function buildSimToolSpecs(
}

const providers = configuredTools.map(({ provider }) => provider)
await annotateDuplicateToolBindings(ctx, providers)

// Withhold a tool's literal values only when that tool's own params resolved a secret, asking
// the registry the same per-input-path question the Agent block asks. A run-wide flag would be
// safe but near-useless here: one `{{API_KEY}}` anywhere in a workflow would blank the literals
// on every Pi tool for the whole run.
const registry = ctx.resolvedSecretTraceRegistry
const withheld = new Set<ProviderToolConfig>()
if (registry) {
for (const { provider, toolIndex } of configuredTools) {
const provenance = registry.exportCommittedProvenanceForInputPaths([
['tools', String(toolIndex), 'params'],
])
// An incomplete projection means the registry cannot vouch for the value; treat that the
// same as carrying a secret.
if (!provenance.complete || provenance.entries.length > 0) withheld.add(provider)
}
}
await annotateToolPinnedParams(ctx, providers, (tool) => withheld.has(tool))
assignProviderToolIdentities(providers)
return configuredTools.map(({ provider, toolIndex }) =>
buildSimToolSpec(ctx, inputTools, provider, toolIndex)
Expand Down
256 changes: 0 additions & 256 deletions apps/sim/executor/utils/tool-binding-labels.test.ts

This file was deleted.

Loading
Loading