Skip to content

@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 it as a development dependency of an engine project:

Terminal window
yarn add --dev @invokta/devtools

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

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

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 --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 <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 <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/mcp HTTP adapter serving the engine on 127.0.0.1 with authentication always in required mode; and
  • the devtools interface on http://localhost:4100/ (or --port): the web interface, a JSON API, a live trace event stream, and a same-origin POST /mcp proxy 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.

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.

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

Terminal window
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

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.