Dify Workflows MCP Server
by localSummer
- build
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
import { loadConfig } from './config.js';
import { DifyClient } from './dify-client.js';
import { UserInputControlType } from './types.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Load configuration
const config = loadConfig(process.env.CONFIG_PATH || path.resolve(__dirname, '../config.yaml'));
// Create server instance
const server = new Server({
name: 'dify-workflow-mcp',
version: '1.0.0'
}, {
capabilities: {
tools: {}
}
});
// cache dify parameters
const difyParametersMap = new Map();
// cache name app sks
const appSkMap = new Map();
// Initialize Dify clients
const difyClients = new Map();
for (const appSk of config.dify_app_sks) {
const client = new DifyClient(config.dify_base_url, appSk);
difyClients.set(appSk, client);
}
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
const tools = [];
let index = 0;
for (const client of difyClients.values()) {
try {
const [appInfo, parameters] = await Promise.all([client.getAppInfo(), client.getParameters()]);
const inputSchema = convertDifyParametersToJsonSchema(parameters);
// Cache Dify parameters
difyParametersMap.set(appInfo.name, parameters);
// Cache app sk
appSkMap.set(appInfo.name, config.dify_app_sks[index++]);
tools.push({
name: appInfo.name,
description: appInfo.description,
inputSchema
});
}
catch (error) {
console.error('Failed to load tool:', error);
}
}
return { tools };
});
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
// Find the corresponding Dify client
const appSk = appSkMap.get(name);
if (!appSk) {
throw new Error('Unsupported tool');
}
const client = difyClients.get(appSk);
if (!client) {
throw new Error('No Dify client available');
}
const difyParameters = difyParametersMap.get(name);
if (!difyParameters) {
throw new Error('No Dify parameters available');
}
// Validate input parameters
const validatedArgs = await validateInputParameters(args, difyParameters);
// Execute the workflow
const result = await client.runWorkflow(validatedArgs);
return {
content: [
{
type: 'text',
text: result
}
]
};
}
catch (error) {
if (error instanceof z.ZodError) {
throw new Error(`Invalid arguments: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`);
}
throw error;
}
});
// Validate input parameters
const validateInputParameters = (args, difyParameters) => {
const schema = z.object(Object.fromEntries(difyParameters.user_input_form.map((form) => {
if (isParagraphInput(form)) {
const { required, label, variable } = form[UserInputControlType.ParagraphInput];
const currentSchema = required
? z.string({
message: `${label} is required!`
})
: z.optional(z.string());
return [variable, currentSchema];
}
if (isTextInput(form)) {
const { required, label, variable } = form[UserInputControlType.TextInput];
const currentSchema = required
? z.string({
message: `${label} is required!`
})
: z.optional(z.string());
return [variable, currentSchema];
}
if (isSelectInput(form)) {
const { required, options, variable } = form[UserInputControlType.SelectInput];
const currentSchema = required
? z.enum(options)
: z.optional(z.enum(options));
return [variable, currentSchema];
}
if (isNumberInput(form)) {
const { required, label, variable } = form[UserInputControlType.NumberInput];
const currentSchema = required
? z.number({
message: `${label} is required!`
})
: z.optional(z.number());
return [variable, currentSchema];
}
throw new Error(`Invalid difyParameters`);
})));
return schema.parse(args);
};
/**
* Convert Dify parameters to JSON Schema
*/
const convertDifyParametersToJsonSchema = (parameters) => {
const inputSchema = {
type: 'object',
properties: {},
required: []
};
for (const input of parameters.user_input_form) {
// 处理 UserInputControlType.TextInput
if (isTextInput(input)) {
inputSchema.properties[input[UserInputControlType.TextInput].variable] = {
type: 'string'
};
}
// 处理 UserInputControlType.ParagraphInput
if (isParagraphInput(input)) {
inputSchema.properties[input[UserInputControlType.ParagraphInput].variable] = {
type: 'string'
};
}
// 处理 UserInputControlType.SelectInput
if (isSelectInput(input)) {
inputSchema.properties[input[UserInputControlType.SelectInput].variable] = {
type: 'array',
enum: input[UserInputControlType.SelectInput].options
};
}
// 处理 UserInputControlType.NumberInput
if (isNumberInput(input)) {
inputSchema.properties[input[UserInputControlType.NumberInput].variable] = {
type: 'number'
};
}
}
return inputSchema;
};
const isTextInput = (input) => {
return input['text'] !== undefined;
};
const isParagraphInput = (input) => {
return input['paragraph'] !== undefined;
};
const isSelectInput = (input) => {
return input['select'] !== undefined;
};
const isNumberInput = (input) => {
return input['number'] !== undefined;
};
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Dify MCP Server running on stdio');
}
main().catch((error) => {
console.error('Fatal error in main():', error);
process.exit(1);
});