> ## Documentation Index
> Fetch the complete documentation index at: https://docs.activeviam.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How connecting to other MCP Servers works

> How an Atoti Server uses the tools of other MCP Servers as its own, covering the license tier, the `none`, `atoti-jwt` and `pass-through` authentication modes that carry the calling user's identity, remote tool naming, the `getConnectedServers` chat tool, connection resilience, and how remote tools reach this server's own MCP endpoint.

<Info>
  ### Atoti Intelligence SDK

  This is part of the Atoti Intelligence SDK offer.
</Info>

An Atoti Server can connect out to other Model Context Protocol (MCP) Servers, typically the other
Atoti Servers of the same deployment, each exposing its own cube. It then uses their tools as its
own, both in chat and on its own MCP endpoint. This page covers the behavior shared by both SDKs;
declaring a connection is SDK-specific.

<Note>
  To follow along, first declare a connection: see
  [Connect to other MCP Servers in Java](./setup-java) or
  [Connect to other MCP Servers in Python](./setup-python). Both assume the Atoti MCP Server is already
  set up; see [How to set up the Atoti MCP Server](../setup/atoti-mcp-server-setup).

  Connecting to other MCP Servers requires the Atoti Intelligence Extension tier, which itself
  requires Essentials, like the rest of Atoti's MCP surface.
</Note>

## What does connecting to other MCP Servers provide?

* A chat user on one Atoti Server can query cubes hosted on another, without duplicating tool logic across servers
* Remote tools appear alongside local cube tools and custom tools, wherever tools are offered to the model
* The remote cube's own role-based data restrictions stay in force for every remote call
* Remote tools are reported on this server's own MCP endpoint too. One server therefore shows an external MCP client the tools of the whole deployment, each client told what its own credential can reach

## Which authentication modes are available?

Each connection names its own mode, in its own declaration.

| Mode             | What it sends                                                                            | When to choose it                                                                                              |
| ---------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `none` (default) | No credential.                                                                           | The remote server is genuinely unauthenticated, for example a local development server.                        |
| `atoti-jwt`      | A fresh Atoti JWT, minted for the calling user, carrying their username and authorities. | The remote server shares this deployment's JWT signing key, the standard setup within one Atoti deployment.    |
| `pass-through`   | The calling user's own bearer token, forwarded unchanged.                                | The remote server validates tokens against an external identity provider instead of this server's signing key. |

<Warning>
  `none` is the default, and deliberately so. Declaring a connection says where a server is, never that it belongs to this deployment, and nothing else in the configuration can say it either. Writing `atoti-jwt` is that assertion, so make it only for servers under the same ownership: the minted token is accepted by every server sharing the deployment's signing key. A third party given one holds a working credential for the whole deployment, in the name of a real user.
</Warning>

`atoti-jwt` mints its token from the authenticated principal rather than replaying one, so it works
whichever way the calling user authenticated: Basic, JWT, OIDC, or LDAP. Both servers must share the
signing key; see [Java](./setup-java#how-to-authenticate-with-atoti-jwt) or
[Python](./setup-python#how-to-authenticate-with-atoti-jwt).

`pass-through` replays either kind of token this server accepts (an Atoti JWT, or an OAuth 2 access
token). This brings two limits. A user who authenticated with Basic or form login presents no bearer
token, so the call carries no credential and is rejected. A replayed OAuth 2 token also keeps the
audience it was issued for, so the remote server accepts it only when configured to trust tokens
issued for this server.

A connection that runs the remote server as a local process, over standard input and output,
identifies itself by being that process, so it accepts no authentication mode at all.

## Why does the calling user's identity carry through?

Every mode derives the outgoing credential from the calling user's security context, evaluated on the
caller's own thread at the moment of the call. A remote tool therefore always runs under the identity
of whoever triggered it. This is what keeps the remote cube's role-based data restrictions in force:
a user denied a measure or a member on the remote server gains nothing by asking through chat.

Listing a remote server's tools is an authenticated call of the same kind. Its result is cached per
calling identity (the username together with its authorities), rather than once for the whole server.
A remote server that varies its tool list by role therefore never has one user's list served to
another.

## How are remote tools named?

Atoti names every remote tool `<connection name>_<tool name>`; the server's own tools stay
unprefixed. The prefix matters because every Atoti Server exposes the same cube tools. Unprefixed, a
second connected server's tools collide with the first one's, and only the first occurrence of
a name survives. Each remote tool's description also states which server it runs on.

The connection name is first normalized to the alphabet tool names may use: a hyphen becomes an
underscore, and anything else outside letters, digits and underscores is dropped. `fo ficc/server:1`
becomes `foficcserver1`, and the `run_mdx_query` tool of a `pnl-server` connection becomes
`pnl_server_run_mdx_query`.

Two rules follow from that alphabet, and from the 64-character limit most model providers impose on a
tool name:

* Two names differing only by dropped characters normalize to the same prefix, for example `pnl-server` and `pnl_server`. Atoti checks the prefixes at startup and refuses to start rather than silently drop a server's tools. The same check catches one name declared twice. Each transport has its own set of connections, and names are not compared across transports.
* When a prefixed name exceeds 64 characters, the prefix is kept whole and the tool's own name is shortened. A short hash is appended so two shortened names never collapse into one. A connection name of no more than 53 characters always keeps its prefix. Past that, the prefix is shortened instead, and `getConnectedServers` then reports no `toolPrefix` for that connection rather than one its tools do not carry.

The scheme can be replaced; see
[Connect to other MCP Servers in Java](./setup-java#which-other-properties-and-beans-matter).

## What does the `getConnectedServers` chat tool do?

A deployment of several connected servers gives the model tools from several cubes, and nothing in
those tools says which server each one reaches. `getConnectedServers` closes that gap: it lists the
server the chat runs on, then every connected server, whichever transport declares it. The model
calls it to name the servers available, to attribute an answer to one of them, or to check that one
is up.

It requires the Atoti Intelligence Extension tier, like the rest of this page, and can be withheld;
see [Connect to other MCP Servers in Java](./setup-java#which-other-properties-and-beans-matter).

<Warning>
  `getConnectedServers` puts deployment-internal addresses into the chat prompt. Those addresses reach the model provider, and the model can repeat them to any user allowed to chat, whatever their role. Atoti registers the tool for chat only, never on this server's own MCP endpoint.
</Warning>

### What does the tool report?

The example below comes from a server named `fo-ficc-server`, declaring the connections `pnl-server`
and `sandbox`, with `sandbox` down:

```json theme={"languages":{"custom":["/engine/python-sdk/6.2/languages/pycon.tmLanguage.json"]}}
[
  {
    "server": "fo-ficc-server",
    "url": "https://atoti.example.com/atoti",
    "current": true,
    "reachable": true,
    "version": "6.2.0",
    "instructions": "Front-office FICC risk cube."
  },
  {
    "server": "pnl-server",
    "url": "https://pnl.example.com",
    "toolPrefix": "pnl_server_",
    "reachable": true,
    "reportedName": "P&L cube",
    "version": "6.2.0"
  },
  {
    "server": "sandbox",
    "url": "http://localhost:9090",
    "toolPrefix": "sandbox_",
    "reachable": false
  }
]
```

| Field          | What it holds                                                                                                                                        |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `server`       | The name this deployment gives the server, which is the name the model calls it by. For a connected server, it is the connection name.               |
| `url`          | Where the server is, whole path and no MCP endpoint on the end. Absent for a connection declared without a URL, such as one running a local process. |
| `toolPrefix`   | The prefix every tool of that server carries, for example `pnl_server_`. Reported only when it is a prefix the tools really carry.                   |
| `current`      | Present, and `true`, on the server the chat runs on. Absent from every other entry.                                                                  |
| `reachable`    | Whether the calling user reached the server on this call.                                                                                            |
| `reportedName` | What the server calls itself in the MCP handshake, a label rather than an address. Reported for a reachable server only.                             |
| `version`      | The version the server reports.                                                                                                                      |
| `instructions` | What the server says it is for, when it says anything.                                                                                               |

An entry describes a server; it is not a way to call one. The model reaches a connected server
through that server's own prefixed tools, never through the reported URL.

**The server running the chat** always comes first, marked `current: true`, and is the only entry
with no `toolPrefix`. This is what tells the model that its unprefixed tools are this server's own.
A server that declares no connection still reports itself, so a question about the available servers
never gets an empty answer. Its `url` is the address the asking client used, up to and including the
context path, read from the request being served rather than from configuration. Two clients of one
server, one direct and one through an ingress, are therefore each told the address they used. Behind
a reverse proxy the server has to be told to trust the forwarded headers, or the reported address is
the proxy's back-end hop; see
[Connect to other MCP Servers in Java](./setup-java#which-other-properties-and-beans-matter).

**A connected server** is reported with the URL its connection was declared with, whole and
unchanged, with no MCP endpoint appended: `url: https://pnl.example.com` is reported as
`https://pnl.example.com`, not `https://pnl.example.com/mcp`. What the report shows is the address a
person recognizes the server by, and can open.

**Reachability** is established per call and per user. The tool pings every connected server with the
calling user's own credential, exactly as a remote tool call does, so a server is reported
`reachable: false` when it is down, when it rejects that credential, or when the user has no access
to it. Such an entry keeps its name and its URL, so the model can say which server is unavailable.
The pings run concurrently and the whole sweep is bounded to ten seconds, so an unreachable server
costs a chat turn a bounded wait rather than its transport's full request timeout.

## How do remote tools appear on this server's own MCP endpoint?

On their own, alongside the local cube tools, for every client that connects. There is nothing to
enable and no identity to configure. No LLM is required either: an LLM is only required for chat.

Each client is told the tools its own credential can reach. An MCP client authenticates to this
server first (an unauthenticated request is answered with a `401` that starts the OAuth 2.1 flow),
so by the time it asks for tools there is a real user to ask on behalf of. Two clients logged in as
different users are therefore told different things. Calling a remote tool through this endpoint runs
it as the calling user, exactly as from chat.

## How resilient is a connection to an unreachable remote server?

An unreachable remote server costs only its own tools. Atoti lists every connected server
separately, so one that is down or that rejects the credential it received leaves the other servers'
tools untouched. The failure is not cached: that server is retried on the next request, and its tools
reappear as soon as it answers. An unreachable peer never stops this server from starting, and never
keeps its MCP endpoint from answering.

## Related reading

* [Connect to other MCP Servers in Java](./setup-java) and [in Python](./setup-python)
* [Migration notes](../../../releases-and-upgrades/migration-notes#spring-ai-mcp-client-defaults) for the Spring AI MCP client defaults this capability changes
* [What is the Atoti MCP Server?](../introduction) and [How to add custom tools](../custom-tools)
