Overview
courier.config.ts, at the project root, names the project's API sources and what the tools around them need. defineConfig types it:
import { defineConfig } from "@noverax/courier/config"
import { api } from "./api/sources"
export default defineConfig({
sources: { api },
studio: { port: 6280 },
server: { allowedOrigins: ["https://app.example.com"] },
runtime: { appOrigin: "http://localhost:3000" },
})Studio reads it, and so do the Studio runtime (attachRuntime()) and the standalone server (courier-server) — both of which run in production. So the file imports nothing meant only for development: mock servers are declared in courier.mocks.ts beside it, which only Studio reads — see Mock Servers.
The file may also be named courier.config.mts, .cts, .js, .mjs or .cjs, looked for in that order after .ts. How Studio Loads the Project says which files Studio watches and how a change reloads them.
sources
Required. The sources the project serves and calls, by the name its callers use: sources: { api } is reached as createProxyCaller<typeof api>("api"). A project without an API yet starts with sources: {}.
imports
The API specifications Studio imported, by key. Studio writes an entry when it imports a specification and reads it on every re-sync, so an entry is usually Studio's to write; editing one by hand is supported.
| Field | Type | |
|---|---|---|
type | "openapi" | "swagger" | "graphql" | "asyncapi" | "postman" | The specification's format, which picks its parser |
importType | "url" | "file" | "raw" | Where it came from. A file's path and a pasted specification are supplied in Studio at each re-sync, never stored |
from | string | url only: the address Studio fetches it from |
auth | { type: "bearer", token } | { type: "basic", username, password } | { type: "header", headers } | url only: sent with every fetch of from, to its own origin only |
pinHash | string | url only: the one specification the import is generated from, by the hash Studio records; another is refused |
outputDir | string | The directory that holds the import's own directory, <outputDir>/<source-name>/ |
sourceName | string | The source's name in the generated defineApi call — default: the key |
validation | "zod" | "valibot" | "arktype" | "none" | The schema library the generated code uses. With ArkType, a reference cycle of more than about 40 schemas — Stripe's core objects form one of 83 — goes past TypeScript's depth limit where an endpoint answers with one of them (TS2321); Zod and Valibot type such a specification |
groupBy | "tag" | "path" | "single" | How endpoints are grouped: by tag, by the resource their path names, or in no group at all. An endpoint with no tag, or with no segment of its path to group by, sits in the API itself, beside the groups — see Import an API |
generateTypes, generateSchemas | boolean | Whether types and schemas are written |
generateClient | "direct" | "proxy" | "both" | "none" | Which callers are generated: the direct caller in the import's index.ts, the proxy caller in proxy.ts, which browser code imports |
addAuthMiddleware | boolean | Whether the specification's security scheme becomes middleware |
includeComments | boolean | Whether descriptions become doc comments. Each endpoint keeps its operation's description as .meta({ description }) either way: an agent's tool describes itself by it, and the OpenAPI written from the definition keeps it |
include, exclude | { tags?, paths?, operationIds? } | The endpoints generated: include names the ones kept, then exclude the ones left out |
transform | (spec) => spec | void | Run on a copy of the parsed specification before anything is generated from it |
See Import an API.
studio
What Studio reads: port, host, mockBasePort and open when it starts, and allowedSpecHosts whenever a fetch would reach a private host. A value Studio cannot use — a port that is not a whole number from 1 to 65535, an empty host, an open that is not true or false — fails the config, and its error names the field.
| Field | Default | |
|---|---|---|
port | 6280 | The port Studio serves on; --port overrides it |
host | "localhost" | The address it binds; --host overrides it |
mockBasePort | 7001 | Where the ports Studio assigns to mocks that name none begin |
open | false | Open Studio in the browser when it starts; --open and --no-open override it |
allowedSpecHosts | — | Hosts on a private network Studio may fetch a specification from, such as ["gitlab.internal", "10.0.0.7:8080"]. This machine needs no entry |
server
What the standalone server reads when its command-line flags say nothing.
| Field | Default | |
|---|---|---|
port | 3001 | --port overrides it |
host | "127.0.0.1" | --host overrides it; "0.0.0.0" or "::" to accept other machines |
allowedOrigins | — | The origins of the browser apps that call the server, written as a browser sends them. Left out, only the server's own origin, and loopback ones outside production. --allowed-origins overrides it |
allowedHosts | — | The host names the server is reached by; a request to another is refused with 421. --allowed-hosts overrides it |
maxBodyBytes | 1 MB | The largest JSON envelope the proxy reads; a larger one is refused with 413 |
maxUploadBytes | 10 MB | The largest multipart/form-data upload the proxy reads |
runtime
What attachRuntime() needs in the application's process.
| Field | |
|---|---|
appOrigin | The origin the application listens on, such as http://localhost:3000. Studio's Playground resolves a relative proxyPath.sse or proxyPath.ws against it. Looked up in turn: this field, COURIER_APP_URL, APP_URL, then, in development, the origin the framework adapter sees on the first request — a deployed server never takes one from a request, whose Host its sender chooses. With none of them, a Playground stream or channel in proxy mode fails with APP_ORIGIN_NOT_CONFIGURED |
agents
What the project offers to AI agents as MCP tools. The server in your editor, Studio and courier-studio diff read it here; an application's own MCP endpoint takes it as createMcpHandler({ agents: config.agents }). Left out, the endpoints marked .agent() are offered and nothing else.
| Field | Default | |
|---|---|---|
offer | "marked" | Which endpoints are offered without .agent(): "marked" — none; "reads" — every read; "all" — every endpoint, with the access its verb implies |
allow | — | Patterns of endpoints to offer although offer leaves them out: ["shop.orders.*"] |
deny | — | Patterns of endpoints no agent sees, whatever offers them: ["billing.**"] |
access | "destructive" | The most a tool may do: "read", "write" or "destructive"; an endpoint that does more is offered to no agent |
confirm | "destructive" | From which access a person confirms each call: "destructive", or "write" for every call that changes something |
A pattern names an endpoint by its call under its source; * stands for any part of one name, ** for any number of names. A key or a value the policy does not know stops the MCP server with AGENT_POLICY_INVALID — a misspelt key under agents is named when Studio loads the config, too. See What agents may call.