Buckets:

HuggingFaceDocBuilder's picture
download
raw
47 kB
import{s as as,n as is,o as rs}from"../chunks/scheduler.2b22cead.js";import{S as cs,i as ps,e as a,s as n,c,h as Ms,a as i,d as s,b as o,f as os,g as p,j as r,k as be,l as ds,m as l,n as M,t as d,o as y,p as T}from"../chunks/index.1a0e8013.js";import{C as ys,H as w,E as Ts}from"../chunks/MermaidChart.svelte_svelte_type_style_lang.895e979d.js";import{C as m}from"../chunks/CodeBlock.867584a7.js";function ws(At){let h,Ie,Ue,fe,j,Ce,b,ve,I,Bt='<a href="https://colab.research.google.com/github/huggingface/OpenEnv/blob/main/examples/mcp_environment.ipynb" rel="nofollow"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/></a>',ge,f,Et='Most agentic work ends up needing the same thing: a way for the model to <strong>call tools</strong> and receive structured feedback, whether that is during RL training or offline evaluation. OpenEnv standardises that surface with <strong><a href="https://modelcontextprotocol.io" rel="nofollow">MCP</a></strong> (Model Context Protocol), so the same tool interface works during training, eval, inference, and external serving. This tutorial covers the four paths you will walk in practice — wiring an MCP-backed environment into a training loop, using the same env for offline eval, inspecting the API underneath both of those, and building your own MCP environment when no existing one fits.',Ae,C,Be,v,Zt="If your tools are just local Python functions, you do not need MCP — pass them to your trainer directly (TRL, torchforge, whatever) and you are done. MCP earns its complexity when the tool surface has to exist as a <strong>process boundary</strong>, not a function call:",Ee,g,$t="<li><strong>The env runs elsewhere</strong> — in a Docker container, a Hugging Face Space, a remote server. MCP is the transport that crosses that boundary.</li> <li><strong>You want to reuse someone else’s env</strong> — the OpenEnv catalog, third-party envs, and community hubs all expose their tools over MCP, so the same env works in your training run without rewriting its interface.</li> <li><strong>You want the env to be callable by other agents</strong> — Claude Desktop, Cursor, inference servers, and any MCP-compatible client can plug into an MCP server. A private Python function doesn’t get that for free.</li> <li><strong>You need tool discovery and schemas</strong> — <code>list_tools()</code> + auto-generated JSON schemas are part of the protocol; models see them the same way they see any MCP server’s tools.</li>",Ze,A,_t="In short: MCP is the answer when your env is more than a helper function in your training script — when the same tools have to be usable from training, inference, and external clients without maintaining three interfaces.",$e,B,_e,E,kt="Inside OpenEnv, MCP plays a specific role in a two-surface split:",ke,Z,Gt="<li><strong>Training / orchestration infrastructure</strong> uses the Gym-style control plane — <code>reset()</code>, <code>step()</code>, <code>state()</code> — over WebSocket (<code>/ws</code>). This is what the trainer needs to roll out episodes, compute rewards, and enforce termination.</li> <li><strong>Agents</strong> use MCP tools over the <code>/mcp</code> JSON-RPC endpoint. Tools are what the model calls to act on the world.</li>",Ge,J,Nt='<p>In simulation mode, MCP tool calls flow <strong>through</strong> <code>step()</code>. The trainer stays in control of timing, rewards, and termination; the MCP action types are just a standardised action schema. The <a href="../guides/mcp-environment-lifecycle">MCP environment lifecycle guide</a> covers the split in depth.</p>',Ne,u,Rt='<p><strong>MCP adoption in OpenEnv is still in flight.</strong> <a href="https://github.com/huggingface/OpenEnv/blob/main/rfcs/003-mcp-support.md" rel="nofollow">RFC 003</a> proposes MCP as the standard interface for <em>all</em> agent-facing actions, but it is still <strong>In Review</strong>. Today only a handful of envs are MCP-backed: <code>echo_env</code> and <code>finqa_env</code> inherit from the canonical <code>openenv.core.env_server.mcp_environment.MCPEnvironment</code>; <code>calendar_env</code> uses a local wrapper with the same shape. The majority (<code>textarena_env</code> / Wordle, <code>openspiel_env</code>, <code>chess_env</code>, <code>browsergym_env</code>, and most others) still use custom action types that you pass through <code>env.step(CustomAction(...))</code> without MCP plumbing. Before using the patterns in this tutorial against a specific env, check whether it inherits from an <code>MCPEnvironment</code> base; if not, the env’s own action schema applies instead.</p>',Re,$,We,_,Wt="An MCP-backed env is consumed like any other OpenEnv env from the trainer’s side. At the atomic level, each agent action is:",Qe,k,Se,G,Qt="That is the only MCP-specific piece. Everything around it — how the trainer generates actions, how tool schemas are surfaced to the model, how rewards are collected — belongs to your training framework, not to MCP.",xe,N,Ve,R,St='If you drive the rollout yourself (a custom loop, <a href="https://github.com/pytorch-labs/torchforge" rel="nofollow">torchforge</a>, an external agent server), you own the full generation path and call <code>env.step()</code> directly:',Fe,W,ze,Q,xt="Whatever policy / generation code you use, <code>env.step(CallToolAction(...))</code> is the only line that talks to the MCP env.",Pe,S,Ye,x,Vt='<a href="https://huggingface.co/docs/trl" rel="nofollow">TRL</a>’s <code>GRPOTrainer</code> takes an <code>environment_factory</code> class whose public methods auto-register as discoverable tools — the trainer then handles the multi-turn generation loop for you. The <a href="wordle-grpo">Wordle GRPO tutorial</a> shows the full recipe (wrapper class, reward function, <code>GRPOTrainer</code> construction) with a non-MCP env. For an MCP-backed env, only the tool method bodies change; they call through to <code>env.step(CallToolAction(...))</code>:',Le,V,He,F,Ft="<code>environment_factory</code> is a TRL API, not an MCP API. It works equally well with non-MCP envs (Wordle uses it with <code>TextArenaAction</code>), and MCP envs work equally well without it (the rollout-loop path above). They compose, but they are orthogonal.",Xe,z,zt="The rest of this tutorial is for the other paths: the API <strong>underneath</strong> <code>env.step(CallToolAction(...))</code> (useful when you need the full observation or want to debug), <strong>using the same env for offline eval</strong>, and <strong>building your own MCP environment</strong> from scratch.",qe,P,Oe,Y,Pt="The two MCP action types are <code>ListToolsAction</code> (discover what’s available) and <code>CallToolAction</code> (invoke one). They behave like any other Gym action — pass them to <code>step()</code> and inspect the returned observation.",De,L,Ke,H,et,X,Yt="Each <code>Tool</code> carries a <code>name</code>, a <code>description</code>, and an <code>input_schema</code> (JSON Schema) describing the accepted arguments. The schema is what lets a language-model agent know which parameters to fill in when it emits a tool call.",tt,q,st,O,lt,D,Lt="<code>CallToolObservation.result</code> is typed as <code>Any</code> in OpenEnv. At runtime, FastMCP commonly returns a <code>fastmcp.client.client.CallToolResult</code> object with <code>.data</code>, <code>.structured_content</code>, and <code>.content</code> attributes, but JSON round-trips or custom environments can surface a plain dict or value instead. Treat <code>.data</code> as a convenience when it exists, not as an OpenEnv-defined wrapper type. <code>obs.error</code> carries <strong>every</strong> failure mode — transport errors, unknown tool names, malformed arguments, <strong>and</strong> exceptions raised from inside the tool function itself (as <code>ToolErrorType.EXECUTION_ERROR</code>). On an error, <code>obs.result</code> is <code>None</code>. Always branch on <code>obs.error is None</code> before reading a runtime result.",nt,K,ot,ee,at,te,Ht="The <code>ToolError.error_type</code> enum (<code>TOOL_NOT_FOUND</code>, <code>INVALID_ARGS</code>, <code>EXECUTION_ERROR</code>, <code>TRANSPORT_ERROR</code>, <code>TIMEOUT</code>) lets training code distinguish between bugs in the agent, bugs in the environment, and transient infrastructure issues — which often warrant different reward signals.",it,se,rt,le,Xt='Environment clients that inherit from <code>MCPToolClient</code> (such as <code>EchoEnv</code> and <code>FinQAEnv</code>) expose a shorter <strong>async</strong> <code>await env.call_tool(&quot;name&quot;, arg=value)</code> helper for a running environment server. It returns the tool’s raw return value directly instead of a <code>CallToolObservation</code> — and it <strong>raises <code>RuntimeError</code></strong> on any tool error (transport failure, unknown tool, invalid arguments, or a tool exception), so you cannot branch on <code>error_type</code> without a <code>try/except</code>. Use <code>step(CallToolAction(...))</code> when you need the whole observation (reward, done, metadata, or graceful error classification); reach for <code>call_tool()</code> in async production scripts where the raw result is all you care about and a failure is allowed to propagate. The <a href="../guides/mcp-environment-lifecycle">lifecycle guide</a> covers the exact trade-offs.',ct,U,qt="<p><code>MCPToolClient</code> and its base <code>MCPClientBase</code> only support <code>mode=&quot;production&quot;</code>; construction raises <code>ValueError</code> for other modes. For direct in-process training or eval snippets like the ones above, call <code>env.step(CallToolAction(...))</code> on the environment class itself.</p>",pt,ne,Mt,oe,Ot="The same mechanics work outside a training loop. For an offline eval — benchmarking a model’s tool use on a static dataset, regression-testing a deployed agent, or scoring a policy — drop the trainer and drive the step loop yourself:",dt,ae,yt,ie,Dt='Pair the loop with a scoring function of your choice — the <a href="../guides/rewards">Reward Design</a> guide covers common patterns (test-pass rate, LLM-as-judge quality, compliance gates) — and aggregate across the dataset. The eval harness integration in <code>src/openenv/core/evals/</code> is still evolving; until that bridge lands, this plain-Python loop is the canonical pattern.',Tt,re,wt,ce,Kt='Reach for this path when no existing environment covers the tools your agent needs — e.g. a new coding sandbox, a game, a proprietary API wrapper. The provider side is small: subclass <code>MCPEnvironment</code>, create a <code>FastMCP</code> server, register tools with the <code>@mcp.tool</code> decorator, and pass the server to <code>super().__init__</code>. Here is the echo environment, trimmed from <a href="https://github.com/huggingface/OpenEnv/blob/main/envs/echo_env/server/echo_environment.py" rel="nofollow"><code>envs/echo_env/server/echo_environment.py</code></a> down to the parts this tutorial covers:',ht,pe,mt,Me,es="A few things worth calling out:",Jt,de,ts="<li><strong>Docstring → schema.</strong> <code>FastMCP</code> inspects each tool’s signature and Google-style docstring to build the <code>input_schema</code> automatically. The <code>Args:</code> block becomes parameter descriptions, and type hints become JSON types. No hand-written schemas.</li> <li><strong>Reserved names.</strong> <code>reset</code>, <code>step</code>, <code>state</code>, and <code>close</code> are reserved and cannot be tool names — they belong to the infrastructure boundary. Trying to register a tool with one of those names raises at construction time.</li> <li><strong><code>_step_impl</code> is required, <code>step</code> is not.</strong> <code>MCPEnvironment.step</code> already routes <code>ListToolsAction</code> and <code>CallToolAction</code> through the FastMCP server for you. Your subclass only has to implement <code>_step_impl</code>, which the base class calls for any <strong>non-MCP</strong> action. In pure-MCP environments like Echo it just returns an error observation; in environments that mix tool calls with other action types (e.g. a terminal “submit” action) it’s where that extra dispatch lives.</li> <li><strong>Rewards and <code>done</code> still work.</strong> Because MCP actions flow through <code>step()</code>, you can compute rewards, flip <code>done</code>, and emit metadata just like in any other OpenEnv environment.</li>",ut,ye,Ut,Te,ss='The repo ships a self-contained walkthrough at <a href="https://github.com/huggingface/OpenEnv/blob/main/examples/echo_mcp_demo.py" rel="nofollow"><code>examples/echo_mcp_demo.py</code></a>. Run it directly from the repo root:',jt,we,bt,he,ls="You will see the discovery call, two tool invocations, and an error case printed in sequence — the same four steps the “Under the hood” section covers, end-to-end against the real <code>EchoEnvironment</code>.",It,me,ft,Je,ns='<li><strong>End-to-end training recipe</strong> — the <a href="wordle-grpo">Wordle GRPO tutorial</a> walks through a full GRPO training run with <code>environment_factory</code>. The wrapper-class shape is the same for an MCP-backed env; inside each tool method, build a <code>CallToolAction(tool_name=..., arguments={...})</code> instead of Wordle’s single-field <code>TextArenaAction(message=guess)</code>.</li> <li><strong>MCP lifecycle details</strong> — the <a href="../guides/mcp-environment-lifecycle">MCP Environment Lifecycle guide</a> covers <code>step()</code> vs <code>step_async()</code>, the <code>call_tool()</code> convenience path, and common debugging questions.</li> <li><strong>A richer MCP environment</strong> — <a href="https://github.com/huggingface/OpenEnv/tree/main/envs/finqa_env" rel="nofollow"><code>envs/finqa_env/</code></a> shows tool calls participating in episode progression, rewards, and terminal submission — not just a stateless echo.</li> <li><strong>Design rationale</strong> — <a href="https://github.com/huggingface/OpenEnv/blob/main/rfcs/003-mcp-support.md" rel="nofollow">RFC 003</a> explains why OpenEnv picked MCP as the agent boundary and how tool-calling and CodeAct styles share the same plumbing.</li> <li><strong>Serving tools to an external agent</strong> — the <code>/mcp</code> JSON-RPC endpoint is available alongside <code>/ws</code> on any MCP environment server. Point an MCP-compatible client at it for production inference without going through the step loop. This direct path bypasses reward computation, step counts, and episode termination, and it exposes only registered MCP tools — not <code>reset</code>, <code>step</code>, or <code>state</code>.</li>',Ct,ue,vt,je,gt;return j=new ys({props:{containerStyle:"float: right; margin-left: 10px; display: inline-flex; position: relative; z-index: 10;"}}),b=new w({props:{title:"MCP Tools in OpenEnv Environments",local:"mcp-tools-in-openenv-environments",headingTag:"h1"}}),C=new w({props:{title:"Why MCP?",local:"why-mcp",headingTag:"h2"}}),B=new w({props:{title:"The dual API boundary",local:"the-dual-api-boundary",headingTag:"h3"}}),$=new w({props:{title:"Using MCP Tools in a Training Loop",local:"using-mcp-tools-in-a-training-loop",headingTag:"h2"}}),k=new m({props:{code:"b2JzJTIwJTNEJTIwZW52LnN0ZXAoQ2FsbFRvb2xBY3Rpb24odG9vbF9uYW1lJTNELi4uJTJDJTIwYXJndW1lbnRzJTNELi4uKSklMEElMjMlMjBvYnMucmVzdWx0JTIwJTIwJTIwJTIwJTIwJTIwJTIwJUUyJTgwJTk0JTIwcnVudGltZSUyMHRvb2wlMjByZXN1bHQlMjBvYmplY3QlMkMlMjBvciUyME5vbmUlMjBvbiUyMGVycm9yJTBBJTIzJTIwb2JzLnJld2FyZCUyMCUyMCUyMCUyMCUyMCUyMCUyMCVFMiU4MCU5NCUyMGVudidzJTIwcmV3YXJkJTIwZm9yJTIwdGhpcyUyMHR1cm4lMjAobWF5JTIwYmUlMjBOb25lKSUwQSUyMyUyMG9icy5kb25lJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJUUyJTgwJTk0JTIwZXBpc29kZSUyMHRlcm1pbmF0ZWQ=",highlighted:`obs = env.step(CallToolAction(tool_name=..., arguments=...))
<span class="hljs-comment"># obs.result — runtime tool result object, or None on error</span>
<span class="hljs-comment"># obs.reward — env&#x27;s reward for this turn (may be None)</span>
<span class="hljs-comment"># obs.done — episode terminated</span>`,lang:"python",wrap:!1}}),N=new w({props:{title:"Framework-agnostic rollout loop",local:"framework-agnostic-rollout-loop",headingTag:"h3"}}),W=new m({props:{code:"b2JzJTIwJTNEJTIwZW52LnJlc2V0KCklMEF0b3RhbF9yZXdhcmQlMjAlM0QlMjAwLjAlMEFmb3IlMjB0dXJuJTIwaW4lMjByYW5nZShtYXhfdHVybnMpJTNBJTBBJTIwJTIwJTIwJTIwdG9vbF9jYWxsJTIwJTNEJTIwbW9kZWwuZGVjaWRlKG9icyklMjAlMjAlMjMlMjB5b3VyJTIwYWdlbnQlMjBwaWNrcyUyMGElMjB0b29sJTIwJTJCJTIwYXJncyUyMGZyb20lMjB0aGUlMjBsYXRlc3QlMjBvYnNlcnZhdGlvbiUwQSUyMCUyMCUyMCUyMG9icyUyMCUzRCUyMGVudi5zdGVwKCUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMENhbGxUb29sQWN0aW9uKHRvb2xfbmFtZSUzRHRvb2xfY2FsbC5uYW1lJTJDJTIwYXJndW1lbnRzJTNEdG9vbF9jYWxsLmFyZ3VtZW50cyklMEElMjAlMjAlMjAlMjApJTBBJTIwJTIwJTIwJTIwdG90YWxfcmV3YXJkJTIwJTJCJTNEJTIwb2JzLnJld2FyZCUyMG9yJTIwMC4wJTBBJTIwJTIwJTIwJTIwaWYlMjBvYnMuZG9uZSUzQSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMGJyZWFr",highlighted:`obs = env.reset()
total_reward = <span class="hljs-number">0.0</span>
<span class="hljs-keyword">for</span> turn <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(max_turns):
tool_call = model.decide(obs) <span class="hljs-comment"># your agent picks a tool + args from the latest observation</span>
obs = env.step(
CallToolAction(tool_name=tool_call.name, arguments=tool_call.arguments)
)
total_reward += obs.reward <span class="hljs-keyword">or</span> <span class="hljs-number">0.0</span>
<span class="hljs-keyword">if</span> obs.done:
<span class="hljs-keyword">break</span>`,lang:"python",wrap:!1}}),S=new w({props:{title:"TRL environment_factory",local:"trl-environmentfactory",headingTag:"h3"}}),V=new m({props:{code:"ZGVmJTIwZWNobyhzZWxmJTJDJTIwbWVzc2FnZSUzQSUyMHN0ciklMjAtJTNFJTIwc3RyJTNBJTBBJTIwJTIwJTIwJTIwJTIyJTIyJTIyRWNobyUyMGJhY2slMjBhJTIwbWVzc2FnZS4lMEElMEElMjAlMjAlMjAlMjBBcmdzJTNBJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwbWVzc2FnZSUzQSUyMFRoZSUyMG1lc3NhZ2UlMjB0byUyMGVjaG8uJTBBJTIwJTIwJTIwJTIwJTIyJTIyJTIyJTBBJTIwJTIwJTIwJTIwc3RlcF9yZXN1bHQlMjAlM0QlMjBzZWxmLmVudi5zdGVwKCUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMENhbGxUb29sQWN0aW9uKHRvb2xfbmFtZSUzRCUyMmVjaG9fbWVzc2FnZSUyMiUyQyUyMGFyZ3VtZW50cyUzRCU3QiUyMm1lc3NhZ2UlMjIlM0ElMjBtZXNzYWdlJTdEKSUwQSUyMCUyMCUyMCUyMCklMEElMjAlMjAlMjAlMjBvYnMlMjAlM0QlMjBzdGVwX3Jlc3VsdC5vYnNlcnZhdGlvbiUwQSUyMCUyMCUyMCUyMHNlbGYucmV3YXJkJTIwJTNEJTIwc3RlcF9yZXN1bHQucmV3YXJkJTIwb3IlMjBvYnMucmV3YXJkJTIwb3IlMjAwLjAlMEElMjAlMjAlMjAlMjByZXN1bHQlMjAlM0QlMjBvYnMucmVzdWx0JTBBJTIwJTIwJTIwJTIwcmV0dXJuJTIwcmVzdWx0LmRhdGElMjBpZiUyMGhhc2F0dHIocmVzdWx0JTJDJTIwJTIyZGF0YSUyMiklMjBlbHNlJTIwcmVzdWx0",highlighted:`<span class="hljs-keyword">def</span> <span class="hljs-title function_">echo</span>(<span class="hljs-params">self, message: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:
<span class="hljs-string">&quot;&quot;&quot;Echo back a message.
Args:
message: The message to echo.
&quot;&quot;&quot;</span>
step_result = self.env.step(
CallToolAction(tool_name=<span class="hljs-string">&quot;echo_message&quot;</span>, arguments={<span class="hljs-string">&quot;message&quot;</span>: message})
)
obs = step_result.observation
self.reward = step_result.reward <span class="hljs-keyword">or</span> obs.reward <span class="hljs-keyword">or</span> <span class="hljs-number">0.0</span>
result = obs.result
<span class="hljs-keyword">return</span> result.data <span class="hljs-keyword">if</span> <span class="hljs-built_in">hasattr</span>(result, <span class="hljs-string">&quot;data&quot;</span>) <span class="hljs-keyword">else</span> result`,lang:"python",wrap:!1}}),P=new w({props:{title:"Under the Hood: CallToolAction and ListToolsAction",local:"under-the-hood-calltoolaction-and-listtoolsaction",headingTag:"h2"}}),L=new w({props:{title:"Discovering tools",local:"discovering-tools",headingTag:"h3"}}),H=new m({props:{code:"ZnJvbSUyMGVjaG9fZW52LnNlcnZlci5lY2hvX2Vudmlyb25tZW50JTIwaW1wb3J0JTIwRWNob0Vudmlyb25tZW50JTBBZnJvbSUyMG9wZW5lbnYuY29yZS5lbnZfc2VydmVyLm1jcF90eXBlcyUyMGltcG9ydCUyMExpc3RUb29sc0FjdGlvbiUyQyUyMExpc3RUb29sc09ic2VydmF0aW9uJTBBJTBBZW52JTIwJTNEJTIwRWNob0Vudmlyb25tZW50KCklMEFlbnYucmVzZXQoKSUwQSUwQW9icyUyMCUzRCUyMGVudi5zdGVwKExpc3RUb29sc0FjdGlvbigpKSUwQWFzc2VydCUyMGlzaW5zdGFuY2Uob2JzJTJDJTIwTGlzdFRvb2xzT2JzZXJ2YXRpb24pJTBBJTBBZm9yJTIwdG9vbCUyMGluJTIwb2JzLnRvb2xzJTNBJTBBJTIwJTIwJTIwJTIwcHJpbnQoZiUyMiU3QnRvb2wubmFtZSU3RCUzQSUyMCU3QnRvb2wuZGVzY3JpcHRpb24lN0QlMjIp",highlighted:`<span class="hljs-keyword">from</span> echo_env.server.echo_environment <span class="hljs-keyword">import</span> EchoEnvironment
<span class="hljs-keyword">from</span> openenv.core.env_server.mcp_types <span class="hljs-keyword">import</span> ListToolsAction, ListToolsObservation
env = EchoEnvironment()
env.reset()
obs = env.step(ListToolsAction())
<span class="hljs-keyword">assert</span> <span class="hljs-built_in">isinstance</span>(obs, ListToolsObservation)
<span class="hljs-keyword">for</span> tool <span class="hljs-keyword">in</span> obs.tools:
<span class="hljs-built_in">print</span>(<span class="hljs-string">f&quot;<span class="hljs-subst">{tool.name}</span>: <span class="hljs-subst">{tool.description}</span>&quot;</span>)`,lang:"python",wrap:!1}}),q=new w({props:{title:"Calling a tool",local:"calling-a-tool",headingTag:"h3"}}),O=new m({props:{code:"ZnJvbSUyMG9wZW5lbnYuY29yZS5lbnZfc2VydmVyLm1jcF90eXBlcyUyMGltcG9ydCUyMENhbGxUb29sQWN0aW9uJTJDJTIwQ2FsbFRvb2xPYnNlcnZhdGlvbiUwQSUwQW9icyUyMCUzRCUyMGVudi5zdGVwKCUwQSUyMCUyMCUyMCUyMENhbGxUb29sQWN0aW9uKCUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMHRvb2xfbmFtZSUzRCUyMmVjaG9fbWVzc2FnZSUyMiUyQyUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMGFyZ3VtZW50cyUzRCU3QiUyMm1lc3NhZ2UlMjIlM0ElMjAlMjJIZWxsbyUyMGZyb20lMjBNQ1AhJTIyJTdEJTJDJTBBJTIwJTIwJTIwJTIwKSUwQSklMEElMEFhc3NlcnQlMjBpc2luc3RhbmNlKG9icyUyQyUyMENhbGxUb29sT2JzZXJ2YXRpb24pJTBBcHJpbnQob2JzLnRvb2xfbmFtZSklMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjMlMjAlMjJlY2hvX21lc3NhZ2UlMjIlMEFwcmludChvYnMuZXJyb3IpJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIzJTIwTm9uZSUwQXJlc3VsdCUyMCUzRCUyMG9icy5yZXN1bHQlMEFwcmludChyZXN1bHQuZGF0YSUyMGlmJTIwaGFzYXR0cihyZXN1bHQlMkMlMjAlMjJkYXRhJTIyKSUyMGVsc2UlMjByZXN1bHQpJTIwJTIwJTIzJTIwJTIySGVsbG8lMjBmcm9tJTIwTUNQISUyMg==",highlighted:`<span class="hljs-keyword">from</span> openenv.core.env_server.mcp_types <span class="hljs-keyword">import</span> CallToolAction, CallToolObservation
obs = env.step(
CallToolAction(
tool_name=<span class="hljs-string">&quot;echo_message&quot;</span>,
arguments={<span class="hljs-string">&quot;message&quot;</span>: <span class="hljs-string">&quot;Hello from MCP!&quot;</span>},
)
)
<span class="hljs-keyword">assert</span> <span class="hljs-built_in">isinstance</span>(obs, CallToolObservation)
<span class="hljs-built_in">print</span>(obs.tool_name) <span class="hljs-comment"># &quot;echo_message&quot;</span>
<span class="hljs-built_in">print</span>(obs.error) <span class="hljs-comment"># None</span>
result = obs.result
<span class="hljs-built_in">print</span>(result.data <span class="hljs-keyword">if</span> <span class="hljs-built_in">hasattr</span>(result, <span class="hljs-string">&quot;data&quot;</span>) <span class="hljs-keyword">else</span> result) <span class="hljs-comment"># &quot;Hello from MCP!&quot;</span>`,lang:"python",wrap:!1}}),K=new w({props:{title:"Error handling",local:"error-handling",headingTag:"h3"}}),ee=new m({props:{code:"b2JzJTIwJTNEJTIwZW52LnN0ZXAoJTBBJTIwJTIwJTIwJTIwQ2FsbFRvb2xBY3Rpb24odG9vbF9uYW1lJTNEJTIyZG9lc19ub3RfZXhpc3QlMjIlMkMlMjBhcmd1bWVudHMlM0QlN0IlN0QpJTJDJTBBKSUwQSUwQWFzc2VydCUyMGlzaW5zdGFuY2Uob2JzJTJDJTIwQ2FsbFRvb2xPYnNlcnZhdGlvbiklMEFwcmludChvYnMuZXJyb3IuZXJyb3JfdHlwZSklMjAlMjAlMjMlMjBUb29sRXJyb3JUeXBlLlRPT0xfTk9UX0ZPVU5EJTBBcHJpbnQob2JzLmVycm9yLm1lc3NhZ2UpJTIwJTIwJTIwJTIwJTIwJTIzJTIwaHVtYW4tcmVhZGFibGUlMjBtZXNzYWdlJTIwZnJvbSUyMEZhc3RNQ1AlMkMlMjBlLmcuJTIwJTIyVW5rbm93biUyMHRvb2wlM0ElMjAnZG9lc19ub3RfZXhpc3QnJTIy",highlighted:`obs = env.step(
CallToolAction(tool_name=<span class="hljs-string">&quot;does_not_exist&quot;</span>, arguments={}),
)
<span class="hljs-keyword">assert</span> <span class="hljs-built_in">isinstance</span>(obs, CallToolObservation)
<span class="hljs-built_in">print</span>(obs.error.error_type) <span class="hljs-comment"># ToolErrorType.TOOL_NOT_FOUND</span>
<span class="hljs-built_in">print</span>(obs.error.message) <span class="hljs-comment"># human-readable message from FastMCP, e.g. &quot;Unknown tool: &#x27;does_not_exist&#x27;&quot;</span>`,lang:"python",wrap:!1}}),se=new w({props:{title:"step(CallToolAction(...)) vs call_tool()",local:"stepcalltoolaction-vs-calltool",headingTag:"h3"}}),ne=new w({props:{title:"Using MCP Tools for Evaluation",local:"using-mcp-tools-for-evaluation",headingTag:"h2"}}),ae=new m({props:{code:"ZnJvbSUyMGVjaG9fZW52LnNlcnZlci5lY2hvX2Vudmlyb25tZW50JTIwaW1wb3J0JTIwRWNob0Vudmlyb25tZW50JTBBZnJvbSUyMG9wZW5lbnYuY29yZS5lbnZfc2VydmVyLm1jcF90eXBlcyUyMGltcG9ydCUyMENhbGxUb29sQWN0aW9uJTBBJTBBZW52JTIwJTNEJTIwRWNob0Vudmlyb25tZW50KCklMEFlbnYucmVzZXQoKSUwQSUwQXJlc3VsdHMlMjAlM0QlMjAlNUIlNUQlMEFmb3IlMjBzYW1wbGUlMjBpbiUyMGV2YWxfZGF0YXNldCUzQSUwQSUyMCUyMCUyMCUyMHRvb2xfY2FsbCUyMCUzRCUyMG1vZGVsLmRlY2lkZShzYW1wbGUpJTIwJTIwJTIwJTIzJTIweW91ciUyMGFnZW50JTIwcGlja3MlMjBhJTIwdG9vbCUyMCUyQiUyMGFyZ3VtZW50cyUwQSUyMCUyMCUyMCUyMG9icyUyMCUzRCUyMGVudi5zdGVwKCUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMENhbGxUb29sQWN0aW9uKHRvb2xfbmFtZSUzRHRvb2xfY2FsbC5uYW1lJTJDJTIwYXJndW1lbnRzJTNEdG9vbF9jYWxsLmFyZ3VtZW50cyklMkMlMEElMjAlMjAlMjAlMjApJTBBJTIwJTIwJTIwJTIwcmVzdWx0cy5hcHBlbmQoJTdCJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIycHJvbXB0JTIyJTNBJTIwc2FtcGxlLnByb21wdCUyQyUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMnJlcGx5JTIyJTNBJTIwKCUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMG9icy5yZXN1bHQuZGF0YSUyMGlmJTIwb2JzLmVycm9yJTIwaXMlMjBOb25lJTIwYW5kJTIwaGFzYXR0cihvYnMucmVzdWx0JTJDJTIwJTIyZGF0YSUyMiklMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBlbHNlJTIwb2JzLnJlc3VsdCUyMGlmJTIwb2JzLmVycm9yJTIwaXMlMjBOb25lJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwZWxzZSUyME5vbmUlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjApJTJDJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIycmV3YXJkJTIyJTNBJTIwb2JzLnJld2FyZCUyMG9yJTIwMC4wJTJDJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIyZXJyb3IlMjIlM0ElMjBvYnMuZXJyb3IlMkMlMEElMjAlMjAlMjAlMjAlN0QpJTBBJTIwJTIwJTIwJTIwZW52LnJlc2V0KCk=",highlighted:`<span class="hljs-keyword">from</span> echo_env.server.echo_environment <span class="hljs-keyword">import</span> EchoEnvironment
<span class="hljs-keyword">from</span> openenv.core.env_server.mcp_types <span class="hljs-keyword">import</span> CallToolAction
env = EchoEnvironment()
env.reset()
results = []
<span class="hljs-keyword">for</span> sample <span class="hljs-keyword">in</span> eval_dataset:
tool_call = model.decide(sample) <span class="hljs-comment"># your agent picks a tool + arguments</span>
obs = env.step(
CallToolAction(tool_name=tool_call.name, arguments=tool_call.arguments),
)
results.append({
<span class="hljs-string">&quot;prompt&quot;</span>: sample.prompt,
<span class="hljs-string">&quot;reply&quot;</span>: (
obs.result.data <span class="hljs-keyword">if</span> obs.error <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span> <span class="hljs-keyword">and</span> <span class="hljs-built_in">hasattr</span>(obs.result, <span class="hljs-string">&quot;data&quot;</span>)
<span class="hljs-keyword">else</span> obs.result <span class="hljs-keyword">if</span> obs.error <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>
<span class="hljs-keyword">else</span> <span class="hljs-literal">None</span>
),
<span class="hljs-string">&quot;reward&quot;</span>: obs.reward <span class="hljs-keyword">or</span> <span class="hljs-number">0.0</span>,
<span class="hljs-string">&quot;error&quot;</span>: obs.error,
})
env.reset()`,lang:"python",wrap:!1}}),re=new w({props:{title:"Building an MCP Environment",local:"building-an-mcp-environment",headingTag:"h2"}}),pe=new m({props:{code:"ZnJvbSUyMHV1aWQlMjBpbXBvcnQlMjB1dWlkNCUwQSUwQWZyb20lMjBmYXN0bWNwJTIwaW1wb3J0JTIwRmFzdE1DUCUwQSUwQWZyb20lMjBvcGVuZW52LmNvcmUuZW52X3NlcnZlci5tY3BfZW52aXJvbm1lbnQlMjBpbXBvcnQlMjBNQ1BFbnZpcm9ubWVudCUwQWZyb20lMjBvcGVuZW52LmNvcmUuZW52X3NlcnZlci50eXBlcyUyMGltcG9ydCUyMEFjdGlvbiUyQyUyME9ic2VydmF0aW9uJTJDJTIwU3RhdGUlMEElMEElMEFjbGFzcyUyMEVjaG9FbnZpcm9ubWVudChNQ1BFbnZpcm9ubWVudCklM0ElMEElMjAlMjAlMjAlMjBTVVBQT1JUU19DT05DVVJSRU5UX1NFU1NJT05TJTIwJTNEJTIwVHJ1ZSUwQSUwQSUyMCUyMCUyMCUyMGRlZiUyMF9faW5pdF9fKHNlbGYpJTNBJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwbWNwJTIwJTNEJTIwRmFzdE1DUCglMjJlY2hvX2VudiUyMiklMEElMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlNDBtY3AudG9vbCUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMGRlZiUyMGVjaG9fbWVzc2FnZShtZXNzYWdlJTNBJTIwc3RyKSUyMC0lM0UlMjBzdHIlM0ElMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjIlMjIlMjJFY2hvJTIwYmFjayUyMHRoZSUyMHByb3ZpZGVkJTIwbWVzc2FnZS4lMEElMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBBcmdzJTNBJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwbWVzc2FnZSUzQSUyMFRoZSUyMG1lc3NhZ2UlMjB0byUyMGVjaG8lMjBiYWNrJTBBJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwUmV0dXJucyUzQSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMFRoZSUyMHNhbWUlMjBtZXNzYWdlJTIwdGhhdCUyMHdhcyUyMHByb3ZpZGVkJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIyJTIyJTIyJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwcmV0dXJuJTIwbWVzc2FnZSUwQSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCU0MG1jcC50b29sJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwZGVmJTIwZWNob193aXRoX2xlbmd0aChtZXNzYWdlJTNBJTIwc3RyKSUyMC0lM0UlMjBkaWN0JTNBJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIyJTIyJTIyRWNobyUyMGJhY2slMjB0aGUlMjBtZXNzYWdlJTIwd2l0aCUyMGl0cyUyMGxlbmd0aC4lMEElMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBBcmdzJTNBJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwbWVzc2FnZSUzQSUyMFRoZSUyMG1lc3NhZ2UlMjB0byUyMGVjaG8lMjBiYWNrJTBBJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwUmV0dXJucyUzQSUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMERpY3Rpb25hcnklMjB3aXRoJTIwdGhlJTIwbWVzc2FnZSUyMGFuZCUyMGl0cyUyMGxlbmd0aCUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMiUyMiUyMiUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMHJldHVybiUyMCU3QiUyMm1lc3NhZ2UlMjIlM0ElMjBtZXNzYWdlJTJDJTIwJTIybGVuZ3RoJTIyJTNBJTIwbGVuKG1lc3NhZ2UpJTdEJTBBJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwc3VwZXIoKS5fX2luaXRfXyhtY3ApJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwc2VsZi5fc3RhdGUlMjAlM0QlMjBTdGF0ZShlcGlzb2RlX2lkJTNEc3RyKHV1aWQ0KCkpJTJDJTIwc3RlcF9jb3VudCUzRDApJTBBJTBBJTIwJTIwJTIwJTIwZGVmJTIwcmVzZXQoc2VsZiUyQyUyMHNlZWQlM0ROb25lJTJDJTIwZXBpc29kZV9pZCUzRE5vbmUlMkMlMjAqKmt3YXJncyklMjAtJTNFJTIwT2JzZXJ2YXRpb24lM0ElMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBzZWxmLl9zdGF0ZSUyMCUzRCUyMFN0YXRlKGVwaXNvZGVfaWQlM0RlcGlzb2RlX2lkJTIwb3IlMjBzdHIodXVpZDQoKSklMkMlMjBzdGVwX2NvdW50JTNEMCklMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjByZXR1cm4lMjBPYnNlcnZhdGlvbihkb25lJTNERmFsc2UlMkMlMjByZXdhcmQlM0QwLjAlMkMlMjBtZXRhZGF0YSUzRCU3QiUyMnN0YXR1cyUyMiUzQSUyMCUyMnJlYWR5JTIyJTdEKSUwQSUwQSUyMCUyMCUyMCUyMGRlZiUyMF9zdGVwX2ltcGwoc2VsZiUyQyUyMGFjdGlvbiUzQSUyMEFjdGlvbiUyQyUyMHRpbWVvdXRfcyUzRE5vbmUlMkMlMjAqKmt3YXJncyklMjAtJTNFJTIwT2JzZXJ2YXRpb24lM0ElMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjMlMjBDYWxsZWQlMjBmb3IlMjBub24tTUNQJTIwYWN0aW9ucy4lMjBFY2hvJTIwZXhwb3NlcyUyME1DUCUyMHRvb2xzJTIwb25seSUyQyUwQSUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMCUyMyUyMHNvJTIwYW55dGhpbmclMjB0aGF0JTIwaXNuJ3QlMjBMaXN0VG9vbHNBY3Rpb24lMjAlMkYlMjBDYWxsVG9vbEFjdGlvbiUyMGlzJTIwYW4lMjBlcnJvci4lMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjByZXR1cm4lMjBPYnNlcnZhdGlvbiglMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBkb25lJTNERmFsc2UlMkMlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjByZXdhcmQlM0QwLjAlMkMlMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjBtZXRhZGF0YSUzRCU3QiUyMmVycm9yJTIyJTNBJTIwZiUyMlVua25vd24lMjBhY3Rpb24lMjB0eXBlJTNBJTIwJTdCdHlwZShhY3Rpb24pLl9fbmFtZV9fJTdEJTIyJTdEJTJDJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwKSUwQSUwQSUyMCUyMCUyMCUyMCU0MHByb3BlcnR5JTBBJTIwJTIwJTIwJTIwZGVmJTIwc3RhdGUoc2VsZiklMjAtJTNFJTIwU3RhdGUlM0ElMEElMjAlMjAlMjAlMjAlMjAlMjAlMjAlMjByZXR1cm4lMjBzZWxmLl9zdGF0ZQ==",highlighted:`<span class="hljs-keyword">from</span> uuid <span class="hljs-keyword">import</span> uuid4
<span class="hljs-keyword">from</span> fastmcp <span class="hljs-keyword">import</span> FastMCP
<span class="hljs-keyword">from</span> openenv.core.env_server.mcp_environment <span class="hljs-keyword">import</span> MCPEnvironment
<span class="hljs-keyword">from</span> openenv.core.env_server.types <span class="hljs-keyword">import</span> Action, Observation, State
<span class="hljs-keyword">class</span> <span class="hljs-title class_">EchoEnvironment</span>(<span class="hljs-title class_ inherited__">MCPEnvironment</span>):
SUPPORTS_CONCURRENT_SESSIONS = <span class="hljs-literal">True</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self</span>):
mcp = FastMCP(<span class="hljs-string">&quot;echo_env&quot;</span>)
<span class="hljs-meta"> @mcp.tool</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">echo_message</span>(<span class="hljs-params">message: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:
<span class="hljs-string">&quot;&quot;&quot;Echo back the provided message.
Args:
message: The message to echo back
Returns:
The same message that was provided
&quot;&quot;&quot;</span>
<span class="hljs-keyword">return</span> message
<span class="hljs-meta"> @mcp.tool</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">echo_with_length</span>(<span class="hljs-params">message: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">dict</span>:
<span class="hljs-string">&quot;&quot;&quot;Echo back the message with its length.
Args:
message: The message to echo back
Returns:
Dictionary with the message and its length
&quot;&quot;&quot;</span>
<span class="hljs-keyword">return</span> {<span class="hljs-string">&quot;message&quot;</span>: message, <span class="hljs-string">&quot;length&quot;</span>: <span class="hljs-built_in">len</span>(message)}
<span class="hljs-built_in">super</span>().__init__(mcp)
self._state = State(episode_id=<span class="hljs-built_in">str</span>(uuid4()), step_count=<span class="hljs-number">0</span>)
<span class="hljs-keyword">def</span> <span class="hljs-title function_">reset</span>(<span class="hljs-params">self, seed=<span class="hljs-literal">None</span>, episode_id=<span class="hljs-literal">None</span>, **kwargs</span>) -&gt; Observation:
self._state = State(episode_id=episode_id <span class="hljs-keyword">or</span> <span class="hljs-built_in">str</span>(uuid4()), step_count=<span class="hljs-number">0</span>)
<span class="hljs-keyword">return</span> Observation(done=<span class="hljs-literal">False</span>, reward=<span class="hljs-number">0.0</span>, metadata={<span class="hljs-string">&quot;status&quot;</span>: <span class="hljs-string">&quot;ready&quot;</span>})
<span class="hljs-keyword">def</span> <span class="hljs-title function_">_step_impl</span>(<span class="hljs-params">self, action: Action, timeout_s=<span class="hljs-literal">None</span>, **kwargs</span>) -&gt; Observation:
<span class="hljs-comment"># Called for non-MCP actions. Echo exposes MCP tools only,</span>
<span class="hljs-comment"># so anything that isn&#x27;t ListToolsAction / CallToolAction is an error.</span>
<span class="hljs-keyword">return</span> Observation(
done=<span class="hljs-literal">False</span>,
reward=<span class="hljs-number">0.0</span>,
metadata={<span class="hljs-string">&quot;error&quot;</span>: <span class="hljs-string">f&quot;Unknown action type: <span class="hljs-subst">{<span class="hljs-built_in">type</span>(action).__name__}</span>&quot;</span>},
)
<span class="hljs-meta"> @property</span>
<span class="hljs-keyword">def</span> <span class="hljs-title function_">state</span>(<span class="hljs-params">self</span>) -&gt; State:
<span class="hljs-keyword">return</span> self._state`,lang:"python",wrap:!1}}),ye=new w({props:{title:"Running the Demo End-to-End",local:"running-the-demo-end-to-end",headingTag:"h2"}}),we=new m({props:{code:"UFlUSE9OUEFUSCUzRHNyYyUzQWVudnMlMjB1diUyMHJ1biUyMHB5dGhvbiUyMGV4YW1wbGVzJTJGZWNob19tY3BfZGVtby5weQ==",highlighted:"PYTHONPATH=src:envs uv run python examples/echo_mcp_demo.py",lang:"bash",wrap:!1}}),me=new w({props:{title:"Next Steps",local:"next-steps",headingTag:"h2"}}),ue=new Ts({props:{source:"https://github.com/huggingface/openenv/blob/main/docs/source/tutorials/mcp-environment.md"}}),{c(){h=a("meta"),Ie=n(),Ue=a("p"),fe=n(),c(j.$$.fragment),Ce=n(),c(b.$$.fragment),ve=n(),I=a("p"),I.innerHTML=Bt,ge=n(),f=a("p"),f.innerHTML=Et,Ae=n(),c(C.$$.fragment),Be=n(),v=a("p"),v.innerHTML=Zt,Ee=n(),g=a("ul"),g.innerHTML=$t,Ze=n(),A=a("p"),A.textContent=_t,$e=n(),c(B.$$.fragment),_e=n(),E=a("p"),E.textContent=kt,ke=n(),Z=a("ul"),Z.innerHTML=Gt,Ge=n(),J=a("blockquote"),J.innerHTML=Nt,Ne=n(),u=a("blockquote"),u.innerHTML=Rt,Re=n(),c($.$$.fragment),We=n(),_=a("p"),_.textContent=Wt,Qe=n(),c(k.$$.fragment),Se=n(),G=a("p"),G.textContent=Qt,xe=n(),c(N.$$.fragment),Ve=n(),R=a("p"),R.innerHTML=St,Fe=n(),c(W.$$.fragment),ze=n(),Q=a("p"),Q.innerHTML=xt,Pe=n(),c(S.$$.fragment),Ye=n(),x=a("p"),x.innerHTML=Vt,Le=n(),c(V.$$.fragment),He=n(),F=a("p"),F.innerHTML=Ft,Xe=n(),z=a("p"),z.innerHTML=zt,qe=n(),c(P.$$.fragment),Oe=n(),Y=a("p"),Y.innerHTML=Pt,De=n(),c(L.$$.fragment),Ke=n(),c(H.$$.fragment),et=n(),X=a("p"),X.innerHTML=Yt,tt=n(),c(q.$$.fragment),st=n(),c(O.$$.fragment),lt=n(),D=a("p"),D.innerHTML=Lt,nt=n(),c(K.$$.fragment),ot=n(),c(ee.$$.fragment),at=n(),te=a("p"),te.innerHTML=Ht,it=n(),c(se.$$.fragment),rt=n(),le=a("p"),le.innerHTML=Xt,ct=n(),U=a("blockquote"),U.innerHTML=qt,pt=n(),c(ne.$$.fragment),Mt=n(),oe=a("p"),oe.textContent=Ot,dt=n(),c(ae.$$.fragment),yt=n(),ie=a("p"),ie.innerHTML=Dt,Tt=n(),c(re.$$.fragment),wt=n(),ce=a("p"),ce.innerHTML=Kt,ht=n(),c(pe.$$.fragment),mt=n(),Me=a("p"),Me.textContent=es,Jt=n(),de=a("ul"),de.innerHTML=ts,ut=n(),c(ye.$$.fragment),Ut=n(),Te=a("p"),Te.innerHTML=ss,jt=n(),c(we.$$.fragment),bt=n(),he=a("p"),he.innerHTML=ls,It=n(),c(me.$$.fragment),ft=n(),Je=a("ul"),Je.innerHTML=ns,Ct=n(),c(ue.$$.fragment),vt=n(),je=a("p"),this.h()},l(e){const t=Ms("svelte-u9bgzb",document.head);h=i(t,"META",{name:!0,content:!0}),t.forEach(s),Ie=o(e),Ue=i(e,"P",{}),os(Ue).forEach(s),fe=o(e),p(j.$$.fragment,e),Ce=o(e),p(b.$$.fragment,e),ve=o(e),I=i(e,"P",{"data-svelte-h":!0}),r(I)!=="svelte-10botz"&&(I.innerHTML=Bt),ge=o(e),f=i(e,"P",{"data-svelte-h":!0}),r(f)!=="svelte-1n4razi"&&(f.innerHTML=Et),Ae=o(e),p(C.$$.fragment,e),Be=o(e),v=i(e,"P",{"data-svelte-h":!0}),r(v)!=="svelte-quer6q"&&(v.innerHTML=Zt),Ee=o(e),g=i(e,"UL",{"data-svelte-h":!0}),r(g)!=="svelte-bm0v14"&&(g.innerHTML=$t),Ze=o(e),A=i(e,"P",{"data-svelte-h":!0}),r(A)!=="svelte-17drqpy"&&(A.textContent=_t),$e=o(e),p(B.$$.fragment,e),_e=o(e),E=i(e,"P",{"data-svelte-h":!0}),r(E)!=="svelte-1x41ulf"&&(E.textContent=kt),ke=o(e),Z=i(e,"UL",{"data-svelte-h":!0}),r(Z)!=="svelte-aayabq"&&(Z.innerHTML=Gt),Ge=o(e),J=i(e,"BLOCKQUOTE",{class:!0,"data-svelte-h":!0}),r(J)!=="svelte-5bkwv7"&&(J.innerHTML=Nt),Ne=o(e),u=i(e,"BLOCKQUOTE",{class:!0,"data-svelte-h":!0}),r(u)!=="svelte-mj5sew"&&(u.innerHTML=Rt),Re=o(e),p($.$$.fragment,e),We=o(e),_=i(e,"P",{"data-svelte-h":!0}),r(_)!=="svelte-1hqq1e8"&&(_.textContent=Wt),Qe=o(e),p(k.$$.fragment,e),Se=o(e),G=i(e,"P",{"data-svelte-h":!0}),r(G)!=="svelte-1y18e54"&&(G.textContent=Qt),xe=o(e),p(N.$$.fragment,e),Ve=o(e),R=i(e,"P",{"data-svelte-h":!0}),r(R)!=="svelte-1fffovk"&&(R.innerHTML=St),Fe=o(e),p(W.$$.fragment,e),ze=o(e),Q=i(e,"P",{"data-svelte-h":!0}),r(Q)!=="svelte-4f1vp6"&&(Q.innerHTML=xt),Pe=o(e),p(S.$$.fragment,e),Ye=o(e),x=i(e,"P",{"data-svelte-h":!0}),r(x)!=="svelte-lqu02z"&&(x.innerHTML=Vt),Le=o(e),p(V.$$.fragment,e),He=o(e),F=i(e,"P",{"data-svelte-h":!0}),r(F)!=="svelte-1mn0mt2"&&(F.innerHTML=Ft),Xe=o(e),z=i(e,"P",{"data-svelte-h":!0}),r(z)!=="svelte-1moqhug"&&(z.innerHTML=zt),qe=o(e),p(P.$$.fragment,e),Oe=o(e),Y=i(e,"P",{"data-svelte-h":!0}),r(Y)!=="svelte-udspd8"&&(Y.innerHTML=Pt),De=o(e),p(L.$$.fragment,e),Ke=o(e),p(H.$$.fragment,e),et=o(e),X=i(e,"P",{"data-svelte-h":!0}),r(X)!=="svelte-1dfwx1i"&&(X.innerHTML=Yt),tt=o(e),p(q.$$.fragment,e),st=o(e),p(O.$$.fragment,e),lt=o(e),D=i(e,"P",{"data-svelte-h":!0}),r(D)!=="svelte-19prv4s"&&(D.innerHTML=Lt),nt=o(e),p(K.$$.fragment,e),ot=o(e),p(ee.$$.fragment,e),at=o(e),te=i(e,"P",{"data-svelte-h":!0}),r(te)!=="svelte-1k0s96f"&&(te.innerHTML=Ht),it=o(e),p(se.$$.fragment,e),rt=o(e),le=i(e,"P",{"data-svelte-h":!0}),r(le)!=="svelte-13kkf3a"&&(le.innerHTML=Xt),ct=o(e),U=i(e,"BLOCKQUOTE",{class:!0,"data-svelte-h":!0}),r(U)!=="svelte-nm8t9w"&&(U.innerHTML=qt),pt=o(e),p(ne.$$.fragment,e),Mt=o(e),oe=i(e,"P",{"data-svelte-h":!0}),r(oe)!=="svelte-fx4olr"&&(oe.textContent=Ot),dt=o(e),p(ae.$$.fragment,e),yt=o(e),ie=i(e,"P",{"data-svelte-h":!0}),r(ie)!=="svelte-11upk12"&&(ie.innerHTML=Dt),Tt=o(e),p(re.$$.fragment,e),wt=o(e),ce=i(e,"P",{"data-svelte-h":!0}),r(ce)!=="svelte-1aps0tn"&&(ce.innerHTML=Kt),ht=o(e),p(pe.$$.fragment,e),mt=o(e),Me=i(e,"P",{"data-svelte-h":!0}),r(Me)!=="svelte-1tds2eg"&&(Me.textContent=es),Jt=o(e),de=i(e,"UL",{"data-svelte-h":!0}),r(de)!=="svelte-1q8h9ql"&&(de.innerHTML=ts),ut=o(e),p(ye.$$.fragment,e),Ut=o(e),Te=i(e,"P",{"data-svelte-h":!0}),r(Te)!=="svelte-1o3lzfy"&&(Te.innerHTML=ss),jt=o(e),p(we.$$.fragment,e),bt=o(e),he=i(e,"P",{"data-svelte-h":!0}),r(he)!=="svelte-qv1npw"&&(he.innerHTML=ls),It=o(e),p(me.$$.fragment,e),ft=o(e),Je=i(e,"UL",{"data-svelte-h":!0}),r(Je)!=="svelte-52azt9"&&(Je.innerHTML=ns),Ct=o(e),p(ue.$$.fragment,e),vt=o(e),je=i(e,"P",{}),os(je).forEach(s),this.h()},h(){be(h,"name","hf:doc:metadata"),be(h,"content",hs),be(J,"class","note"),be(u,"class","note"),be(U,"class","note")},m(e,t){ds(document.head,h),l(e,Ie,t),l(e,Ue,t),l(e,fe,t),M(j,e,t),l(e,Ce,t),M(b,e,t),l(e,ve,t),l(e,I,t),l(e,ge,t),l(e,f,t),l(e,Ae,t),M(C,e,t),l(e,Be,t),l(e,v,t),l(e,Ee,t),l(e,g,t),l(e,Ze,t),l(e,A,t),l(e,$e,t),M(B,e,t),l(e,_e,t),l(e,E,t),l(e,ke,t),l(e,Z,t),l(e,Ge,t),l(e,J,t),l(e,Ne,t),l(e,u,t),l(e,Re,t),M($,e,t),l(e,We,t),l(e,_,t),l(e,Qe,t),M(k,e,t),l(e,Se,t),l(e,G,t),l(e,xe,t),M(N,e,t),l(e,Ve,t),l(e,R,t),l(e,Fe,t),M(W,e,t),l(e,ze,t),l(e,Q,t),l(e,Pe,t),M(S,e,t),l(e,Ye,t),l(e,x,t),l(e,Le,t),M(V,e,t),l(e,He,t),l(e,F,t),l(e,Xe,t),l(e,z,t),l(e,qe,t),M(P,e,t),l(e,Oe,t),l(e,Y,t),l(e,De,t),M(L,e,t),l(e,Ke,t),M(H,e,t),l(e,et,t),l(e,X,t),l(e,tt,t),M(q,e,t),l(e,st,t),M(O,e,t),l(e,lt,t),l(e,D,t),l(e,nt,t),M(K,e,t),l(e,ot,t),M(ee,e,t),l(e,at,t),l(e,te,t),l(e,it,t),M(se,e,t),l(e,rt,t),l(e,le,t),l(e,ct,t),l(e,U,t),l(e,pt,t),M(ne,e,t),l(e,Mt,t),l(e,oe,t),l(e,dt,t),M(ae,e,t),l(e,yt,t),l(e,ie,t),l(e,Tt,t),M(re,e,t),l(e,wt,t),l(e,ce,t),l(e,ht,t),M(pe,e,t),l(e,mt,t),l(e,Me,t),l(e,Jt,t),l(e,de,t),l(e,ut,t),M(ye,e,t),l(e,Ut,t),l(e,Te,t),l(e,jt,t),M(we,e,t),l(e,bt,t),l(e,he,t),l(e,It,t),M(me,e,t),l(e,ft,t),l(e,Je,t),l(e,Ct,t),M(ue,e,t),l(e,vt,t),l(e,je,t),gt=!0},p:is,i(e){gt||(d(j.$$.fragment,e),d(b.$$.fragment,e),d(C.$$.fragment,e),d(B.$$.fragment,e),d($.$$.fragment,e),d(k.$$.fragment,e),d(N.$$.fragment,e),d(W.$$.fragment,e),d(S.$$.fragment,e),d(V.$$.fragment,e),d(P.$$.fragment,e),d(L.$$.fragment,e),d(H.$$.fragment,e),d(q.$$.fragment,e),d(O.$$.fragment,e),d(K.$$.fragment,e),d(ee.$$.fragment,e),d(se.$$.fragment,e),d(ne.$$.fragment,e),d(ae.$$.fragment,e),d(re.$$.fragment,e),d(pe.$$.fragment,e),d(ye.$$.fragment,e),d(we.$$.fragment,e),d(me.$$.fragment,e),d(ue.$$.fragment,e),gt=!0)},o(e){y(j.$$.fragment,e),y(b.$$.fragment,e),y(C.$$.fragment,e),y(B.$$.fragment,e),y($.$$.fragment,e),y(k.$$.fragment,e),y(N.$$.fragment,e),y(W.$$.fragment,e),y(S.$$.fragment,e),y(V.$$.fragment,e),y(P.$$.fragment,e),y(L.$$.fragment,e),y(H.$$.fragment,e),y(q.$$.fragment,e),y(O.$$.fragment,e),y(K.$$.fragment,e),y(ee.$$.fragment,e),y(se.$$.fragment,e),y(ne.$$.fragment,e),y(ae.$$.fragment,e),y(re.$$.fragment,e),y(pe.$$.fragment,e),y(ye.$$.fragment,e),y(we.$$.fragment,e),y(me.$$.fragment,e),y(ue.$$.fragment,e),gt=!1},d(e){e&&(s(Ie),s(Ue),s(fe),s(Ce),s(ve),s(I),s(ge),s(f),s(Ae),s(Be),s(v),s(Ee),s(g),s(Ze),s(A),s($e),s(_e),s(E),s(ke),s(Z),s(Ge),s(J),s(Ne),s(u),s(Re),s(We),s(_),s(Qe),s(Se),s(G),s(xe),s(Ve),s(R),s(Fe),s(ze),s(Q),s(Pe),s(Ye),s(x),s(Le),s(He),s(F),s(Xe),s(z),s(qe),s(Oe),s(Y),s(De),s(Ke),s(et),s(X),s(tt),s(st),s(lt),s(D),s(nt),s(ot),s(at),s(te),s(it),s(rt),s(le),s(ct),s(U),s(pt),s(Mt),s(oe),s(dt),s(yt),s(ie),s(Tt),s(wt),s(ce),s(ht),s(mt),s(Me),s(Jt),s(de),s(ut),s(Ut),s(Te),s(jt),s(bt),s(he),s(It),s(ft),s(Je),s(Ct),s(vt),s(je)),s(h),T(j,e),T(b,e),T(C,e),T(B,e),T($,e),T(k,e),T(N,e),T(W,e),T(S,e),T(V,e),T(P,e),T(L,e),T(H,e),T(q,e),T(O,e),T(K,e),T(ee,e),T(se,e),T(ne,e),T(ae,e),T(re,e),T(pe,e),T(ye,e),T(we,e),T(me,e),T(ue,e)}}}const hs='{"title":"MCP Tools in OpenEnv Environments","local":"mcp-tools-in-openenv-environments","sections":[{"title":"Why MCP?","local":"why-mcp","sections":[{"title":"The dual API boundary","local":"the-dual-api-boundary","sections":[],"depth":3}],"depth":2},{"title":"Using MCP Tools in a Training Loop","local":"using-mcp-tools-in-a-training-loop","sections":[{"title":"Framework-agnostic rollout loop","local":"framework-agnostic-rollout-loop","sections":[],"depth":3},{"title":"TRL environment_factory","local":"trl-environmentfactory","sections":[],"depth":3}],"depth":2},{"title":"Under the Hood: CallToolAction and ListToolsAction","local":"under-the-hood-calltoolaction-and-listtoolsaction","sections":[{"title":"Discovering tools","local":"discovering-tools","sections":[],"depth":3},{"title":"Calling a tool","local":"calling-a-tool","sections":[],"depth":3},{"title":"Error handling","local":"error-handling","sections":[],"depth":3},{"title":"step(CallToolAction(...)) vs call_tool()","local":"stepcalltoolaction-vs-calltool","sections":[],"depth":3}],"depth":2},{"title":"Using MCP Tools for Evaluation","local":"using-mcp-tools-for-evaluation","sections":[],"depth":2},{"title":"Building an MCP Environment","local":"building-an-mcp-environment","sections":[],"depth":2},{"title":"Running the Demo End-to-End","local":"running-the-demo-end-to-end","sections":[],"depth":2},{"title":"Next Steps","local":"next-steps","sections":[],"depth":2}],"depth":1}';function ms(At){return rs(()=>{new URLSearchParams(window.location.search).get("fw")}),[]}class bs extends cs{constructor(h){super(),ps(this,h,ms,ws,as,{})}}export{bs as component};

Xet Storage Details

Size:
47 kB
·
Xet hash:
009407576fcbfb7bf31f7978e7557134c4d8f85d773913cafc392d3d559b1658

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.