1. Migrate to internal MCP server

Feature Name

Migrate to internal MCP server

Start Date

Jul 13th, 2026

Category

Architecture

Summary

It is possible to simplify the architecture and installation of Trento by substituting the MCP server with an Elixir MCP server that reuses the tools developed for Liz.

Use Cases outline

  • As a user, I want to be able to publish capabilities of Trento using the MCP protocol, so that it can be used by an external agent.

  • As a user, I want to have a similar experience when I am using the internal AI agent (Liz) and when I am using the external MCP agent, so that I can use Trento in a consistent way.

  • As a user, I want to be able to enable/disable the MCP server

  • As a user, I want to be able to configure the MCP server.

  • As a user, I want to use the MCPHost/Kit package included in SLES 16 to connect to the MCP server in Trento.

  • As a user, I want to use both streamable and sse as the transport protocols for the MCP server, so that I can use the one that is more suitable for my environment.

  • As a developer, I want to reduce the maintenance needed for the alignment of tools used by Liz and by the MCP server, so that the customer gets the same capabilities and responses independently of how they use it.

Motivation

The purpose of this RFC is to offer an alternative proposal for the MCP server, simplifying the architecture and improving the alignment, reducing maintenance for the overall solution. - Define the tradeoffs of one solution over the other. - Define a path forward to migrate from one solution to the new one.

Detailed design

Considering the outlined use cases we need to:

  • define the capabilities already present in the MCP server and how they are used by the external agent.

  • define the changes required to migrate to an internal MCP server, and what would be the tradeoffs.

  • define a migration path to move from the current MCP server to the new internal one.

The current MCP server is a Go application that implements the MCP protocol, and it is used by the external agent to communicate with Trento. It is created using a third party dependency (Go openapi-mcp), that is not widely used, although we contribute with patches when we find bugs. Installing the MCP server requires independent configuration and connection to an existing installation, introducing a bigger attack surface and configuration problems. The Trento MCP server requires: - A running Trento server (version 3.x or later). - An MCP-compatible AI assistant (MCP client) - A Trento Personal Access Token (PAT), generated from your Trento user profile for secure API access. - Configuration in /etc/trento/mcp-server-trento

The Trento MCP server generates automatically the tools available for the MCP clients based on the OpenAPI specification, using labels to define which tools are available for the MCP clients.

With the addition of the Liz AI assistant in Trento, the same OpenAPI specification is used to generate tools for Liz, using the specification as a way to keep both solutions synchronized. However, you can run an MCP server directly using the native Elixir MCP packages, thus eliminating the need for the configuration and the synchronization, and simplifying the connection. The two main packages are:

  • anubis_mcp (formerly hermes_mcp). A comprehensive, production-ready SDK that supports STDIO (for local clients) and SSE/HTTP (for remote/web clients). It allows for the dynamic registration of tools.

  • emcp. A minimalistic, lightweight MCP client/server library designed to be mounted directly as a Plug in your Phoenix router.

We also considered using Node.js, building a tiny Node MCP server that uses the official @modelcontextprotocol/sdk to fetch tool definitions from the Elixir backend and proxies execution requests back to an Elixir endpoint.

Security

The Go mcp-server acts as a multi-user proxy between the client (your IDE or agent) and the Elixir trento-web API. The job of the MCP is to fetch OpenAPI specs.

  • Token Delivery: The MCP client (e.g., VS Code or Claude Desktop) is configured with an Authorization header containing the user’s Trento Personal Access Token (PAT).

  • Session Mapping: When the client initializes the connection (via SSE/HTTP), the Go mcp-server intercepts the header, associates the PAT with the unique sessionID, and stores it.

  • Tool Call Execution & Auth forwarding: When a tool is executed, the Go server locks the execution context, injects the PAT into the process-global BEARER_TOKEN environment variable, runs the tool (which makes an HTTP request to trento-web), and restores the environment.

  • Validation in Elixir: trento-web receives the request, validates the token against the DB (via Trento.PersonalAccessTokens.validate/1), and executes the operation under that user’s scope.

Configuration options

You can create an mcp-server-trento configuration file in the working directory or specify a custom path using the --config flag.

The Trento MCP Server searches for configuration files in the following order:

  1. System-wide directory (/etc/trento/mcp-server-trento or /usr/etc/trento/mcp-server-trento).

  2. Custom path specified with the --config flag.

    NOTE: SLES 16 uses UsrEtc as the configuration scheme for packages. In UsrEtc, packages store the configuration by default as a .conf file or directory in /usr, and the actual configuration is either in a subdirectory of /etc/ or a .conf file in /etc. The configuration file should thus be upgraded to be either mcp-server-trento.conf or mcp-server-trento.d/ to comply with the UsrEtc scheme. Ideally, the configuration should be in /etc/trento/mcp-server-trento.conf.

Configuration for the internal MCP server would be easier, as the following environment variables would not be needed any more:

Current option Anubis option Why

TRENTO_URL

Gone

The server is Trento; calls are in-process (contexts/Ecto), not HTTP.

OAS_PATH

Gone

Tools are declared in Elixir with Anubis' component DSL, not generated from OpenAPI.

AUTODISCOVERY_PATHS

Gone

No spec discovery.

TAG_FILTER

Gone

You choose which tools to expose by defining them; nothing to filter.

INSECURE_SKIP_TLS_VERIFY

Gone

No outbound HTTPS fetch of specs.

HEADER_NAME

Replaced

Auth becomes a Phoenix plug on the MCP route (reuse Trento’s existing PAT/Bearer auth), not a header the bridge blindly forwards.

ENABLE_HEALTH_CHECK / HEALTH_PORT / HEALTH_API_PATH

Gone / reused

Phoenix/Trento already has its own liveness/readiness endpoints.

PORT

Merged

For HTTP transport it’s just the existing Phoenix endpoint port + a mounted route, not a separate listener.

TRANSPORT (streamable/sse)

Kept

Anubis supports the same transports (StreamableHTTP, SSE, plus STDIO).

VERBOSITY

Kept, different form

Elixir Logger level via config, not a -v flag.

Not every option is superfluous, but the configuration is simpler and more aligned with the rest of Trento’s configuration. The only options that remain are:

Environment variable default value Detail

TRENTO_MCP_ENABLED

false

Whether the internal MCP server is enabled or not. If false, the internal MCP server will not be started.

TRENTO_MCP_PORT

5000

The port on which the MCP server listens for incoming connections with HTTP transport

TRENTO_MCP_LOG_LEVEL

info

The verbosity level of the MCP server logs. Possible values are: debug, info, warn, error

Update to configuration options

Anubis config lives in config/.exs and the supervision tree, not CLI flags/TRENTO_MCP_ env vars. The minimal set:

  • Server registration — add the server module to Trento’s supervision tree, declaring name, version, and enabled capabilities (tools/prompts/resources). This is the mandatory piece.

  • Transport — choose :streamable_http (recommended, MCP’s current standard) or :sse, and mount its route/plug in the Phoenix router/endpoint (e.g. under /mcp). This replaces PORT + TRANSPORT.

  • Authentication plug — a plug on the MCP scope that validates the Trento PAT/session before the request reaches the server. This replaces HEADER_NAME and is required for anything non-local.

  • Tool definitions — one Anubis component module per exposed operation. This is the real work that OAS_PATH + TAG_FILTER were doing automatically; here it’s explicit and hand-written.

Setting Default Notes

config :trento, :mcp_server, enabled

false

Gates the supervised server + makes /mcp inert when off

ENABLE_MCP_SERVER (runtime env)

false

Sets the above in config/runtime.exs

Caveats

  1. The Go bridge that we were using before gives you the API for free, only taking into account the tags required to expose it. Using Anubis in Elixir is already automated. The same "MCP" tags used in the Trento server are used to configure the AI assistant’s toolset and so that they continue being aligned automatically. The only new work is the authentication plug.

  2. Additional work needs to be done to add a health check endpoint to the internal MCP server, as the current health check is done by the Go bridge. The health check should be implemented in Elixir, reusing the existing liveness/readiness endpoints.

  3. There is no way to install the MCP server as a separate application. By design, the internal MCP server is part of the Trento application, and it is not possible to install it separately.

  4. You can’t use command-line flags to configure the internal MCP server. The configuration is done via the Elixir config files or environment variables. Additional UX design should be done to include them as part of the command line for Trento itself.

Additional considerations

  • It could be beneficial for the project to isolate the MCP server from the rest of Trento, running the MCP server as an application inside the BEAM (umbrella application) and define at runtime if the MCP server is running or not. However, that is out of scope for this proposal as it would require additional architectural changes. The current proposal is to keep the MCP server as part of the Trento application, but it would be nice to have the option to run it as a separate application in the future if we decide to do so.

  • The endpoint is currently defined as trento-server/mcp-server_endpoint/mcp and published in port 5000. We should consider in the implementation if that could be simplified so the endpoint is trento-server/mcp in the same port as a new route in the Trento server. The implementation should decide the best way to do this.

  • The MCP server currently has a log level that is independent of the Trento server log level. We should consider if that is the best approach or if we should unify the log level for both servers. The implementation should decide the best way to do this.