Skip to content
Merged
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
23 changes: 19 additions & 4 deletions actions/setup/js/safe_outputs_mcp_server_http.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,25 @@ function createMCPServer(options = {}) {

// Register predefined tools
for (const tool of toolsWithHandlers) {
// Check if tool is enabled in configuration
if (!enabledTools.has(tool.name)) {
logger.debug(`Skipping tool ${tool.name} - not enabled in config`);
continue;
// Check if this is a dispatch_workflow tool (has _workflow_name metadata)
// These tools are dynamically generated with workflow-specific names
const isDispatchWorkflowTool = !!tool._workflow_name;

if (isDispatchWorkflowTool) {
logger.debug(`Found dispatch_workflow tool: ${tool.name} (_workflow_name: ${tool._workflow_name})`);
if (!safeOutputsConfig.dispatch_workflow) {
logger.debug(` WARNING: dispatch_workflow config is missing or falsy - tool will NOT be registered`);
logger.debug(` Config keys: ${Object.keys(safeOutputsConfig).join(", ")}`);
logger.debug(` config.dispatch_workflow value: ${JSON.stringify(safeOutputsConfig.dispatch_workflow)}`);
continue;
}
logger.debug(` dispatch_workflow config exists, registering tool`);
} else {
// Check if regular tool is enabled in configuration
if (!enabledTools.has(tool.name)) {
logger.debug(`Skipping tool ${tool.name} - not enabled in config`);
continue;
}
}

logger.debug(`Registering tool: ${tool.name}`);
Expand Down
196 changes: 196 additions & 0 deletions actions/setup/js/safe_outputs_mcp_server_http_dispatch.test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// @ts-check
import { describe, it, expect, beforeEach, vi } from "vitest";

/**
* Test for dispatch_workflow tool registration in HTTP server
*
* This test validates that the HTTP server correctly registers dispatch_workflow tools
* that have _workflow_name metadata. These tools use workflow-specific names
* (e.g., "test_workflow") while the config key is "dispatch_workflow".
*
* Reference: Issue where dispatch_workflow tools were not being registered because
* the HTTP server's registration loop only checked if the tool name matched a config key,
* but dispatch_workflow tools don't match - they have workflow-specific names with metadata.
*/
describe("safe_outputs_mcp_server_http dispatch_workflow registration", () => {
it("should register dispatch_workflow tools when config.dispatch_workflow exists", () => {
// Simulate the tool with _workflow_name metadata
const tool = {
name: "test_workflow",
_workflow_name: "test-workflow",
description: "Dispatch the 'test-workflow' workflow",
inputSchema: {
type: "object",
properties: {
test_param: {
type: "string",
description: "Test parameter",
},
},
additionalProperties: false,
},
};

// Simulate the config with dispatch_workflow key
const config = {
dispatch_workflow: {
max: 1,
workflows: ["test-workflow"],
workflow_files: {
"test-workflow": ".yml",
},
},
missing_tool: {},
missing_data: {},
noop: { max: 1 },
};

// Simulate the enabledTools set (based on config keys)
const enabledTools = new Set();
for (const [toolName, enabled] of Object.entries(config)) {
if (enabled) {
enabledTools.add(toolName);
}
}

// Test the registration logic (extracted from safe_outputs_mcp_server_http.cjs)
const isDispatchWorkflowTool = !!tool._workflow_name;
let shouldRegister = false;

if (isDispatchWorkflowTool) {
// Dispatch workflow tools should be registered if config.dispatch_workflow exists
if (config.dispatch_workflow) {
shouldRegister = true;
}
} else {
// Regular tools should be registered if their name is in enabledTools
if (enabledTools.has(tool.name)) {
shouldRegister = true;
}
}

// Verify that the dispatch_workflow tool should be registered
expect(shouldRegister).toBe(true);
expect(isDispatchWorkflowTool).toBe(true);
expect(config.dispatch_workflow).toBeTruthy();
});

it("should NOT register dispatch_workflow tools when config.dispatch_workflow is missing", () => {
// Simulate the tool with _workflow_name metadata
const tool = {
name: "test_workflow",
_workflow_name: "test-workflow",
description: "Dispatch the 'test-workflow' workflow",
inputSchema: { type: "object", properties: {} },
};

// Config WITHOUT dispatch_workflow
const config = {
missing_tool: {},
missing_data: {},
noop: { max: 1 },
};

const enabledTools = new Set();
for (const [toolName, enabled] of Object.entries(config)) {
if (enabled) {
enabledTools.add(toolName);
}
}

const isDispatchWorkflowTool = !!tool._workflow_name;
let shouldRegister = false;

if (isDispatchWorkflowTool) {
if (config.dispatch_workflow) {
shouldRegister = true;
}
} else {
if (enabledTools.has(tool.name)) {
shouldRegister = true;
}
}

// Verify that the tool should NOT be registered
expect(shouldRegister).toBe(false);
expect(isDispatchWorkflowTool).toBe(true);
expect(config.dispatch_workflow).toBeFalsy();
});

it("should register regular tools when their name is in config", () => {
// Regular tool (no _workflow_name)
const tool = {
name: "missing_tool",
description: "Report missing tool",
inputSchema: { type: "object", properties: {} },
};

const config = {
missing_tool: {},
missing_data: {},
};

const enabledTools = new Set();
for (const [toolName, enabled] of Object.entries(config)) {
if (enabled) {
enabledTools.add(toolName);
}
}

const isDispatchWorkflowTool = !!tool._workflow_name;
let shouldRegister = false;

if (isDispatchWorkflowTool) {
if (config.dispatch_workflow) {
shouldRegister = true;
}
} else {
if (enabledTools.has(tool.name)) {
shouldRegister = true;
}
}

// Verify that regular tool should be registered
expect(shouldRegister).toBe(true);
expect(isDispatchWorkflowTool).toBe(false);
expect(enabledTools.has(tool.name)).toBe(true);
});

it("should NOT register regular tools when their name is not in config", () => {
const tool = {
name: "some_other_tool",
description: "Some other tool",
inputSchema: { type: "object", properties: {} },
};

const config = {
missing_tool: {},
missing_data: {},
};

const enabledTools = new Set();
for (const [toolName, enabled] of Object.entries(config)) {
if (enabled) {
enabledTools.add(toolName);
}
}

const isDispatchWorkflowTool = !!tool._workflow_name;
let shouldRegister = false;

if (isDispatchWorkflowTool) {
if (config.dispatch_workflow) {
shouldRegister = true;
}
} else {
if (enabledTools.has(tool.name)) {
shouldRegister = true;
}
}

// Verify that the tool should NOT be registered
expect(shouldRegister).toBe(false);
expect(isDispatchWorkflowTool).toBe(false);
expect(enabledTools.has(tool.name)).toBe(false);
});
});
Loading