import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
ListPromptsRequestSchema,
GetPromptRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import * as fs from "fs";
import * as path from "path";
const server = new Server(
{ name: "my-first-mcp-server", version: "1.0.0" },
{ capabilities: { tools: {}, resources: {}, prompts: {} } }
);
const TOOLS = [
{
name: "calculate",
description: "Evaluate a basic arithmetic expression. Supports +, -, *, / and parentheses.",
inputSchema: {
type: "object" as const,
properties: {
expression: { type: "string", description: "The arithmetic expression to evaluate." },
},
required: ["expression"],
},
},
{
name: "read_file",
description: "Read the contents of a file from the local filesystem.",
inputSchema: {
type: "object" as const,
properties: {
file_path: { type: "string", description: "Absolute or relative path to the file." },
max_lines: { type: "number", description: "Maximum lines to return (default: 100)." },
},
required: ["file_path"],
},
},
];
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "calculate") {
const { expression } = args as { expression: string };
if (!/^[\d\s+\-*/().]+$/.test(expression)) {
return { content: [{ type: "text", text: "Invalid expression." }], isError: true };
}
try {
const result = Function(`"use strict"; return (${expression})`)();
return { content: [{ type: "text", text: `${expression} = ${result}` }] };
} catch (e) {
return { content: [{ type: "text", text: `Error: ${(e as Error).message}` }], isError: true };
}
}
if (name === "read_file") {
const { file_path, max_lines = 100 } = args as { file_path: string; max_lines?: number };
const resolved = path.resolve(file_path);
try {
if (!fs.existsSync(resolved)) {
return { content: [{ type: "text", text: `File not found: ${resolved}` }], isError: true };
}
const lines = fs.readFileSync(resolved, "utf-8").split("\n");
const truncated = lines.length > max_lines;
const output = lines.slice(0, max_lines).join("\n");
return {
content: [{
type: "text",
text: truncated ? `${output}\n\n[Truncated: ${max_lines}/${lines.length} lines shown]` : output,
}],
};
} catch (e) {
return { content: [{ type: "text", text: `Error: ${(e as Error).message}` }], isError: true };
}
}
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
});
server.setRequestHandler(ListResourcesRequestSchema, async () => {
const cwd = process.cwd();
const entries = fs.readdirSync(cwd);
return {
resources: entries.map((name) => ({
uri: `file:///${path.join(cwd, name)}`,
name,
mimeType: fs.statSync(path.join(cwd, name)).isDirectory() ? "inode/directory" : "text/plain",
})),
};
});
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const filePath = request.params.uri.replace(/^file:\/\/\//, "/");
const content = fs.readFileSync(filePath, "utf-8");
return { contents: [{ uri: request.params.uri, mimeType: "text/plain", text: content }] };
});
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
prompts: [{
name: "review_file",
description: "Review a file for bugs, style issues, and improvements.",
arguments: [{ name: "file_path", description: "Path to the file.", required: true }],
}],
}));
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const fp = request.params.arguments?.file_path ?? "unknown";
return {
messages: [{
role: "user",
content: { type: "text", text: `Review the file at ${fp}. Read it with read_file, then give bugs, style issues, and improvements.` },
}],
};
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP server running on stdio");
}
main().catch((e) => { console.error(e); process.exit(1); });