Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 34 additions & 23 deletions examples/local_echo_env.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
#!/usr/bin/env python3
"""
Simple test showing how users will use EchoEnv.from_docker_image().
"""Minimal Echo environment client example.

Start the Echo server first:

PYTHONPATH=src:envs uv run python -m echo_env.server.app

This is the simplest possible usage
Then run this example:

PYTHONPATH=src:envs uv run python examples/local_echo_env.py
"""

import sys
from pathlib import Path
import os

from echo_env import EchoAction, EchoEnv
from echo_env import EchoEnv


def main():
"""Test EchoEnv.from_docker_image()."""
"""Exercise the MCP Echo tools through the Python client."""
base_url = os.getenv("ECHO_BASE_URL", "http://localhost:8000")

print("=" * 60)
print("EchoEnv.from_docker_image() Test")
print("EchoEnv local client example")
print("=" * 60)
print()

try:
# This is what users will do - just one line!
print("Creating client from Docker image...")
print(" EchoEnv.from_docker_image('echo-env:latest')")
print("Connecting to Echo server...")
print(f" EchoEnv(base_url={base_url!r})")
print()

client = EchoEnv(base_url="http://localhost:8000")
client = EchoEnv(base_url=base_url).sync()

print("Client created and container started!\n")
print("Client created.\n")

# Now use it like any other client
print("Testing the environment:")
Expand All @@ -35,12 +40,18 @@ def main():
# Reset
print("\n1. Reset:")
result = client.reset()
print(f" Message: {result.observation.echoed_message}")
print(f" Status: {result.observation.metadata.get('status')}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified correct against a running server: EchoEnvironment.reset() returns Observation(reward=0.0, metadata={'status': 'ready', 'message': 'Echo environment ready!'}), so both metadata.get(...) lookups resolve as intended (printed Status: ready / Message: Echo environment ready!).

print(f" Message: {result.observation.metadata.get('message')}")
print(f" Reward: {result.reward}")
print(f" Done: {result.done}")

print("\n2. List tools:")
tools = client.list_tools()
for tool in tools:
print(f" - {tool.name}: {tool.description}")

# Send some messages
print("\n2. Send messages:")
print("\n3. Send messages:")

messages = [
"Hello, World!",
Expand All @@ -49,29 +60,29 @@ def main():
]

for i, msg in enumerate(messages, 1):
result = client.step(EchoAction(message=msg))
echoed = client.call_tool("echo_message", message=msg)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good use of the call_tool convenience method — it unwraps the FastMCP result envelope to .data, so echoed is the plain string and with_length (line 67) is the plain {'message': ..., 'length': N} dict (verified: lengths 13/24/16). This avoids the raw observation.result envelope gotcha, and dropping the per-call reward print is correct since MCP tool steps return reward=None (only reset() is 0.0).

with_length = client.call_tool("echo_with_length", message=msg)
print(f" {i}. '{msg}'")
print(f" → Echoed: '{result.observation.echoed_message}'")
print(f" → Length: {result.observation.message_length}")
print(f" → Reward: {result.reward}")
print(f" Echoed: '{echoed}'")
print(f" Length: {with_length['length']}")

print("\n" + "-" * 60)
print("\n✓ All operations successful!")
print("\nAll operations successful.")
print()

print("Cleaning up...")
client.close()
print("✓ Container stopped and removed")
print("Client closed.")
print()

print("=" * 60)
print("Test completed successfully! 🎉")
print("Test completed successfully.")
print("=" * 60)

return True

except Exception as e:
print(f"\n❌ Test failed: {e}")
print(f"\nTest failed: {e}")
import traceback

traceback.print_exc()
Expand Down
Loading