Skip to main content
Glama
index.ts16 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, Tool, } from "@modelcontextprotocol/sdk/types.js"; import { checkValeInstalled, checkFile, syncValeStyles } from "./vale-runner.js"; import { loadConfig, verifyConfigFile, resolveConfigPath, findValeIniInWorkingDir, } from "./config.js"; import * as path from "path"; import * as fs from "fs"; import { fileURLToPath } from "url"; // Get version from package.json const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const packageJsonPath = path.join(__dirname, "..", "package.json"); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")); const VERSION = packageJson.version; // Parse command line arguments for debug mode const args = process.argv.slice(2); const DEBUG = args.includes("--debug") || args.includes("--verbose") || args.includes("-v"); // Show help and exit if (args.includes("--help") || args.includes("-h")) { console.error(` Vale MCP Server v${VERSION} A Model Context Protocol server for Vale prose linting. Usage: vale-mcp-server [options] Options: --debug, --verbose, -v Enable debug logging --help, -h Show this help message --version Show version number The server communicates via stdio for MCP protocol. Configuration priority: 1. Per-file: Config discovered from file's directory (check_file) 2. Server-wide: VALE_CONFIG_PATH environment variable 3. Working directory: .vale.ini in process.cwd() 4. Vale defaults: Global config or built-in rules Environment Variables: VALE_CONFIG_PATH Path to specific .vale.ini file (overrides auto-detection) Example: # Use specific config file VALE_CONFIG_PATH=/path/to/.vale.ini vale-mcp-server # Enable debug logging vale-mcp-server --debug Documentation: https://vale.sh/docs/ `); process.exit(0); } // Show version and exit if (args.includes("--version")) { console.error(VERSION); process.exit(0); } /** * Debug logger - only logs if DEBUG is enabled */ function debug(...args: any[]) { if (DEBUG) { console.error("[DEBUG]", new Date().toISOString(), ...args); } } // Server configuration const config = loadConfig(); let valeConfigPath: string | undefined; /** * Get platform-specific installation instructions for Vale */ function getInstallationInstructions(): { platform: string; methods: Array<{ name: string; command: string; url?: string }>; documentation: string; } { const platform = process.platform; const instructions: any = { platform, documentation: "https://vale.sh/docs/vale-cli/installation/", methods: [], }; switch (platform) { case "darwin": instructions.methods = [ { name: "Homebrew (recommended)", command: "brew install vale", }, { name: "Download binary", command: "Download from GitHub releases", url: "https://github.com/errata-ai/vale/releases", }, ]; break; case "linux": instructions.methods = [ { name: "Snap", command: "sudo snap install vale", }, { name: "Download binary", command: "Download from GitHub releases and add to PATH", url: "https://github.com/errata-ai/vale/releases", }, ]; break; case "win32": instructions.methods = [ { name: "Chocolatey", command: "choco install vale", }, { name: "Scoop", command: "scoop install vale", }, { name: "Download binary", command: "Download from GitHub releases", url: "https://github.com/errata-ai/vale/releases", }, ]; break; default: instructions.methods = [ { name: "Download binary", command: "Download from GitHub releases", url: "https://github.com/errata-ai/vale/releases", }, ]; } return instructions; } /** * Helper function to generate "Vale not installed" error response (Fix #2: reduce duplication) */ function createValeNotInstalledResponse() { return { content: [ { type: "text", text: JSON.stringify({ error: "Vale is not installed or not found in PATH", vale_required: true, installation_instructions: getInstallationInstructions(), message: "Please install Vale to use this feature. Vale is a command-line tool for prose linting.", }, null, 2), }, ], }; } /** * Check if an error is an E100 styles directory error (Fix #4: more efficient detection) */ function isStylesDirectoryError(errorMessage: string): boolean { // E100 errors typically contain these patterns return /E100|does not exist|Runtime error/i.test(errorMessage); } // Initialize the MCP server const server = new Server( { name: "vale-mcp-server", version: VERSION, }, { capabilities: { tools: {}, }, } ); // Define the available tools const TOOLS: Tool[] = [ { name: "vale_status", description: "Check if Vale (vale.sh) is installed and accessible. Use this first if other Vale tools fail. Returns installation status, version if available, and installation instructions for the current platform.", inputSchema: { type: "object", properties: {}, }, }, { name: "vale_sync", description: "Download Vale styles and packages by running 'vale sync'. Use this when you see errors about missing styles directories (E100 errors like 'The path does not exist'). This command reads the .vale.ini configuration and downloads the required style packages.", inputSchema: { type: "object", properties: { config_path: { type: "string", description: "Optional path to .vale.ini file. If not provided, uses the server's configured path or searches in the current directory.", }, }, }, }, { name: "check_file", description: "Lint a file at a specific path against Vale style rules. Returns issues found with their locations and severity. If Vale is not installed, returns error with installation guidance.", inputSchema: { type: "object", properties: { path: { type: "string", description: "Absolute or relative path to the file to check", }, }, required: ["path"], }, }, ]; // Handler for listing available tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: TOOLS, }; }); // Handler for tool execution server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; debug(`Tool called: ${name}`, JSON.stringify(args, null, 2)); try { switch (name) { case "vale_status": { debug("Checking Vale installation status..."); const valeCheck = await checkValeInstalled(); debug(`Vale installed: ${valeCheck.installed}, version: ${valeCheck.version}`); return { content: [ { type: "text", text: JSON.stringify({ installed: valeCheck.installed, version: valeCheck.version, platform: process.platform, installation_instructions: valeCheck.installed ? null : getInstallationInstructions(), message: valeCheck.installed ? `Vale is installed and ready to use (${valeCheck.version})` : "Vale is not installed. Please install it to use Vale linting tools.", }, null, 2), }, ], }; } case "vale_sync": { const { config_path } = args as { config_path?: string }; debug(`vale_sync called - config_path: ${config_path}`); // Check if Vale is available const valeCheck = await checkValeInstalled(); if (!valeCheck.installed) { return createValeNotInstalledResponse(); } // Determine which config to use const effectiveConfigPath = config_path || valeConfigPath; // Run vale sync const syncResult = await syncValeStyles(effectiveConfigPath); debug(`vale_sync result - success: ${syncResult.success}`); if (syncResult.success) { return { content: [ { type: "text", text: `✅ **Vale Sync Successful** ${syncResult.message} ${syncResult.output ? `**Output:**\n\`\`\`\n${syncResult.output}\n\`\`\`` : ""} The styles have been downloaded and are ready to use. You can now run \`check_file\` again.`, }, ], }; } else { return { content: [ { type: "text", text: `❌ **Vale Sync Failed** ${syncResult.message} ${syncResult.error ? `**Error:**\n\`\`\`\n${syncResult.error}\n\`\`\`` : ""} Please check your .vale.ini configuration and ensure: 1. The StylesPath is correct 2. Packages are properly defined 3. You have internet connectivity to download packages See Vale documentation: https://vale.sh/docs/topics/packages/`, }, ], }; } } case "check_file": { const { path: filePath } = args as { path: string }; debug(`check_file called - path: ${filePath}`); if (!filePath) { return { content: [ { type: "text", text: JSON.stringify({ error: "Missing required parameter: path", }), }, ], }; } // Check if Vale is available const valeCheck = await checkValeInstalled(); if (!valeCheck.installed) { return createValeNotInstalledResponse(); } const result = await checkFile(filePath, valeConfigPath); debug(`check_file result - file: ${result.file}, issues found: ${result.issues.length}, errors: ${result.summary.errors}, warnings: ${result.summary.warnings}, suggestions: ${result.summary.suggestions}`); return { content: [ { type: "text", text: result.formatted, }, ], _meta: { structured_data: { file: result.file, issues: result.issues, summary: result.summary, }, }, }; } default: debug(`Unknown tool called: ${name}`); return { content: [ { type: "text", text: JSON.stringify({ error: `Unknown tool: ${name}`, }), }, ], }; } } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; const errorDetails = error instanceof Error ? error.stack : "No details available"; // Check if this is an E100 error about missing styles (Fix #4: efficient detection) if (isStylesDirectoryError(errorMessage)) { return { content: [ { type: "text", text: `❌ **Vale Configuration Error** ${errorMessage} This error indicates that Vale's styles directory is missing or the configured packages haven't been downloaded yet. **Solution:** Run the \`vale_sync\` tool to download the required style packages: \`\`\` vale_sync \`\`\` This will: 1. Read your .vale.ini configuration 2. Download all configured style packages 3. Create the necessary styles directory After running \`vale_sync\`, you can try \`check_file\` again. For more information, see: https://vale.sh/docs/topics/packages/`, }, ], }; } return { content: [ { type: "text", text: JSON.stringify({ error: errorMessage, details: errorDetails, }), }, ], }; } }); /** * Initialize Vale configuration */ async function initializeValeConfig(): Promise<void> { // Check if Vale is installed const valeCheck = await checkValeInstalled(); if (!valeCheck.installed) { console.error("WARNING: Vale is not installed or not in PATH"); console.error("The server will start, but linting tools will not work until Vale is installed."); console.error(""); console.error("Installation instructions:"); const instructions = getInstallationInstructions(); instructions.methods.forEach((method) => { console.error(` ${method.name}: ${method.command}`); if (method.url) { console.error(` ${method.url}`); } }); console.error(""); console.error(`Documentation: ${instructions.documentation}`); console.error(""); console.error("Use the 'vale_status' tool to check installation status after installing."); console.error(""); // Don't exit - allow server to start return; } console.error(`Vale version: ${valeCheck.version}`); // Priority 1: Check for VALE_CONFIG_PATH environment variable if (config.configPath) { const resolvedPath = resolveConfigPath(config.configPath); if (await verifyConfigFile(resolvedPath)) { valeConfigPath = resolvedPath; console.error(`Using Vale config from VALE_CONFIG_PATH: ${valeConfigPath}`); return; } else { console.error(`WARNING: VALE_CONFIG_PATH points to non-existent file: ${config.configPath}`); } } // Priority 2: Check for .vale.ini in the current working directory const workingDirConfig = await findValeIniInWorkingDir(); if (workingDirConfig) { // Use .vale.ini from working directory valeConfigPath = workingDirConfig; console.error(`Using .vale.ini from working directory: ${valeConfigPath}`); } else { // No .vale.ini found - warn but continue console.error("WARNING: No .vale.ini file found in the current working directory"); console.error(`Current working directory: ${process.cwd()}`); console.error(""); console.error("Vale will use default settings or search parent directories."); console.error("For best results, create a .vale.ini file in your project directory."); console.error(""); console.error("Example .vale.ini:"); console.error(" StylesPath = styles"); console.error(" Packages = write-good, proselint"); console.error(""); console.error(" [*]"); console.error(" BasedOnStyles = write-good, proselint"); console.error(""); } } /** * Main entry point */ async function main() { try { // Show version and startup info const title = `Vale MCP Server v${VERSION}`; const boxWidth = Math.max(title.length + 4, 43); // At least 43 chars wide const padding = ' '.repeat(Math.floor((boxWidth - title.length - 2) / 2)); const titleLine = `║${padding}${title}${padding}${title.length % 2 === 0 ? ' ' : ''}║`; const border = '═'.repeat(boxWidth - 2); console.error(`\n╔${border}╗`); console.error(titleLine); console.error(`╚${border}╝\n`); if (DEBUG) { console.error("🐛 Debug mode enabled\n"); } // Initialize Vale configuration await initializeValeConfig(); // Start the server const transport = new StdioServerTransport(); await server.connect(transport); console.error(`✓ Vale MCP Server v${VERSION} running on stdio`); if (DEBUG) { console.error("✓ Debug logging active - tool calls will be logged"); } console.error(""); } catch (error) { console.error("Failed to start server:", error); process.exit(1); } } main();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/theletterf/vale-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server