Skip to content

trading_agent.extensions.mcp_server_configuration

Reads mcp_servers.json, which lists the MCP servers every desk can use.

The file uses the "mcpServers" format of Claude Code and Claude Desktop, so a server can be copied from their documentation unchanged:

{ "mcpServers": { "everything": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-everything"], "env": {"SOME_TOKEN": "${SOME_TOKEN}"} }, "deepwiki": { "type": "http", "url": "https://mcp.deepwiki.com/mcp", "headers": {"Authorization": "Bearer ${DEEPWIKI_TOKEN:-}"} } } }

A server started as a program (stdio) receives only a few safe variables, such as PATH and HOME, plus those listed in its "env". ${NAME} and ${NAME:-default} in a command, an argument, an env value, a URL or a header are replaced from the gateway's environment, as in Claude Code.

Typical usage example:

servers = McpConfigurationReader(Path('mcp_servers.json')).read()

McpConfigurationError

Bases: ValueError

mcp_servers.json is not valid JSON, or a server entry is not valid.

McpServerConfiguration

Bases: BaseModel

One MCP server.

Attributes:

Name Type Description
name str

The server's name, the key in "mcpServers".

type Literal['stdio', 'http']

"stdio" for a program the gateway starts, "http" for a streamable HTTP server.

command str | None

The program to start, for stdio.

args list[str]

Its arguments, for stdio.

env dict[str, str]

The environment variables it receives besides the safe defaults, for stdio.

cwd str | None

The folder to start it in, for stdio, or None for the gateway's folder.

url str | None

The endpoint, for http.

headers dict[str, str]

HTTP headers sent with every request, for http.

McpConfigurationReader

McpConfigurationReader(path, environment=None)

Reads and expands mcp_servers.json.

Creates the reader.

Parameters:

Name Type Description Default
path Path

The file.

required
environment Mapping[str, str] | None

Where ${NAME} values come from; None uses os.environ.

None
Source code in src/trading_agent/extensions/mcp_server_configuration.py
def __init__(
    self,
    path: Path,
    environment: Mapping[str, str] | None = None,
):
    """Creates the reader.

    Args:
        path (Path): The file.
        environment (Mapping[str, str] | None): Where ${NAME} values come from; None uses os.environ.
    """
    self._path = path
    self._environment = environment

read

read()

Reads every server, with variables expanded.

Returns:

Type Description
list[McpServerConfiguration]

The servers in file order, or an empty list when the file does not exist.

Raises:

Type Description
McpConfigurationError

The file is not valid JSON, has no "mcpServers" object, or a server is not valid.

Source code in src/trading_agent/extensions/mcp_server_configuration.py
def read(self) -> list[McpServerConfiguration]:
    """Reads every server, with variables expanded.

    Returns:
        list[McpServerConfiguration]: The servers in file order, or an empty list when the file does not exist.

    Raises:
        McpConfigurationError: The file is not valid JSON, has no "mcpServers" object, or a server is not valid.
    """
    return self._read_file(self._path, None)

read_with_packages

read_with_packages(package_files)

Reads the project's servers, then each package's.

A package server whose name is already taken is left out with a warning, so the user's own file always wins. A package server without a cwd starts in its package's folder, so it can name a program inside the package.

Parameters:

Name Type Description Default
package_files Sequence[Path]

The mcp_servers.json files of installed packages.

required

Returns:

Type Description
list[McpServerConfiguration]

Every server, the project's first.

Raises:

Type Description
McpConfigurationError

A file is not valid JSON or has an invalid server.

Source code in src/trading_agent/extensions/mcp_server_configuration.py
def read_with_packages(
    self,
    package_files: Sequence[Path],
) -> list[McpServerConfiguration]:
    """Reads the project's servers, then each package's.

    A package server whose name is already taken is left out with a warning, so the user's own file always wins. A package server without a cwd starts in its package's folder, so it can name a program inside the package.

    Args:
        package_files (Sequence[Path]): The mcp_servers.json files of installed packages.

    Returns:
        list[McpServerConfiguration]: Every server, the project's first.

    Raises:
        McpConfigurationError: A file is not valid JSON or has an invalid server.
    """
    servers = self.read()
    names = set()
    for server in servers:
        names.add(server.name)
    for path in package_files:
        for server in self._read_file(path, path.parent):
            if server.name in names:
                _LOGGER.warning(
                    'MCP server %s from %s is left out: the name is taken',
                    server.name,
                    path,
                )
                continue
            names.add(server.name)
            servers.append(server)
    return servers