Skip to content

API Referencecourier.config.ts

courier.config.ts

Every field of courier.config.ts: the sources, the API imports Studio keeps in sync, Studio, the standalone server, the runtime and what agents may call.

6 min readboth modes

Overview

courier.config.ts, at the project root, names the project's API sources and what the tools around them need. defineConfig types it:

courier.config.ts
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.

FieldType
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
fromstringurl 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
pinHashstringurl only: the one specification the import is generated from, by the hash Studio records; another is refused
outputDirstringThe directory that holds the import's own directory, <outputDir>/<source-name>/
sourceNamestringThe 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, generateSchemasbooleanWhether 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
addAuthMiddlewarebooleanWhether the specification's security scheme becomes middleware
includeCommentsbooleanWhether 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 | voidRun 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.

FieldDefault
port6280The port Studio serves on; --port overrides it
host"localhost"The address it binds; --host overrides it
mockBasePort7001Where the ports Studio assigns to mocks that name none begin
openfalseOpen 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.

FieldDefault
port3001--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
maxBodyBytes1 MBThe largest JSON envelope the proxy reads; a larger one is refused with 413
maxUploadBytes10 MBThe largest multipart/form-data upload the proxy reads

runtime

What attachRuntime() needs in the application's process.

Field
appOriginThe 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.

FieldDefault
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.