Skip to content

@invokta/deploy

@invokta/deploy turns an engine with an MCP Streamable HTTP entry point into a reviewable container build context, probes an existing endpoint, and inspects OAuth discovery readiness. It is a development tool, not a runtime adapter or deployment service.

The toolkit generates and validates text. It never runs a shell, package manager, compiler, or container tool. init and package make no network requests. probe makes one bounded HTTP request; inspect-oauth performs a bounded, credential-free discovery sequence.

Terminal window
yarn add --dev @invokta/deploy

The package is native ESM, requires Node.js 22.20.0 or later, exports its command API from the package root and its pure HTTP planner from ./scaffold, and provides the invokta-deploy binary.

invokta-deploy init
invokta-deploy package
invokta-deploy probe --url <url> [--expect alive|ready]
[--bearer-env NAME] [--host-header HOST] [--timeout-ms N]
invokta-deploy inspect-oauth --url <url> [--timeout-ms N]
invokta-deploy --help
invokta-deploy --version

Commands are non-interactive. Progress and diagnostics go to standard error; only help and version write to standard output.

Exit Meaning
0 Command succeeded, probe is healthy, or OAuth discovery is ready
1 Project input, generated-file conflict, endpoint health, or OAuth inspection failed
2 Usage, manifest, initialization, or unexpected command failure

init accepts no arguments and scaffolds five user-owned files:

File Purpose
invokta.deploy.json Closed deployment manifest with explicit defaults
src/mcp-http.ts Production-shaped HTTP composition root
src/http-auth.ts Required authentication hook that fails closed until implemented
src/env.ts Bounded environment-file loader and required-name check
.env.example Empty, secret-free declarations from the manifest

Until the authentication hook is implemented, the HTTP entry point exits 1 with one sanitized instruction on standard error and no stack trace.

Existing targets are reported as skipped and are never overwritten. A valid existing manifest parameterizes the other generated files; an invalid manifest fails before writes. The five exclusive writes are sequential rather than one cross-file transaction.

The generated root expects a named engine export from src/engine.ts. It validates environment configuration before dynamically importing the engine.

The five scaffold entries also have one pure programmatic authority:

import {
createMcpHttpScaffoldFiles,
starterDeployManifest,
} from "@invokta/deploy/scaffold";
const files = createMcpHttpScaffoldFiles(starterDeployManifest);

The returned project-relative UTF-8 text entries and their ordered array are immutable. Planning performs no filesystem, process, network, engine, or capability operation. The engine creator consumes this public subpath and merges the complete plan before writing; it does not copy templates or invoke init.

package validates, in order:

  1. invokta.deploy.json;
  2. package name, version, and build script;
  3. exactly one supported lockfile; and
  4. the built entry module.

Preflight failure writes nothing. A successful preflight generates:

File Purpose
Dockerfile Multi-stage install, build, production prune, and non-root runtime
.dockerignore Excludes Git data, dependencies, tests, coverage, and every .env*
deploy/healthcheck.mjs One bounded MCP liveness/readiness request using Node built-ins
deploy/DEPLOYMENT.md Operator notes derived from the manifest

Each generated file starts with a toolkit marker. A marked file may be updated; an unmarked existing file is a per-file conflict and remains untouched while other files may still be generated. Status is created, updated, unchanged, or conflict in lexicographic path order.

Writes use a private temporary file followed by rename. Identical regeneration does not rewrite bytes. Output contains no timestamp, hostname, username, absolute local path, or random value.

Lockfile Install text Production prune text
package-lock.json npm ci npm ci --omit=dev
pnpm-lock.yaml pnpm install --frozen-lockfile pnpm install --prod --frozen-lockfile
yarn.lock yarn install --frozen-lockfile yarn install --production --frozen-lockfile
Terminal window
invokta-deploy probe \
--url https://engine.example/mcp \
--expect alive
ENGINE_PROBE_TOKEN='runtime-secret' \
invokta-deploy probe \
--url https://engine.example/mcp \
--expect ready \
--bearer-env ENGINE_PROBE_TOKEN
Option Contract
--url Required absolute URL whose path is /mcp or a canonical engine mount path ending in /mcp, at most 2,048 characters, without credentials, query, or fragment
--expect alive by default or ready
--bearer-env Environment variable name, allowed only with ready; token is non-empty visible ASCII up to 4,096 characters
--host-header Visible ASCII Host override up to 255 characters; changes the header, not the network destination
--timeout-ms Whole-exchange decimal timeout from 1 through 60000; default 3000

Plain HTTP is allowed only for literal 127.0.0.1 or [::1], not localhost. Every option uses a separate name and value; duplicates, positionals, unknown flags, or --flag=value are invalid.

The probe sends one MCP initialize POST, follows no redirect, performs no retry, and reuses no connection. It accepts JSON or SSE framing and caps the response at 1 MiB.

The current success parser requires JSON-RPC 2.0, no error, an object result, and a non-empty result.protocolVersion. It does not require the response ID to equal the request ID or require the negotiated version to equal the requested version. This is probe health classification, not a complete MCP conformance test.

Expectation Healthy result
alive A 401 with a Bearer challenge or a usable 200 initialize result
ready A usable 200 initialize result, with the named bearer token when supplied

Connection failure, timeout, a malformed response, or any status outside the accepted 200/401 cases is unhealthy. A credential enters through an environment variable and never appears in argv or diagnostics.

Terminal window
invokta-deploy inspect-oauth \
--url https://engine.example/mcp \
--timeout-ms 10000

This is a read-only OAuth discovery readiness check. It sends an unauthenticated MCP initialize, validates the Bearer challenge and Protected Resource Metadata, follows the MCP-defined OAuth/OIDC metadata discovery order, requires Authorization Code and S256 PKCE advertisements, classifies CIMD/DCR availability, and validates a JWKS when one is advertised. A missing jwks_uri is accepted because access tokens are not required to be JWTs.

The command sends no token, cookie, client credential, or user data. It does not register a client, perform login or consent, exchange a code, refresh a token, or mutate the remote service. It follows no redirect and performs no retry. OAuth/OIDC well-known fallbacks are separate protocol attempts.

The target path is /mcp or a canonical engine mount path ending in /mcp, sent exactly as written, and excludes userinfo, query, and fragment. HTTPS is required except for literal loopback addresses. --timeout-ms is one deadline for the complete sequence, defaults to 10000, and is bounded to 1..60000. Each response is capped at 256 KiB.

Success reports only the resource, issuer, challenged scopes, registration classification, and JWKS status. Failure reports OAUTH_INSPECTION_FAILED with stable stage and reason values; raw headers, response bodies, metadata, query values, and transport messages are never printed.

{
"schemaVersion": 1,
"entry": "dist/mcp-http.js",
"env": {
"required": ["SUPPORT_API_TOKEN"],
"optional": ["INVOKTA_HTTP_ALLOWED_ORIGINS"]
},
"image": {
"baseImage": "node:22-slim",
"port": 3000
},
"healthcheck": {
"expect": "alive"
}
}

The schema rejects unknown keys and reports every detectable issue in JSON pointer order without echoing rejected values.

Field Contract
schemaVersion Required exact number 1
entry Required relative in-project .js or .mjs path without empty or .. segments
env.required, env.optional Unique names matching ^[A-Z_][A-Z0-9_]{0,127}$; at most 64 total
image.baseImage Non-empty text without whitespace or NUL; default node:22-slim
image.port Integer 1..65535; default 3000
healthcheck.expect alive by default or ready
healthcheck.bearerEnv Environment name allowed only with ready

The encoded document is limited to 65,536 bytes and each string to 1,024 Unicode scalars.

The generated application code recognizes:

Variable Meaning
INVOKTA_HTTP_HOST Bind host, default 127.0.0.1
INVOKTA_HTTP_PORT or PORT Bind port; the Invokta name wins
INVOKTA_HTTP_ALLOWED_HOSTS Comma-separated host allowlist, required for non-loopback
INVOKTA_HTTP_ALLOWED_ORIGINS Comma-separated browser origins
INVOKTA_HTTP_MAX_BODY_BYTES HTTP body limit override
INVOKTA_ENV_FILE Environment-file path, default .env

Process environment, including an empty value, has precedence over the file; file values have precedence over defaults. A missing default .env is ignored, but an explicitly named missing file fails. The loader uses Node’s util.parseEnv, rejects symlinks, invalid UTF-8, NUL, oversized files, invalid names, more than 256 applied keys, or values over 4,096 Unicode scalars.

Required manifest names must be present and non-empty before the engine module is imported. Production secrets belong in the platform; generated .dockerignore excludes all environment files.

Deployment errors are toolkit contracts, not EngineError values:

MANIFEST_NOT_FOUND, MANIFEST_INVALID, PACKAGE_JSON_INVALID, LOCKFILE_MISSING, LOCKFILE_AMBIGUOUS, ENTRY_NOT_BUILT, GENERATED_FILE_CONFLICT, WRITE_FAILED, PROBE_UNREACHABLE, and PROBE_UNHEALTHY, plus OAUTH_INSPECTION_FAILED.

DeployError contains a stable code, exitCode, and sanitized details. It stores no cause. renderDeployDiagnostic emits the code and message followed by indented details.

The package root exports:

  • command functions: runDeployCli, runInit, runPackage, runProbe, runInspectOAuth;
  • context functions: createDeployContext, writeDiagnostic;
  • command/context types: DeployCommandName, RunDeployCliOptions, DeployCommandRun, DeployContext, DeployContextOverrides, DeployIo, DeployExitCode;
  • errors: DeployError, DeployErrorCode, DeployErrorOptions, error tables, and renderDeployDiagnostic;
  • manifest functions: loadDeployManifest, parseDeployManifest, and toDeployError;
  • manifest types: HttpDeployManifest, its nested environment/image/healthcheck types, load options, result/success/failure, and issue/reason types; and
  • manifest constants: deployManifestFileName, deployManifestDefaults, deployManifestLimits, deployManifestIssueMessages, and environmentNamePattern.

The @invokta/deploy/scaffold subpath exports createMcpHttpScaffoldFiles, starterDeployManifest, and the immutable McpHttpScaffoldFile entry type.

const exitCode = await runDeployCli({
argv: [
"probe",
"--url",
"https://engine.example/mcp",
],
cwd: process.cwd(),
});
process.exitCode = exitCode;

The orchestration functions return an exit code and never terminate the process. runInit, runPackage, runProbe, and runInspectOAuth accept command arguments plus a DeployContext for embedding and tests.

Follow the deployment guide for the normal workflow and the @invokta/mcp reference for the server being packaged.