@invokta/devtools
@invokta/devtools is the development-time surface for Invokta: an MCP
workbench for installed servers, a CLI workbench for installed Invokta
executables, an installation verifier, and — for one built engine — a
read-only doctor and a local dev server with a web interface for browsing
capabilities, invoking them, following a live trace, and switching test
identities backed by Principal values.
The package adds no runtime contract, adapter, or transport. Every capability
execution reaches the engine exclusively through the unmodified
@invokta/mcp stateless Streamable HTTP adapter, so invocations traverse the
single engine.invoke pipeline with source mcp-http.
Install
Section titled “Install”Install it as a development dependency of an engine project:
yarn add --dev @invokta/devtoolsThe package is native ESM, requires Node.js 22.20.0 or later, and exposes the
invokta-devtools binary. Projects generated by create-invokta-engine
already include it together with devtools and devtools:doctor scripts.
Commands
Section titled “Commands”The invokta-devtools binary provides four commands: open, verify,
doctor, and serve. Every invocation accepts --help (or -h) to print
the usage and --version (or -v) to print the installed version.
invokta-devtools open
Section titled “invokta-devtools open”invokta-devtools [--mcp | --cli] [--port <number>]invokta-devtools open [--mcp | --cli] [--port <number>]Bare invocation and open are equivalent. Both serve the two idle workbenches
from one loopback origin on http://localhost:4100/ (or --port) and land on
the chooser: / chooses, /mcp is the MCP workbench, and /cli is the CLI
workbench. --mcp and --cli land on that workbench instead — the ready line
points at its path — and leave the other one mounted. The workbench header
carries the way back to the chooser and the switch to the other workbench, and
an idle workbench repeats both as links next to its Connect form. Neither loads a workspace, spawns a target, or
opens an outbound connection until you select Connect, and each keeps its own
browser session.
The MCP workbench is the attach UI: from the Connection view you attach either a structured stdio command or a Streamable HTTP URL; the attached UI provides Tools, Activity, and Connection validation, and the argument editor is seeded from each tool’s advertised input schema.
The CLI workbench connects exactly one structured descriptor — command,
arguments, optional working directory, and environment. Connect runs
<command> <args...> list once (shell: false) and waits for the child to
exit. Selecting a capability runs describe <id>. Run is enabled only after a
successful list and describe, and only when you press Run, as
<command> <args...> run <id> --input '<json>'. The workbench never passes
--stdin, --format, actor flags, or login flags. Each verb starts a new
process; DevTools does not supply a principal. The CLI UI exposes Commands,
Activity, and Connection only. Switching workbench carries no target,
connection, or activity across, and there is no verify --cli command.
Interactive OAuth
Section titled “Interactive OAuth”For an HTTP server that uses OAuth, select OAuth, then Connect, and
continue through the provider in the new tab; the workbench resumes after the
loopback callback completes. The flow uses Authorization Code with PKCE, the
server’s advertised MCP OAuth metadata, and its advertised dynamic client
registration endpoint — a preconfigured client ID or client secret is never
accepted. The authorization servers the resource’s own Protected Resource
Metadata advertises are followed, including ones on another origin — which is
what every hosted identity provider is. That document is still read only from
the resource’s own origin, so the resource stays the authority on who may issue
tokens for it, and a loopback HTTP authorization server is accepted only behind
a loopback HTTP resource. Tokens, PKCE material, client registration data, and
discovery documents stay in process memory and are cleared on disconnect or
process exit. OAuth is intentionally interactive and
UI-only; verify supports none, bearer, and custom-header authentication so
automation stays deterministic.
invokta-devtools verify
Section titled “invokta-devtools verify”invokta-devtools verify --stdio <executable> [--arg <value>]... [--cwd <directory>] [--env <child-name>=<source-environment-name>]...invokta-devtools verify --http <url> [--auth <none|bearer|headers>] [--bearer-env <environment-name>] [--header-env <header-name>=<environment-name>]...verify checks an installed MCP server the way a client would: it performs
initialization and the complete paginated tools/list, and it never calls a
tool. Exactly one of --stdio and --http is required.
The stdio target is spawned without a shell. Repeat --arg for additional
arguments, use --cwd <directory> to select the child working directory, and
repeat --env CHILD=SOURCE to copy an already-set environment value into the
child under a new name.
Plain HTTP is accepted only for a literal loopback address; every other target
requires HTTPS. For --auth bearer, --bearer-env <name> names the
environment variable holding the token — the token value is never a
command-line argument. For --auth headers, repeat
--header-env HEADER=SOURCE to map each header name to the environment
variable that provides its value.
| Option | Contract |
|---|---|
--json |
Writes the verification report as JSON to standard output |
--timeout-ms <ms> |
Overrides the verification deadline; an expired deadline fails with TIMEOUT |
--max-tools <n> |
Bounds the paginated tools/list; a target advertising more tools fails verification |
| Exit | Meaning |
|---|---|
0 |
Validation passed |
1 |
The target or the protocol failed |
2 |
The command or target descriptor is invalid, including a missing environment value |
Usage errors name the specific cause, and a missing environment value names the variable.
invokta-devtools doctor
Section titled “invokta-devtools doctor”invokta-devtools doctor <esm-module> [--export <name>] [--json]| Argument | Contract |
|---|---|
<esm-module> |
Exactly one non-empty path to an already-built ESM module, resolved from the current working directory |
--export <name> |
Optional own module export to inspect; defaults to engine and may appear once |
--json |
Writes the report as JSON to standard output for automation |
The doctor verifies that the export is an engine, reads every capability
summary and description, and checks that the published JSON Schemas are
readable. Missing titles or annotations and the presence of the
invokta.mcp.json manifest are advisory notes. The doctor never invokes a
capability, starts a transport, mutates the filesystem, or performs a network
request.
| Exit | Meaning |
|---|---|
0 |
The engine passed the checks; notes may be reported |
1 |
The doctor reported findings |
2 |
Invalid usage, a load failure, a missing export, or a non-engine export |
Diagnostics are deterministic, stack-free, secret-free, and written only to standard error.
invokta-devtools serve
Section titled “invokta-devtools serve”invokta-devtools serve <esm-module> [--export <name>] [--port <number>] [--engine-port <number>] [--watch --build <command>] [--watch-include <path>] [--watch-ignore <pattern>] [--trace-capacity <n>]serve preflights the engine with the doctor checks and then starts two
loopback servers:
- the engine host: the
@invokta/mcpHTTP adapter serving the engine on127.0.0.1with authentication always inrequiredmode; and - the devtools interface on
http://localhost:4100/(or--port): the web interface, a JSON API, a live trace event stream, and a same-originPOST /mcpproxy to the engine host.
Standard output carries exactly one ready line, Invokta devtools listening on http://localhost:<port>/. The engine name@version, the capability count, and
the watch status accompany it on standard error. Terminate with SIGINT or
SIGTERM for a clean shutdown. A port already in use is not a failure: the
next free port is taken and standard error reports port: <requested> is in use, using <selected> instead.
Adapters
Section titled “Adapters”Capabilities runs one capability call through the execution path you select, so the same arguments can be compared across every path the engine publishes:
| Adapter | What runs | ExecutionContext.source |
|---|---|---|
| Direct | engine.invoke, the way an embedding application calls it |
direct |
| CLI | the @invokta/cli adapter as a process, with its exit code and streams |
cli |
| MCP stdio | the serveMcpStdio server, called the way an MCP client calls it |
mcp-stdio |
| MCP HTTP | one Streamable HTTP request to the running engine host | mcp-http |
Every emulation performs a real call through the published adapter. Direct,
CLI, and MCP stdio each run in a child process that imports the same built
module you passed to serve; the process is started per call and exits with
it. MCP HTTP reuses the running engine host. The dev server bounds how many
emulations run at once and answers 429 beyond that.
The result pane reads the same for every adapter — a result, an engine error,
or an adapter failure, with the elapsed time — while Adapter exchange
shows what that path actually carried: the request and response bodies with
the HTTP status, the tools/call frames, or the command with its standard
output, standard error, and exit code. A capability error arrives with the
same code from all four paths, which is what the single execution path looks
like from the outside.
Direct and CLI carry the arguments in the command line, so a payload beyond
what the operating system allows in one argument is refused with
ARGUMENTS_TOO_LARGE; the MCP adapters carry the same payload in the protocol.
Identity and authentication
Section titled “Identity and authentication”The adapter bar separates the two, because the framework does.
Identity is the development Principal the call acts as. It applies to
every adapter — it is what an access rule sees — and includes an explicit
Anonymous choice so a rule can be denied on purpose. The three process
adapters start as the selected identity, the way a composition root supplies
it: no credential exists and no authentication step runs, so nothing else is
asked of you.
Entry appears for CLI and MCP stdio and decides which composition root runs the call:
| Entry point | Who supplies the principal |
|---|---|
| Devtools (default) | the identity selected here, so an access rule can be exercised as different actors |
| Project | your own built entry point, spawned as it is — its root decides, including principal: null, which is what the generated starter passes |
The two disagree by design, and the disagreement is the point: a generated
starter authenticates nobody on those paths, while every example in the Invokta
repository supplies a fixed localPrincipal carrying domain attributes.
Selecting your entry point shows what the shipped command does — the identity
control turns off and says so, and the reproduction command becomes the command
you would type. You name the path; the devtools proposes the conventional
sibling of the served module, discovers nothing, and requires the path to stay
inside the directory serve runs in. A direct call has no project entry point,
because a generated src/direct.ts is a demonstration script bound to one
capability rather than an adapter with an invocation contract.
Authentication appears only for MCP HTTP, the one path that authenticates. It selects where the call goes and how it presents itself:
| Target | Authentication |
|---|---|
| Devtools host (default) | the selected identity’s session token, or no credential, which exercises the adapter’s own fail-closed 401 challenge |
| External endpoint | none, bearer, custom headers, or interactive OAuth |
An external endpoint is a Streamable HTTP MCP URL you run yourself, typically
your own built HTTP entry point, so the authentication you actually ship — the
hook in src/http-auth.ts — runs against the same arguments the other three
paths use. The acting principal then comes from that server’s own hook, not
from the devtools identity, and a capability the endpoint does not publish
fails on an unknown tool name. Plain HTTP is accepted only for literal
loopback; every other endpoint must use HTTPS, and the URL may carry no
credentials, query, or fragment.
OAuth reuses the ephemeral Authorization Code with PKCE flow of attached inspection: the server’s advertised MCP OAuth metadata, its advertised dynamic client registration, a loopback callback, and no preconfigured client ID or secret. An Authorization Server on a different origin than the engine — which is what every hosted identity provider is — is reached as long as the engine’s own Protected Resource Metadata advertises it; that document is still read only from the engine’s own origin, so the engine remains the authority on who may issue tokens for it.
Check runs the discovery chain and reports it leg by leg: the 401
challenge and whether it advertises resource_metadata, the Protected Resource
Metadata document, the Authorization Server’s RFC 8414 metadata, and whether
dynamic client registration is advertised. A leg that could not run says what it
was waiting for rather than reporting a failure it never attempted, and
Authorize stays disabled until the chain resolves. The check authorizes
nothing and sends no credential — it exercises the discovery half, which is the
half the engine is responsible for.
A credential value starting with $ names an environment variable the dev
server reads, matching how verify keeps a token out of an argument vector.
Whatever you supply lives in the dev-server process memory for as long as the
endpoint stays selected: it is never persisted, never written to your project,
and never echoed back — reading the selection returns the URL, the
authentication type, and header or variable names only.
Test identities
Section titled “Test identities”Test identities is where the identities themselves are created, edited,
rotated, and removed; Capabilities selects among them. Each one lists what it
recently managed to do — the capability, the adapter, and the outcome of the
last emulated calls made as it — so an access rule can be attributed to the
identity that met it rather than guessed at. Each one maps to an in-memory
development Principal and receives an opaque bearer token. The engine host
resolves presented tokens through its authentication hook, so access rules
run against real principals, and a request without a valid token receives the
adapter’s own 401 Bearer challenge. Tokens exist only in process memory and
in the browser session that minted them; the insecure
dangerously-disabled-for-development mode is never used.
The interface receives one trace entry per invocation — capability, outcome,
error code, and duration — one entry per emulated call, tagged with the adapter
that carried it, and the raw MCP request and response of every exchange the
interface itself sent. The trace is a bounded in-memory buffer scoped to the
dev-server process; --trace-capacity <n> resizes the buffer. Nothing is
persisted or exported.
Watch mode
Section titled “Watch mode”--watch requires --build and runs the engine host in a replaceable child
process. Project changes run the explicit build command from the project
directory; only a successful build replaces the child, and a failed build
leaves the running host serving. Modules are never reloaded in process. Use
--watch-include <path> to add a path to the watched set and
--watch-ignore <pattern> to exclude paths from triggering a rebuild.
invokta-devtools serve dist/engine.js --watch --build "tsc -p tsconfig.json"| Exit | Meaning |
|---|---|
0 |
The dev server shut down cleanly |
1 |
The preflight reported findings or the server could not start |
2 |
Invalid usage, a load failure, a missing export, or a non-engine export |
Boundaries
Section titled “Boundaries”The devtools binds only 127.0.0.1, allows exactly its own origin at the
engine host, emits no Access-Control-* headers, and validates the Host
header of every request. It discovers nothing: the module path is always
explicit. Adapter emulation adds no execution path of its own: each adapter is
the published one, reached through engine.invoke, and no adapter process
survives the call that started it. An external endpoint is an invocation target
only — the catalog, the schemas, the doctor report, and the trace always
describe the loaded module — and every credential it needs stays in process
memory. The normative contract lives in the engine devtools dev server
specification and ADR 0021, ADR 0028, ADR 0029, and ADR 0030 in the
repository.