VEXLO_API_URL and VEXLO_TOKEN once, makes exactly two HTTP calls in order, and registers no
tool until both have answered. Each failure exits with its own code and one line on stderr.
The two calls
Both bodies, from the server these pages were captured against. The server writes compact
JSON, so each arrived as one line; the blocks below are
jq . renderings of it:
team_ids is the token’s team-set,
which every scoped tool compares a requested team against before it builds a query.
limits supplies the bounds the tool schemas carry: the search ceiling is the lower of 100 and
search_limit_max, the function and tag list ceiling the lower of 200 and the same field,
vexlo_get_values accepts values_max_paths paths, and vexlo_get_trace asks for
trace_steps_limit_max steps per call. service_scope and environment_scope carry the
token’s own pins, null when it has none; vexlo token create takes -service and
-environment for a read token too, which
authentication and tokens covers. A service-pinned read token never
gets past this call, so in any session that did start service_scope is null, and a
misspelled service name is indistinguishable from a real, empty one until the server answers:
find covers vexlo_check_coverage, which is how an AI agent learns the real
names. features is stored and read by no tool today.
One module reads the two variables, once, before anything touches the network or stdio, and the
session it builds is frozen. Nothing re-reads either variable and nothing re-runs the two calls,
so a token revoked while the process is up still passed startup: the next tool call that uses it
returns authentication_failed, which errors, rate limits,
retries covers. Restarting the process is what picks up a new
token or a new server.
stdout carries the protocol, stderr carries everything else
The MCP stdio transport owns stdout for as long as the process runs: every byte on it is newline-delimited JSON-RPC. Diagnostics, including every message below, go to stderr. A wrapper script that prints anything to stdout breaks the session.Exit codes
Code 70 is
EX_SOFTWARE from sysexits.h, kept distinct from 1 because Node’s own default for
an uncaught failure is 1, which would collide with the configuration case. Two process-level
handlers route a late failure to it as well, so a bug after the session is up never exits 1.
A missing variable names itself:
vexlo build reports an api_version other than v1, and the whole limits block
is parsed before the versions are compared, so the line below came from a stand-in that answers
GET /v1/capabilities with api_version: "v2" and a complete limits object:
VEXLO_TOKEN is unset, the port is empty, and the
request URL cannot be built.
A token the server does not know fails at the second call, carrying the server’s own message:
service_scope to be an array of names and the server
sends a bare string:
/v1/whoami with
"service_scope": "delivery-service" and HTTP 200, and an environment-pinned read token starts
normally. Mint the MCP server’s read token without -service.
A token of the wrong role names the role it resolved to:
VEXLO_API_URL has to carry its scheme. localhost:18300 parses as a URL whose scheme is
localhost:, so it passes the check behind code 1 and fails when the first request is built:
http://127.0.0.1:18300 is the same address written correctly. 127.0.0.1:18300 exits 1
instead: 127.0.0.1 cannot be a URL scheme, where localhost can.
What startup costs
As the server sent them, one line each, the two bodies measure 580 bytes (198 tokens) and 92 bytes (26 tokens), so the handshake is 672 bytes and 224 tokens, measured on this server with the tokenizer token costs names. The indented blocks above are wider than that, because indenting them added bytes the wire never carried. The handshake is paid once per process launch and appears in no tool result. A largerfeatures or limits block on a future
server moves the capabilities half of that figure.
Each startup call gets 10 seconds; each later tool call gets 30. Both are chosen, not measured:
long enough for a cold local server, short enough that a hung connection does not leave the host
waiting a full minute.