Skip to content

InsightsAI Infrastructure

Connecting AI assistants to internal systems with the Model Context Protocol

How MCP servers expose internal tools and data to AI assistants, and how to authenticate, scope, deploy and audit them so that an assistant can only do what the user could do.

Published September 1, 20266 min read

Every useful internal AI assistant eventually needs to reach internal systems: the ticket tracker, the order database, the runbook wiki, the deployment API. Before the Model Context Protocol, each assistant and each system needed its own integration, written against a vendor-specific plugin or function-calling format.

MCP standardizes that connection. An internal system is wrapped once as an MCP server, and any assistant that speaks MCP can use it: desktop chat clients, IDE agents, command-line coding tools and your own agents. That makes integrations cheap to build, which is exactly why they need the same care as any other internal API.

Hosts, clients and servers

The protocol has three roles:

  • Host. The application the user interacts with, such as a chat client or IDE. It runs the model and decides what to send to it.
  • Client. A connector inside the host that holds one connection to one server.
  • Server. A program that exposes capabilities from one system, such as the orders service or the incident tracker.

Messages are JSON-RPC. Servers run either locally as a subprocess over standard input and output, or remotely over the Streamable HTTP transport. For internal systems shared by many people, remote servers over HTTP are the pattern to standardize on: they run in your infrastructure, under your access controls, and users do not need credentials for backend systems on their laptops.

Tools, resources and prompts

A server exposes three kinds of capabilities:

PrimitiveControlled byUse it for
ToolsThe model, which decides when to call themActions and queries with parameters: look up an order, create a ticket
ResourcesThe application or user, which choose what to attachRead-only context identified by a URI: a runbook page, a schema, a file
PromptsThe user, usually as a command or templateReusable workflows such as "summarize this incident"

Most internal servers start with tools. The official Python SDK makes a minimal server short:

orders_mcp/server.py
from mcp.server.fastmcp import FastMCP
 
from orders_mcp.auth import current_user
from orders_mcp.backend import orders_api
 
mcp = FastMCP("orders")
 
 
@mcp.tool()
def get_order(order_id: str) -> dict:
    """Look up one order by ID: status, items, and delivery estimate."""
    user = current_user()
    return orders_api.get_order(order_id, on_behalf_of=user)
 
 
@mcp.tool()
def search_orders(customer_email: str, limit: int = 10) -> list[dict]:
    """Find recent orders for a customer email address. Returns at most 25."""
    user = current_user()
    return orders_api.search(
        email=customer_email, limit=min(limit, 25), on_behalf_of=user
    )
 
 
@mcp.resource("orders://status-codes")
def status_codes() -> str:
    """Explanation of each order status code."""
    return orders_api.status_code_reference()
 
 
if __name__ == "__main__":
    mcp.run(transport="streamable-http")

The docstrings and type hints become the tool descriptions and input schemas the model sees. Write them for the model: say what the tool returns, what limits apply, and when not to use it. Vague descriptions lead to wrong calls.

Authentication

For remote servers, the MCP specification defines authorization based on OAuth 2.1. The MCP server acts as an OAuth resource server. It publishes protected resource metadata that tells clients which authorization server to use, and it accepts bearer tokens issued by that server.

In an internal setting, the authorization server is your existing identity provider. The flow then looks familiar:

  1. The user adds the internal server in their assistant.
  2. The client discovers the authorization server and sends the user through the normal company sign-in, including single sign-on and multi-factor authentication.
  3. The client receives an access token scoped to that MCP server, and sends it with every request.
  4. The server validates the token, including its audience, and knows exactly which user is calling.

Two rules in the specification deserve attention:

  • Validate the audience. The server must only accept tokens issued for it. A token meant for another service must be rejected, even if the signature is valid.
  • No token passthrough. The server must not forward the client's token to backend APIs. If it needs to call the orders API on the user's behalf, it should obtain its own token for that API, for instance through a token exchange with the identity provider, so that each hop has a credential scoped to it.

Scoping: the assistant can do what the user can, at most

An MCP server is a new path into internal systems, driven by a model that may be following injected instructions from a document or web page it read. Design the server so that a successful prompt injection cannot do more than the user could do by hand, and ideally much less.

  • Act on behalf of the user. Every backend call carries the user's identity, and the backend enforces its normal authorization. A shared service account with broad access turns every assistant into a privilege escalation.
  • Expose narrow tools. get_order and search_orders are safe to reason about. execute_sql is not.
  • Separate read from write. Start with read-only servers. Add write tools deliberately, and mark them with tool annotations such as readOnlyHint and destructiveHint so hosts can ask for confirmation. Treat annotations as hints for the user interface, not as a security control.
  • Bound the output. Cap result sizes, paginate, and strip fields the assistant does not need. Every field returned goes into the model's context and may end up in a response.
  • Map OAuth scopes to tools. A user who only has the read scope should not see write tools listed at all.

Deploying internal MCP servers

Treat MCP servers like any other internal service, not like scripts:

  • Run them on the platform. Containers behind the internal ingress, with TLS, health checks, resource limits and the usual deployment pipeline. Stateless servers scale horizontally without sticky sessions.
  • One server per system, owned by that system's team. The team that owns the orders service knows which operations are safe to expose and how to describe them.
  • Keep a registry of approved servers. Publish the URL, owner, scopes and data classification of each server. Many assistant hosts let administrators restrict which servers users can connect, so use the registry as that allowlist.
  • Version tool definitions. Renaming a tool or changing its parameters breaks prompts and workflows that depend on it. Add new tools instead of changing existing ones where possible.
  • Review third-party servers like dependencies. A locally run server from a public repository executes with the user's local permissions. Pin versions and review the code, or run it in a sandbox.

Auditing

You will need to answer questions such as "which assistant sessions read this customer's orders last week?". Log every tool call at the server, where it cannot be skipped:

tool call audit record
{
  "timestamp": "2026-09-01T09:14:27Z",
  "server": "orders",
  "server_version": "1.4.0",
  "user": "jane.doe@company.internal",
  "client": "ide-assistant",
  "tool": "search_orders",
  "arguments": { "customer_email": "[redacted]", "limit": 10 },
  "result": { "status": "ok", "items": 7 },
  "duration_ms": 182
}

Redact sensitive arguments, record result sizes rather than full results, and ship the records to the same log pipeline and retention policy as the backend's own access logs. Alert on unusual patterns: a single session making hundreds of search calls, or a burst of authorization failures.

Where to start

  1. Pick one system that people already copy data out of into chat, and one read-only use case.
  2. Build a remote MCP server for it, with tools named and described for the model.
  3. Connect it to your identity provider, validate token audience, and call the backend as the signed-in user.
  4. Deploy it through the normal pipeline, behind internal ingress, with audit logging.
  5. Add it to an approved server registry, and restrict assistant hosts to approved servers.
  6. Add write tools only after the read path has been in use for a while, with confirmation required.