Tools: I Built an MCP Server for My ERP — Here's What I Learned

Tools: I Built an MCP Server for My ERP — Here's What I Learned

Source: Dev.to

What the MCP Server Does ## Architecture ## What I Learned Building It ## 1. MCP tools need great descriptions ## 2. Input validation is critical ## 3. Bilingual tool responses matter ## 4. The remote MCP is harder than local ## 5. Open source builds trust ## The Difference Between API and MCP ## Real Usage Patterns I've Seen ## Try It There's a new way to manage a business. Not an app, not a dashboard, not another browser tab. You talk to your AI assistant and it accesses your invoices, expenses, and clients directly. I built Frihet, an AI-native ERP for freelancers. When Anthropic released the MCP protocol, I knew the ERP needed to speak it natively. So I built an MCP server. Here's what I learned. 31 tools across 6 resources: In practice, you tell Claude: "Create an invoice for Acme Ltd for 2,500 EUR for January consulting" The invoice is created with correct tax details, VAT calculated, and invoice number assigned. No browser needed. The MCP server is a thin bridge between any MCP client and Frihet's REST API. It runs in two modes: Local (stdio): For Claude Code, Cursor, Windsurf. Runs as a local process. Remote (streamable-http): Via mcp.frihet.io, a Cloudflare Worker that accepts MCP connections from anywhere. The AI reads your tool descriptions to decide which tool to use. Vague descriptions = wrong tool selection. I spent more time writing tool descriptions than writing the actual tool code. AI models will send unexpected input shapes. Zod schemas for every tool input saved me from countless edge cases. The AI doesn't always respect your schema perfectly, so validate aggressively and return clear error messages. Frihet supports ES and EN. The MCP server accepts a locale parameter and returns responses in the user's language. This sounds trivial but it makes a huge difference in UX when the AI passes through the response. Local stdio is straightforward. The remote Cloudflare Worker using streamable-http had more edge cases: CORS, authentication headers, connection timeouts, and the fact that MCP sessions are stateful but Cloudflare Workers are stateless. When your MCP server accesses financial data, users need to trust it. Making it MIT and putting the code on GitHub was a deliberate choice. Users can inspect exactly what the server does with their data. No black boxes. Frihet already had a REST API. The MCP server is fundamentally different: A freelancer who doesn't code but uses Claude daily can now manage invoicing without opening a browser. A dev team in Cursor can query company data without switching context. "Create invoice for [client] for [amount]" — Most common. The AI resolves the client by name, applies tax rules, assigns the number. "Show unpaid invoices older than 30 days" — Uses search_invoices with filters. The AI formats results into a readable summary. "What's my total revenue this quarter?" — Combines multiple API calls. The AI calculates totals from invoice data. "Record a 47.50 EUR fuel expense from yesterday" — Creates expense with correct date, category suggestion, and VAT deduction. Source: github.com/berthelius/frihet-mcp npm: @frihet/mcp-server Docs: docs.frihet.io MIT license. 31 tools. Works with Claude Code, Cursor, Windsurf, and any MCP client. If you're building an MCP server for your product, happy to share more details in the comments. I'm Viktor, solo founder of Frihet — an AI-native ERP for freelancers. Free plan, no credit card: app.frihet.io Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: User → AI Assistant (Claude/Cursor) ↓ MCP Protocol @frihet/mcp-server (npm) ↓ REST API calls api.frihet.io/v1 ↓ Auth (X-API-Key) Firebase Cloud Functions ↓ Firestore Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: User → AI Assistant (Claude/Cursor) ↓ MCP Protocol @frihet/mcp-server (npm) ↓ REST API calls api.frihet.io/v1 ↓ Auth (X-API-Key) Firebase Cloud Functions ↓ Firestore CODE_BLOCK: User → AI Assistant (Claude/Cursor) ↓ MCP Protocol @frihet/mcp-server (npm) ↓ REST API calls api.frihet.io/v1 ↓ Auth (X-API-Key) Firebase Cloud Functions ↓ Firestore CODE_BLOCK: { "mcpServers": { "frihet": { "command": "npx", "args": ["-y", "@frihet/mcp-server"], "env": { "FRIHET_API_KEY": "fri_your_key" } } } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: { "mcpServers": { "frihet": { "command": "npx", "args": ["-y", "@frihet/mcp-server"], "env": { "FRIHET_API_KEY": "fri_your_key" } } } } CODE_BLOCK: { "mcpServers": { "frihet": { "command": "npx", "args": ["-y", "@frihet/mcp-server"], "env": { "FRIHET_API_KEY": "fri_your_key" } } } } CODE_BLOCK: name: "create_invoice" description: "Creates an invoice" Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: name: "create_invoice" description: "Creates an invoice" CODE_BLOCK: name: "create_invoice" description: "Creates an invoice" CODE_BLOCK: name: "create_invoice" description: "Create a new invoice for a client. Requires client_id, at least one line item with description and amount. Optionally accepts tax_rate, due_date, currency, and notes. Returns the created invoice with assigned number and calculated totals." Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: name: "create_invoice" description: "Create a new invoice for a client. Requires client_id, at least one line item with description and amount. Optionally accepts tax_rate, due_date, currency, and notes. Returns the created invoice with assigned number and calculated totals." CODE_BLOCK: name: "create_invoice" description: "Create a new invoice for a client. Requires client_id, at least one line item with description and amount. Optionally accepts tax_rate, due_date, currency, and notes. Returns the created invoice with assigned number and calculated totals." CODE_BLOCK: npx @frihet/mcp-server Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: npx @frihet/mcp-server CODE_BLOCK: npx @frihet/mcp-server - API = for developers who write code - MCP = for anyone who uses an AI assistant - "Create invoice for [client] for [amount]" — Most common. The AI resolves the client by name, applies tax rules, assigns the number. - "Show unpaid invoices older than 30 days" — Uses search_invoices with filters. The AI formats results into a readable summary. - "What's my total revenue this quarter?" — Combines multiple API calls. The AI calculates totals from invoice data. - "Record a 47.50 EUR fuel expense from yesterday" — Creates expense with correct date, category suggestion, and VAT deduction.