Rust server scaffolding for OpenEnv-compatible environments.
This crate is intentionally independent of any simulation engine. An environment
implements Environment; the crate supplies the OpenEnv-compatible HTTP,
WebSocket, schema, OpenAPI, session lifecycle, and MCP protocol surfaces.
The server mirrors the Python OpenEnv server shape:
GET /healthGET /metadataGET /schemaGET /openapi.jsonWS /wsfor persistentreset,step,state,close, and embeddedmcpmessagesPOST /mcpfor Python OpenEnv-compatible JSON-RPC MCP requestsWS /mcpfor persistent MCP JSON-RPC sessions- production mode omits direct HTTP
/reset,/step, and/state - simulation mode exposes stateless HTTP
/reset,/step, and/state openenv/session/createandopenenv/session/closefor HTTP MCP sessionstools/listandtools/call, using RMCPCallToolResultwire shape
MCP tool names reset, step, state, and close are reserved, matching
Python OpenEnv's separation between orchestration controls and agent tools.
use async_trait::async_trait;
use openenv::{Environment, OpenEnvServer, StepResult};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Default, Serialize, Deserialize, JsonSchema)]
struct Reset;
#[derive(Serialize, Deserialize, JsonSchema)]
struct Action {
delta: i64,
}
#[derive(Serialize, JsonSchema)]
struct Observation {
count: i64,
}
#[derive(Serialize, JsonSchema)]
struct State {
count: i64,
}
struct Counter {
count: i64,
}
#[async_trait]
impl Environment for Counter {
type Reset = Reset;
type Action = Action;
type Observation = Observation;
type State = State;
async fn reset(&mut self, _: Reset) -> openenv::Result<StepResult<Observation>> {
self.count = 0;
Ok(StepResult::new(Observation { count: self.count }))
}
async fn step(&mut self, action: Action) -> openenv::Result<StepResult<Observation>> {
self.count += action.delta;
Ok(StepResult::new(Observation { count: self.count }))
}
async fn state(&mut self) -> openenv::Result<State> {
Ok(State { count: self.count })
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = OpenEnvServer::new(|| Counter { count: 0 })
.production()
.router();
let listener = tokio::net::TcpListener::bind("127.0.0.1:8000").await?;
axum::serve(listener, app).await?;
Ok(())
}