KChad commited on
Commit
0f6657d
·
0 Parent(s):

initial base program that we will work on

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +35 -0
  2. Dockerfile +85 -0
  3. README.md +177 -0
  4. __init__.py +29 -0
  5. build/lib/build/lib/server/__init__.py +11 -0
  6. build/lib/build/lib/server/app.py +57 -0
  7. build/lib/build/lib/server/echo_environment.py +102 -0
  8. build/lib/echo_env/__init__.py +29 -0
  9. build/lib/echo_env/client.py +81 -0
  10. build/lib/echo_env/server/__init__.py +11 -0
  11. build/lib/echo_env/server/app.py +60 -0
  12. build/lib/echo_env/server/echo_environment.py +196 -0
  13. build/lib/server/__init__.py +11 -0
  14. build/lib/server/app.py +57 -0
  15. build/lib/server/echo_environment.py +102 -0
  16. client.py +81 -0
  17. envs/echo_env/README.md +160 -0
  18. envs/echo_env/__init__.py +29 -0
  19. envs/echo_env/build/lib/build/lib/server/__init__.py +11 -0
  20. envs/echo_env/build/lib/build/lib/server/app.py +57 -0
  21. envs/echo_env/build/lib/build/lib/server/echo_environment.py +102 -0
  22. envs/echo_env/build/lib/echo_env/__init__.py +29 -0
  23. envs/echo_env/build/lib/echo_env/client.py +81 -0
  24. envs/echo_env/build/lib/echo_env/server/__init__.py +11 -0
  25. envs/echo_env/build/lib/echo_env/server/app.py +60 -0
  26. envs/echo_env/build/lib/echo_env/server/echo_environment.py +196 -0
  27. envs/echo_env/build/lib/server/__init__.py +11 -0
  28. envs/echo_env/build/lib/server/app.py +57 -0
  29. envs/echo_env/build/lib/server/echo_environment.py +102 -0
  30. envs/echo_env/client.py +81 -0
  31. envs/echo_env/openenv.yaml +6 -0
  32. envs/echo_env/openenv_echo_env.egg-info/PKG-INFO +13 -0
  33. envs/echo_env/openenv_echo_env.egg-info/SOURCES.txt +15 -0
  34. envs/echo_env/openenv_echo_env.egg-info/dependency_links.txt +1 -0
  35. envs/echo_env/openenv_echo_env.egg-info/entry_points.txt +2 -0
  36. envs/echo_env/openenv_echo_env.egg-info/requires.txt +9 -0
  37. envs/echo_env/openenv_echo_env.egg-info/top_level.txt +1 -0
  38. envs/echo_env/pyproject.toml +40 -0
  39. envs/echo_env/server/Dockerfile +81 -0
  40. envs/echo_env/server/__init__.py +11 -0
  41. envs/echo_env/server/app.py +61 -0
  42. envs/echo_env/server/echo_environment.py +211 -0
  43. models.py +34 -0
  44. openenv.yaml +6 -0
  45. openenv_echo_env.egg-info/PKG-INFO +13 -0
  46. openenv_echo_env.egg-info/SOURCES.txt +15 -0
  47. openenv_echo_env.egg-info/dependency_links.txt +1 -0
  48. openenv_echo_env.egg-info/entry_points.txt +2 -0
  49. openenv_echo_env.egg-info/requires.txt +9 -0
  50. openenv_echo_env.egg-info/top_level.txt +1 -0
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ # Multi-stage build using openenv-base
8
+ # This Dockerfile is flexible and works for both:
9
+ # - In-repo environments (with local src/core)
10
+ # - Standalone environments (with openenv-core from pip)
11
+ # The build script (openenv build) handles context detection and sets appropriate build args.
12
+
13
+ ARG BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest
14
+ FROM ghcr.io/meta-pytorch/openenv-base:latest AS builder
15
+
16
+ WORKDIR /app
17
+
18
+ # Build argument to control whether we're building standalone or in-repo
19
+ ARG BUILD_MODE=in-repo
20
+
21
+ # Copy environment code (always at root of build context)
22
+ COPY . /app/env
23
+
24
+ # For in-repo builds, openenv-core is already in the pyproject.toml dependencies
25
+ # For standalone builds, openenv-core will be installed from pip via pyproject.toml
26
+ WORKDIR /app/env
27
+
28
+ # Ensure uv is available (for local builds where base image lacks it)
29
+ RUN if ! command -v uv >/dev/null 2>&1; then \
30
+ curl -LsSf https://astral.sh/uv/install.sh | sh && \
31
+ mv /root/.local/bin/uv /usr/local/bin/uv && \
32
+ mv /root/.local/bin/uvx /usr/local/bin/uvx; \
33
+ fi
34
+
35
+ # Install git for building from git repos (build-time only)
36
+ RUN apt-get update && apt-get install -y --no-install-recommends \
37
+ git \
38
+ && rm -rf /var/lib/apt/lists/*
39
+
40
+ # Install dependencies using uv sync
41
+ # First pass: install dependencies without the project (for better caching)
42
+ # Second pass: install the project itself
43
+ RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \
44
+ install -m 0755 /root/.local/bin/uv /usr/local/bin/uv && \
45
+ install -m 0755 /root/.local/bin/uvx /usr/local/bin/uvx
46
+
47
+ RUN --mount=type=cache,target=/root/.cache/uv \
48
+ if [ -f uv.lock ]; then \
49
+ uv sync --no-install-project --no-editable; \
50
+ else \
51
+ uv sync --no-install-project --no-editable; \
52
+ fi
53
+
54
+ RUN --mount=type=cache,target=/root/.cache/uv \
55
+ if [ -f uv.lock ]; then \
56
+ uv sync --no-editable; \
57
+ else \
58
+ uv sync --no-editable; \
59
+ fi
60
+
61
+ # Final runtime stage
62
+ FROM ghcr.io/meta-pytorch/openenv-base:latest
63
+
64
+ WORKDIR /app
65
+
66
+ # Copy the virtual environment from builder
67
+ COPY --from=builder /app/env/.venv /app/.venv
68
+
69
+ # Copy the environment code
70
+ COPY --from=builder /app/env /app/env
71
+
72
+ # Set PATH to use the virtual environment
73
+ ENV PATH="/app/.venv/bin:$PATH"
74
+
75
+ # Set PYTHONPATH so imports work correctly
76
+ ENV PYTHONPATH="/app/env:$PYTHONPATH"
77
+ ENV ENABLE_WEB_INTERFACE=true
78
+
79
+ # Health check using Python (more portable than curl/wget)
80
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
81
+ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
82
+
83
+ # Run the FastAPI server
84
+ # The module path is constructed to work with the /app/env structure
85
+ CMD ["sh", "-c", "cd /app/env && uvicorn server.app:app --host 0.0.0.0 --port 8000"]
README.md ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Echo Environment Server
3
+ emoji: 🔊
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: docker
7
+ pinned: false
8
+ app_port: 8000
9
+ base_path: /web
10
+ tags:
11
+ - openenv-0.2.3
12
+ - openenv
13
+ ---
14
+
15
+ ## Hugging Face Space Deployment
16
+
17
+ This Space is built from OpenEnv environment `echo_env`.
18
+
19
+ - Space URL: `https://huggingface.co/spaces/openenv/echo_env`
20
+ - OpenEnv pinned ref: `0.2.3`
21
+ - Hub tag: `openenv`
22
+
23
+ ### Connecting from Code
24
+
25
+ ```python
26
+ from envs.echo_env import EchoEnv
27
+
28
+ env = EchoEnv(base_url="https://huggingface.co/spaces/openenv/echo_env")
29
+ ```
30
+
31
+ # Echo Environment
32
+
33
+ A simple test environment that echoes back messages. Perfect for testing the env APIs as well as demonstrating environment usage patterns.
34
+
35
+ ## Quick Start
36
+
37
+ The simplest way to use the Echo environment is through the `EchoEnv` class. The client is **async by default**:
38
+
39
+ ```python
40
+ import asyncio
41
+ from echo_env import EchoAction, EchoEnv
42
+
43
+ async def main():
44
+ # Create environment from Docker image
45
+ client = await EchoEnv.from_docker_image("echo-env:latest")
46
+
47
+ async with client:
48
+ # Reset
49
+ result = await client.reset()
50
+ print(f"Reset: {result.observation.echoed_message}")
51
+
52
+ # Send multiple messages
53
+ messages = ["Hello, World!", "Testing echo", "Final message"]
54
+
55
+ for msg in messages:
56
+ result = await client.step(EchoAction(message=msg))
57
+ print(f"Sent: '{msg}'")
58
+ print(f" → Echoed: '{result.observation.echoed_message}'")
59
+ print(f" → Length: {result.observation.message_length}")
60
+ print(f" → Reward: {result.reward}")
61
+
62
+ asyncio.run(main())
63
+ ```
64
+
65
+ For **synchronous usage**, use the `.sync()` wrapper:
66
+
67
+ ```python
68
+ from echo_env import EchoAction, EchoEnv
69
+
70
+ with EchoEnv(base_url="http://localhost:8000").sync() as client:
71
+ result = client.reset()
72
+ result = client.step(EchoAction(message="Hello!"))
73
+ print(result.observation.echoed_message)
74
+ ```
75
+
76
+ The `EchoEnv.from_docker_image()` method handles:
77
+ - Starting the Docker container
78
+ - Waiting for the server to be ready
79
+ - Connecting to the environment
80
+ - Container cleanup when the context manager exits
81
+
82
+ ## Building the Docker Image
83
+
84
+ Before using the environment, you need to build the Docker image:
85
+
86
+ ```bash
87
+ # From project root
88
+ docker build -t echo-env:latest -f envs/echo_env/server/Dockerfile .
89
+ ```
90
+
91
+ ## Environment Details
92
+
93
+ ### Action
94
+ **EchoAction**: Contains a single field
95
+ - `message` (str) - The message to echo back
96
+
97
+ ### Observation
98
+ **EchoObservation**: Contains the echo response and metadata
99
+ - `echoed_message` (str) - The message echoed back
100
+ - `message_length` (int) - Length of the message
101
+ - `reward` (float) - Reward based on message length (length × 0.1)
102
+ - `done` (bool) - Always False for echo environment
103
+ - `metadata` (dict) - Additional info like step count
104
+
105
+ ### Reward
106
+ The reward is calculated as: `message_length × 0.1`
107
+ - "Hi" → reward: 0.2
108
+ - "Hello, World!" → reward: 1.3
109
+ - Empty message → reward: 0.0
110
+
111
+ ## Advanced Usage
112
+
113
+ ### Connecting to an Existing Server
114
+
115
+ If you already have an Echo environment server running, you can connect directly:
116
+
117
+ ```python
118
+ from echo_env import EchoAction, EchoEnv
119
+
120
+ # Async usage
121
+ async with EchoEnv(base_url="http://localhost:8000") as client:
122
+ result = await client.reset()
123
+ result = await client.step(EchoAction(message="Hello!"))
124
+
125
+ # Sync usage
126
+ with EchoEnv(base_url="http://localhost:8000").sync() as client:
127
+ result = client.reset()
128
+ result = client.step(EchoAction(message="Hello!"))
129
+ ```
130
+
131
+ Note: When connecting to an existing server, closing the client will NOT stop the server.
132
+
133
+ ## Development & Testing
134
+
135
+ ### Direct Environment Testing
136
+
137
+ Test the environment logic directly without starting the HTTP server:
138
+
139
+ ```bash
140
+ # From the server directory
141
+ python3 envs/echo_env/server/test_echo_env.py
142
+ ```
143
+
144
+ This verifies that:
145
+ - Environment resets correctly
146
+ - Step executes actions properly
147
+ - State tracking works
148
+ - Rewards are calculated correctly
149
+
150
+ ### Running the Full Example
151
+
152
+ Run the complete example that demonstrates the full workflow:
153
+
154
+ ```bash
155
+ python3 examples/local_echo_env.py
156
+ ```
157
+
158
+ This example shows:
159
+ - Creating an environment from a Docker image
160
+ - Resetting and stepping through the environment
161
+ - Automatic cleanup with `close()`
162
+
163
+ ## Project Structure
164
+
165
+ ```
166
+ echo_env/
167
+ ├── __init__.py # Module exports
168
+ ├── README.md # This file
169
+ ├── client.py # EchoEnv client implementation
170
+ ├── models.py # Action and Observation models
171
+ └── server/
172
+ ├── __init__.py # Server module exports
173
+ ├── echo_environment.py # Core environment logic
174
+ ├── app.py # FastAPI application
175
+ ├── test_echo_env.py # Direct environment tests
176
+ └── Dockerfile # Container image definition
177
+ ```
__init__.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ Echo Environment - A pure MCP environment for testing and demonstration.
9
+
10
+ This environment exposes all functionality through MCP tools:
11
+ - `echo_message(message)`: Echo back the provided message
12
+ - `echo_with_length(message)`: Echo back the message with its length
13
+
14
+ Example:
15
+ >>> from echo_env import EchoEnv
16
+ >>>
17
+ >>> with EchoEnv(base_url="http://localhost:8000") as env:
18
+ ... env.reset()
19
+ ... tools = env.list_tools()
20
+ ... result = env.call_tool("echo_message", message="Hello!")
21
+ ... print(result) # "Hello!"
22
+ """
23
+
24
+ # Re-export MCP types for convenience
25
+ from openenv.core.env_server.mcp_types import CallToolAction, ListToolsAction
26
+
27
+ from .client import EchoEnv
28
+
29
+ __all__ = ["EchoEnv", "CallToolAction", "ListToolsAction"]
build/lib/build/lib/server/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """Echo environment server components."""
8
+
9
+ from .echo_environment import EchoEnvironment
10
+
11
+ __all__ = ["EchoEnvironment"]
build/lib/build/lib/server/app.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ FastAPI application for the Echo Environment.
9
+
10
+ This module creates an HTTP server that exposes the EchoEnvironment
11
+ over HTTP and WebSocket endpoints, compatible with EnvClient.
12
+
13
+ Usage:
14
+ # Development (with auto-reload):
15
+ uvicorn server.app:app --reload --host 0.0.0.0 --port 8000
16
+
17
+ # Production:
18
+ uvicorn server.app:app --host 0.0.0.0 --port 8000 --workers 4
19
+
20
+ # Or run directly:
21
+ uv run --project . server
22
+ """
23
+
24
+ # Support both in-repo and standalone imports
25
+ try:
26
+ # In-repo imports (when running from OpenEnv repository)
27
+ from openenv.core.env_server.http_server import create_app
28
+ from ..models import EchoAction, EchoObservation
29
+ from .echo_environment import EchoEnvironment
30
+ except ImportError:
31
+ # Standalone imports (when environment is standalone with openenv from pip)
32
+ from openenv.core.env_server.http_server import create_app
33
+ from models import EchoAction, EchoObservation
34
+ from server.echo_environment import EchoEnvironment
35
+
36
+ # Create the app with web interface and README integration
37
+ # Pass the class (factory) instead of an instance for WebSocket session support
38
+ app = create_app(EchoEnvironment, EchoAction, EchoObservation, env_name="echo_env")
39
+
40
+
41
+ def main():
42
+ """
43
+ Entry point for direct execution via uv run or python -m.
44
+
45
+ This function enables running the server without Docker:
46
+ uv run --project . server
47
+ python -m envs.echo_env.server.app
48
+ openenv serve echo_env
49
+
50
+ """
51
+ import uvicorn
52
+
53
+ uvicorn.run(app, host="0.0.0.0", port=8000)
54
+
55
+
56
+ if __name__ == "__main__":
57
+ main()
build/lib/build/lib/server/echo_environment.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ Echo Environment Implementation.
9
+
10
+ A simple test environment that echoes back messages sent to it.
11
+ Perfect for testing HTTP server infrastructure.
12
+ """
13
+
14
+ from uuid import uuid4
15
+
16
+ # Support both in-repo and standalone imports
17
+ try:
18
+ # In-repo imports (when running from OpenEnv repository)
19
+ from openenv.core.env_server.interfaces import Environment
20
+ from openenv.core.env_server.types import State
21
+ from ..models import EchoAction, EchoObservation
22
+ except ImportError:
23
+ # Standalone imports (when environment is standalone with openenv from pip)
24
+ from openenv.core.env_server.interfaces import Environment
25
+ from openenv.core.env_server.types import State
26
+ from models import EchoAction, EchoObservation
27
+
28
+
29
+ class EchoEnvironment(Environment):
30
+ """
31
+ A simple echo environment that echoes back messages.
32
+
33
+ This environment is designed for testing the HTTP server infrastructure.
34
+ It maintains minimal state and simply echoes back whatever message it receives.
35
+
36
+ Example:
37
+ >>> env = EchoEnvironment()
38
+ >>> obs = env.reset()
39
+ >>> print(obs.echoed_message) # "Echo environment ready!"
40
+ >>>
41
+ >>> obs = env.step(EchoAction(message="Hello"))
42
+ >>> print(obs.echoed_message) # "Hello"
43
+ >>> print(obs.message_length) # 5
44
+ """
45
+
46
+ def __init__(self):
47
+ """Initialize the echo environment."""
48
+ self._state = State(episode_id=str(uuid4()), step_count=0)
49
+ self._reset_count = 0
50
+
51
+ def reset(self) -> EchoObservation:
52
+ """
53
+ Reset the environment.
54
+
55
+ Returns:
56
+ EchoObservation with a ready message
57
+ """
58
+ self._state = State(episode_id=str(uuid4()), step_count=0)
59
+ self._reset_count += 1
60
+
61
+ return EchoObservation(
62
+ echoed_message="Echo environment ready!",
63
+ message_length=0,
64
+ done=False,
65
+ reward=0.0,
66
+ )
67
+
68
+ def step(self, action: EchoAction) -> EchoObservation: # type: ignore[override]
69
+ """
70
+ Execute a step in the environment by echoing the message.
71
+
72
+ Args:
73
+ action: EchoAction containing the message to echo
74
+
75
+ Returns:
76
+ EchoObservation with the echoed message and its length
77
+ """
78
+ self._state.step_count += 1
79
+
80
+ message = action.message
81
+ length = len(message)
82
+
83
+ # Simple reward: longer messages get higher rewards
84
+ reward = length * 0.1
85
+
86
+ return EchoObservation(
87
+ echoed_message=message,
88
+ message_length=length,
89
+ done=False,
90
+ reward=reward,
91
+ metadata={"original_message": message, "step": self._state.step_count},
92
+ )
93
+
94
+ @property
95
+ def state(self) -> State:
96
+ """
97
+ Get the current environment state.
98
+
99
+ Returns:
100
+ Current State with episode_id and step_count
101
+ """
102
+ return self._state
build/lib/echo_env/__init__.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ Echo Environment - A pure MCP environment for testing and demonstration.
9
+
10
+ This environment exposes all functionality through MCP tools:
11
+ - `echo_message(message)`: Echo back the provided message
12
+ - `echo_with_length(message)`: Echo back the message with its length
13
+
14
+ Example:
15
+ >>> from echo_env import EchoEnv
16
+ >>>
17
+ >>> with EchoEnv(base_url="http://localhost:8000") as env:
18
+ ... env.reset()
19
+ ... tools = env.list_tools()
20
+ ... result = env.call_tool("echo_message", message="Hello!")
21
+ ... print(result) # "Hello!"
22
+ """
23
+
24
+ from .client import EchoEnv
25
+
26
+ # Re-export MCP types for convenience
27
+ from openenv.core.env_server.mcp_types import CallToolAction, ListToolsAction
28
+
29
+ __all__ = ["EchoEnv", "CallToolAction", "ListToolsAction"]
build/lib/echo_env/client.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ Echo Environment Client.
9
+
10
+ This module provides the client for connecting to an Echo Environment server.
11
+ EchoEnv extends MCPToolClient to provide tool-calling style interactions.
12
+
13
+ Example:
14
+ >>> with EchoEnv(base_url="http://localhost:8000") as env:
15
+ ... env.reset()
16
+ ...
17
+ ... # Discover tools
18
+ ... tools = env.list_tools()
19
+ ... print([t.name for t in tools]) # ['echo_message', 'echo_with_length']
20
+ ...
21
+ ... # Call tools
22
+ ... result = env.call_tool("echo_message", message="Hello!")
23
+ ... print(result) # "Hello!"
24
+ ...
25
+ ... result = env.call_tool("echo_with_length", message="Test")
26
+ ... print(result) # {"message": "Test", "length": 4}
27
+ """
28
+
29
+ from openenv.core.mcp_client import MCPToolClient
30
+
31
+
32
+ class EchoEnv(MCPToolClient):
33
+ """
34
+ Client for the Echo Environment.
35
+
36
+ This client provides a simple interface for interacting with the Echo
37
+ Environment via MCP tools. It inherits all functionality from MCPToolClient:
38
+ - `list_tools()`: Discover available tools
39
+ - `call_tool(name, **kwargs)`: Call a tool by name
40
+ - `reset(**kwargs)`: Reset the environment
41
+ - `step(action)`: Execute an action (for advanced use)
42
+
43
+ Example:
44
+ >>> # Connect to a running server
45
+ >>> with EchoEnv(base_url="http://localhost:8000") as env:
46
+ ... env.reset()
47
+ ...
48
+ ... # List available tools
49
+ ... tools = env.list_tools()
50
+ ... for tool in tools:
51
+ ... print(f"{tool.name}: {tool.description}")
52
+ ...
53
+ ... # Echo a message
54
+ ... result = env.call_tool("echo_message", message="Hello!")
55
+ ... print(result) # "Hello!"
56
+ ...
57
+ ... # Echo with length
58
+ ... result = env.call_tool("echo_with_length", message="Test")
59
+ ... print(result) # {"message": "Test", "length": 4}
60
+
61
+ Example with Docker:
62
+ >>> # Automatically start container and connect
63
+ >>> env = EchoEnv.from_docker_image("echo-env:latest")
64
+ >>> try:
65
+ ... env.reset()
66
+ ... tools = env.list_tools()
67
+ ... result = env.call_tool("echo_message", message="Hello!")
68
+ ... finally:
69
+ ... env.close()
70
+
71
+ Example with HuggingFace Space:
72
+ >>> # Run from HuggingFace Space
73
+ >>> env = EchoEnv.from_env("openenv/echo-env")
74
+ >>> try:
75
+ ... env.reset()
76
+ ... result = env.call_tool("echo_message", message="Hello!")
77
+ ... finally:
78
+ ... env.close()
79
+ """
80
+
81
+ pass # MCPToolClient provides all needed functionality
build/lib/echo_env/server/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """Echo environment server components."""
8
+
9
+ from .echo_environment import EchoEnvironment
10
+
11
+ __all__ = ["EchoEnvironment"]
build/lib/echo_env/server/app.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ FastAPI application for the Echo Environment.
9
+
10
+ This module creates an HTTP server that exposes the EchoEnvironment
11
+ over HTTP and WebSocket endpoints, compatible with MCPToolClient.
12
+
13
+ Usage:
14
+ # Development (with auto-reload):
15
+ uvicorn server.app:app --reload --host 0.0.0.0 --port 8000
16
+
17
+ # Production:
18
+ uvicorn server.app:app --host 0.0.0.0 --port 8000 --workers 4
19
+
20
+ # Or run directly:
21
+ uv run --project . server
22
+ """
23
+
24
+ # Support both in-repo and standalone imports
25
+ try:
26
+ # In-repo imports (when running from OpenEnv repository)
27
+ from openenv.core.env_server.http_server import create_app
28
+ from openenv.core.env_server.mcp_types import CallToolAction, CallToolObservation
29
+ from .echo_environment import EchoEnvironment
30
+ except ImportError:
31
+ # Standalone imports (when environment is standalone with openenv from pip)
32
+ from openenv.core.env_server.http_server import create_app
33
+ from openenv.core.env_server.mcp_types import CallToolAction, CallToolObservation
34
+ from server.echo_environment import EchoEnvironment
35
+
36
+ # Create the app with web interface and README integration
37
+ # Pass the class (factory) instead of an instance for WebSocket session support
38
+ # Use MCP types for action/observation since this is a pure MCP environment
39
+ app = create_app(
40
+ EchoEnvironment, CallToolAction, CallToolObservation, env_name="echo_env"
41
+ )
42
+
43
+
44
+ def main():
45
+ """
46
+ Entry point for direct execution via uv run or python -m.
47
+
48
+ This function enables running the server without Docker:
49
+ uv run --project . server
50
+ python -m envs.echo_env.server.app
51
+ openenv serve echo_env
52
+
53
+ """
54
+ import uvicorn
55
+
56
+ uvicorn.run(app, host="0.0.0.0", port=8000)
57
+
58
+
59
+ if __name__ == "__main__":
60
+ main()
build/lib/echo_env/server/echo_environment.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ Echo Environment Implementation.
9
+
10
+ A pure MCP environment that echoes back messages sent to it.
11
+ This demonstrates how to build an MCPEnvironment with inline FastMCP tools.
12
+
13
+ All interactions happen through MCP tools:
14
+ - `echo_message(message)`: Echo back the provided message
15
+ - `echo_with_length(message)`: Echo back the message with its length
16
+
17
+ Example:
18
+ >>> from openenv.core.env_server.mcp_types import ListToolsAction, CallToolAction
19
+ >>> env = EchoEnvironment()
20
+ >>> env.reset()
21
+ >>>
22
+ >>> # List available tools
23
+ >>> obs = env.step(ListToolsAction())
24
+ >>> print([t.name for t in obs.tools]) # ["echo_message", "echo_with_length"]
25
+ >>>
26
+ >>> # Call a tool
27
+ >>> obs = env.step(CallToolAction(tool_name="echo_message", arguments={"message": "Hi!"}))
28
+ >>> print(obs.result) # "Hi!"
29
+ """
30
+
31
+ from typing import Any, Optional
32
+ from uuid import uuid4
33
+
34
+ # Support both in-repo and standalone imports
35
+ try:
36
+ # In-repo imports (when running from OpenEnv repository)
37
+ from openenv.core.env_server.mcp_environment import MCPEnvironment
38
+ from openenv.core.env_server.types import Action, Observation, State
39
+ except ImportError:
40
+ # Standalone imports (when environment is standalone with openenv from pip)
41
+ from openenv.core.env_server.mcp_environment import MCPEnvironment
42
+ from openenv.core.env_server.types import Action, Observation, State
43
+
44
+ from fastmcp import FastMCP
45
+
46
+
47
+ class EchoEnvironment(MCPEnvironment):
48
+ """
49
+ A pure MCP echo environment that echoes back messages.
50
+
51
+ This environment exposes all functionality through MCP tools:
52
+ - `echo_message`: Echo back the provided message
53
+ - `echo_with_length`: Echo back the message with its length
54
+
55
+ The environment inherits MCP support (ListToolsAction, CallToolAction)
56
+ from the MCPEnvironment base class. No legacy action types are supported.
57
+
58
+ Example using MCPToolClient:
59
+ >>> from openenv.core.mcp_client import MCPToolClient
60
+ >>>
61
+ >>> with MCPToolClient(base_url="http://localhost:8000") as env:
62
+ ... env.reset()
63
+ ... tools = env.list_tools()
64
+ ... print([t.name for t in tools])
65
+ ... result = env.call_tool("echo_message", message="Hello!")
66
+ ... print(result)
67
+ """
68
+
69
+ def __init__(self):
70
+ """Initialize the echo environment with MCP server and tools."""
71
+ # Create MCP server and define tools inline
72
+ mcp = FastMCP("echo_env")
73
+
74
+ @mcp.tool
75
+ def echo_message(message: str) -> str:
76
+ """
77
+ Echo back the provided message.
78
+
79
+ Args:
80
+ message: The message to echo back
81
+
82
+ Returns:
83
+ The same message that was provided
84
+ """
85
+ return message
86
+
87
+ @mcp.tool
88
+ def echo_with_length(message: str) -> dict:
89
+ """
90
+ Echo back the message with its length.
91
+
92
+ Args:
93
+ message: The message to echo back
94
+
95
+ Returns:
96
+ Dictionary with the message and its length
97
+ """
98
+ return {"message": message, "length": len(message)}
99
+
100
+ # Pass the MCP server to the base class
101
+ super().__init__(mcp)
102
+ self._state = State(episode_id=str(uuid4()), step_count=0)
103
+ self._reset_count = 0
104
+
105
+ def reset(
106
+ self,
107
+ seed: Optional[int] = None,
108
+ episode_id: Optional[str] = None,
109
+ **kwargs: Any,
110
+ ) -> Observation:
111
+ """
112
+ Reset the environment.
113
+
114
+ Args:
115
+ seed: Optional random seed (unused in echo env)
116
+ episode_id: Optional episode ID to use
117
+ **kwargs: Additional reset options
118
+
119
+ Returns:
120
+ Observation indicating the environment is ready
121
+ """
122
+ self._state = State(
123
+ episode_id=episode_id or str(uuid4()),
124
+ step_count=0,
125
+ )
126
+ self._reset_count += 1
127
+
128
+ return Observation(
129
+ done=False,
130
+ reward=0.0,
131
+ metadata={"status": "ready", "message": "Echo environment ready!"},
132
+ )
133
+
134
+ def _step_impl(
135
+ self,
136
+ action: Action,
137
+ timeout_s: Optional[float] = None,
138
+ **kwargs: Any,
139
+ ) -> Observation:
140
+ """
141
+ Handle non-MCP actions.
142
+
143
+ This environment only supports MCP actions (ListToolsAction, CallToolAction).
144
+ Any other action type returns an error observation.
145
+
146
+ Args:
147
+ action: The action to execute
148
+ timeout_s: Optional timeout (unused)
149
+ **kwargs: Additional arguments
150
+
151
+ Returns:
152
+ Observation with error for unknown action types
153
+ """
154
+ return Observation(
155
+ done=False,
156
+ reward=0.0,
157
+ metadata={
158
+ "error": f"Unknown action type: {type(action).__name__}. "
159
+ "Use ListToolsAction or CallToolAction for MCP interactions."
160
+ },
161
+ )
162
+
163
+ def step(
164
+ self,
165
+ action: Action,
166
+ timeout_s: Optional[float] = None,
167
+ **kwargs: Any,
168
+ ) -> Observation:
169
+ """
170
+ Execute a step in the environment.
171
+
172
+ Delegates to base class for MCP actions. Increments step count for all actions.
173
+
174
+ Args:
175
+ action: The MCP action to execute (ListToolsAction or CallToolAction)
176
+ timeout_s: Optional timeout for the action
177
+ **kwargs: Additional arguments
178
+
179
+ Returns:
180
+ Observation from the action execution
181
+ """
182
+ # Increment step count for all actions
183
+ self._state.step_count += 1
184
+
185
+ # Let the base class handle MCP actions and non-MCP routing
186
+ return super().step(action, timeout_s=timeout_s, **kwargs)
187
+
188
+ @property
189
+ def state(self) -> State:
190
+ """
191
+ Get the current environment state.
192
+
193
+ Returns:
194
+ Current State with episode_id and step_count
195
+ """
196
+ return self._state
build/lib/server/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """Echo environment server components."""
8
+
9
+ from .echo_environment import EchoEnvironment
10
+
11
+ __all__ = ["EchoEnvironment"]
build/lib/server/app.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ FastAPI application for the Echo Environment.
9
+
10
+ This module creates an HTTP server that exposes the EchoEnvironment
11
+ over HTTP and WebSocket endpoints, compatible with EnvClient.
12
+
13
+ Usage:
14
+ # Development (with auto-reload):
15
+ uvicorn server.app:app --reload --host 0.0.0.0 --port 8000
16
+
17
+ # Production:
18
+ uvicorn server.app:app --host 0.0.0.0 --port 8000 --workers 4
19
+
20
+ # Or run directly:
21
+ uv run --project . server
22
+ """
23
+
24
+ # Support both in-repo and standalone imports
25
+ try:
26
+ # In-repo imports (when running from OpenEnv repository)
27
+ from openenv.core.env_server.http_server import create_app
28
+ from ..models import EchoAction, EchoObservation
29
+ from .echo_environment import EchoEnvironment
30
+ except ImportError:
31
+ # Standalone imports (when environment is standalone with openenv from pip)
32
+ from openenv.core.env_server.http_server import create_app
33
+ from models import EchoAction, EchoObservation
34
+ from server.echo_environment import EchoEnvironment
35
+
36
+ # Create the app with web interface and README integration
37
+ # Pass the class (factory) instead of an instance for WebSocket session support
38
+ app = create_app(EchoEnvironment, EchoAction, EchoObservation, env_name="echo_env")
39
+
40
+
41
+ def main():
42
+ """
43
+ Entry point for direct execution via uv run or python -m.
44
+
45
+ This function enables running the server without Docker:
46
+ uv run --project . server
47
+ python -m envs.echo_env.server.app
48
+ openenv serve echo_env
49
+
50
+ """
51
+ import uvicorn
52
+
53
+ uvicorn.run(app, host="0.0.0.0", port=8000)
54
+
55
+
56
+ if __name__ == "__main__":
57
+ main()
build/lib/server/echo_environment.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ Echo Environment Implementation.
9
+
10
+ A simple test environment that echoes back messages sent to it.
11
+ Perfect for testing HTTP server infrastructure.
12
+ """
13
+
14
+ from uuid import uuid4
15
+
16
+ # Support both in-repo and standalone imports
17
+ try:
18
+ # In-repo imports (when running from OpenEnv repository)
19
+ from openenv.core.env_server.interfaces import Environment
20
+ from openenv.core.env_server.types import State
21
+ from ..models import EchoAction, EchoObservation
22
+ except ImportError:
23
+ # Standalone imports (when environment is standalone with openenv from pip)
24
+ from openenv.core.env_server.interfaces import Environment
25
+ from openenv.core.env_server.types import State
26
+ from models import EchoAction, EchoObservation
27
+
28
+
29
+ class EchoEnvironment(Environment):
30
+ """
31
+ A simple echo environment that echoes back messages.
32
+
33
+ This environment is designed for testing the HTTP server infrastructure.
34
+ It maintains minimal state and simply echoes back whatever message it receives.
35
+
36
+ Example:
37
+ >>> env = EchoEnvironment()
38
+ >>> obs = env.reset()
39
+ >>> print(obs.echoed_message) # "Echo environment ready!"
40
+ >>>
41
+ >>> obs = env.step(EchoAction(message="Hello"))
42
+ >>> print(obs.echoed_message) # "Hello"
43
+ >>> print(obs.message_length) # 5
44
+ """
45
+
46
+ def __init__(self):
47
+ """Initialize the echo environment."""
48
+ self._state = State(episode_id=str(uuid4()), step_count=0)
49
+ self._reset_count = 0
50
+
51
+ def reset(self) -> EchoObservation:
52
+ """
53
+ Reset the environment.
54
+
55
+ Returns:
56
+ EchoObservation with a ready message
57
+ """
58
+ self._state = State(episode_id=str(uuid4()), step_count=0)
59
+ self._reset_count += 1
60
+
61
+ return EchoObservation(
62
+ echoed_message="Echo environment ready!",
63
+ message_length=0,
64
+ done=False,
65
+ reward=0.0,
66
+ )
67
+
68
+ def step(self, action: EchoAction) -> EchoObservation: # type: ignore[override]
69
+ """
70
+ Execute a step in the environment by echoing the message.
71
+
72
+ Args:
73
+ action: EchoAction containing the message to echo
74
+
75
+ Returns:
76
+ EchoObservation with the echoed message and its length
77
+ """
78
+ self._state.step_count += 1
79
+
80
+ message = action.message
81
+ length = len(message)
82
+
83
+ # Simple reward: longer messages get higher rewards
84
+ reward = length * 0.1
85
+
86
+ return EchoObservation(
87
+ echoed_message=message,
88
+ message_length=length,
89
+ done=False,
90
+ reward=reward,
91
+ metadata={"original_message": message, "step": self._state.step_count},
92
+ )
93
+
94
+ @property
95
+ def state(self) -> State:
96
+ """
97
+ Get the current environment state.
98
+
99
+ Returns:
100
+ Current State with episode_id and step_count
101
+ """
102
+ return self._state
client.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ Echo Environment Client.
9
+
10
+ This module provides the client for connecting to an Echo Environment server.
11
+ EchoEnv extends MCPToolClient to provide tool-calling style interactions.
12
+
13
+ Example:
14
+ >>> with EchoEnv(base_url="http://localhost:8000") as env:
15
+ ... env.reset()
16
+ ...
17
+ ... # Discover tools
18
+ ... tools = env.list_tools()
19
+ ... print([t.name for t in tools]) # ['echo_message', 'echo_with_length']
20
+ ...
21
+ ... # Call tools
22
+ ... result = env.call_tool("echo_message", message="Hello!")
23
+ ... print(result) # "Hello!"
24
+ ...
25
+ ... result = env.call_tool("echo_with_length", message="Test")
26
+ ... print(result) # {"message": "Test", "length": 4}
27
+ """
28
+
29
+ from openenv.core.mcp_client import MCPToolClient
30
+
31
+
32
+ class EchoEnv(MCPToolClient):
33
+ """
34
+ Client for the Echo Environment.
35
+
36
+ This client provides a simple interface for interacting with the Echo
37
+ Environment via MCP tools. It inherits all functionality from MCPToolClient:
38
+ - `list_tools()`: Discover available tools
39
+ - `call_tool(name, **kwargs)`: Call a tool by name
40
+ - `reset(**kwargs)`: Reset the environment
41
+ - `step(action)`: Execute an action (for advanced use)
42
+
43
+ Example:
44
+ >>> # Connect to a running server
45
+ >>> with EchoEnv(base_url="http://localhost:8000") as env:
46
+ ... env.reset()
47
+ ...
48
+ ... # List available tools
49
+ ... tools = env.list_tools()
50
+ ... for tool in tools:
51
+ ... print(f"{tool.name}: {tool.description}")
52
+ ...
53
+ ... # Echo a message
54
+ ... result = env.call_tool("echo_message", message="Hello!")
55
+ ... print(result) # "Hello!"
56
+ ...
57
+ ... # Echo with length
58
+ ... result = env.call_tool("echo_with_length", message="Test")
59
+ ... print(result) # {"message": "Test", "length": 4}
60
+
61
+ Example with Docker:
62
+ >>> # Automatically start container and connect
63
+ >>> env = EchoEnv.from_docker_image("echo-env:latest")
64
+ >>> try:
65
+ ... env.reset()
66
+ ... tools = env.list_tools()
67
+ ... result = env.call_tool("echo_message", message="Hello!")
68
+ ... finally:
69
+ ... env.close()
70
+
71
+ Example with HuggingFace Space:
72
+ >>> # Run from HuggingFace Space
73
+ >>> env = EchoEnv.from_env("openenv/echo-env")
74
+ >>> try:
75
+ ... env.reset()
76
+ ... result = env.call_tool("echo_message", message="Hello!")
77
+ ... finally:
78
+ ... env.close()
79
+ """
80
+
81
+ pass # MCPToolClient provides all needed functionality
envs/echo_env/README.md ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Echo Environment Server
3
+ emoji: 🔊
4
+ colorFrom: blue
5
+ colorTo: blue
6
+ sdk: docker
7
+ pinned: false
8
+ app_port: 8000
9
+ base_path: /web
10
+ tags:
11
+ - openenv
12
+ ---
13
+
14
+ # Echo Environment
15
+
16
+ A simple test environment that echoes back messages. Perfect for testing the env APIs as well as demonstrating environment usage patterns.
17
+
18
+ ## Quick Start
19
+
20
+ The simplest way to use the Echo environment is through the `EchoEnv` class. The client is **async by default**:
21
+
22
+ ```python
23
+ import asyncio
24
+ from echo_env import EchoAction, EchoEnv
25
+
26
+ async def main():
27
+ # Create environment from Docker image
28
+ client = await EchoEnv.from_docker_image("echo-env:latest")
29
+
30
+ async with client:
31
+ # Reset
32
+ result = await client.reset()
33
+ print(f"Reset: {result.observation.echoed_message}")
34
+
35
+ # Send multiple messages
36
+ messages = ["Hello, World!", "Testing echo", "Final message"]
37
+
38
+ for msg in messages:
39
+ result = await client.step(EchoAction(message=msg))
40
+ print(f"Sent: '{msg}'")
41
+ print(f" → Echoed: '{result.observation.echoed_message}'")
42
+ print(f" → Length: {result.observation.message_length}")
43
+ print(f" → Reward: {result.reward}")
44
+
45
+ asyncio.run(main())
46
+ ```
47
+
48
+ For **synchronous usage**, use the `.sync()` wrapper:
49
+
50
+ ```python
51
+ from echo_env import EchoAction, EchoEnv
52
+
53
+ with EchoEnv(base_url="http://localhost:8000").sync() as client:
54
+ result = client.reset()
55
+ result = client.step(EchoAction(message="Hello!"))
56
+ print(result.observation.echoed_message)
57
+ ```
58
+
59
+ The `EchoEnv.from_docker_image()` method handles:
60
+ - Starting the Docker container
61
+ - Waiting for the server to be ready
62
+ - Connecting to the environment
63
+ - Container cleanup when the context manager exits
64
+
65
+ ## Building the Docker Image
66
+
67
+ Before using the environment, you need to build the Docker image:
68
+
69
+ ```bash
70
+ # From project root
71
+ docker build -t echo-env:latest -f envs/echo_env/server/Dockerfile .
72
+ ```
73
+
74
+ ## Environment Details
75
+
76
+ ### Action
77
+ **EchoAction**: Contains a single field
78
+ - `message` (str) - The message to echo back
79
+
80
+ ### Observation
81
+ **EchoObservation**: Contains the echo response and metadata
82
+ - `echoed_message` (str) - The message echoed back
83
+ - `message_length` (int) - Length of the message
84
+ - `reward` (float) - Reward based on message length (length × 0.1)
85
+ - `done` (bool) - Always False for echo environment
86
+ - `metadata` (dict) - Additional info like step count
87
+
88
+ ### Reward
89
+ The reward is calculated as: `message_length × 0.1`
90
+ - "Hi" → reward: 0.2
91
+ - "Hello, World!" → reward: 1.3
92
+ - Empty message → reward: 0.0
93
+
94
+ ## Advanced Usage
95
+
96
+ ### Connecting to an Existing Server
97
+
98
+ If you already have an Echo environment server running, you can connect directly:
99
+
100
+ ```python
101
+ from echo_env import EchoAction, EchoEnv
102
+
103
+ # Async usage
104
+ async with EchoEnv(base_url="http://localhost:8000") as client:
105
+ result = await client.reset()
106
+ result = await client.step(EchoAction(message="Hello!"))
107
+
108
+ # Sync usage
109
+ with EchoEnv(base_url="http://localhost:8000").sync() as client:
110
+ result = client.reset()
111
+ result = client.step(EchoAction(message="Hello!"))
112
+ ```
113
+
114
+ Note: When connecting to an existing server, closing the client will NOT stop the server.
115
+
116
+ ## Development & Testing
117
+
118
+ ### Direct Environment Testing
119
+
120
+ Test the environment logic directly without starting the HTTP server:
121
+
122
+ ```bash
123
+ # From the server directory
124
+ python3 envs/echo_env/server/test_echo_env.py
125
+ ```
126
+
127
+ This verifies that:
128
+ - Environment resets correctly
129
+ - Step executes actions properly
130
+ - State tracking works
131
+ - Rewards are calculated correctly
132
+
133
+ ### Running the Full Example
134
+
135
+ Run the complete example that demonstrates the full workflow:
136
+
137
+ ```bash
138
+ python3 examples/local_echo_env.py
139
+ ```
140
+
141
+ This example shows:
142
+ - Creating an environment from a Docker image
143
+ - Resetting and stepping through the environment
144
+ - Automatic cleanup with `close()`
145
+
146
+ ## Project Structure
147
+
148
+ ```
149
+ echo_env/
150
+ ├── __init__.py # Module exports
151
+ ├── README.md # This file
152
+ ├── client.py # EchoEnv client implementation
153
+ ├── models.py # Action and Observation models
154
+ └── server/
155
+ ├── __init__.py # Server module exports
156
+ ├── echo_environment.py # Core environment logic
157
+ ├── app.py # FastAPI application
158
+ ├── test_echo_env.py # Direct environment tests
159
+ └── Dockerfile # Container image definition
160
+ ```
envs/echo_env/__init__.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ Echo Environment - A pure MCP environment for testing and demonstration.
9
+
10
+ This environment exposes all functionality through MCP tools:
11
+ - `echo_message(message)`: Echo back the provided message
12
+ - `echo_with_length(message)`: Echo back the message with its length
13
+
14
+ Example:
15
+ >>> from echo_env import EchoEnv
16
+ >>>
17
+ >>> with EchoEnv(base_url="http://localhost:8000") as env:
18
+ ... env.reset()
19
+ ... tools = env.list_tools()
20
+ ... result = env.call_tool("echo_message", message="Hello!")
21
+ ... print(result) # "Hello!"
22
+ """
23
+
24
+ # Re-export MCP types for convenience
25
+ from openenv.core.env_server.mcp_types import CallToolAction, ListToolsAction
26
+
27
+ from .client import EchoEnv
28
+
29
+ __all__ = ["EchoEnv", "CallToolAction", "ListToolsAction"]
envs/echo_env/build/lib/build/lib/server/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """Echo environment server components."""
8
+
9
+ from .echo_environment import EchoEnvironment
10
+
11
+ __all__ = ["EchoEnvironment"]
envs/echo_env/build/lib/build/lib/server/app.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ FastAPI application for the Echo Environment.
9
+
10
+ This module creates an HTTP server that exposes the EchoEnvironment
11
+ over HTTP and WebSocket endpoints, compatible with EnvClient.
12
+
13
+ Usage:
14
+ # Development (with auto-reload):
15
+ uvicorn server.app:app --reload --host 0.0.0.0 --port 8000
16
+
17
+ # Production:
18
+ uvicorn server.app:app --host 0.0.0.0 --port 8000 --workers 4
19
+
20
+ # Or run directly:
21
+ uv run --project . server
22
+ """
23
+
24
+ # Support both in-repo and standalone imports
25
+ try:
26
+ # In-repo imports (when running from OpenEnv repository)
27
+ from openenv.core.env_server.http_server import create_app
28
+ from ..models import EchoAction, EchoObservation
29
+ from .echo_environment import EchoEnvironment
30
+ except ImportError:
31
+ # Standalone imports (when environment is standalone with openenv from pip)
32
+ from openenv.core.env_server.http_server import create_app
33
+ from models import EchoAction, EchoObservation
34
+ from server.echo_environment import EchoEnvironment
35
+
36
+ # Create the app with web interface and README integration
37
+ # Pass the class (factory) instead of an instance for WebSocket session support
38
+ app = create_app(EchoEnvironment, EchoAction, EchoObservation, env_name="echo_env")
39
+
40
+
41
+ def main():
42
+ """
43
+ Entry point for direct execution via uv run or python -m.
44
+
45
+ This function enables running the server without Docker:
46
+ uv run --project . server
47
+ python -m envs.echo_env.server.app
48
+ openenv serve echo_env
49
+
50
+ """
51
+ import uvicorn
52
+
53
+ uvicorn.run(app, host="0.0.0.0", port=8000)
54
+
55
+
56
+ if __name__ == "__main__":
57
+ main()
envs/echo_env/build/lib/build/lib/server/echo_environment.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ Echo Environment Implementation.
9
+
10
+ A simple test environment that echoes back messages sent to it.
11
+ Perfect for testing HTTP server infrastructure.
12
+ """
13
+
14
+ from uuid import uuid4
15
+
16
+ # Support both in-repo and standalone imports
17
+ try:
18
+ # In-repo imports (when running from OpenEnv repository)
19
+ from openenv.core.env_server.interfaces import Environment
20
+ from openenv.core.env_server.types import State
21
+ from ..models import EchoAction, EchoObservation
22
+ except ImportError:
23
+ # Standalone imports (when environment is standalone with openenv from pip)
24
+ from openenv.core.env_server.interfaces import Environment
25
+ from openenv.core.env_server.types import State
26
+ from models import EchoAction, EchoObservation
27
+
28
+
29
+ class EchoEnvironment(Environment):
30
+ """
31
+ A simple echo environment that echoes back messages.
32
+
33
+ This environment is designed for testing the HTTP server infrastructure.
34
+ It maintains minimal state and simply echoes back whatever message it receives.
35
+
36
+ Example:
37
+ >>> env = EchoEnvironment()
38
+ >>> obs = env.reset()
39
+ >>> print(obs.echoed_message) # "Echo environment ready!"
40
+ >>>
41
+ >>> obs = env.step(EchoAction(message="Hello"))
42
+ >>> print(obs.echoed_message) # "Hello"
43
+ >>> print(obs.message_length) # 5
44
+ """
45
+
46
+ def __init__(self):
47
+ """Initialize the echo environment."""
48
+ self._state = State(episode_id=str(uuid4()), step_count=0)
49
+ self._reset_count = 0
50
+
51
+ def reset(self) -> EchoObservation:
52
+ """
53
+ Reset the environment.
54
+
55
+ Returns:
56
+ EchoObservation with a ready message
57
+ """
58
+ self._state = State(episode_id=str(uuid4()), step_count=0)
59
+ self._reset_count += 1
60
+
61
+ return EchoObservation(
62
+ echoed_message="Echo environment ready!",
63
+ message_length=0,
64
+ done=False,
65
+ reward=0.0,
66
+ )
67
+
68
+ def step(self, action: EchoAction) -> EchoObservation: # type: ignore[override]
69
+ """
70
+ Execute a step in the environment by echoing the message.
71
+
72
+ Args:
73
+ action: EchoAction containing the message to echo
74
+
75
+ Returns:
76
+ EchoObservation with the echoed message and its length
77
+ """
78
+ self._state.step_count += 1
79
+
80
+ message = action.message
81
+ length = len(message)
82
+
83
+ # Simple reward: longer messages get higher rewards
84
+ reward = length * 0.1
85
+
86
+ return EchoObservation(
87
+ echoed_message=message,
88
+ message_length=length,
89
+ done=False,
90
+ reward=reward,
91
+ metadata={"original_message": message, "step": self._state.step_count},
92
+ )
93
+
94
+ @property
95
+ def state(self) -> State:
96
+ """
97
+ Get the current environment state.
98
+
99
+ Returns:
100
+ Current State with episode_id and step_count
101
+ """
102
+ return self._state
envs/echo_env/build/lib/echo_env/__init__.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ Echo Environment - A pure MCP environment for testing and demonstration.
9
+
10
+ This environment exposes all functionality through MCP tools:
11
+ - `echo_message(message)`: Echo back the provided message
12
+ - `echo_with_length(message)`: Echo back the message with its length
13
+
14
+ Example:
15
+ >>> from echo_env import EchoEnv
16
+ >>>
17
+ >>> with EchoEnv(base_url="http://localhost:8000") as env:
18
+ ... env.reset()
19
+ ... tools = env.list_tools()
20
+ ... result = env.call_tool("echo_message", message="Hello!")
21
+ ... print(result) # "Hello!"
22
+ """
23
+
24
+ from .client import EchoEnv
25
+
26
+ # Re-export MCP types for convenience
27
+ from openenv.core.env_server.mcp_types import CallToolAction, ListToolsAction
28
+
29
+ __all__ = ["EchoEnv", "CallToolAction", "ListToolsAction"]
envs/echo_env/build/lib/echo_env/client.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ Echo Environment Client.
9
+
10
+ This module provides the client for connecting to an Echo Environment server.
11
+ EchoEnv extends MCPToolClient to provide tool-calling style interactions.
12
+
13
+ Example:
14
+ >>> with EchoEnv(base_url="http://localhost:8000") as env:
15
+ ... env.reset()
16
+ ...
17
+ ... # Discover tools
18
+ ... tools = env.list_tools()
19
+ ... print([t.name for t in tools]) # ['echo_message', 'echo_with_length']
20
+ ...
21
+ ... # Call tools
22
+ ... result = env.call_tool("echo_message", message="Hello!")
23
+ ... print(result) # "Hello!"
24
+ ...
25
+ ... result = env.call_tool("echo_with_length", message="Test")
26
+ ... print(result) # {"message": "Test", "length": 4}
27
+ """
28
+
29
+ from openenv.core.mcp_client import MCPToolClient
30
+
31
+
32
+ class EchoEnv(MCPToolClient):
33
+ """
34
+ Client for the Echo Environment.
35
+
36
+ This client provides a simple interface for interacting with the Echo
37
+ Environment via MCP tools. It inherits all functionality from MCPToolClient:
38
+ - `list_tools()`: Discover available tools
39
+ - `call_tool(name, **kwargs)`: Call a tool by name
40
+ - `reset(**kwargs)`: Reset the environment
41
+ - `step(action)`: Execute an action (for advanced use)
42
+
43
+ Example:
44
+ >>> # Connect to a running server
45
+ >>> with EchoEnv(base_url="http://localhost:8000") as env:
46
+ ... env.reset()
47
+ ...
48
+ ... # List available tools
49
+ ... tools = env.list_tools()
50
+ ... for tool in tools:
51
+ ... print(f"{tool.name}: {tool.description}")
52
+ ...
53
+ ... # Echo a message
54
+ ... result = env.call_tool("echo_message", message="Hello!")
55
+ ... print(result) # "Hello!"
56
+ ...
57
+ ... # Echo with length
58
+ ... result = env.call_tool("echo_with_length", message="Test")
59
+ ... print(result) # {"message": "Test", "length": 4}
60
+
61
+ Example with Docker:
62
+ >>> # Automatically start container and connect
63
+ >>> env = EchoEnv.from_docker_image("echo-env:latest")
64
+ >>> try:
65
+ ... env.reset()
66
+ ... tools = env.list_tools()
67
+ ... result = env.call_tool("echo_message", message="Hello!")
68
+ ... finally:
69
+ ... env.close()
70
+
71
+ Example with HuggingFace Space:
72
+ >>> # Run from HuggingFace Space
73
+ >>> env = EchoEnv.from_env("openenv/echo-env")
74
+ >>> try:
75
+ ... env.reset()
76
+ ... result = env.call_tool("echo_message", message="Hello!")
77
+ ... finally:
78
+ ... env.close()
79
+ """
80
+
81
+ pass # MCPToolClient provides all needed functionality
envs/echo_env/build/lib/echo_env/server/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """Echo environment server components."""
8
+
9
+ from .echo_environment import EchoEnvironment
10
+
11
+ __all__ = ["EchoEnvironment"]
envs/echo_env/build/lib/echo_env/server/app.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ FastAPI application for the Echo Environment.
9
+
10
+ This module creates an HTTP server that exposes the EchoEnvironment
11
+ over HTTP and WebSocket endpoints, compatible with MCPToolClient.
12
+
13
+ Usage:
14
+ # Development (with auto-reload):
15
+ uvicorn server.app:app --reload --host 0.0.0.0 --port 8000
16
+
17
+ # Production:
18
+ uvicorn server.app:app --host 0.0.0.0 --port 8000 --workers 4
19
+
20
+ # Or run directly:
21
+ uv run --project . server
22
+ """
23
+
24
+ # Support both in-repo and standalone imports
25
+ try:
26
+ # In-repo imports (when running from OpenEnv repository)
27
+ from openenv.core.env_server.http_server import create_app
28
+ from openenv.core.env_server.mcp_types import CallToolAction, CallToolObservation
29
+ from .echo_environment import EchoEnvironment
30
+ except ImportError:
31
+ # Standalone imports (when environment is standalone with openenv from pip)
32
+ from openenv.core.env_server.http_server import create_app
33
+ from openenv.core.env_server.mcp_types import CallToolAction, CallToolObservation
34
+ from server.echo_environment import EchoEnvironment
35
+
36
+ # Create the app with web interface and README integration
37
+ # Pass the class (factory) instead of an instance for WebSocket session support
38
+ # Use MCP types for action/observation since this is a pure MCP environment
39
+ app = create_app(
40
+ EchoEnvironment, CallToolAction, CallToolObservation, env_name="echo_env"
41
+ )
42
+
43
+
44
+ def main():
45
+ """
46
+ Entry point for direct execution via uv run or python -m.
47
+
48
+ This function enables running the server without Docker:
49
+ uv run --project . server
50
+ python -m envs.echo_env.server.app
51
+ openenv serve echo_env
52
+
53
+ """
54
+ import uvicorn
55
+
56
+ uvicorn.run(app, host="0.0.0.0", port=8000)
57
+
58
+
59
+ if __name__ == "__main__":
60
+ main()
envs/echo_env/build/lib/echo_env/server/echo_environment.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ Echo Environment Implementation.
9
+
10
+ A pure MCP environment that echoes back messages sent to it.
11
+ This demonstrates how to build an MCPEnvironment with inline FastMCP tools.
12
+
13
+ All interactions happen through MCP tools:
14
+ - `echo_message(message)`: Echo back the provided message
15
+ - `echo_with_length(message)`: Echo back the message with its length
16
+
17
+ Example:
18
+ >>> from openenv.core.env_server.mcp_types import ListToolsAction, CallToolAction
19
+ >>> env = EchoEnvironment()
20
+ >>> env.reset()
21
+ >>>
22
+ >>> # List available tools
23
+ >>> obs = env.step(ListToolsAction())
24
+ >>> print([t.name for t in obs.tools]) # ["echo_message", "echo_with_length"]
25
+ >>>
26
+ >>> # Call a tool
27
+ >>> obs = env.step(CallToolAction(tool_name="echo_message", arguments={"message": "Hi!"}))
28
+ >>> print(obs.result) # "Hi!"
29
+ """
30
+
31
+ from typing import Any, Optional
32
+ from uuid import uuid4
33
+
34
+ # Support both in-repo and standalone imports
35
+ try:
36
+ # In-repo imports (when running from OpenEnv repository)
37
+ from openenv.core.env_server.mcp_environment import MCPEnvironment
38
+ from openenv.core.env_server.types import Action, Observation, State
39
+ except ImportError:
40
+ # Standalone imports (when environment is standalone with openenv from pip)
41
+ from openenv.core.env_server.mcp_environment import MCPEnvironment
42
+ from openenv.core.env_server.types import Action, Observation, State
43
+
44
+ from fastmcp import FastMCP
45
+
46
+
47
+ class EchoEnvironment(MCPEnvironment):
48
+ """
49
+ A pure MCP echo environment that echoes back messages.
50
+
51
+ This environment exposes all functionality through MCP tools:
52
+ - `echo_message`: Echo back the provided message
53
+ - `echo_with_length`: Echo back the message with its length
54
+
55
+ The environment inherits MCP support (ListToolsAction, CallToolAction)
56
+ from the MCPEnvironment base class. No legacy action types are supported.
57
+
58
+ Example using MCPToolClient:
59
+ >>> from openenv.core.mcp_client import MCPToolClient
60
+ >>>
61
+ >>> with MCPToolClient(base_url="http://localhost:8000") as env:
62
+ ... env.reset()
63
+ ... tools = env.list_tools()
64
+ ... print([t.name for t in tools])
65
+ ... result = env.call_tool("echo_message", message="Hello!")
66
+ ... print(result)
67
+ """
68
+
69
+ def __init__(self):
70
+ """Initialize the echo environment with MCP server and tools."""
71
+ # Create MCP server and define tools inline
72
+ mcp = FastMCP("echo_env")
73
+
74
+ @mcp.tool
75
+ def echo_message(message: str) -> str:
76
+ """
77
+ Echo back the provided message.
78
+
79
+ Args:
80
+ message: The message to echo back
81
+
82
+ Returns:
83
+ The same message that was provided
84
+ """
85
+ return message
86
+
87
+ @mcp.tool
88
+ def echo_with_length(message: str) -> dict:
89
+ """
90
+ Echo back the message with its length.
91
+
92
+ Args:
93
+ message: The message to echo back
94
+
95
+ Returns:
96
+ Dictionary with the message and its length
97
+ """
98
+ return {"message": message, "length": len(message)}
99
+
100
+ # Pass the MCP server to the base class
101
+ super().__init__(mcp)
102
+ self._state = State(episode_id=str(uuid4()), step_count=0)
103
+ self._reset_count = 0
104
+
105
+ def reset(
106
+ self,
107
+ seed: Optional[int] = None,
108
+ episode_id: Optional[str] = None,
109
+ **kwargs: Any,
110
+ ) -> Observation:
111
+ """
112
+ Reset the environment.
113
+
114
+ Args:
115
+ seed: Optional random seed (unused in echo env)
116
+ episode_id: Optional episode ID to use
117
+ **kwargs: Additional reset options
118
+
119
+ Returns:
120
+ Observation indicating the environment is ready
121
+ """
122
+ self._state = State(
123
+ episode_id=episode_id or str(uuid4()),
124
+ step_count=0,
125
+ )
126
+ self._reset_count += 1
127
+
128
+ return Observation(
129
+ done=False,
130
+ reward=0.0,
131
+ metadata={"status": "ready", "message": "Echo environment ready!"},
132
+ )
133
+
134
+ def _step_impl(
135
+ self,
136
+ action: Action,
137
+ timeout_s: Optional[float] = None,
138
+ **kwargs: Any,
139
+ ) -> Observation:
140
+ """
141
+ Handle non-MCP actions.
142
+
143
+ This environment only supports MCP actions (ListToolsAction, CallToolAction).
144
+ Any other action type returns an error observation.
145
+
146
+ Args:
147
+ action: The action to execute
148
+ timeout_s: Optional timeout (unused)
149
+ **kwargs: Additional arguments
150
+
151
+ Returns:
152
+ Observation with error for unknown action types
153
+ """
154
+ return Observation(
155
+ done=False,
156
+ reward=0.0,
157
+ metadata={
158
+ "error": f"Unknown action type: {type(action).__name__}. "
159
+ "Use ListToolsAction or CallToolAction for MCP interactions."
160
+ },
161
+ )
162
+
163
+ def step(
164
+ self,
165
+ action: Action,
166
+ timeout_s: Optional[float] = None,
167
+ **kwargs: Any,
168
+ ) -> Observation:
169
+ """
170
+ Execute a step in the environment.
171
+
172
+ Delegates to base class for MCP actions. Increments step count for all actions.
173
+
174
+ Args:
175
+ action: The MCP action to execute (ListToolsAction or CallToolAction)
176
+ timeout_s: Optional timeout for the action
177
+ **kwargs: Additional arguments
178
+
179
+ Returns:
180
+ Observation from the action execution
181
+ """
182
+ # Increment step count for all actions
183
+ self._state.step_count += 1
184
+
185
+ # Let the base class handle MCP actions and non-MCP routing
186
+ return super().step(action, timeout_s=timeout_s, **kwargs)
187
+
188
+ @property
189
+ def state(self) -> State:
190
+ """
191
+ Get the current environment state.
192
+
193
+ Returns:
194
+ Current State with episode_id and step_count
195
+ """
196
+ return self._state
envs/echo_env/build/lib/server/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """Echo environment server components."""
8
+
9
+ from .echo_environment import EchoEnvironment
10
+
11
+ __all__ = ["EchoEnvironment"]
envs/echo_env/build/lib/server/app.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ FastAPI application for the Echo Environment.
9
+
10
+ This module creates an HTTP server that exposes the EchoEnvironment
11
+ over HTTP and WebSocket endpoints, compatible with EnvClient.
12
+
13
+ Usage:
14
+ # Development (with auto-reload):
15
+ uvicorn server.app:app --reload --host 0.0.0.0 --port 8000
16
+
17
+ # Production:
18
+ uvicorn server.app:app --host 0.0.0.0 --port 8000 --workers 4
19
+
20
+ # Or run directly:
21
+ uv run --project . server
22
+ """
23
+
24
+ # Support both in-repo and standalone imports
25
+ try:
26
+ # In-repo imports (when running from OpenEnv repository)
27
+ from openenv.core.env_server.http_server import create_app
28
+ from ..models import EchoAction, EchoObservation
29
+ from .echo_environment import EchoEnvironment
30
+ except ImportError:
31
+ # Standalone imports (when environment is standalone with openenv from pip)
32
+ from openenv.core.env_server.http_server import create_app
33
+ from models import EchoAction, EchoObservation
34
+ from server.echo_environment import EchoEnvironment
35
+
36
+ # Create the app with web interface and README integration
37
+ # Pass the class (factory) instead of an instance for WebSocket session support
38
+ app = create_app(EchoEnvironment, EchoAction, EchoObservation, env_name="echo_env")
39
+
40
+
41
+ def main():
42
+ """
43
+ Entry point for direct execution via uv run or python -m.
44
+
45
+ This function enables running the server without Docker:
46
+ uv run --project . server
47
+ python -m envs.echo_env.server.app
48
+ openenv serve echo_env
49
+
50
+ """
51
+ import uvicorn
52
+
53
+ uvicorn.run(app, host="0.0.0.0", port=8000)
54
+
55
+
56
+ if __name__ == "__main__":
57
+ main()
envs/echo_env/build/lib/server/echo_environment.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ Echo Environment Implementation.
9
+
10
+ A simple test environment that echoes back messages sent to it.
11
+ Perfect for testing HTTP server infrastructure.
12
+ """
13
+
14
+ from uuid import uuid4
15
+
16
+ # Support both in-repo and standalone imports
17
+ try:
18
+ # In-repo imports (when running from OpenEnv repository)
19
+ from openenv.core.env_server.interfaces import Environment
20
+ from openenv.core.env_server.types import State
21
+ from ..models import EchoAction, EchoObservation
22
+ except ImportError:
23
+ # Standalone imports (when environment is standalone with openenv from pip)
24
+ from openenv.core.env_server.interfaces import Environment
25
+ from openenv.core.env_server.types import State
26
+ from models import EchoAction, EchoObservation
27
+
28
+
29
+ class EchoEnvironment(Environment):
30
+ """
31
+ A simple echo environment that echoes back messages.
32
+
33
+ This environment is designed for testing the HTTP server infrastructure.
34
+ It maintains minimal state and simply echoes back whatever message it receives.
35
+
36
+ Example:
37
+ >>> env = EchoEnvironment()
38
+ >>> obs = env.reset()
39
+ >>> print(obs.echoed_message) # "Echo environment ready!"
40
+ >>>
41
+ >>> obs = env.step(EchoAction(message="Hello"))
42
+ >>> print(obs.echoed_message) # "Hello"
43
+ >>> print(obs.message_length) # 5
44
+ """
45
+
46
+ def __init__(self):
47
+ """Initialize the echo environment."""
48
+ self._state = State(episode_id=str(uuid4()), step_count=0)
49
+ self._reset_count = 0
50
+
51
+ def reset(self) -> EchoObservation:
52
+ """
53
+ Reset the environment.
54
+
55
+ Returns:
56
+ EchoObservation with a ready message
57
+ """
58
+ self._state = State(episode_id=str(uuid4()), step_count=0)
59
+ self._reset_count += 1
60
+
61
+ return EchoObservation(
62
+ echoed_message="Echo environment ready!",
63
+ message_length=0,
64
+ done=False,
65
+ reward=0.0,
66
+ )
67
+
68
+ def step(self, action: EchoAction) -> EchoObservation: # type: ignore[override]
69
+ """
70
+ Execute a step in the environment by echoing the message.
71
+
72
+ Args:
73
+ action: EchoAction containing the message to echo
74
+
75
+ Returns:
76
+ EchoObservation with the echoed message and its length
77
+ """
78
+ self._state.step_count += 1
79
+
80
+ message = action.message
81
+ length = len(message)
82
+
83
+ # Simple reward: longer messages get higher rewards
84
+ reward = length * 0.1
85
+
86
+ return EchoObservation(
87
+ echoed_message=message,
88
+ message_length=length,
89
+ done=False,
90
+ reward=reward,
91
+ metadata={"original_message": message, "step": self._state.step_count},
92
+ )
93
+
94
+ @property
95
+ def state(self) -> State:
96
+ """
97
+ Get the current environment state.
98
+
99
+ Returns:
100
+ Current State with episode_id and step_count
101
+ """
102
+ return self._state
envs/echo_env/client.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ Echo Environment Client.
9
+
10
+ This module provides the client for connecting to an Echo Environment server.
11
+ EchoEnv extends MCPToolClient to provide tool-calling style interactions.
12
+
13
+ Example:
14
+ >>> with EchoEnv(base_url="http://localhost:8000") as env:
15
+ ... env.reset()
16
+ ...
17
+ ... # Discover tools
18
+ ... tools = env.list_tools()
19
+ ... print([t.name for t in tools]) # ['echo_message', 'echo_with_length']
20
+ ...
21
+ ... # Call tools
22
+ ... result = env.call_tool("echo_message", message="Hello!")
23
+ ... print(result) # "Hello!"
24
+ ...
25
+ ... result = env.call_tool("echo_with_length", message="Test")
26
+ ... print(result) # {"message": "Test", "length": 4}
27
+ """
28
+
29
+ from openenv.core.mcp_client import MCPToolClient
30
+
31
+
32
+ class EchoEnv(MCPToolClient):
33
+ """
34
+ Client for the Echo Environment.
35
+
36
+ This client provides a simple interface for interacting with the Echo
37
+ Environment via MCP tools. It inherits all functionality from MCPToolClient:
38
+ - `list_tools()`: Discover available tools
39
+ - `call_tool(name, **kwargs)`: Call a tool by name
40
+ - `reset(**kwargs)`: Reset the environment
41
+ - `step(action)`: Execute an action (for advanced use)
42
+
43
+ Example:
44
+ >>> # Connect to a running server
45
+ >>> with EchoEnv(base_url="http://localhost:8000") as env:
46
+ ... env.reset()
47
+ ...
48
+ ... # List available tools
49
+ ... tools = env.list_tools()
50
+ ... for tool in tools:
51
+ ... print(f"{tool.name}: {tool.description}")
52
+ ...
53
+ ... # Echo a message
54
+ ... result = env.call_tool("echo_message", message="Hello!")
55
+ ... print(result) # "Hello!"
56
+ ...
57
+ ... # Echo with length
58
+ ... result = env.call_tool("echo_with_length", message="Test")
59
+ ... print(result) # {"message": "Test", "length": 4}
60
+
61
+ Example with Docker:
62
+ >>> # Automatically start container and connect
63
+ >>> env = EchoEnv.from_docker_image("echo-env:latest")
64
+ >>> try:
65
+ ... env.reset()
66
+ ... tools = env.list_tools()
67
+ ... result = env.call_tool("echo_message", message="Hello!")
68
+ ... finally:
69
+ ... env.close()
70
+
71
+ Example with HuggingFace Space:
72
+ >>> # Run from HuggingFace Space
73
+ >>> env = EchoEnv.from_env("openenv/echo-env")
74
+ >>> try:
75
+ ... env.reset()
76
+ ... result = env.call_tool("echo_message", message="Hello!")
77
+ ... finally:
78
+ ... env.close()
79
+ """
80
+
81
+ pass # MCPToolClient provides all needed functionality
envs/echo_env/openenv.yaml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ spec_version: 1
2
+ name: echo_env
3
+ type: space
4
+ runtime: fastapi
5
+ app: server.app:app
6
+ port: 8000
envs/echo_env/openenv_echo_env.egg-info/PKG-INFO ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: openenv-echo-env
3
+ Version: 0.1.0
4
+ Summary: Echo Environment for OpenEnv - simple test environment that echoes back messages
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: openenv-core[core]>=0.2.1
7
+ Requires-Dist: fastapi>=0.115.0
8
+ Requires-Dist: pydantic>=2.0.0
9
+ Requires-Dist: uvicorn>=0.24.0
10
+ Requires-Dist: requests>=2.31.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
13
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
envs/echo_env/openenv_echo_env.egg-info/SOURCES.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ README.md
2
+ __init__.py
3
+ client.py
4
+ pyproject.toml
5
+ ./__init__.py
6
+ ./client.py
7
+ openenv_echo_env.egg-info/PKG-INFO
8
+ openenv_echo_env.egg-info/SOURCES.txt
9
+ openenv_echo_env.egg-info/dependency_links.txt
10
+ openenv_echo_env.egg-info/entry_points.txt
11
+ openenv_echo_env.egg-info/requires.txt
12
+ openenv_echo_env.egg-info/top_level.txt
13
+ server/__init__.py
14
+ server/app.py
15
+ server/echo_environment.py
envs/echo_env/openenv_echo_env.egg-info/dependency_links.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
envs/echo_env/openenv_echo_env.egg-info/entry_points.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [console_scripts]
2
+ server = echo_env.server.app:main
envs/echo_env/openenv_echo_env.egg-info/requires.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ openenv-core[core]>=0.2.1
2
+ fastapi>=0.115.0
3
+ pydantic>=2.0.0
4
+ uvicorn>=0.24.0
5
+ requests>=2.31.0
6
+
7
+ [dev]
8
+ pytest>=8.0.0
9
+ pytest-cov>=4.0.0
envs/echo_env/openenv_echo_env.egg-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ echo_env
envs/echo_env/pyproject.toml ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ [build-system]
8
+ requires = ["setuptools>=45", "wheel"]
9
+ build-backend = "setuptools.build_meta"
10
+
11
+ [project]
12
+ name = "openenv-echo-env"
13
+ version = "0.1.0"
14
+ description = "Echo Environment for OpenEnv - simple test environment that echoes back messages"
15
+ requires-python = ">=3.10"
16
+ dependencies = [
17
+ # Core OpenEnv dependencies (required for server functionality)
18
+ "openenv-core[core] @ git+https://github.com/meta-pytorch/OpenEnv.git@v0.2.3",
19
+ "fastapi>=0.115.0",
20
+ "pydantic>=2.0.0",
21
+ "uvicorn>=0.24.0",
22
+ "requests>=2.31.0",
23
+ # No additional environment-specific dependencies needed for echo_env
24
+ ]
25
+
26
+ [project.optional-dependencies]
27
+ dev = [
28
+ "pytest>=8.0.0",
29
+ "pytest-cov>=4.0.0",
30
+ ]
31
+
32
+ [project.scripts]
33
+ # Server entry point - enables running via: uv run --project . server
34
+ # or: python -m echo_env.server.app
35
+ server = "echo_env.server.app:main"
36
+
37
+ [tool.setuptools]
38
+ include-package-data = true
39
+ packages = ["echo_env", "echo_env.server"]
40
+ package-dir = { "echo_env" = ".", "echo_env.server" = "server" }
envs/echo_env/server/Dockerfile ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ # Multi-stage build using openenv-base
8
+ # This Dockerfile is flexible and works for both:
9
+ # - In-repo environments (with local src/core)
10
+ # - Standalone environments (with openenv-core from pip)
11
+ # The build script (openenv build) handles context detection and sets appropriate build args.
12
+
13
+ ARG BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest
14
+ FROM ${BASE_IMAGE} AS builder
15
+
16
+ WORKDIR /app
17
+
18
+ # Build argument to control whether we're building standalone or in-repo
19
+ ARG BUILD_MODE=in-repo
20
+
21
+ # Copy environment code (always at root of build context)
22
+ COPY . /app/env
23
+
24
+ # For in-repo builds, openenv-core is already in the pyproject.toml dependencies
25
+ # For standalone builds, openenv-core will be installed from pip via pyproject.toml
26
+ WORKDIR /app/env
27
+
28
+ # Ensure uv is available (for local builds where base image lacks it)
29
+ RUN if ! command -v uv >/dev/null 2>&1; then \
30
+ curl -LsSf https://astral.sh/uv/install.sh | sh && \
31
+ mv /root/.local/bin/uv /usr/local/bin/uv && \
32
+ mv /root/.local/bin/uvx /usr/local/bin/uvx; \
33
+ fi
34
+
35
+ # Install git for building from git repos (build-time only)
36
+ RUN apt-get update && apt-get install -y --no-install-recommends \
37
+ git \
38
+ && rm -rf /var/lib/apt/lists/*
39
+
40
+ # Install dependencies using uv sync
41
+ # First pass: install dependencies without the project (for better caching)
42
+ # Second pass: install the project itself
43
+ RUN --mount=type=cache,target=/root/.cache/uv \
44
+ if [ -f uv.lock ]; then \
45
+ uv sync --frozen --no-install-project --no-editable; \
46
+ else \
47
+ uv sync --no-install-project --no-editable; \
48
+ fi
49
+
50
+ RUN --mount=type=cache,target=/root/.cache/uv \
51
+ if [ -f uv.lock ]; then \
52
+ uv sync --frozen --no-editable; \
53
+ else \
54
+ uv sync --no-editable; \
55
+ fi
56
+
57
+ # Final runtime stage
58
+ FROM ${BASE_IMAGE}
59
+
60
+ WORKDIR /app
61
+
62
+ # Copy the virtual environment from builder
63
+ COPY --from=builder /app/env/.venv /app/.venv
64
+
65
+ # Copy the environment code
66
+ COPY --from=builder /app/env /app/env
67
+
68
+ # Set PATH to use the virtual environment
69
+ ENV PATH="/app/.venv/bin:$PATH"
70
+
71
+ # Set PYTHONPATH so imports work correctly
72
+ ENV PYTHONPATH="/app/env:$PYTHONPATH"
73
+ ENV ENABLE_WEB_INTERFACE=true
74
+
75
+ # Health check using Python (more portable than curl/wget)
76
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
77
+ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
78
+
79
+ # Run the FastAPI server
80
+ # The module path is constructed to work with the /app/env structure
81
+ CMD ["sh", "-c", "cd /app/env && uvicorn server.app:app --host 0.0.0.0 --port 8000"]
envs/echo_env/server/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """Echo environment server components."""
8
+
9
+ from .echo_environment import EchoEnvironment
10
+
11
+ __all__ = ["EchoEnvironment"]
envs/echo_env/server/app.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ FastAPI application for the Echo Environment.
9
+
10
+ This module creates an HTTP server that exposes the EchoEnvironment
11
+ over HTTP and WebSocket endpoints, compatible with MCPToolClient.
12
+
13
+ Usage:
14
+ # Development (with auto-reload):
15
+ uvicorn server.app:app --reload --host 0.0.0.0 --port 8000
16
+
17
+ # Production:
18
+ uvicorn server.app:app --host 0.0.0.0 --port 8000 --workers 4
19
+
20
+ # Or run directly:
21
+ uv run --project . server
22
+ """
23
+
24
+ # Support both in-repo and standalone imports
25
+ try:
26
+ # In-repo imports (when running from OpenEnv repository)
27
+ from openenv.core.env_server.http_server import create_app
28
+ from openenv.core.env_server.mcp_types import CallToolAction, CallToolObservation
29
+
30
+ from .echo_environment import EchoEnvironment
31
+ except ImportError:
32
+ # Standalone imports (when environment is standalone with openenv from pip)
33
+ from openenv.core.env_server.http_server import create_app
34
+ from openenv.core.env_server.mcp_types import CallToolAction, CallToolObservation
35
+ from server.echo_environment import EchoEnvironment
36
+
37
+ # Create the app with web interface and README integration
38
+ # Pass the class (factory) instead of an instance for WebSocket session support
39
+ # Use MCP types for action/observation since this is a pure MCP environment
40
+ app = create_app(
41
+ EchoEnvironment, CallToolAction, CallToolObservation, env_name="echo_env"
42
+ )
43
+
44
+
45
+ def main():
46
+ """
47
+ Entry point for direct execution via uv run or python -m.
48
+
49
+ This function enables running the server without Docker:
50
+ uv run --project . server
51
+ python -m envs.echo_env.server.app
52
+ openenv serve echo_env
53
+
54
+ """
55
+ import uvicorn
56
+
57
+ uvicorn.run(app, host="0.0.0.0", port=8000)
58
+
59
+
60
+ if __name__ == "__main__":
61
+ main()
envs/echo_env/server/echo_environment.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ Echo Environment Implementation.
9
+
10
+ A pure MCP environment that echoes back messages sent to it.
11
+ This demonstrates how to build an MCPEnvironment with inline FastMCP tools.
12
+
13
+ All interactions happen through MCP tools:
14
+ - `echo_message(message)`: Echo back the provided message
15
+ - `echo_with_length(message)`: Echo back the message with its length
16
+
17
+ Example:
18
+ >>> from openenv.core.env_server.mcp_types import ListToolsAction, CallToolAction
19
+ >>> env = EchoEnvironment()
20
+ >>> env.reset()
21
+ >>>
22
+ >>> # List available tools
23
+ >>> obs = env.step(ListToolsAction())
24
+ >>> print([t.name for t in obs.tools]) # ["echo_message", "echo_with_length"]
25
+ >>>
26
+ >>> # Call a tool
27
+ >>> obs = env.step(CallToolAction(tool_name="echo_message", arguments={"message": "Hi!"}))
28
+ >>> print(obs.result) # "Hi!"
29
+ """
30
+
31
+ from typing import Any, Optional
32
+ from uuid import uuid4
33
+
34
+ # Support both in-repo and standalone imports
35
+ try:
36
+ # In-repo imports (when running from OpenEnv repository)
37
+ from openenv.core.env_server.mcp_environment import MCPEnvironment
38
+ from openenv.core.env_server.types import Action, Observation, State
39
+ except ImportError:
40
+ # Standalone imports (when environment is standalone with openenv from pip)
41
+ from openenv.core.env_server.mcp_environment import MCPEnvironment
42
+ from openenv.core.env_server.types import Action, Observation, State
43
+
44
+ from fastmcp import FastMCP
45
+
46
+
47
+ class EchoEnvironment(MCPEnvironment):
48
+ """
49
+ A pure MCP echo environment that echoes back messages.
50
+
51
+ This environment exposes all functionality through MCP tools:
52
+ - `echo_message`: Echo back the provided message
53
+ - `echo_with_length`: Echo back the message with its length
54
+
55
+ The environment inherits MCP support (ListToolsAction, CallToolAction)
56
+ from the MCPEnvironment base class. No legacy action types are supported.
57
+
58
+ Example using MCPToolClient:
59
+ >>> from openenv.core.mcp_client import MCPToolClient
60
+ >>>
61
+ >>> with MCPToolClient(base_url="http://localhost:8000") as env:
62
+ ... env.reset()
63
+ ... tools = env.list_tools()
64
+ ... print([t.name for t in tools])
65
+ ... result = env.call_tool("echo_message", message="Hello!")
66
+ ... print(result)
67
+ """
68
+
69
+ def __init__(self):
70
+ """Initialize the echo environment with MCP server and tools."""
71
+ # Create MCP server and define tools inline
72
+ mcp = FastMCP("echo_env")
73
+
74
+ @mcp.tool
75
+ def echo_message(message: str) -> str:
76
+ """
77
+ Echo back the provided message.
78
+
79
+ Args:
80
+ message: The message to echo back
81
+
82
+ Returns:
83
+ The same message that was provided
84
+ """
85
+ return message
86
+
87
+ @mcp.tool
88
+ def echo_with_length(message: str) -> dict:
89
+ """
90
+ Echo back the message with its length.
91
+
92
+ Args:
93
+ message: The message to echo back
94
+
95
+ Returns:
96
+ Dictionary with the message and its length
97
+ """
98
+ return {"message": message, "length": len(message)}
99
+
100
+ # Pass the MCP server to the base class
101
+ super().__init__(mcp)
102
+ self._state = State(episode_id=str(uuid4()), step_count=0)
103
+ self._reset_count = 0
104
+
105
+ def reset(
106
+ self,
107
+ seed: Optional[int] = None,
108
+ episode_id: Optional[str] = None,
109
+ **kwargs: Any,
110
+ ) -> Observation:
111
+ """
112
+ Reset the environment.
113
+
114
+ Args:
115
+ seed: Optional random seed (unused in echo env)
116
+ episode_id: Optional episode ID to use
117
+ **kwargs: Additional reset options
118
+
119
+ Returns:
120
+ Observation indicating the environment is ready
121
+ """
122
+ self._state = State(
123
+ episode_id=episode_id or str(uuid4()),
124
+ step_count=0,
125
+ )
126
+ self._reset_count += 1
127
+
128
+ return Observation(
129
+ done=False,
130
+ reward=0.0,
131
+ metadata={"status": "ready", "message": "Echo environment ready!"},
132
+ )
133
+
134
+ def _step_impl(
135
+ self,
136
+ action: Action,
137
+ timeout_s: Optional[float] = None,
138
+ **kwargs: Any,
139
+ ) -> Observation:
140
+ """
141
+ Handle non-MCP actions.
142
+
143
+ This environment only supports MCP actions (ListToolsAction, CallToolAction).
144
+ Any other action type returns an error observation.
145
+
146
+ Args:
147
+ action: The action to execute
148
+ timeout_s: Optional timeout (unused)
149
+ **kwargs: Additional arguments
150
+
151
+ Returns:
152
+ Observation with error for unknown action types
153
+ """
154
+ return Observation(
155
+ done=False,
156
+ reward=0.0,
157
+ metadata={
158
+ "error": f"Unknown action type: {type(action).__name__}. "
159
+ "Use ListToolsAction or CallToolAction for MCP interactions."
160
+ },
161
+ )
162
+
163
+ def step(
164
+ self,
165
+ action: Action,
166
+ timeout_s: Optional[float] = None,
167
+ **kwargs: Any,
168
+ ) -> Observation:
169
+ """
170
+ Execute a step in the environment.
171
+
172
+ Delegates to base class for MCP actions. Increments step count for all actions.
173
+
174
+ Args:
175
+ action: The MCP action to execute (ListToolsAction or CallToolAction)
176
+ timeout_s: Optional timeout for the action
177
+ **kwargs: Additional arguments
178
+
179
+ Returns:
180
+ Observation from the action execution
181
+ """
182
+ # Increment step count for all actions
183
+ self._state.step_count += 1
184
+
185
+ # Let the base class handle MCP actions and non-MCP routing
186
+ return super().step(action, timeout_s=timeout_s, **kwargs)
187
+
188
+ async def step_async(
189
+ self,
190
+ action: Action,
191
+ timeout_s: Optional[float] = None,
192
+ **kwargs: Any,
193
+ ) -> Observation:
194
+ """
195
+ Async step used by the WebSocket handler.
196
+
197
+ Increments step count then delegates to MCPEnvironment.step_async,
198
+ which routes MCP actions without going through run_async_safely.
199
+ """
200
+ self._state.step_count += 1
201
+ return await super().step_async(action, timeout_s=timeout_s, **kwargs)
202
+
203
+ @property
204
+ def state(self) -> State:
205
+ """
206
+ Get the current environment state.
207
+
208
+ Returns:
209
+ Current State with episode_id and step_count
210
+ """
211
+ return self._state
models.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ Data models for the Echo Environment.
9
+
10
+ The Echo environment is a simple test environment that echoes back messages.
11
+ """
12
+
13
+ from pydantic import Field
14
+
15
+ # Support both in-repo and standalone imports
16
+ try:
17
+ # In-repo imports (when running from OpenEnv repository)
18
+ from openenv.core.env_server.types import Action, Observation
19
+ except ImportError:
20
+ # Standalone imports (when environment is standalone with openenv from pip)
21
+ from openenv.core.env_server.types import Action, Observation
22
+
23
+
24
+ class EchoAction(Action):
25
+ """Action for the Echo environment - just a message to echo."""
26
+
27
+ message: str = Field(..., min_length=1, description="Message to echo back")
28
+
29
+
30
+ class EchoObservation(Observation):
31
+ """Observation from the Echo environment - the echoed message."""
32
+
33
+ echoed_message: str = Field(..., description="The echoed message from the environment")
34
+ message_length: int = Field(default=0, ge=0, description="Length of the echoed message")
openenv.yaml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ spec_version: 1
2
+ name: echo_env
3
+ type: space
4
+ runtime: fastapi
5
+ app: server.app:app
6
+ port: 8000
openenv_echo_env.egg-info/PKG-INFO ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: openenv-echo-env
3
+ Version: 0.1.0
4
+ Summary: Echo Environment for OpenEnv - simple test environment that echoes back messages
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: openenv-core[core]>=0.2.1
7
+ Requires-Dist: fastapi>=0.115.0
8
+ Requires-Dist: pydantic>=2.0.0
9
+ Requires-Dist: uvicorn>=0.24.0
10
+ Requires-Dist: requests>=2.31.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
13
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
openenv_echo_env.egg-info/SOURCES.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ README.md
2
+ __init__.py
3
+ client.py
4
+ pyproject.toml
5
+ ./__init__.py
6
+ ./client.py
7
+ openenv_echo_env.egg-info/PKG-INFO
8
+ openenv_echo_env.egg-info/SOURCES.txt
9
+ openenv_echo_env.egg-info/dependency_links.txt
10
+ openenv_echo_env.egg-info/entry_points.txt
11
+ openenv_echo_env.egg-info/requires.txt
12
+ openenv_echo_env.egg-info/top_level.txt
13
+ server/__init__.py
14
+ server/app.py
15
+ server/echo_environment.py
openenv_echo_env.egg-info/dependency_links.txt ADDED
@@ -0,0 +1 @@
 
 
1
+
openenv_echo_env.egg-info/entry_points.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [console_scripts]
2
+ server = echo_env.server.app:main
openenv_echo_env.egg-info/requires.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ openenv-core[core]>=0.2.1
2
+ fastapi>=0.115.0
3
+ pydantic>=2.0.0
4
+ uvicorn>=0.24.0
5
+ requests>=2.31.0
6
+
7
+ [dev]
8
+ pytest>=8.0.0
9
+ pytest-cov>=4.0.0
openenv_echo_env.egg-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ echo_env