Six chapters in, the engine has a finished thing in its hands. The schema compiler turned a form into a tool contract. The collection abstraction bound that contract to a spreadsheet, a SQL table, or a REST endpoint. The runtime serves it over the protocol. The security plane wrapped it in auth, rate limits, and an audit log. What comes out the far end of that pipeline is a compiled artifact: the contract, the binding, the runtime, and the security policy, frozen into one deployable unit.

This chapter is about the last edge of the pipeline, the part labelled SHIP. There is exactly one artifact, and there are two ways to run it. The design constraint that makes the whole series hang together is that those two runtimes are protocol-identical. An agent that calls a hosted AppElixir server and an agent that calls your self-hosted Docker container send the same tools/call payload and get the same structured result back. Nothing in the Model Context Protocol exposes the runtime, so nothing in the agent's behaviour depends on it.

One artifact, two runtimes

The artifact is the same object the runtime chapter described: a server that answers the initialize handshake, lists tools, and executes tools/call over stdio or streamable HTTP. The engine does not produce a "hosted build" and a "self-host build." It produces one build and chooses how to inject the things that differ.

Hosted

You hit deploy. The engine schedules the artifact on AppElixir's multi-tenant runtime and hands back three things: an HTTPS URL, an agent token, and a config snippet you paste into Claude Desktop, Cursor, or any MCP client. The per-tool rate limits and the audit log from the security plane are already wired, because they are part of the artifact, not part of the hosting. You manage agent tokens and read the audit log from the dashboard. There is nothing to operate.

Self-hosted

You click export instead. The engine writes the same artifact into a Docker image and gives you a registry reference plus a one-file run recipe. You run it on Fly, Railway, Render, a docker compose on a VM, or a Deployment in your own Kubernetes cluster. The rate limiter and audit log still run, in-process, exactly as they did hosted, because they ship inside the artifact. The difference is only where the audit rows land and who holds the keys. If your reason for self-hosting is data residency or sovereignty, that is the lever: the bytes and the logs stay inside your boundary. We wrote about when that tradeoff is worth it in self-hosted no-code, when it matters and EU data residency on no-code.

The agent does not get a vote. It reads a server URL, completes the handshake, sees the same tool list, and calls the same tools. Protocol parity is not a nice-to-have here. It is the contract that lets a no-code engine promise "host it yourself" without forking the runtime, and it is the same reason the protocol exists at all. The reference implementations in modelcontextprotocol/servers are containerized for the same reason: an MCP server is supposed to be a portable unit, and the protocol spec says nothing about where it runs.

Config and secrets: injected, never baked

The hard part of "one image, two runtimes" is keeping secrets out of the image. If the data-source password were a layer in the Docker build, the hosted and self-hosted images would differ, and a leaked registry pull would leak your database. So the engine compiles the artifact with zero credentials in it. The image carries the contract and the runtime. Everything sensitive is read from the environment at start.

By hand

Hand-rolling a server with the SDKs, the discipline is on you. It is genuinely easy to COPY .env into the image during a late-night fix, hard-code a Postgres URL into a default argument, or commit a token into the config you tested with. Each of those bakes a secret into a layer, and layers are forever. You have to remember the rule on every build.

With the engine

The compiler refuses to embed a credential. Data-source connection details and agent tokens are declared as environment inputs in the contract, and the artifact reads them at boot. The same image runs hosted (AppElixir injects the values from its secret store) and self-hosted (you inject them through your platform's secret mechanism). The build is identical; only the injection point moves.

The run recipe makes the boundary explicit. Secrets arrive as environment variables, the artifact reads them once at start, and nothing sensitive is ever written to a layer:

# Export the compiled artifact, then run it anywhere
docker pull registry.appelixir.com/acme/lookup-customer:1.4.0

docker run --rm -p 8080:8080 \
  -e DATA_SOURCE_URL="$DATA_SOURCE_URL" \
  -e AGENT_TOKEN="$AGENT_TOKEN" \
  -e AUDIT_SINK="stdout" \
  registry.appelixir.com/acme/lookup-customer:1.4.0

# The agent reaches it over streamable HTTP at /mcp
# identical to the hosted URL, identical handshake

The same shape drops straight into a Fly secrets set, a Railway variable, a Render environment group, or a Kubernetes Secret mounted as env. None of those touch the image. If you care about whether you can walk away with your work, this is the concrete answer: the artifact is yours, the image is portable, and the secrets were never ours to begin with. We treat that as a feature, not a footnote, in do you own your AI-built app.

The inspector is a fake agent

Before any of this ships, you test it, and the engine ships the test harness in the box. The built-in inspector is a fake-agent UI: it completes the same initialize handshake a real client does, reads your tool list, and lets you fire a tools/call with the exact JSON shape a real agent sends. It mirrors the open-source MCP Inspector from the protocol project, so what you see in the harness is what Claude or Cursor will see in production.

Three calls catch most bugs before an agent ever touches the server:

Here is the malformed-input call as the inspector sends it, and the structured error the security plane returns instead of leaking internals:

// tools/call with a bad type for "limit"
{
  "method": "tools/call",
  "params": {
    "name": "lookup_customer",
    "arguments": { "email": "ada@example.com", "limit": "ten" }
  }
}

// structured result the agent can reason about
{
  "isError": true,
  "content": [{ "type": "text",
    "text": "Invalid argument 'limit': expected integer, got string." }]
}

That error shape is not cosmetic. As covered in the security plane chapter, validation is a boundary: a bad call bounces before it reaches your data source, and the agent gets a message it can correct itself with rather than a 500 it gets confused by.

The pre-ship contract check

The inspector is for you. The contract check is for the artifact, and it runs automatically before either runtime serves a single request. It is the last gate on the pipeline, and it folds in the work of the earlier chapters:

A server that fails any of these does not deploy and does not export. The point is that the gate is identical for hosted and self-hosted, so the artifact you run on your own k8s passed exactly the checks the hosted one did.

Tying the pipeline together

This is the finale, so it is worth saying the whole thing in one breath. A form schema is a tool contract. The compiler turns that schema into typed MCP input and output. The collection abstraction binds the contract to one data source, kept small and composable rather than a megaserver. The runtime serves it over the protocol with a real handshake, tool listing, and structured results. The security plane adds per-agent auth, per-tool rate limits, an audit log, and schema validation as a boundary, read-only by default. And the ship stage compiles all of that into one artifact that runs hosted or as a Docker image you own, validated by the inspector and the contract check before it goes live.

The reason a no-code layer can produce a correct MCP server, and not a toy, is the thesis from the very first chapter: the plumbing is identical for every server, so only the data and the tool design are differentiated. That is exactly the part a human should be doing by hand, and exactly the part the engine leaves to you. Everything else, the engine compiles. If you want the full map of how those pieces fit, that is chapter one, the architecture overview, where the teardown began.