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:
- Read-only tools get broad availability, aggressive caching and generous rate limits.
- 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_summarybeatsselect_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
- Pick one workflow with a clear owner, for example "prepare a client review".
- List the questions users ask during that workflow and design three to eight read-only tools around them.
- Wire authentication and row-level permissions first, before any tool logic.
- Ship to a small group with audit logging and tracing enabled from day one.
- Read the logs. Rewrite descriptions, merge unused tools, add missing ones.
- 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.