Echolot
Reference

Workspace files

The workspace format, file by file and key by key, for reading diffs, writing files by hand and generating them.

A workspace is a folder ending in .echolot with plain TOML files in it: one per request, environment, MCP server, mock and flow. The Finder shows it as one document; in Terminal, Git and your editor it is a normal folder. Echolot writes every file the same way every time (keys in a fixed order, nothing written that has its default value), so a change in the app is a small change in the diff. See Git and teams.

Alpenglow.echolot/
  workspace.toml                     the workspace: ID, name, variables, auth, agents, proxy, certificates
  environments/
    staging.toml                     one environment
  requests/
    _folder.toml                     the order of the top level
    _folder.pre.js                   the workspace's pre-request script (and _folder.test.js)
    auth/
      _folder.toml                   a folder: name, order, docs, variables, auth
      sign-in.req.toml               an HTTP request
      sign-in.body.json              its body
      sign-in.pre.js                 its pre-request script
      sign-in.test.js                its test script
    catalog/
      search-products.gql.toml       a GraphQL request
      search-products.graphql        its query
  mcp-servers/
    alpenglow-mcp.toml               an MCP server
  specs/
    alpenglow.yaml                   an OpenAPI document
    shop.graphql                     a GraphQL schema
    inventory.proto                  a protobuf file
  mocks/
    shop.toml                        a mock server: settings and routes
    shop.js                          its routes in JavaScript
  flows/
    place-test-order.flow.toml       a flow
  assistant/
    checking-orders.md               a shared Assistant conversation, to read
    checking-orders.json             the same, to open in the app

Rules for every file

  • Files are TOML 1.0, UTF-8.
  • Each starts with echolot = 1, the format's version. A file with a higher number was saved by a newer Echolot, which Echolot says instead of opening it.
  • File names are slugs: lowercase letters, digits and hyphens, at most 80 characters, made from the item's name (Sign in is sign-in). The name itself is the name key.
  • Paths between items are slugs joined by /, relative to requests/: auth/sign-in, orders.
  • Text fields can hold {{variables}} and dynamic values.
  • Lists of things are TOML arrays of tables ([[headers]]). Anything with enabled = false is kept but switched off; without the key it is on.

What never goes in the files

  • Secret values. A secret variable is written by its name with secret = true and no value; the value lives in the Keychain on your Mac. Fields that need a secret (a password, a proxy password, a client secret) should hold a {{variable}} that is a secret. Echolot offers to move a password you type into the Keychain, and imports do so by themselves. See secrets.
  • OAuth tokens and MCP sign-ins: in the Keychain.
  • Values that scripts set with pm.environment.set and the like: they last for the window or the command.
  • History and responses: in Application Support on your Mac, one history per workspace. See history.
  • Your own layout: the pane layout and its sizes, the open tabs, the environment you chose.

So a workspace is safe to commit and share. A teammate sets their own secret values.

workspace.toml

# Echolot workspace. Requests live in requests/, one file each.
echolot = 1
id = "0b7d4c1e-2f6a-4e38-9c51-7a1e3d9b5f20"
name = "Alpenglow"

[[variables]]
name = "baseURL"
value = "https://demo.echolot.dev/v1"

[[variables]]
name = "token"
secret = true
KeyMeaning
idThe workspace's ID. Secrets in the Keychain belong to it, so copies with the same ID share them. An exported copy gets a new one.
nameThe workspace's name.
[[variables]]Workspace variables. See variables.
[auth]Auth for every request that inherits it. See auth. Default: none.
[agents]Rules for AI agents. See agents.
[proxy]The workspace's own proxy, instead of the app's.
[[certificates]]CA certificates to trust and client certificates to send, per host.

Proxy

KeyMeaning
modemanual (default), system, environment (the HTTPS_PROXY family) or none.
urlThe proxy: http://host:port, https://… or socks5://host:port.
username, passwordCredentials for the proxy. Write the password as a secret {{variable}}.
bypassHosts to reach directly, as an array.

Certificates

Each [[certificates]] table is a rule for one host:

KeyMeaning
hostThe host the rule is for.
trustPEM files of CA certificates to trust, as an array.
certificateA client certificate: a PEM file, or a .p12/.pfx file.
keyThe client certificate's PEM key, when the certificate file has none.
passphraseThe key's or PKCS#12 file's passphrase. Write it as a secret {{variable}}.

Agents

[agents] sets what AI agents may do in this workspace, through Echolot's MCP server and the Assistant. See agents.

[agents]
allowed-hosts = ["demo.echolot.dev"]
max-requests = 200
max-requests-per-minute = 30

[agents.rules]
unsafe-send = "ask"
destructive = "refuse"
KeyMeaning
enabledfalse turns agents off for the workspace: they get nothing from it. Default true.
allowed-hostsHosts that count as the workspace's own, besides those its requests use.
max-requestsRequests one agent session may send in all.
max-requests-per-minuteRequests one agent session may send per minute.
[agents.rules]A rule per kind of action: allow, ask or refuse.

The kinds of action are read, safe-send, unsafe-send, protected-send, new-host, workspace-write and destructive. A stricter rule always counts. A looser one counts only once you trust it (the app asks; the command line needs --trust-workspace-policy). protected-send and new-host can never be set to allow.

environments/

One file per environment; its file name is its slug, used by --env.

echolot = 1
name = "Production"
protected = true

[[variables]]
name = "baseURL"
value = "https://api.alpenglow.example/v1"

[[variables]]
name = "password"
secret = true
KeyMeaning
nameThe environment's name.
protectedtrue makes it a protected environment: every send asks first.
[[variables]]Its variables.

Variables

The same in workspace.toml, environments, folders and requests:

KeyMeaning
nameThe variable's name, used as {{name}}.
valueIts value. Left out for a secret.
secrettrue for a secret: the value is in the Keychain.
enabledfalse switches it off.

requests/

The requests folder holds the tree you see in the sidebar. Each folder in it is a folder of the tree, each request file a request. The kind of request comes from the file's suffix:

SuffixKind
.req.tomlHTTP, including Server-Sent Events
.gql.tomlGraphQL
.grpc.tomlgRPC
.ws.tomlWebSocket
.sio.tomlSocket.IO
.mqtt.tomlMQTT
.mcp.tomlA call to an MCP server

Beside a request, sharing its slug:

FileHolds
<slug>.body.<ext>The request's body in its own format: .json, .xml, .txt, .html, .graphql or .form.txt.
<slug>.graphqlA GraphQL request's query.
<slug>.pre.jsThe pre-request script.
<slug>.test.jsThe test script.

_folder.toml

Every folder has one, and so does requests/ itself, for the top level.

echolot = 1
name = "Auth"
order = ["sign-in", "who-am-i", "client-credentials"]
docs = "Send \"Sign in\" first: it captures the access token into {{token}}."
KeyMeaning
nameThe folder's name. Not in requests/_folder.toml.
orderThe slugs of what is in the folder, in the order the sidebar and runs use. Anything not listed comes after, sorted by slug.
docsThe folder's documentation, in Markdown.
specThe spec the folder follows, such as specs/alpenglow.yaml.
spec-urlWhere that spec is fetched from, when it came from a URL.
agent-toolstrue offers the folder's requests to agents as tools.
[[variables]]The folder's variables, for everything in it.
[auth]The folder's auth, for everything in it that inherits. Default: inherit.

Beside it, _folder.pre.js and _folder.test.js are the folder's scripts; in requests/ they are the workspace's. They run for every request inside, from the workspace's down to the request's own. A folder that follows a spec also has _spec-lock.json, what it last took from the spec; let Echolot keep it.

Keys of every request

echolot = 1
name = "Sign in"
method = "POST"
url = "{{baseURL}}/auth/token"

[body]
syntax = "json"
file = "sign-in.body.json"

[[headers]]
name = "Content-Type"
value = "application/json"

[[assertions]]
target = "status"
check = "equals"
expected = "200"

[[captures]]
variable = "token"
from = "json"
path = "$.access_token"
scope = "workspace"
secret = true
KeyMeaning
nameThe request's name.
urlThe URL, with its query string. Every kind but MCP calls has one.
docsThe request's documentation, in Markdown.
spec-operationThe spec operation it was made from, such as POST /auth/token.
[[headers]]name, value, and enabled = false for one that is off.
[[disabled-params]]Query parameters that are switched off: name, value. The ones that are on are in the URL.
[[disabled-form-fields]]Form fields that are switched off: name, value.
[[variables]]The request's own variables.
[[assertions]]Its checks.
[[captures]]Values it stores in variables after each response.
[auth]Its auth. Default: inherit from its folder.
[settings]Settings that differ from the app's.

HTTP requests (.req.toml)

KeyMeaning
methodThe method: GET, POST or any token. Default GET.
[body]The body; none when left out.

The [body] table takes one of three forms:

FormKeys
Textsyntax: json, xml, text, html, graphql or form (URL-encoded form fields). file: the body file beside the request.
A file streamed from diskstream-from: the file's path.
Multipartsyntax = "multipart", and a [[body.parts]] table per part: name, then value (text) or file (a path), content-type, enabled.

GraphQL requests (.gql.toml)

echolot = 1
name = "Search products"
url = "{{baseURL}}/graphql"
operation = "SearchProducts"
schema = "specs/shop.graphql"
query-file = "search-products.graphql"
graphql-variables = """
{
  "text": "ultralight"
}"""
KeyMeaning
query-fileThe file beside the request with the query, <slug>.graphql.
operationThe operation to run, when the query has several.
graphql-variablesThe GraphQL variables, as JSON text. Older files call it variables.
schemaThe schema it is checked and completed against: a file in specs/.
transportpost (default), get, or websocket for subscriptions.
persisted-queriestrue sends only the query's SHA-256 first, and the query when the server doesn't know it (automatic persisted queries).
batchtrue sends a document with several operations as one array, one entry per operation.
[[uploads]]File uploads: variable (the variable the file goes to) and path.

gRPC requests (.grpc.toml)

KeyMeaning
protoThe .proto file describing the service, such as specs/inventory.proto.
reflectiontrue asks the server's reflection instead.
serviceThe service, such as alpenglow.inventory.v1.Inventory.
rpcThe method, such as Reserve.
messageThe message as JSON text; an array of messages for a client-streaming method.
deadline-msThe deadline in milliseconds.
compressiongzip or deflate for the messages sent.

WebSocket requests (.ws.toml)

KeyMeaning
subprotocolsSubprotocols to offer, as an array.
[[messages]]Saved messages: name, text, enabled.

Socket.IO requests (.sio.toml)

KeyMeaning
versionThe server's Socket.IO version: 4 (default), 3 or 2.
pathWhere the server listens. Default /socket.io/.
namespaceThe namespace to join, such as /support.
socket-authThe auth payload, as JSON text.
[[emits]]Saved events: name, event, arguments (JSON text), ack = true to ask for an acknowledgement, enabled.

MQTT requests (.mqtt.toml)

KeyMeaning
version5 (default) or 3.1.1.
client-idThe client ID; a random one when empty.
clean-startfalse asks the broker to keep the session. Default true.
keep-aliveSeconds between pings. Default 60.
[will]The last will: topic, payload, qos, retain.
[[subscriptions]]filter, qos (default 0), no-local, enabled.
[[messages]]Saved messages to publish: name, topic, payload, qos, retain, content-type, enabled.

The user name and password are the request's Basic auth.

MCP calls (.mcp.toml)

echolot = 1
name = "search_catalog"
server = "alpenglow-mcp"
tool = "search_catalog"
arguments = """
{
  "query": "ultralight tent",
  "limit": 3
}"""
KeyMeaning
serverThe server's slug in mcp-servers/.
tool, resource, prompt or methodWhat to call: a tool by name, a resource by URI, a prompt by name, or a raw JSON-RPC method. One of them.
argumentsThe arguments as JSON text.

Assertions

KeyMeaning
targetWhat is checked: status, header, json, body, time (milliseconds), size (bytes), schema (JSON Schema), graphql-errors, mcp-error, mcp-output-schema, openapi, grpc-status.
nameFor header: the header's name.
pathFor json: a JSONPath, such as $.items.length.
checkequals, not-equals, contains, not-contains, less-than, at-most, greater-than, at-least, exists, not-exists, matches (a regular expression), is-type, is-one-of, matches-schema.
expectedThe value to compare with; not written for checks that need none.
enabledfalse switches it off.

See scripts and tests for which checks each target offers.

Captures

KeyMeaning
variableThe variable to store the value in.
fromjson, form, msgpack, xml, header, cookie, status or body.
pathWhere the value is: a JSONPath, an XPath, a header or cookie name. Not for status and body.
scopeenvironment (default: the active environment) or workspace.
secrettrue stores the value as a secret, in the Keychain.
enabledfalse switches it off.

A response of 400 or more, or a value that isn't there, leaves the variable as it was. See captures.

Settings

KeyMeaning
timeoutSeconds for the whole exchange.
rawtrue sends no automatic headers.
verify-tlsfalse skips certificate verification.
follow-redirectsfalse stops at a redirect.
max-redirectsHow many redirects to follow.
decompressfalse keeps the body as the server encoded it.
cookiesfalse neither sends nor stores cookies.
http-versionautomatic, http1, http2 or http3.
stream-limitSeconds to listen to an event stream or a session.

See request settings.

Auth

[auth] has a type, and the keys of that type. A request or folder without [auth] inherits; the workspace's default is no auth.

typeKeys
inheritNone: take the folder's.
noneNone: no auth, even if the folder has some.
basic, digestusername, password
bearertoken; prefix when not Bearer
api-keyname, value, in (header or query)
oauth2grant (authorization-code, client-credentials, device-code), authorization-url, token-url, device-authorization-url, client-id, client-secret, scope, audience, redirect-uri; pkce = false to turn PKCE off; client-authentication = "body" to send the client's credentials in the body instead of Basic; header-prefix when not Bearer
aws-signatureaccess-key-id, secret-access-key, session-token, region, service
jwtalgorithm (HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384), secret, secret-base64, payload, header, issued-at = false to leave out iat, expires-in (seconds, default 300), in (header or query), prefix, query-name (default token)
hawkid, key, algorithm (sha256 or sha1), ext, payload-hash
edgegridclient-token, client-secret, access-token, max-body (default 131072), headers-to-sign
ntlmusername, password, domain, workstation

Write every password, token and key as a secret {{variable}}. See auth.

mcp-servers/

One file per MCP server; the file name is the slug that MCP calls name.

echolot = 1
name = "Alpenglow MCP"
transport = "http"
url = "https://demo.echolot.dev/mcp"
KeyMeaning
nameThe server's name.
transporthttp (Streamable HTTP) or stdio (a program Echolot starts).
urlFor http: the endpoint.
[[headers]]For http: name, value, enabled.
commandFor stdio: the program to start.
argumentsFor stdio: its arguments, as an array.
working-directoryFor stdio: the folder it starts in.
[[environment]]For stdio: environment variables, name, value, enabled.
[oauth]Client settings for a server that signs in with OAuth: client-id, client-secret, scope, redirect-uri.

See adding servers.

specs/

Spec files as they are: OpenAPI documents (.yaml, .yml, .json), GraphQL schemas (.graphql SDL, or an introspection result as JSON) and protobuf files (.proto). Echolot edits them in place and keeps your formatting. Requests and mocks name them by their path, such as specs/alpenglow.yaml. See specs.

mocks/

One file per mock server, and its JavaScript routes beside it.

echolot = 1
name = "Shop"
spec = "specs/alpenglow.yaml"
port = 4010
delay = 200

[[scenarios]]
operation = "GET /products/{sku}"
status = "404"

[[routes]]
id = "e5f6a7b8"
method = "GET"
path = "/users/{id}"
summary = "One user"

[[routes.responses]]
name = "OK"
status = 200
content-type = "application/json"
headers = [{ name = "X-Request-Id", value = "{{$randomUUID}}" }]
body = """
{
  "id": "{{request.params.id}}",
  "name": "{{$randomFullName}}"
}"""

[[routes.responses]]
name = "Not found"
status = 404
content-type = "application/json"
rules = [{ source = "path", name = "id", value = "0" }]
body = """{ "error": "no user {{request.params.id}}" }"""
KeyMeaning
nameThe mock's name.
specThe spec it serves. Without it, only the mock's routes answer.
portThe port. Default 4010.
share-on-networktrue answers on every network interface, not only this Mac.
delayMilliseconds before each answer.
failure-ratePercent of requests answered with an injected 500, 0 to 100.
validate-requestsfalse answers requests that break the spec. Default true.
[[scenarios]]The answer chosen for an operation of the spec: operation (such as GET /pets), status, example (the example's name).
[[routes]]A route made by hand: id (stays the same while the route changes), method (or ANY), path ({id} for a parameter), summary, enabled = false for a route that is off, response (the name of the default response; the first when left out), request-content-type and request-body (what requests made from it send).
[[routes.responses]]One answer of the route above: name, status, content-type, headers (name and value), rules (source is path, query, header or body; name; value, with * for any and empty for none) and body.

mocks/<slug>.js holds the mock's routes in JavaScript, which answer before the routes made by hand and the spec. For the spec's operations, responses never live in the mock: examples go into the spec. See mocks, routes and fake data and scripted routes.

flows/

One file per flow, <slug>.flow.toml. Echolot writes blocks, wires and notes in a fixed order, so moving a block changes two lines.

echolot = 1
name = "Place a test order"
description = "Places an order, then asks a person whether to keep it."

[mcp]
tool = "place_test_order"
description = "Places a test order on the demo shop, asking a person before keeping it."

[[blocks]]
id = "signin"
kind = "request"
title = "Sign in"
position = [330, 250]

[blocks.settings]
request = "auth/sign-in"

[[wires]]
from = "start.run"
to = "signin.run"
KeyMeaning
name, descriptionThe flow's name and what it does.
[mcp]Offers the flow to agents as a tool: tool (its name) and description.
[schedule]Runs it on a schedule: every-minutes, or cron (such as "0 6 * * *").
[webhook]Runs it when a request arrives: method (default POST) and path.
[[inputs]]Its inputs: name, type (any, text, number, bool, object, list, secret), description, default, required.
[[blocks]]Its blocks: id, kind, title, position ([x, y] on the canvas), paused, color, and [blocks.settings] with the kind's settings.
[[wires]]Links: from and to, each block.port.
[[groups]]Groups of blocks: title, blocks (their IDs), color.
[[notes]]Notes on the canvas: text, frame ([x, y, width, height]).

The block kinds and their settings are on blocks.

assistant/

Conversations with the Assistant that you share in the workspace: <slug>.md to read in any editor, and <slug>.json for Echolot to open again.

Other files

Files Echolot doesn't know are left alone, so a README, a .gitignore or data files can live in the workspace. Data an agent or a flow saves (such as exports/products.csv) goes where it was asked to, inside the workspace.

On this page