MCP (Model Context Protocol) lets AI assistants call external tools. Instead of copy-pasting data into Claude, the AI calls your tool directly.
I built an MCP server that extracts structured data from text. Here's exactly how, step by step.
What We're Building
An MCP server that exposes one tool: extract_structured_data. When an AI assistant needs to parse a receipt, invoice, or email, it calls this tool automatically.
Prerequisites
- Node.js 18+
- TypeScript
- An MCP-compatible client (Claude Desktop, Cursor, etc.)
Step 1: Set Up the Project
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
Create tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"declaration": true
},
"include": ["src/**/*"]
}
Important: Use module: "Node16" and moduleResolution: "Node16". The MCP SDK requires these settings.
Step 2: Create the Server
Create src/index.ts:
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "my-tool",
version: "1.0.0",
});
Three key imports:
-
McpServer— the server itself -
StdioServerTransport— communicates over stdin/stdout -
z(zod) — defines tool parameter schemas
Step 3: Define a Tool
server.tool(
"my_tool_name",
"Description of what this tool does",
{
// Parameters defined with zod
input_text: z.string().describe("The text to process"),
format: z.enum(["json", "csv"]).optional().describe("Output format"),
},
async ({ input_text, format }) => {
// Your logic here
const result = await processText(input_text, format);
return {
content: [
{
type: "text" as const,
text: JSON.stringify(result, null, 2),
},
],
};
}
);
The tool definition has four parts:
- Name — what the AI calls
- Description — helps the AI decide when to use it
- Schema — zod schema for parameters
- Handler — async function that does the work
Step 4: Connect to Transport
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch(console.error);
Step 5: Build and Test
npx tsc
node dist/index.js
The server starts and waits for MCP messages on stdin. To test properly, use the MCP Inspector:
npx @modelcontextprotocol/inspector node dist/index.js
Step 6: Connect to Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"my-tool": {
"command": "node",
"args": ["/absolute/path/to/dist/index.js"]
}
}
}
Restart Claude Desktop. Your tool now appears in the tools menu.
Real Example: Calling an External API
Here's how to wrap any REST API as an MCP tool:
server.tool(
"extract_data",
"Extract structured JSON from unstructured text",
{
text: z.string().describe("Text to extract from"),
schema: z.enum(["receipt", "invoice", "email", "resume"]),
},
async ({ text, schema }) => {
const response = await fetch("https://your-api.com/extract", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text, schema }),
});
const result = await response.json();
return {
content: [{ type: "text" as const, text: JSON.stringify(result.data, null, 2) }],
};
}
);
Tips
- Tool descriptions matter. The AI reads them to decide which tool to use. Be specific.
-
Use zod
.describe()on every parameter. It helps the AI fill in the right values. -
Return
type: "text"for all results. The AI can parse JSON from text. - Handle errors gracefully. Return error messages as text, don't throw.
Try a Working Example
Want to skip the tutorial and use a real MCP server?
git clone https://github.com/avatrix1/structureai-mcp.git
cd structureai-mcp && npm install && npm run build
This server extracts structured data from receipts, invoices, emails, and more. 10 free requests included, no API key needed.
Built by Avatrix LLC. Full source at github.com/avatrix1/structureai-mcp.
Top comments (0)