Skip to content

Reference & cheat sheet ​

Everything from the labs on one page. ./lab help prints the command list in your terminal.

The ./lab helper ​

Get set up ​

CommandWhat it does
./lab doctorChecks that everything is ready: Node.js, npm packages, aigw, Envoy, otel-tui, free ports, LLM.
./lab llm [provider]Chooses the agent's LLM: workshop, openai, anthropic, gemini, ollama, custom or scripted. Saves to .env and sends a test request.
./lab setupLocal machines only: downloads aigw v1.1.0 (~280 MB) and otel-tui.

Do the labs ​

CommandWhat it does
./lab start <1|2|3>Copies a lab's starting config to workspace/ and starts the router. Backs up your file first.
./lab runChecks workspace/mcproute.yaml, then (re)starts the router with it. Run it after every edit.
./lab agentRuns the sample agent through the router.
./lab agent --as <bot>…as triage-bot or release-bot.
./lab agent --direct…straight to the five servers, without the router (Lab 0).
./lab agent --task "…"…with a different task, e.g. "Deploy checkout 1.4.3 to production".
./lab agent --list…prints the tools it can see, then exits.
./lab agent --brain scripted…without an LLM, for this run only.
./lab agent --token "$T"…with any token, e.g. one you minted.
./lab tools [--as <bot>]Lists the tools the router exposes, to that identity. ⚠ marks destructive tools.
./lab tools --server <name>Lists one server's tools directly, without the router.
./lab check <1|2|3>Verifies a lab's checkpoint and says what's missing. ./lab check 3 then asks three questions; --no-quiz skips them.
./lab solution <1|2|3>Applies a lab's solution and restarts the router. Your version goes to .lab/backups/.

Look around ​

CommandWhat it does
./lab token <bot>Prints a JWT for triage-bot or release-bot. Use it as $(./lab token release-bot).
./lab token <bot> --decodePrints the token's claims instead.
./lab token --sub <name> --scopes "…" --claim k=vMints a token for your own identity. --ttl sets its lifetime (default 12h).
./lab logsThe router's access log, one line per MCP request. -f follows it, --all adds initialize and notifications, --raw prints the JSON.
./lab otelOpens otel-tui to browse traces (Lab 3). Enter opens a trace, Esc goes back, Ctrl+C quits.
./lab connect [--as <bot>]Config snippets to connect your own agent.
./lab statusWhat's running, which lab, which LLM.
./lab stopStops the router and the MCP servers.
./lab resetStops everything and deletes workspace/ (backed up first). --yes skips the question.

Files ​

PathWhat it is
workspace/mcproute.yamlThe file you edit. Your MCPRoute.
workspace/telemetry.envThe router's telemetry settings (Lab 3).
labs/0N-*/start.yaml, solution.yamlEach lab's starting point and solution.
labs/base/*.yamlShared router plumbing: Gateway, Envoy settings, access-log format, the five backends.
labs/keys/The workshop's signing key and JWKS. Public on purpose; never reuse it.
.lab/config.yamlThe full config ./lab run assembled and gave to aigw.
.lab/access.logThe raw access log. Reset each time the router starts.
.lab/router.logThe router's own output. Look here when it fails to start.
.lab/backups/Your previous versions, saved by ./lab start and ./lab solution.
.envYour LLM choice. Git ignores it.
.agents/skills/Agent skills for your coding agent: the lab partner and the three building blocks. .claude/skills/ links to them. See Your coding agent.
AGENTS.md, CLAUDE.mdOrientation for coding agents that open the repository.
.mcp.json, .vscode/mcp.jsonThe Envoy docs MCP server, for Claude Code and VS Code.

Ports ​

PortWhat listens
1975Agent Router: MCP at /mcp, LLM at /v1
1064Router admin: /metrics (Prometheus), /health
3001–3005The MCP servers: issues, ci, deploy, docs, chat (each at /mcp)
4318OTLP over HTTP: otel-tui (it also listens on 4317 for gRPC)
9901Envoy admin (localhost only)

The 8 tools the job needs ​

Through the routerServerInput
issues__get_issueissues{ number }
issues__add_commentissues{ number, body }
ci__list_pipeline_runsci{ branch?, status?, limit? }
ci__get_job_logsci{ run_id, job }
docs__search_docsdocs{ query }
deploy__deploydeploy{ service, version, environment: "staging" | "production" }
deploy__get_deployment_statusdeploy{ deployment_id }
chat__post_messagechat{ channel, text }

The router names every tool <backend>__<tool>. In toolSelector and in authorization rules, use the server's own name (get_issue) and the backend name separately.

Identities ​

IdentityScopesClaims
triage-botissues:read issues:write ci:read docs:read chat:writeteam: checkout
release-botthe same, plus deploy:writeteam: checkout

Every workshop token has iss: https://auth.lakeshore.example, aud: http://localhost:1975/mcp and sub: <identity>, and is valid for 12 hours.

MCPRoute fields used in the labs ​

yaml
apiVersion: aigateway.envoyproxy.io/v1beta1
kind: MCPRoute
spec:
  parentRefs: [...]                  # which Gateway (listener) serves this route
  path: /mcp                         # where agents connect
  backendRefs:                       # one entry per MCP server
    - name: docs                     # backend name, also the tool prefix (docs__…)
      kind: Backend
      group: gateway.envoyproxy.io
      path: /mcp                     # the server's MCP path
      toolSelector:                  # Lab 1: which tools exist on this endpoint
        include: [search_docs]       #   exact names (allow-list)
        # includeRegex: ["^search_"] #   RE2 regex, matched anywhere unless anchored
        # exclude: [delete_page]     #   exact names to remove (wins over include)
        # excludeRegex: ["delete"]   #   regex to remove
      # securityPolicy:              # credentials the router adds toward this server
      #   apiKey: { secretRef: { name: github-token } }
  securityPolicy:
    oauth:                           # Lab 2: authentication
      issuer: https://auth.lakeshore.example
      audiences: [http://localhost:1975/mcp]
      jwks:
        localJWKS: { ... }           # or remoteJWKS, or omit to discover from the issuer
      claimToHeaders:                # Lab 3: copy token claims into request headers
        - claim: sub
          header: x-agent-id
      protectedResourceMetadata:     # served at /.well-known/oauth-protected-resource/mcp
        resource: http://localhost:1975/mcp
        resourceName: Lakeshore Labs ship-it tools
        scopesSupported: [issues:read, deploy:write, ...]
    authorization:                   # Lab 2: who may see and call what
      defaultAction: Deny            # when no rule matches
      rules:                         # evaluated in order; first match wins
        - source:                    # optional: no source = everyone
            jwt:
              scopes: [deploy:write] # the token must have all of these
              # claims: [{ name: team, values: [checkout] }]
          target:                    # optional: no target = every tool
            tools:
              - backend: deploy
                tool: deploy
          cel: '...'                 # optional extra condition, see below
          action: Allow              # Allow (default) or Deny

Rules apply to tools/list too: an agent only sees the tools it could call. Full API: Agent Router MCP docs.

CEL in authorization rules ​

VariableTypeExample
request.mcp.methodstring"tools/call", "tools/list"
request.mcp.backendstring"deploy"
request.mcp.toolstring"deploy" (no prefix)
request.mcp.paramsobjectrequest.mcp.params.?arguments.?environment
request.auth.jwt.claimsmaprequest.auth.jwt.claims.?team.orValue("")
request.auth.jwt.scopeslist of strings"deploy:write" in request.auth.jwt.scopes
request.headersmap, lowercase keysrequest.headers["x-agent-id"]
request.method, request.pathstring"POST", "/mcp"

Read arguments with optional access. .? selects a field only if it exists, and .orValue(x) supplies a default:

text
request.mcp.params.?arguments.?environment.orValue("") == "production"

At tools/list time there are no arguments, and other tools on the same backend have different ones. A plain request.mcp.params.arguments.environment raises an evaluation error in those cases, and an error counts as "no match".

Deny-first. For a condition on arguments, write a Deny rule with the condition, followed by a plain Allow rule. An Allow rule that depends on arguments never matches at list time, so it hides the tool.

Telemetry settings (workspace/telemetry.env) ​

VariableWhat it does
OTEL_EXPORTER_OTLP_ENDPOINTWhere to send traces, e.g. http://localhost:4318 (otel-tui). The sample agent uses it too.
OTEL_BSP_SCHEDULE_DELAYHow often spans are exported, in ms. 500 is good for a workshop.
OTEL_METRICS_EXPORTERnone keeps metrics on the Prometheus endpoint (:1064/metrics) only.
OTEL_AIGW_REQUEST_HEADER_ATTRIBUTESheader:attribute pairs, comma-separated, added to spans, metrics and access logs. Lab 3: x-agent-id:agent.id.
OTEL_AIGW_SPAN_REQUEST_HEADER_ATTRIBUTES, …_METRICS_…, …_LOG_…The same, for one signal only. Spans and logs map agent-session-id to session.id by default; metrics never get session ids.
AI_GATEWAY_TRACING_SEMCONVopeninference (default; MCP spans are CallTool, ListTools) or gen_ai (OpenTelemetry GenAI and MCP conventions; spans are tools/call <tool>, tools/list).
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENTWith gen_ai: true records tool-call arguments and results on spans. Off by default for privacy.

Access-log fields ​

./lab logs --raw prints these JSON lines (format defined in labs/base/gateway.yaml):

FieldMeaning
kindmcp: a request the router sent to an MCP server. door: a request rejected at the front door (401 no token, 403 denied by policy). llm: a model request.
timeWhen the request started.
mcp.method.nametools/list, tools/call, initialize, …
mcp.provider.nameThe backend: issues, deploy, …
mcp.tool.nameThe tool, without prefix.
jsonrpc.request.idThe JSON-RPC id of the request.
session.idFrom the client's agent-session-id header.
agent.idFrom x-agent-id, once Lab 3's two edits are in place.
response_code, duration_msHTTP status and latency.
gen_ai.request.model, gen_ai.usage.*On llm lines: model and token counts.

Metrics ​

From curl -s localhost:1064/metrics | grep mcp_:

MetricLabels
mcp_method_count_totalmcp_backend, mcp_method_name, status, plus agent_id after Lab 3
mcp_request_duration_*request latency histogram
mcp_initialization_duration_*session setup latency per backend
mcp_capabilities_negotiated_totalcapability_type, capability_side, mcp_backend

Agent Router is an Agentic AI Foundation project, built on Envoy. Workshop by Erica Hughberg (Tetrate).