fix(workflow-executor): align getMcpServerConfigs port with Record shape#1583
Merged
hercemer42 merged 3 commits intoMay 20, 2026
Conversation
|
Coverage Impact ⬆️ Merging this pull request will increase total coverage on Modified Files with Diff Coverage (10) 🛟 Help
|
hercemer42
commented
May 18, 2026
hercemer42
commented
May 18, 2026
hercemer42
commented
May 18, 2026
hercemer42
commented
May 19, 2026
4 tasks
The orchestrator's /liana/mcp-server-configs-with-details endpoint returns Record<string, ToolConfig>, but the executor port was typed McpConfiguration[] and runner.fetchRemoteTools called .map on it — every MCP-typed step crashed with "TypeError: configs.map is not a function" before reaching loadRemoteTools. Tests masked the bug by mocking mockResolvedValue([]) at 8 call sites, which matched the wrong type and short-circuited the buggy branch. Refs: PRD-357
…mize test server name Per @hercemer42: - Remove `McpConfiguration` re-export from the port and barrel: it's only used internally by the AiModelPort/adapters now, imported directly from @forestadmin/ai-proxy. - Drop the wire-shape comment on getMcpServerConfigs: the McpServers type and the route URL in the adapter are self-documenting. - Rename test config key from "data-gouv" to "mcp-server-1" to keep the test free of real-world references. Refs: PRD-357
32c1afb to
480148c
Compare
* fix(workflow-executor): match MCP tools by config id The executor's filter compared `tool.sourceId` (server display name) against `step.mcpServerId` (DB id written by the frontend), so any workflow that specified an MCP server failed with NoMcpToolsError regardless of configuration. Threads the stable DB id from each ToolConfig entry through ai-proxy (McpClient, ForestIntegrationClient and the integration factories) onto RemoteTool.mcpServerId, and switches the executor to match by id. Enriches NoMcpToolsError's technical message with the requested id and the loaded id list so misconfigurations are diagnosable from the structured logs; the user-facing message stays generic per the dual-message convention. The new tool-side field is named `mcpServerId` (not `id`) to read honestly at the consumer site — `tool.mcpServerId === step.mcpServerId` expresses the FK relationship plainly. `McpServerConfig.id` and `ForestIntegrationConfig.id` keep `id` since those mirror the wire shape (which itself mirrors the `ai_mcp_configs` PK column). fixes PRD-362
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Runner.fetchRemoteTools()crashed withTypeError: configs.map is not a functionon every MCP-typed step. The orchestrator'sGET /liana/mcp-server-configs-with-detailsreturnsRecord<string, ToolConfig>, but the executor port was typedPromise<McpConfiguration[]>and the runner called.mapon the object.McpServers(Record<string, McpServerConfig>, from@forestadmin/ai-proxy), runner wraps the record into{ configs }forloadRemoteTools, with an empty-Record short-circuit.mockResolvedValue([])plus the[{ configs: {} }] as nevercast inrunner.test.ts) to the real Record shape so the contract can't silently regress again. Adds two TDD tests underMCP lazy loading (via once thunk)for the non-empty-Record happy path and the empty-Record short-circuit.Dependency bumps (heads-up)
packages/workflow-executor/package.jsonbumps:@forestadmin/ai-proxy1.8.0 → 1.9.0 — load-bearing for this fix (theMcpServerstype only exists from 1.9.0 onward).@forestadmin/forestadmin-client1.39.5 → 1.39.7 — incidental, was already staged in the user's pre-existing main-branch state.These cannot be split into a separate PR because the workspace-resolved import in
src/ports/workflow-port.tswould fail to compile against ai-proxy 1.8.0.Test plan
yarn workspace @forestadmin/workflow-executor jest --runInBand --forceExit --testMatch='**/runner.test.ts'— 83/83 pass (including 3 new MCP-lazy-loading tests)yarn workspace @forestadmin/workflow-executor jest --runInBand --forceExiton the 9 changed test files — 361 tests greennpx eslint,npx prettier --check,npx tsc --noEmiton all 13 changed files — cleandata-gouvMCP server on theexecutordev project — pre-fix run aborted with the generic"An unexpected error occurred."(masking the TypeError); post-fix run reportsNoMcpToolsErrorcleanly, which is the legitimate downstream behaviour when no tools materializeRefs: PRD-357
Note
Fix
getMcpServerConfigsreturn type to useMcpServersRecord shape in workflow-executorMcpConfiguration[]array withMcpServers(aRecord<string, ToolConfig>map) as the return type ofgetMcpServerConfigsacross workflow-port.ts, forest-server-workflow-port.ts, and the public exports in index.ts.Runner.fetchRemoteToolsin runner.ts to skip callingaiModelPort.loadRemoteToolswhen the config map is empty, and passes{ configs }(the Record wrapped in an object) when non-empty.@forestadmin/ai-proxyto 1.9.0 and@forestadmin/forestadmin-clientto 1.39.7.loadRemoteToolsis no longer called when the orchestrator returns an empty MCP config map; callers relying on the old array format forgetMcpServerConfigswill need to update to the Record shape.Changes since #1583 opened
McpStepExecutor.getFilteredToolsto filter tools bytool.mcpServerIdinstead oftool.sourceIdwhen a step specifiesmcpServerId, and updatedNoMcpToolsErrorto include diagnostic context showing the requestedmcpServerIdand list of availablemcpServerIdswhen filtering yields no matches [53618a7]mcpServerIdproperty toRemoteToolclass and threaded it throughServerRemoteToolandMcpServerRemoteToolsubclass constructors [53618a7]McpClientto extractidfrom each server config entry, store it in an internalmcpServerIdsByNamemap, and populatemcpServerIdonMcpServerRemoteToolinstances during tool loading, and changedMcpConfiguration.configstype fromMultiServerMCPClient['config']['mcpServers']toRecord<string, McpServerConfig>whereMcpServerConfigincludes optionalid?: string[53618a7]ForestIntegrationConfiginterface with optionalid?: stringand updatedForestIntegrationClient.loadToolsto thread thisidasmcpServerIdparameter togetZendeskTools,getKolarTools, andgetSnowflakeToolsfactory functions, which were each extended to accept optionalmcpServerIdand pass it toServerRemoteToolconstructor [53618a7]mcpServerIdpropagation acrossForestIntegrationClient, integration tool factories (getZendeskTools,getKolarTools,getSnowflakeTools),McpClient,McpStepExecutorfiltering behavior, andNoMcpToolsErrormessage construction [53618a7]Macroscope summarized 480148c.