DEV Community

Cover image for How I Built a 55-Tool MCP Server That Runs an Entire Business
berthelius for Frihet

Posted on

How I Built a 55-Tool MCP Server That Runs an Entire Business

Every ERP has a REST API. Ours has one too. But what made everything click was building an MCP server on top of it — 55 tools that let any AI agent manage invoices, expenses, clients, taxes, and accounting through natural language.

This is the story of how I built it, what I learned, and why MCP might be the most underrated protocol in business software right now.

Why MCP, not just REST

I had a perfectly good REST API. Cursor pagination, field selection, 6 filter types, OpenAPI spec. But here is the thing: APIs are designed for developers. MCP is designed for AI agents.

With REST, you need to:

  1. Read the docs
  2. Figure out the auth
  3. Chain multiple endpoints
  4. Parse responses
  5. Handle errors

With MCP, an AI agent gets a tool catalog with typed parameters, calls what it needs, and gets structured responses. No docs required. The schema IS the documentation.

// REST: 4 calls, manual orchestration
const clients = await fetch('/api/v1/clients?q=Acme', { headers });
const client = await clients.json();
const invoice = await fetch('/api/v1/invoices', {
  method: 'POST',
  body: JSON.stringify({ clientId: client.data[0].id, items: [...] })
});

// MCP: 1 natural language instruction
// "Create an invoice for Acme Corp, 10 hours of consulting at €150/hr"
// The agent calls search_clients → create_invoice automatically
Enter fullscreen mode Exit fullscreen mode

The architecture: 55 tools in 6 domains

I organized the tools by business domain, not by CRUD operations. This is the key insight — agents think in workflows, not in database tables.

Domain Tools Examples
Invoicing 12 create_invoice, send_invoice, record_payment, generate_pdf
Expenses 8 create_expense, scan_receipt (OCR), categorize_expenses
Clients/CRM 10 search_clients, create_client, client_health_score
Products 6 list_products, create_product, update_stock
Accounting 11 trial_balance, profit_and_loss, record_journal_entry
System 8 configure_webhook, get_dashboard, export_data

Every tool has structured output

This was a hard lesson. Early versions returned free-text descriptions. Agents would hallucinate field names, misparse amounts, confuse dates. Now every tool returns outputSchema + structuredContent:

// Tool definition with input AND output schema
{
  name: 'create_invoice',
  description: 'Create a new invoice for a client',
  inputSchema: {
    type: 'object',
    properties: {
      clientId: { type: 'string', description: 'Client ID or search term' },
      items: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            description: { type: 'string' },
            quantity: { type: 'number' },
            unitPrice: { type: 'number' },
            taxRate: { type: 'number', description: 'Tax rate (e.g., 21 for 21% VAT)' }
          }
        }
      },
      currency: { type: 'string', default: 'EUR' }
    },
    required: ['clientId', 'items']
  },
  outputSchema: {
    type: 'object',
    properties: {
      id: { type: 'string' },
      number: { type: 'string' },
      total: { type: 'number' },
      status: { type: 'string', enum: ['draft', 'issued'] },
      pdfUrl: { type: 'string' }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

5 Resources: static context without API calls

Tools execute actions. But agents also need reference data — tax rates, filing deadlines, expense categories. Making an API call every time the agent needs to know the VAT rate is wasteful.

MCP Resources solve this. They are static data the agent can read at connection time:

frihet://tax/rates          → VAT 21/10/4%, IGIC 7/3/0%, IRPF rates
frihet://tax/calendar       → Quarterly filing deadlines (Modelo 303, 130, 420)
frihet://config/categories  → 8 expense categories with deductibility rules
frihet://config/statuses    → Invoice lifecycle: draft → issued → sent → paid
frihet://api/schema         → All endpoints, auth methods, rate limits
Enter fullscreen mode Exit fullscreen mode

The agent reads these once and has permanent context. No API calls consumed. No hallucinated tax rates.

5 Prompts: guided multi-step workflows

This is where it gets interesting. A Prompt chains multiple tools into a workflow:

monthly-close       → Review unpaid invoices + categorize expenses + tax summary
onboard-client      → Detect tax type by location + create record + welcome quote
quarterly-tax-prep  → Gather period invoices + calculate VAT/IGIC + IRPF preview
overdue-followup    → Find overdue invoices + draft reminders + schedule sends  
collections-report  → Aging analysis + risk scoring + action recommendations
Enter fullscreen mode Exit fullscreen mode

A monthly close that takes an hour of clicking through dashboards becomes a 2-minute conversation.

Remote endpoint: zero install

The server runs at mcp.frihet.io — no npm install needed. Any MCP client can connect:

{
  "mcpServers": {
    "frihet": {
      "url": "https://mcp.frihet.io/sse",
      "headers": {
        "Authorization": "Bearer fri_your_api_key"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Works with Claude Desktop, Cursor, Windsurf, Cline, Gemini CLI, and any MCP-compatible client.

Or install locally:

npx @frihet/mcp-server --api-key fri_your_key
Enter fullscreen mode Exit fullscreen mode

What I learned building this

1. Domain-first, not CRUD-first. create_invoice is obvious. monthly_close is where the real value is. Agents need business workflows, not database operations.

2. Structured output is non-negotiable. Without outputSchema, agents hallucinate field names across chained tool calls. One wrong field name cascades into a broken workflow.

3. Resources eliminate the "context window tax". Every time an agent asks "what is the VAT rate in Spain?" and you answer, that is context window wasted. Resources pre-load this knowledge.

4. Security annotations matter. Each tool declares if it is readOnly or mutating, and its impact level (low/medium/high). Creating an invoice is high — the agent knows to confirm before executing.

5. The 55-tool ceiling is artificial. I could have 200 tools. But agents perform better with fewer, well-designed tools that compose cleanly. 55 covers every business workflow without overwhelming the tool catalog.

The numbers

  • 55 tools across 6 business domains
  • 5 resources for tax and configuration context
  • 5 prompts for guided multi-step workflows
  • v1.5.4 on npm, 1,796 downloads
  • 17 languages supported in responses
  • 365 fiscal positions across 123 countries
  • Zero install via remote endpoint at mcp.frihet.io

Try it

The MCP server is open for any Frihet user (free plan included):

  1. Create a free account
  2. Generate an API key in Settings → Integrations
  3. Add the server config above to your MCP client
  4. Ask your AI: "Show me my dashboard"

The full documentation covers every tool, resource, and prompt with examples.


This is Part 1 of the "Building an AI-Native ERP" series. Next: how we architected a production ERP with React + Firebase that handles real money.

Frihet is a free, AI-native ERP for modern businesses. API Docs · SDK · MCP Server · GitHub

Top comments (0)