wyreup-mcp
Version:
Production-ready MCP server that transforms automation platform webhooks into reliable, agent-callable tools
192 lines (172 loc) • 6.96 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import chalk from 'chalk';
// Import from new lib modules
import { CONFIG_PATH, DEBUG, TRANSPORT, PORT, HOST, argv } from './lib/config.js';
import { loadManifest, validateManifest } from './lib/manifest.js';
import { createMcpServer } from './lib/mcp-server.js';
// Replicate __dirname for ES Modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const INIT_FLAG = argv.init;
const VALIDATE_FLAG = argv.validate;
const HELP_FLAG = argv.help || argv.h;
let toolsConfig = {};
function createDefaultManifest() {
const defaultManifestPath = path.resolve(process.cwd(), 'wyreup.json');
if (fs.existsSync(defaultManifestPath)) {
console.warn(chalk.yellow(`Warning: ${defaultManifestPath} already exists. No action taken.`));
process.exit(0);
}
const defaultContent = {
username: "your-username",
base_url: "https://wyreup.com/tools-mock/", // This will be interpolated by manifest loader
tools: [
{
name: "random-quote",
description: "Fetches a random quote.",
webhook: "random-quote", // Relative to base_url
input: { type: "object", properties: {}, required: [] },
output: {
type: "object",
properties: {
quote: { type: "string", description: "The random quote." },
author: { type: "string", description: "The author of the quote." }
},
required: ["quote", "author"]
},
public: true,
paid: false
},
{
name: "current-time",
description: "Gets the current server time.",
webhook: "current-time",
input: {
type: "object",
properties: {
timezone: { type: "string", description: "Optional timezone in TZ format (e.g., America/New_York). Defaults to UTC." }
},
required: []
},
output: {
type: "object",
properties: {
time: { type: "string", format: "date-time", description: "The current time." },
timezone_used: { type: "string", description: "The timezone used for the calculation." }
},
required: ["time", "timezone_used"]
},
public: true,
paid: false
}
]
};
try {
fs.writeFileSync(defaultManifestPath, JSON.stringify(defaultContent, null, 2));
console.log(chalk.green(`Successfully created default manifest at ${defaultManifestPath}`));
process.exit(0);
} catch (error) {
console.error(chalk.red(`Error creating default manifest: ${error.message}`));
process.exit(1);
}
}
if (INIT_FLAG) {
createDefaultManifest();
}
function displayHelp() {
console.log(`
Usage: wyreup-mcp [options]
Options:
--config [file] Path to a wyreup.json manifest (default: ./${CONFIG_PATH})
--transport [type] Communication transport: stdio or sse (default: stdio)
--port [number] Port for SSE transport (default: 3333)
--host [host] Host for SSE transport (default: localhost)
--init Create a starter wyreup.json in the current folder
--validate Check wyreup.json for structural issues
--debug Enable detailed logging for tool execution
--help, -h Show this help message
Transport modes:
stdio Standard input/output (for Claude Desktop and other MCP clients) [default]
sse Server-Sent Events (for remote MCP clients via HTTP)
Note: Server starts automatically unless --init, --validate, or --help is specified.
`);
process.exit(0);
}
if (HELP_FLAG) {
displayHelp();
}
if (VALIDATE_FLAG) {
console.log(chalk.blue(`Validating manifest: ${path.resolve(process.cwd(), CONFIG_PATH)}`));
// Pass relevant flags to manifest loading/validation
const loadedConfig = loadManifest(CONFIG_PATH, {
VALIDATE_FLAG: true, // Explicitly in validate mode
SERVE_FLAG: false, // Not serving
shouldStartServer: false, // Not starting server
configFlagUsed: !!argv.config, // Was --config used?
INIT_FLAG: false // Not initializing
});
// validateManifest is called within loadManifest. If it fails, loadManifest exits.
// If we reach here, it means the manifest structure is valid as per loadManifest's internal check.
// We call validateManifest again just to get the boolean for the success message,
// though loadManifest would have already printed errors and exited if invalid.
if (validateManifest(loadedConfig, path.resolve(process.cwd(), CONFIG_PATH), { VALIDATE_FLAG: true, SERVE_FLAG: false, shouldStartServer: false })) {
console.log(chalk.green.bold('\nManifest valid.'));
} else {
// This else block might not be reached if loadManifest exits on validation failure.
// Kept for logical completeness, but loadManifest handles the exit.
console.error(chalk.red.bold('\nManifest invalid. Please check the errors above.'));
process.exit(1); // Ensure exit if somehow reached
}
process.exit(0); // Exit after validation
}
// Server starts by default unless a specific action flag is provided
const shouldStartServer = !INIT_FLAG && !VALIDATE_FLAG && !HELP_FLAG;
async function startServer() {
console.log(chalk.blue(`Attempting to start MCP server with manifest: ${CONFIG_PATH}`));
console.log(chalk.blue(`Transport mode: ${TRANSPORT}`));
toolsConfig = loadManifest(CONFIG_PATH, {
VALIDATE_FLAG: false, // Not just validating
SERVE_FLAG: true, // Attempting to serve
shouldStartServer: true, // Server should start
configFlagUsed: !!argv.config,
INIT_FLAG: false
});
if (!toolsConfig) { // Should not happen if loadManifest exits on error
console.error(chalk.red("Failed to load tools configuration. Server cannot start."));
process.exit(1);
}
// Handle different transport modes
if (TRANSPORT === 'stdio') {
// Run MCP server over stdio
const mcpServer = createMcpServer(toolsConfig, { DEBUG });
try {
await mcpServer.runStdio();
// Server will run indefinitely in stdio mode
} catch (error) {
console.error(chalk.red(`Failed to start MCP server in stdio mode: ${error.message}`));
process.exit(1);
}
} else if (TRANSPORT === 'sse') {
// Run MCP server over SSE
const mcpServer = createMcpServer(toolsConfig, { DEBUG });
try {
await mcpServer.runSse(PORT, HOST);
// Server will run indefinitely in SSE mode
} catch (error) {
console.error(chalk.red(`Failed to start MCP server in SSE mode: ${error.message}`));
process.exit(1);
}
} else {
console.error(chalk.red(`Unknown transport mode: ${TRANSPORT}`));
process.exit(1);
}
}
if (shouldStartServer) {
startServer().catch(error => {
console.error(chalk.red(`Server startup failed: ${error.message}`));
process.exit(1);
});
}