#!/usr/bin/env node
import { resolve as resolvePath } from 'path';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
CallToolResult,
} from '@modelcontextprotocol/sdk/types.js';
import { PathResolver } from './utils/path-resolver.js';
import {
readMdxTool,
readMdxInputSchema,
} from './tools/read-mdx.js';
import {
searchMdxTool,
searchMdxInputSchema,
} from './tools/search-mdx.js';
import {
convertMdxTool,
convertMdxInputSchema,
} from './tools/convert-mdx.js';
import {
getFrontmatterTool,
getFrontmatterInputSchema,
} from './tools/get-frontmatter.js';
/**
* MDX MCP Server
* Provides tools for working with MDX files: reading, searching, converting, and extracting frontmatter
*/
function parseWorkspaceRootArgument(argv: string[]): string | undefined {
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
if (arg === '--workspace-root' || arg === '-w') {
const value = argv[i + 1];
if (!value) {
console.error('Error: --workspace-root flag requires a path value');
process.exit(1);
}
return value;
}
if (arg.startsWith('--workspace-root=')) {
const value = arg.split('=').slice(1).join('=');
if (!value) {
console.error('Error: --workspace-root flag requires a path value');
process.exit(1);
}
return value;
}
if (arg.startsWith('-w=')) {
const value = arg.split('=').slice(1).join('=');
if (!value) {
console.error('Error: -w flag requires a path value');
process.exit(1);
}
return value;
}
}
return undefined;
}
// Allow workspace root to be provided via CLI argument, environment variable, or fallback to cwd
const cliWorkspaceRoot = parseWorkspaceRootArgument(process.argv.slice(2));
const envWorkspaceRoot = process.env.WORKSPACE_ROOT;
const workspaceRoot = resolvePath(cliWorkspaceRoot || envWorkspaceRoot || process.cwd());
const pathResolver = new PathResolver(workspaceRoot);
// Create the MCP server
const server = new Server(
{
name: 'mdx-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
/**
* Handler for listing available tools
*/
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'read_mdx',
description: 'Read an MDX file and return its contents as Markdown',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Path to the MDX file relative to workspace root',
},
},
required: ['path'],
},
},
{
name: 'search_mdx',
description: 'Search for content within an MDX file',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Path to the MDX file relative to workspace root',
},
query: {
type: 'string',
description: 'Search query string',
},
contextLines: {
type: 'number',
description: 'Number of lines of context around matches (default: 2)',
default: 2,
},
},
required: ['path', 'query'],
},
},
{
name: 'convert_mdx_to_md',
description: 'Convert an MDX file to a Markdown file on disk',
inputSchema: {
type: 'object',
properties: {
sourcePath: {
type: 'string',
description: 'Path to source MDX file relative to workspace root',
},
outputPath: {
type: 'string',
description: 'Path for output MD file relative to workspace root',
},
},
required: ['sourcePath', 'outputPath'],
},
},
{
name: 'get_mdx_frontmatter',
description: 'Extract frontmatter from an MDX file',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Path to the MDX file relative to workspace root',
},
},
required: ['path'],
},
},
],
};
});
/**
* Handler for tool execution
*/
server.setRequestHandler(CallToolRequestSchema, async (request): Promise<CallToolResult> => {
try {
const { name, arguments: args } = request.params;
switch (name) {
case 'read_mdx': {
const input = readMdxInputSchema.parse(args);
const result = await readMdxTool(input, pathResolver);
return result as CallToolResult;
}
case 'search_mdx': {
const input = searchMdxInputSchema.parse(args);
const result = await searchMdxTool(input, pathResolver);
return result as CallToolResult;
}
case 'convert_mdx_to_md': {
const input = convertMdxInputSchema.parse(args);
const result = await convertMdxTool(input, pathResolver);
return result as CallToolResult;
}
case 'get_mdx_frontmatter': {
const input = getFrontmatterInputSchema.parse(args);
const result = await getFrontmatterTool(input, pathResolver);
return result as CallToolResult;
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Error: ${errorMessage}`,
},
],
isError: true,
} as CallToolResult;
}
});
/**
* Start the server
*/
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('MDX MCP Server running on stdio');
console.error(`Workspace root: ${workspaceRoot}`);
}
main().catch((error) => {
console.error('Fatal error in main():', error);
process.exit(1);
});