BLOG · 15 SEPT 2026 · 6 MIN READ

How to build a custom MCP server for your company data (with a real architecture)

A practical MCP server architecture for company data: auth, per-user scoping, tool design and sandboxed processing, EU-hosted. Free roadmap in 24h.

MCPAI agentsArchitecture

Most companies that want "AI on our data" end up in the same place: a chatbot that can see a few PDFs and nothing else. The interesting value sits in the systems of record, the CRM, the ERP, the reporting warehouse, the document store, and those are exactly the places you cannot just paste into a prompt. The Model Context Protocol (MCP) gives you a standard way to connect them, but a production MCP server for company data is much more than a protocol adapter. It is an API gateway, a permission system and a data-shaping layer at the same time.

We built a custom MCP server and agentic AI architecture for Asora, a wealth management platform in Ireland: dozens of domain tools exposing portfolio, reporting and family-office data to AI models, with secure sandboxed processing of large financial datasets. This article describes a generic reference architecture similar to what we used, plus the design decisions that matter most.

What MCP actually is

MCP is an open protocol that standardises how AI applications talk to external systems. Messages are JSON-RPC 2.0 requests, responses and notifications. There are three roles:

  • Host: the AI application the user interacts with, for example a desktop assistant, an IDE or your own agent runtime.
  • Client: a connector inside the host that maintains one session with one server.
  • Server: your service, which exposes capabilities to the client.

A server offers three kinds of primitives:

Primitive Controlled by Typical use
Tools The model Actions and queries: search clients, fetch a report, create a draft
Resources The application Readable context identified by URI: a policy document, a schema
Prompts The user Reusable templates: "quarterly review for client X"

For transport, MCP defines stdio (the host launches the server as a local process) and Streamable HTTP (the server is a remote HTTP endpoint that can stream responses). The earlier HTTP with Server-Sent Events transport is now considered legacy. For company data used by many people, Streamable HTTP is the practical choice, because it lets you put authentication and governance in one central place.

Official SDKs exist for TypeScript (@modelcontextprotocol/sdk) and Python (mcp), among others, so you rarely need to handle the wire format yourself.

A reference architecture for company data

Here is the shape we recommend for a multi-user MCP server over sensitive business data:

AI host (desktop assistant, agent runtime, internal app)
        |
        |  Streamable HTTP + OAuth 2.1 access token
        v
+-------------------------------------------------------+
| MCP gateway                                           |
|  - token validation, scopes, tenant + user context    |
|  - rate limits and quotas per user and per tool       |
|  - audit log of every call (who, what, args, result)  |
+-------------------------------------------------------+
        |
        v
+-------------------------------------------------------+
| MCP server (tools, resources, prompts)                |
|  - read-only tools        - write tools (separate)    |
|  - result shaping: summaries, pagination, redaction   |
|  - cache for expensive aggregates                     |
+-------------------------------------------------------+
        |                          |
        v                          v
 Domain APIs / DB with       Sandboxed compute workers
 row-level permissions       for large dataset analysis

Authentication and per-user scoping

The MCP specification builds its HTTP authorization on OAuth 2.1. In practice that means your server acts as a resource server, the host obtains a token through your identity provider, and every tool call arrives with a bearer token you validate. The critical rule: the AI never gets a service account. Every call executes with the identity and permissions of the human behind the session.

Pass the user context all the way down to the data layer. If your database supports row-level security (PostgreSQL does), set the user or tenant on the connection and let the database enforce it. That way a badly worded prompt, a prompt injection hidden in a document, or a bug in a tool cannot leak another client's records.

Read-only versus write tools

Split tools into two groups and treat them differently:

  1. Read-only tools get broad availability, aggressive caching and generous rate limits.
  2. Write tools require explicit scopes, strict argument validation, idempotency keys and, for anything destructive or financial, a human confirmation step in the host.

MCP tool annotations such as readOnlyHint and destructiveHint help hosts present this to users, but they are hints. Enforcement belongs on the server.

Sandboxed processing for large datasets

Models are bad at reading 50,000 rows and good at writing code that analyses them. For large datasets we prefer a pattern where a tool loads the data into an isolated worker, the model submits analysis code (for example Python with pandas), and only the computed result goes back into the context. The sandbox has no network access, strict CPU, memory and time limits, a read-only copy of exactly the data the user is allowed to see, and is destroyed after the job. This keeps sensitive raw data out of the prompt and keeps token costs predictable.

Designing tools models can actually use

Tool design is where most MCP projects succeed or fail. A few rules that hold up in practice:

  • Domain-level, not table-level. get_portfolio_summary beats select_from_positions. Map tools to questions users really ask.
  • Fewer tools, better descriptions. The description is the model's only documentation. Say what the tool returns, when to use it and when not to.
  • Structured summaries, not raw dumps. Return totals, top items and a short explanation, with IDs the model can use to drill down.
  • Pagination everywhere. Use cursors and sensible default limits so a single call cannot flood the context window.
  • Typed, constrained inputs. Enums, date formats and ID patterns in the schema prevent a whole class of bad calls.
  • Useful errors. "Client not found, use search_clients first" lets the model recover on its own.

Here is a sketch of a read-only tool with the TypeScript SDK. Treat it as an illustration; check the current SDK docs for exact signatures.

// Sketch: a domain-level, read-only MCP tool with scoping and pagination
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

const server = new McpServer({ name: "company-data", version: "1.0.0" });

server.registerTool(
  "search_clients",
  {
    title: "Search clients",
    description:
      "Search clients the current user is allowed to see. Returns a short summary per client " +
      "(id, name, segment, total assets). Use get_client_overview with an id for details.",
    inputSchema: {
      query: z.string().min(2).describe("Name, email or reference number"),
      segment: z.enum(["private", "corporate", "family_office"]).optional(),
      cursor: z.string().optional(),
      limit: z.number().int().min(1).max(25).default(10),
    },
    annotations: { readOnlyHint: true },
  },
  async ({ query, segment, cursor, limit }, extra) => {
    const user = requireUser(extra); // resolved from the validated OAuth token
    const page = await clientsRepo.search(user, { query, segment, cursor, limit });

    const result = {
      items: page.items.map((c) => ({
        id: c.id,
        name: c.name,
        segment: c.segment,
        totalAssets: c.totalAssetsFormatted,
      })),
      nextCursor: page.nextCursor ?? null,
      totalMatches: page.total,
    };

    return {
      content: [{ type: "text", text: JSON.stringify(result) }],
      structuredContent: result,
    };
  }
);

Note what is not there: no SQL from the model, no unbounded result sets, no fields the user is not entitled to.

Operations: rate limits, caching, audit and observability

A company MCP server is production infrastructure and should be run like it.

  • Rate limits and quotas per user, per tool and per tenant. Agents loop; a runaway loop should hit a limit, not your ERP.
  • Caching for expensive aggregates, keyed by user permissions as well as arguments, with short TTLs for data that changes intraday.
  • Audit logs that record user, tool, arguments, result size, latency and outcome. Store them separately from application logs, with a defined retention period.
  • Observability with traces spanning host request, tool call and downstream queries. OpenTelemetry works well here. Track which tools are called, which fail and which are never used.
  • Evaluation. Keep a set of realistic user questions and check that the model picks the right tools and produces correct answers after every change to tool descriptions.

A minimal deployment config for the gateway might express these policies declaratively:

# Sketch: gateway policy
auth:
  issuer: https://id.example.eu
  audience: mcp-company-data
rateLimits:
  default: { perUserPerMinute: 60 }
  tools:
    run_analysis: { perUserPerMinute: 5, maxConcurrent: 1 }
writeTools:
  requireScope: "data:write"
  requireConfirmation: true
audit:
  sink: postgres
  retentionDays: 365

Hosting and GDPR

If the data is personal or financial, where the MCP server runs matters. We host client workloads on our own dedicated hardware in German data centers, GDPR by default, and use AWS in a hybrid setup when a specific managed service makes sense. See our hosting approach and why we run our own EU hardware.

Keep in mind that the MCP server is only one link in the chain. The model provider receiving tool results is a processor too, so check its data processing agreement and processing region, or run a model yourself. We cover that trade-off in self-hosted AI for European SMBs.

A pragmatic rollout plan

  1. Pick one workflow with a clear owner, for example "prepare a client review".
  2. List the questions users ask during that workflow and design three to eight read-only tools around them.
  3. Wire authentication and row-level permissions first, before any tool logic.
  4. Ship to a small group with audit logging and tracing enabled from day one.
  5. Read the logs. Rewrite descriptions, merge unused tools, add missing ones.
  6. Add write tools last, with confirmation and idempotency.

If you want this built properly, our MCP server development service covers the gateway, tools and hosting, and our custom AI work covers the agents on top. If you are also thinking about support automation, read the mistakes we see with AI customer support agents.

Have a data source you want AI to use safely? Tell us about it and we will send you a free project roadmap within 24 hours, written by the engineers who would build it.

Written by

Founder of Crowie and senior full-stack engineer. 15+ years building enterprise systems for banking, aerospace, identity verification and telecom, now shipping production AI agents and MCP servers.

Published 15 Sept 2026 · Updated 15 Sept 2026

FAQ

Questions
answered.

A

What is an MCP server in simple terms?

An MCP server is a small service that exposes your company's data and actions to AI applications through the Model Context Protocol. Instead of building a custom integration for every AI tool, you describe tools, resources and prompts once, and any MCP-compatible client such as a desktop assistant or an internal agent can discover and call them with the permissions of the signed-in user.
B

Should we use stdio or Streamable HTTP transport?

Use stdio for local, single-user tools that run on a developer machine next to the AI client. For company data shared by many users, use Streamable HTTP behind a gateway, because you need central authentication, per-user scoping, rate limits and audit logs. The older HTTP plus SSE transport is legacy and only worth supporting for clients that have not upgraded yet.
C

How many tools should a company MCP server expose?

Fewer than most teams expect. Start with a handful of domain-level tools that map to real questions users ask, such as getting a portfolio summary or searching contracts, rather than one tool per database table or REST endpoint. Models choose tools better when descriptions are clear and the list is short. Grow the catalogue only when usage data shows a gap.
D

Is it safe to let an AI model write data through MCP?

It can be, if write tools are treated like any other privileged API. Keep them separate from read-only tools, require explicit scopes, validate every argument server side, make operations idempotent, and ask for human confirmation on anything destructive or financial. Log every call with the user, arguments and result so you can audit and reverse mistakes.
E

Can an MCP server be GDPR compliant?

The protocol itself is neutral; compliance depends on how you run it. Host the server and its logs in the EU, minimise the personal data returned to the model, enforce per-user access, keep audit trails with retention limits, and check where the connected model provider processes prompts. A data processing agreement with every processor in the chain is essential.
START

Ready to transform
your idea?

Get a precise development roadmap in 24 hours, completely free. Tell us what you are building and a senior engineer replies, not a sales rep.

Get a free project roadmap in 24h

Response within 24 hours on business days · patrik.kelemen@crowie.io