Skip to content

MCP servers

You want a model to do something rather than only describe it - read a ticket, query a table, call an internal API. An MCP server is the component that offers those actions as tools, and holds the credentials they need.

The property that matters is what an MCP server does not hand over. The model asks for an action and receives a result. The database password, the API token and the network position stay on the server. A model that is manipulated can act only through the tools you wrote.

Prerequisites

  • A project and CLI access - see Get started
  • An MCP server image, or one from the marketplace
  • The system the tools will talk to, reachable from the cluster

The step-by-step guide and the console walkthrough cover the operations. The rest of this page explains how it fits together.

Where the credential stops

flowchart LR
    MODEL["model"] -->|"tool call - name and arguments"| SRV
    SRV["MCP server<br/>holds the credential"] -->|"authenticated query"| DB[("internal system")]
    DB -->|rows| SRV
    SRV -->|"result only"| MODEL
    SRV -.->|denied by NetworkPolicy| NET(["everything else"])

Two boundaries are doing work here, and they fail differently.

The credential boundary means a leaked conversation is not a leaked password. It holds as long as the server never returns the secret in a tool result.

The network boundary is what a NetworkPolicy gives you: the server can reach the one system it needs and nothing else. Without it, a tool with an argument you did not validate becomes a way to reach anything the cluster can reach.

Tool arguments are model output

This is the part that makes an MCP server unlike an ordinary internal service, and it is worth being blunt about.

The arguments arriving in a tool call were written by a model, and that model has been reading things - documents, web pages, ticket comments, the output of earlier tools. Any of that content can contain instructions aimed at the model. So a tool argument is not input from your application; it is input from whoever wrote the text the model last read.

Treat every argument as hostile input from an anonymous source, because that is what it is:

  • Validate server-side against an allow-list of what the tool may act on. Do not rely on the tool description to constrain the model - it is a suggestion, not a control.
  • Use parameterised queries. Never build SQL or a shell command by interpolation.
  • Scope the credential to what the tool needs. A read-only tool gets a read-only role.
  • Prefer specific tools over general ones. get_order(id) is safe in a way that run_query(sql) cannot be.

A server offering both read and network access can chain the two

A tool that reads private data and a tool that reaches the network are each fine alone. A server offering both lets one instruction turn the first into the second. Split them across servers with different egress policies when you can.

Designing tools for a caller that guesses

A model picks tools from their names and descriptions, without your context and without certainty. A tool that is easy to confuse will be called wrongly.

Works well Causes trouble
Naming one obvious verb and object near-synonyms across several tools
Arguments few, typed, required many optional flags
Errors say what to do differently a stack trace
Scope one job per tool one tool with a mode argument
Results just what was asked for whole records, including secrets

Return the minimum that answers the call. Everything a tool returns enters the model's context, becomes billable tokens, and can be repeated back to whoever is talking to it.

What is fixed after creation

Changeable
The tools the server offers yes - clients rediscover them
Its egress policy yes
The credential it holds yes - rotate in the Secret
A tool name clients have configured not safely

MCP authentication is still being settled

The model above and the security properties to design for are stable. How a client authenticates to a server is not yet fixed on the platform, so treat the exposure pattern in the step-by-step guide as the current answer rather than a settled one.