# Creating your own environment (https://www.agentenvframework.com/docs/environments/creating)

> Write an environment as an AgentEnvEnvironment, with the tools an agent calls and the data plane the framework uses

You write an environment as a subclass of `AgentEnvEnvironment`, from the
`agentenv-framework-protocol` package. Decorators on its methods tell the framework what each method
is for. This server, the one these pages follow, holds an inbox that an agent can search and a sent
folder that records what the agent sends:

```python title="email/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="email")
class EmailEnv(AgentEnvEnvironment):
    def __init__(self) -> None:
        self.inbox: list[dict] = []
        self.sent: list[dict] = []

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

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

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

    @tool(name="{environment_name}_search")
    async def search(self, query: str) -> dict:
        """Find inbox emails whose subject or body contains the query."""
        q = query.lower()
        return {"emails": [e for e in self.inbox if q in (e["subject"] + " " + e["body"]).lower()]}

    @tool()
    async def send(self, to: str, subject: str, body: str) -> dict:
        """Send an email."""
        self.sent.append({"to": to, "subject": subject, "body": body})
        return {"sent": len(self.sent)}


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

## Tools

The two `@tool` methods are what the agent can do. Each one becomes an MCP tool: its docstring
becomes the tool's description, and its type hints become the tool's input schema. `send`
therefore takes three required strings, `to`, `subject` and `body`.

A tool's name is its method's name, unless you pass one to `@tool`. `search` passes
`{environment_name}_search`, and when the server starts, the placeholder becomes the environment's
name, so the tool is `email_search`. `send` passes none, so its tool is `send`. The prefix is
optional. It exists because of the [gateway](https://www.agentenvframework.com/docs/environments/gateway-topology.md), a proxy the
framework runs, by default, in front of every env instance: when [Composing
environments](https://www.agentenvframework.com/docs/environments/composing.md) puts a contacts server behind the same gateway, prefixes
keep the two servers' tools apart.

## The data plane

The `@reset_data`, `@add_data` and `@get_data` methods form the data plane: a second endpoint,
`/agentenv`, that the framework uses to control the environment's state. It speaks JSON-RPC, where
each request is a JSON object that names a method. Each decorator answers one method: `data/reset`
empties the inbox and the sent folder, `data/add` loads emails into them, inline or from a file,
and `data/get` reads both back.

The data plane is not an MCP tool, so the agent is never offered it.

`EmailEnv` is one small environment, and a task often needs more than one.
[Composing environments](https://www.agentenvframework.com/docs/environments/composing.md) writes a second one and combines the two,
and [Deploying your environment](https://www.agentenvframework.com/docs/environments/deploying.md) runs `EmailEnv` in a sandbox and
calls its data plane.