<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alexander Shishenko</title>
    <description>The latest articles on DEV Community by Alexander Shishenko (@gamepad64).</description>
    <link>https://dev.to/gamepad64</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F385815%2F10ab8751-e48c-4b19-a16a-d94b15747346.png</url>
      <title>DEV Community: Alexander Shishenko</title>
      <link>https://dev.to/gamepad64</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gamepad64"/>
    <language>en</language>
    <item>
      <title>MCP servers, sandboxed — introducing ACT</title>
      <dc:creator>Alexander Shishenko</dc:creator>
      <pubDate>Fri, 08 May 2026 17:01:35 +0000</pubDate>
      <link>https://dev.to/gamepad64/mcp-servers-sandboxed-introducing-act-5fa2</link>
      <guid>https://dev.to/gamepad64/mcp-servers-sandboxed-introducing-act-5fa2</guid>
      <description>&lt;p&gt;Setting up an MCP server for your AI agent today usually looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; @some-org/mcp-server &lt;span class="c"&gt;# or&lt;/span&gt;
uvx some-mcp-server &lt;span class="c"&gt;# or the occasional&lt;/span&gt;
curl https://example.com/install.sh | bash

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server runs as you. It can read your home directory. It sees your SSH keys, your &lt;code&gt;.env&lt;/code&gt; files, your shell history, your browser cookies, your GPG keyring. If the server has a bug — or a malicious dependency sneaks in — the code that reads those files also runs as you. If your kernel or any installed binary has an unpatched local privilege escalation, the agent-invoked tool just inherited that escalation path too.&lt;/p&gt;

&lt;p&gt;That isn't a failure mode of any particular MCP server; it's the default deployment model. &lt;strong&gt;Ambient-permission native processes, shipped by anyone, invoked on demand by an LLM that's notoriously easy to talk into misusing them.&lt;/strong&gt;"Your agent has your credentials and runs strangers' code on request" is the baseline security posture of every MCP setup built on &lt;code&gt;npx&lt;/code&gt; / &lt;code&gt;uvx&lt;/code&gt; / &lt;code&gt;curl | bash&lt;/code&gt; today. It's a full-blown security nightmare that the industry has collectively decided not to look at.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ACT&lt;/strong&gt; — Agent Component Tools — is the model that looks at it.&lt;/p&gt;

&lt;p&gt;Every ACT tool is a WebAssembly component running inside &lt;a href="https://wasmtime.dev" rel="noopener noreferrer"&gt;&lt;code&gt;wasmtime&lt;/code&gt;&lt;/a&gt; — a full VM with a JIT, linear memory, and no ambient host syscalls. Out of the box the component has zero filesystem access, zero outbound network, and no way to spawn a process. Each capability it does use (&lt;code&gt;wasi:filesystem&lt;/code&gt;, &lt;code&gt;wasi:http&lt;/code&gt;) is &lt;strong&gt;declared&lt;/strong&gt; in the component manifest at build time and &lt;strong&gt;granted&lt;/strong&gt; by the operator at run time. The host enforces the intersection: a permissive operator can't escalate past the component's stated intent, a lazy component can't silently exceed the operator's grant. You hand a tool from &lt;code&gt;ghcr.io/someone-else/whatever&lt;/code&gt; to your agent, and the worst-case blast radius is still bounded by the policy you wrote.&lt;/p&gt;

&lt;p&gt;That's the core trade ACT offers. The rest of this post is about why the WebAssembly-component substrate makes it practical.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distribution, for free
&lt;/h2&gt;

&lt;p&gt;A side benefit of picking WebAssembly: the artifact is a single binary that runs everywhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;act info ghcr.io/actpkg/random:latest &lt;span class="nt"&gt;--tools&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That command pulls a small component from GitHub's container registry, reads its metadata from a WASM custom section (no instantiation), and prints the tools it exposes. First pull is cached, every subsequent invocation hits the local &lt;code&gt;~/.cache/act/components&lt;/code&gt;. The artifact is signed by GitHub's attestation workflow and comes with an SBOM — all upstream machinery; ACT just uses it.&lt;/p&gt;

&lt;p&gt;Same bytes, same SHA256, on Linux x86_64, macOS arm64, Windows, Android (validated), Raspberry Pi, inside a browser tab, inside a serverless runtime. No per-platform wheels, no native shims, no build toolchain required on anyone's machine but yours. And because the artifact is a registry object rather than three separate npm/pip/cargo packages, there's one supply-chain path to audit instead of three.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;act&lt;/code&gt; accepts components from any of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;local path: &lt;code&gt;./my-component.wasm&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;HTTP URL: &lt;code&gt;https://example.com/tool.wasm&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;OCI ref: &lt;code&gt;ghcr.io/your-org/tool:1.2.3&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No "my-tool-npm" and "my-tool-pypi" and "my-tool-cargo". One artifact. One namespace.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the sandbox actually works
&lt;/h2&gt;

&lt;p&gt;The isolation comes from three stacked layers, and it's worth separating them because "WASI sandbox" isn't quite the right phrase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;wasmtime&lt;/strong&gt; is the actual isolation. It's a WebAssembly VM: linear-memory bounds enforced, no direct syscalls, no pointer aliasing, no escape outside of explicit host imports. Every ACT target runs the same wasmtime, so the isolation is identical on every OS and CPU.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WASI&lt;/strong&gt; is the capability-import layer on top. A component asks for &lt;code&gt;wasi:filesystem&lt;/code&gt; or &lt;code&gt;wasi:http&lt;/code&gt; imports; the host either wires them up, provides a gated proxy, or leaves them unlinked. There's no "deny" at the capability level — a component either has the import or it doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ACT&lt;/strong&gt; is the policy layer on top of WASI. Filesystem and outbound network are deny-by-default. The component's manifest declares what it needs (&lt;code&gt;[std.capabilities."wasi:filesystem"]&lt;/code&gt;, &lt;code&gt;"wasi:http"&lt;/code&gt;) — this is a ceiling. The operator's runtime flags (&lt;code&gt;--fs-allow /tmp/db.sqlite&lt;/code&gt;, &lt;code&gt;--http-allow host=api.example.com&lt;/code&gt;) are the grant. The host computes the intersection and refuses to wire up anything outside it.&lt;/p&gt;

&lt;p&gt;Deny-CIDR rules sit in front of DNS resolution, so a component that tries to reach &lt;code&gt;169.254.169.254&lt;/code&gt; (the cloud-metadata service) fails with a &lt;code&gt;DnsError&lt;/code&gt; before a socket opens. HTTP redirects are re-checked per-hop, so a 302 to a denied host fails mid-chain instead of quietly succeeding. Details are in the &lt;a href="https://actcore.dev/blog/" rel="noopener noreferrer"&gt;capability-layer deep-dive&lt;/a&gt; (next post).&lt;/p&gt;

&lt;p&gt;The VM gives us isolation. WASI gives us capability imports. ACT gives us the declaration-plus-ceiling model that makes those capabilities safe to hand to third-party code.&lt;/p&gt;

&lt;h2&gt;
  
  
  One component, any transport
&lt;/h2&gt;

&lt;p&gt;Because tools are components, not native processes, the host can serve them over whatever wire format the caller wants.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Claude Desktop / Cursor / Cline → stdio JSON-RPC&lt;/span&gt;
act run ghcr.io/actpkg/sqlite:latest &lt;span class="nt"&gt;--mcp&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--fs-policy&lt;/span&gt; allowlist &lt;span class="nt"&gt;--fs-allow&lt;/span&gt; /tmp/demo.sqlite

&lt;span class="c"&gt;# Web backend → REST-ish HTTP with SSE streaming&lt;/span&gt;
act run ghcr.io/actpkg/sqlite:latest &lt;span class="nt"&gt;--http&lt;/span&gt; &lt;span class="nt"&gt;--listen&lt;/span&gt; &lt;span class="s2"&gt;"[::1]:3000"&lt;/span&gt;

&lt;span class="c"&gt;# Script / CI → one-shot direct call&lt;/span&gt;
act call ghcr.io/actpkg/sqlite:latest query &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--args&lt;/span&gt; &lt;span class="s1"&gt;'{"sql": "SELECT sqlite_version()"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--metadata&lt;/span&gt; &lt;span class="s1"&gt;'{"database_path":"/tmp/demo.sqlite"}'&lt;/span&gt;

&lt;span class="c"&gt;# Browser tab → jco transpile, no server at all&lt;/span&gt;
jco transpile ghcr.io/actpkg/sqlite:latest &lt;span class="nt"&gt;-o&lt;/span&gt; dist/

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same component, same tool, four deployments. Whatever new transport shows up next, the component doesn't change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing one
&lt;/h2&gt;

&lt;p&gt;Rust, the whole SDK surface for a trivial tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;act_sdk&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[act_component]&lt;/span&gt;
&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;#[act_tool(description&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Reverse a string"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;read_only)]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ActResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="nf"&gt;.chars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.rev&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cargo build --target wasm32-wasip2 --release&lt;/code&gt; + &lt;code&gt;act-build pack&lt;/code&gt; and you have a &lt;code&gt;.wasm&lt;/code&gt; that speaks MCP, HTTP, and the CLI. &lt;code&gt;#[act_tool]&lt;/code&gt; derives the JSON Schema from the function signature; &lt;code&gt;#[act_component]&lt;/code&gt; emits the WIT export. Python has the same shape with &lt;code&gt;@component&lt;/code&gt; / &lt;code&gt;@tool&lt;/code&gt; decorators on top of &lt;a href="https://github.com/bytecodealliance/componentize-py" rel="noopener noreferrer"&gt;&lt;code&gt;componentize-py&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is
&lt;/h2&gt;

&lt;p&gt;Early, and deliberately narrow.&lt;/p&gt;

&lt;p&gt;The core spec lives in &lt;a href="https://github.com/actcore/act-spec" rel="noopener noreferrer"&gt;actcore/act-spec&lt;/a&gt; — a small WIT package for cross-cutting types and an opt-in interface package for tool dispatch, with stateful capabilities (sessions, events, resources) layered on as separate opt-in packages. The host ships as &lt;code&gt;act&lt;/code&gt; on npm, cargo, and PyPI. A growing set of components is published on &lt;a href="https://github.com/orgs/actpkg/packages" rel="noopener noreferrer"&gt;&lt;code&gt;ghcr.io/actpkg&lt;/code&gt;&lt;/a&gt; — &lt;code&gt;sqlite&lt;/code&gt;, &lt;code&gt;http-client&lt;/code&gt;, &lt;code&gt;crypto&lt;/code&gt;, &lt;code&gt;encoding&lt;/code&gt;, &lt;code&gt;filesystem&lt;/code&gt;, &lt;code&gt;openwallet&lt;/code&gt;, &lt;code&gt;python-eval&lt;/code&gt;, &lt;code&gt;js-eval&lt;/code&gt;, &lt;code&gt;random&lt;/code&gt;, &lt;code&gt;time&lt;/code&gt;, plus three bridges (&lt;code&gt;mcp-bridge&lt;/code&gt;, &lt;code&gt;openapi-bridge&lt;/code&gt;, &lt;code&gt;act-http-bridge&lt;/code&gt;). Rust and Python SDKs are live; JavaScript via &lt;code&gt;componentize-js&lt;/code&gt; is &lt;a href="https://github.com/bytecodealliance/ComponentizeJS/issues/335" rel="noopener noreferrer"&gt;blocked on upstream async-export support&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;More on the architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://actcore.dev/blog/2026-04-24-capability-ceiling/" rel="noopener noreferrer"&gt;The capability ceiling&lt;/a&gt; — declaration-as-ceiling, DNS-level deny-CIDR, per-hop redirect re-check, ancestor traversal, and what goes wrong when any of those is missing.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://actcore.dev/blog/2026-05-07-act-07-sessions/" rel="noopener noreferrer"&gt;Sessions and bridges&lt;/a&gt; — how stateful components (databases, OpenAPI clients, MCP-server proxies) own their per-session state and where authentication actually belongs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you write MCP servers, build agent tooling, or work on the component model, we'd love your thoughts. Start at &lt;a href="https://actcore.dev/docs/" rel="noopener noreferrer"&gt;actcore.dev/docs&lt;/a&gt;, browse &lt;a href="https://github.com/actcore" rel="noopener noreferrer"&gt;github.com/actcore&lt;/a&gt;, or open a thread in &lt;a href="https://github.com/actcore/act-spec/discussions" rel="noopener noreferrer"&gt;Discussions&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>ai</category>
      <category>opensource</category>
      <category>webassembly</category>
    </item>
  </channel>
</rss>
