MCP Markdown Conversion Server
by FradSer
Verified
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import dotenv from "dotenv";
import fs from 'fs';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
dotenv.config();
const CLOUDFLARE_API_TOKEN = process.env.CLOUDFLARE_API_TOKEN;
const CLOUDFLARE_ACCOUNT_ID = process.env.CLOUDFLARE_ACCOUNT_ID;
if (!CLOUDFLARE_API_TOKEN || !CLOUDFLARE_ACCOUNT_ID) {
throw new Error("CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID environment variables are required");
}
// Cloudflare API configuration
const CLOUDFLARE_API = {
BASE_URL: 'https://api.cloudflare.com/client/v4',
TO_MARKDOWN: (accountId: string) => `/accounts/${accountId}/ai/tomarkdown`
};
// Debug logger
const log = (message: string): void => {
console.error(`[DEBUG] ${message}`);
};
// Response type from Cloudflare API
interface CloudflareResponse {
result: Array<{
name: string;
mimeType: string;
format: string;
tokens: number;
data: string;
}>;
success: boolean;
errors: any[];
messages: any[];
}
class ToMarkdownServer {
private server: McpServer;
private requestCounter: number = 0;
constructor() {
this.server = new McpServer({
name: "mcp-server-to-markdown",
version: "1.0.0",
description: "Generate markdown descriptions for files using Cloudflare AI"
});
log("Cloudflare Markdown Server initialized");
}
private registerTools(): void {
this.server.tool(
"to-markdown",
"Generate markdown description for files including PDF (.pdf), Images (.jpeg, .jpg, .png, .webp, .svg), HTML (.html), XML (.xml), Microsoft Office (.xlsx, .xlsm, .xlsb, .xls, .et), Open Document Format (.ods), CSV (.csv), and Apple Documents (.numbers)",
{
filePaths: z.array(z.string())
.describe("Array of absolute file paths to generate descriptions for")
},
async ({ filePaths }) => {
const requestId = `req-${++this.requestCounter}`;
log(`[${requestId}] Processing request for ${filePaths.length} files`);
try {
// Validate files exist
for (const filePath of filePaths) {
if (!fs.existsSync(filePath)) {
throw new Error(`File not found: ${filePath}`);
}
log(`[${requestId}] Validated file exists: ${filePath}`);
}
// Build API endpoint URL
const apiEndpoint = `${CLOUDFLARE_API.BASE_URL}${CLOUDFLARE_API.TO_MARKDOWN(CLOUDFLARE_ACCOUNT_ID as string)}`;
// Construct curl command
let curlCommand = `curl -s -X POST "${apiEndpoint}" -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}"`;
// Add each file to the curl command
filePaths.forEach(filePath => {
curlCommand += ` -F "files=@${filePath}"`;
});
log(`[${requestId}] Executing request to Cloudflare API`);
const { stdout, stderr } = await execAsync(curlCommand);
if (stderr) {
log(`[${requestId}] Warning - stderr output: ${stderr}`);
}
// Parse and validate response
try {
const response = JSON.parse(stdout) as CloudflareResponse;
if (!response.success) {
const errorMessages = response.errors?.map(e => e.message).join(', ') || 'Unknown error';
throw new Error(`Cloudflare API error: ${errorMessages}`);
}
// Format successful response
const results = response.result.map(item => ({
filename: item.name,
mimeType: item.mimeType,
description: item.data,
tokens: item.tokens
}));
log(`[${requestId}] Successfully generated descriptions for ${results.length} files`);
return {
content: [{
type: "text" as const,
text: JSON.stringify(results, null, 2)
}]
};
} catch (parseError) {
log(`[${requestId}] Failed to parse API response: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
throw new Error(`Failed to parse Cloudflare API response: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
}
} catch (error) {
log(`[${requestId}] Error: ${error instanceof Error ? error.message : String(error)}`);
return {
content: [{
type: "text" as const,
text: `Error generating descriptions: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
log("Tools registered successfully");
}
async start(): Promise<void> {
try {
// Register tools
this.registerTools();
// Create transport and connect
const transport = new StdioServerTransport();
transport.onerror = (error) => {
log(`Transport error: ${error.message}`);
};
log("Starting Cloudflare Markdown Server...");
await this.server.connect(transport);
log("Cloudflare Markdown Server running");
} catch (error) {
log(`Fatal error: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
}
}
}
// Initialize and start server
(async () => {
try {
const server = new ToMarkdownServer();
await server.start();
} catch (error) {
log(`Startup error: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
}
})();