# Composing environments (https://www.agentenvframework.com/docs/environments/composing)

> Combine small environments into a multi env that an agent sees as one MCP server

A task often needs more than one system. Dana's email in
[Creating your own environment](https://www.agentenvframework.com/docs/environments/creating.md) asks for the Q3 numbers to go to Sam
Lee in finance, and nothing in the inbox says what Sam's address is; a contacts book does. Instead
of adding contacts to `EmailEnv`, you write a second small environment and compose the two.

1. The registry holds single envs, each registered on its own with the tools its server offers.
2. `agent-env env multi put` composes `office`, a new env that pins `email:1` and `contacts:1`.
3. Deployed, `office` is one MCP server with its children’s tools in one list: `email_search`, `send` and `contacts_lookup`.
4. `planner` pins `email:1` and `calendar:1`. The same `email` backs both multi envs, and neither changes it.
5. `workspace` pins all three, so it serves all five tools. Any set of registered envs composes a new env.

Parts of the scene:

- **The command**: `agent-env env multi put --id` stores a `multi` env under a new id, and each `--mcp-server` pins one child as `id:version`. It builds no image and starts nothing.
- **A single env**: One MCP server, registered on its own with `agent-env env mcp-server put`. `email` and `contacts` are the servers on this page; `calendar` stands for any other env you register.
- **Its tools**: The `@tool` methods of the env’s server. A multi env serves them unchanged, so the `{environment_name}_` prefix keeps two children’s tools from colliding.
- **A multi env**: What `agent-env env multi put` stores: an env of type `multi`, with its own id and versions, that pins its children and adds no server code. A child can be in any number of multi envs, but a multi env cannot contain another.
- **A pinned child**: `email:1` is version 1 of `email`. Putting `email` again adds version 2 and leaves the pin alone; put the multi env again to take the new version.
- **One MCP server**: What an agent sees when the multi env is deployed: one MCP server, behind one gateway, with every child’s tools in one list. The stored env holds only the pins.

## A second environment

`ContactsEnv` follows the pattern of `EmailEnv`: one tool, `contacts_lookup`, and the three data
methods over its list of contacts:

```python title="contacts/server.py"
import json
from pathlib import Path

from agentenv_protocol import (
    AgentEnvEnvironment,
    DataPart,
    FilePart,
    add_data,
    environment_card,
    get_data,
    reset_data,
    tool,
)


@environment_card(name="contacts")
class ContactsEnv(AgentEnvEnvironment):
    def __init__(self) -> None:
        self.contacts: list[dict] = []

    @reset_data
    async def _reset(self) -> None:
        self.contacts.clear()

    @add_data
    async def _add(self, parts: list) -> None:
        for part in parts:
            if isinstance(part, DataPart):
                self.contacts.extend(part.data.get("contacts", []))
            elif isinstance(part, FilePart):
                seed = Path(part.file.uri.removeprefix("file://"))
                self.contacts.extend(json.loads(seed.read_text()).get("contacts", []))

    @get_data
    async def _state(self) -> list:
        return [DataPart(data={"contacts": self.contacts})]

    @tool(name="{environment_name}_lookup")
    async def lookup(self, name: str) -> dict:
        """Find contacts whose name contains the given text."""
        n = name.lower()
        return {"contacts": [c for c in self.contacts if n in c["name"].lower()]}


if __name__ == "__main__":
    ContactsEnv().serve()
```

With a copy of the Dockerfile in `contacts/`, you register it with the command that
[Register it](https://www.agentenvframework.com/docs/environments/deploying.md#register-it) uses for `email`:

```bash title="Terminal"
agent-env env mcp-server put --id contacts --dockerfile contacts/Dockerfile
```

## Compose the two

A `multi` env pins its children by id and version and gives them one name. It adds no server code
of its own, so each child stays a small server that you build and version on its own. This
command composes `email` and `contacts` into `office`:

```bash title="Terminal"
agent-env env multi put --id office --name office \
  --mcp-server email:1 --mcp-server contacts:1
```

```text title="Output"
Fetching MCPServerEnv: id=email version=1...
  Found: id=email version=1
Fetching MCPServerEnv: id=contacts version=1...
  Found: id=contacts version=1
Creating MultiEnv...
Created MultiEnv: id=office version=1
```

The stored `multi` env holds the two pinned references and the name. `email:1` stays at version 1
even after you put `email` again. To take a newer child, you put `office` again, which appends a
new version of `office`.

You deploy `office` like any other env, with the command from
[Deploy it](https://www.agentenvframework.com/docs/environments/deploying.md#deploy-it):

```bash title="Terminal"
agent-env env deploy --id office
```

```text title="Output (end)"
Deployed!
Instance ID: office-jk24hzlq
Env MCP Url: http://localhost:61854/mcp
Env Gateway Url: http://localhost:61854
Env DB Web Url: http://localhost:61855/
Env DB MCP Url: http://localhost:61856/mcp
Expires At (UTC): 2026-09-29 09:25 UTC
```

Every env deploys in a topology, the shape its instances take. A single env such as `email` can
be registered in any of them, as [Choose the environment's
topology](https://www.agentenvframework.com/docs/environments/topology.md) covers, and a `multi` env always takes the default, the
[gateway topology](https://www.agentenvframework.com/docs/environments/gateway-topology.md): each child runs in its own container, side
by side behind one gateway. An agent connected to the instance's MCP URL sees one MCP server named
`office`, with every child's tools in one list: `email_search`, `send` and `contacts_lookup`.