Fundamentals
Servers and ports
One listener, many doors. Every door that speaks HTTP mounts a path on a named server, and unless you say otherwise that server is the one called default. This page covers declaring it, what mounts where, and when to give a door a port of its own.
The default server
A server is a listener you declare under servers, by name. Every surface that serves HTTP mounts onto the one named default unless it names another:
// craft.config.ts
import { defineConfig } from "@routecraft/routecraft";
import "@routecraft/ai";
export const craftConfig = defineConfig({
servers: {
default: { port: 8080, allowedHostnames: ["mcp.example.com"] },
},
http: {}, // http() routes, at /
mcp: {
transport: "http", // MCP, at /mcp
resource: { url: "https://mcp.example.com/mcp" },
},
ops: {}, // health and the ops API, at /health and /ops
});
One port, three doors. MCP over HTTP also names its public resource.url, which it requires outside NODE_ENV=development and test; Expose to an agent has the full block with authentication. The listener is not implied: a surface that mounts HTTP on a server nobody declared stops the start with RC5003, naming the server and the line to add. A project that serves nothing over HTTP needs no server at all, which is why a new project, serving MCP over stdio, has none.
What mounts where
Every mount declares the paths it answers, and all of them are checked against each other before the listener binds. Two surfaces claiming the same path on one server is a startup failure with RC5003, never a race decided by registration order. The one deliberate overlap: when ops shares a server with http, the http built-in /health stands down and ops answers it with the real report.
A door on its own port
Declare a second server and point the surface at it by name. The common split keeps the public doors on one port and the operational surface on another, reachable only from inside the network:
// craft.config.ts
import { defineConfig } from "@routecraft/routecraft";
import "@routecraft/ai";
export const craftConfig = defineConfig({
servers: {
default: {
host: "0.0.0.0",
port: 8080,
allowedHostnames: ["mcp.example.com"],
},
internal: { host: "0.0.0.0", port: 9090 },
},
http: {},
mcp: {
transport: "http",
resource: { url: "https://mcp.example.com/mcp" },
},
ops: { server: "internal" },
});
mcp, acp, ops and http all take server. A named server that ends up with no mount on it is a configuration mistake and fails the start, and two servers cannot claim the same host:port (except port: 0, where the operating system gives each its own). Splitting is a deployment choice, not a different kind of instance: the same capabilities answer on whichever port their door is mounted.
Who may connect
Host. A server binds 127.0.0.1 unless you set host, so a fresh instance is reachable only from the machine it runs on. Bind 0.0.0.0 in a container or on a server. A Kubernetes probe and a reverse-proxy health check reach the pod from outside, so an operational port that must stay private is kept private at the network layer, not by binding loopback.
Hostnames. MCP and the editor protocol check the Host header against the names the server is bound to, which defeats DNS rebinding from a browser. Behind a public hostname, list it in allowedHostnames, or those requests are refused: { port: 8080, allowedHostnames: ["mcp.example.com"] }.
Credentials. A server can carry an auth validator, and every mount on it inherits that validator unless it sets its own. auth: false on a mount removes its wall while keeping the server's validator available to routes that check identity with .authorize(). Credentials and identity explains whose credential a call carries, and Securing capabilities has every validator.
Starting and stopping
port: 0 lets the operating system pick a free port, which is what tests want; the port it chose arrives on the server:listening event. On shutdown a server stops accepting, lets in-flight requests finish for up to shutdownGrace (30 seconds by default), then closes. Deployment covers running the instance always on.
Related
Doors and triggers
Every way a capability is reached, and what starts one when nobody asks.
Expose to an agent
MCP over stdio and over HTTP, and what a client sees.
serversPlugin reference
Every listener option, mounts and claims, and the lifecycle.
Configuration
The servers, http and ops keys.