Orbitron¶
Kinetis does not hand you a generic dashboard or force your application into a prebuilt scaffold. It gives you something more adaptable: Orbitron, the development harness that equips your chosen AI coding agent with Kinetis context, project inspection, verification and controlled scaffolding. Describe the application you need, and build against the packages and versions actually installed.
kinetis/orbitron is a development-only construction harness, and a
harness rather than an agent: it contains no model, talks to none, and
generates no application of its own. You bring the agent. What Orbitron
gives it is four documents: a portable Kinetis context document, this
project’s installed kinetis/* package inventory as JSON, a
deterministic verification of this project’s Composer layout, and one
health-endpoint scaffold that is previewed before it is applied. None of
them is evidence that application code is correct — Agent Correctness Review
and the project’s own test suite are.
Reach them either way. Any agent that can run a shell command uses the
four commands on vendor/bin/kinetis; an agent that speaks MCP registers
vendor/bin/kinetis-orbitron-mcp and calls the same documents as tools
(see Over MCP). Both adapt the same services, so neither can
report something the other does not.
Over MCP that one server is also the whole project-local registration.
Alongside Orbitron’s own context and tools it publishes these
documentation pages as kinetis://docs/* resources, fetched by
MCP Documentation Server from inside the same process — so an agent reads the
current guidance on the connection it is already on, and there is no
second server to configure or to skip.
kinetis/skeleton arrives with the whole wiring in place;
Equip an existing project is the same
wiring as a recipe for a project that already exists.
Install¶
composer require --dev kinetis/orbitron
--dev is the supported installation. Orbitron registers one scan root
and nothing else — no bootstrap, no route, no listener, no runtime
service, no configuration key. Its MCP server is a binary a client
launches, not a registration. Application production code never depends
on it, and composer remove --dev kinetis/orbitron changes nothing an
application does.
Read the context¶
vendor/bin/kinetis orbitron:context
One document, rendered whole to STDOUT:
what Orbitron is, and what it does not establish;
links to the authoritative guides — Agent Workflow, Application Recipes, Agent Correctness Review and Reference;
the workflow below;
what each command may and may not change;
the launcher behavior described under Trust boundary;
the installed
kinetis/*packages and their versions.
--format accepts markdown (the default) and json. Both render the
same document from the same facts, so an agent that parses JSON and a
human reading Markdown see the same claims.
The document links to the guides rather than reprinting them, and it claims nothing about an application’s correctness. Static context does not prove that a change preserves request isolation or non-blocking I/O — Agent Correctness Review is the review that does, and the project’s own test suite is the evidence.
Read the installed versions¶
vendor/bin/kinetis orbitron:inspect
JSON only: this document exists to be parsed. Omitting --format and
writing --format=json are the same invocation.
{
"schemaVersion": 1,
"orbitronVersion": "1.1.0",
"packages": [
{
"name": "kinetis/framework",
"version": "1.12.0"
},
{
"name": "kinetis/mcp-docs",
"version": "1.4.0"
},
{
"name": "kinetis/mcp-protocol",
"version": "1.0.0"
},
{
"name": "kinetis/orbitron",
"version": "1.1.0"
}
]
}
packages carries every installed package under the kinetis/ vendor,
ordered by name, one entry per name. A name Composer lists only because
an installed package replaces or provides it carries no version and
no install path, and is not reported. Install paths are read to make
that distinction and never reach the output.
The Composer root project is left out too. Composer lists it among the
installed packages, but it is the project being developed rather than
something the project installed, so a root under the kinetis/ vendor —
kinetis/skeleton, or kinetis/orbitron itself while that package is
developed — is not a dependency to report.
orbitronVersion is the detected kinetis/orbitron package version
rather than a constant maintained beside it, so the two cannot
disagree. It is read internally, from the same retained set, so it still
names the version when Orbitron is the root project it leaves out of
packages. In the Kinetis monorepo it reads dev-main.
This is what Agent Workflow’s current-main/installed-version
boundary needs: an agent reads each guide for the versions installed
here, not for whatever main currently holds.
Verify the project layout¶
vendor/bin/kinetis orbitron:verify
JSON only, for the same reason as orbitron:inspect. One deterministic
document, with a fixed key and check order:
{
"schemaVersion": 1,
"orbitronVersion": "1.1.0",
"status": "pass",
"checks": [
{
"name": "composerManifest",
"state": "pass",
"code": "manifest_read"
},
{
"name": "productionNamespace",
"state": "pass",
"code": "namespace_unique"
},
{
"name": "testNamespace",
"state": "pass",
"code": "namespace_unique"
}
],
"namespaces": {
"production": "App\\",
"test": "App\\Tests\\"
}
}
schemaVersion is 1, and moves only when the document’s shape
changes. status is error when any check failed and pass
otherwise — the command exits 3 in the first case. code is the
stable machine value to branch on: it names an outcome, and never
echoes the manifest, an exception message, or a path. namespaces is
reported only when the whole layout is the admitted one; a
half-recognized project reports null rather than one known half.
The layout it admits¶
exactly one
autoload.psr-4prefix whose mapping is the single string"src/";exactly one
autoload-dev.psr-4prefix whose mapping is the single string"tests/";each prefix a non-empty PSR-4 namespace ending in
\.
This is the skeleton layout. Any other mapping in either map is left alone, array-valued ones included — a project declares whatever else it needs. Two prefixes pointing at the same fixed path, or an array-valued mapping that reaches it, are rejected rather than guessed at.
Check |
State |
Code |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
A manifest that could not be used makes both namespace checks skip
rather than error: the layout was never seen, so nothing about it is
claimed either way.
What it proves, and what it does not¶
It proves one thing: that this project’s Composer layout is the narrow one Orbitron supports. That layout is a deliberate prerequisite for the scaffolding built on it — a fixed pair of namespaces is what lets a generated class be placed without guessing — and it is narrower than anything Kinetis itself demands.
Discovery asks for much less. Kinetis\Cache\NamespaceScanner walks
every string prefix under autoload.psr-4, at any directory, accepting
array-valued mappings, and it never reads autoload-dev at all. So an
error here does not mean discovery is broken: a project with three
production prefixes, or with its tests under a path this rejects, still
has every route, command, tool and listener discovered as usual — see
CLI and Caching & AOT Compilation.
One real discovery failure is inside what the production check catches,
though. A project with no usable autoload.psr-4 map has no root to
scan at all, so discovery finds nothing and says so through a single
error_log() line. That project reports psr4_map_missing or
path_unmapped here instead, in a document an agent can read.
It proves nothing else. It is not evidence about request isolation, non-blocking I/O, security, route uniqueness, or the correctness of any application code. Agent Correctness Review is the review that addresses those, and the project’s own test suite is the evidence.
Scaffold the health endpoint¶
vendor/bin/kinetis orbitron:scaffold
vendor/bin/kinetis orbitron:scaffold --apply
JSON only, for the same reason as the two commands above. Without
--apply this is a preview: every precondition is read and the plan is
written, with nothing touched on disk.
{
"schemaVersion": 1,
"orbitronVersion": "1.1.0",
"mode": "preview",
"status": "ready",
"codes": [
"scaffold_ready"
],
"targets": [
"src/Http/HealthController.php",
"tests/Http/HealthControllerTest.php"
],
"remainingFiles": []
}
schemaVersion is 1 and moves only when the document’s shape changes.
mode is preview or apply. status is ready, created,
refused or failed. codes are the stable machine values to branch
on, in a fixed order, and they name outcomes only — never a file’s
contents, an exception message, or an absolute path. targets is the
complete write set, always both paths and always in this order.
remainingFiles is empty unless a rollback could not put the project
back.
The two files¶
Nothing about them is configurable. There is no scaffold name, custom path, template, source body, saved plan or plugin registry — one workflow, built one way:
src/Http/HealthController.php, under the production namespace the layout check found, declaring#[Get('/health')]and returning['status' => 'ok']. The dispatcher encodes an array return as JSON with the route’s status, so the response is{"status":"ok"}— see Routing & Validation.tests/Http/HealthControllerTest.php, under the test namespace, extendingKinetis\Testing\ApplicationTestCaseand issuing two sequentialGET /healthrequests against one booted application, asserting the same success response both times. The second request is the point: a persistent worker answers it from whatever the first left behind — see Testing.
Neither file names Orbitron. They import the framework and its testing
API only, so composer remove --dev kinetis/orbitron leaves them
working.
What must already be true¶
the layout above, with both namespaces valid;
src,src/Http,testsandtests/Httppresent as real directories, none of them a symlink, each resolving inside the project root;neither target occupied. A regular file, a directory, a symlink and a symlink pointing at nothing all count as occupied —
file_exists()answersfalsefor the last of those, and the path is taken all the same.
Orbitron creates no directory. A project without src/Http is refused,
not filled in.
Status |
Code |
Meaning |
|---|---|---|
|
|
The preview holds: both files can be created. |
|
|
Both files exist, every byte written, flushed and closed. |
|
the layout codes above, |
The layout is not the admitted one, reported by the same reader |
|
|
One of the four fixed directories is absent, or is not a directory. |
|
|
One of them resolves outside the project root. |
|
|
One of them is a symlink. |
|
|
One of the two targets is occupied. |
|
|
A file could not be created or written, and everything this invocation created was removed. |
|
|
The removal failed too. |
Preview, then apply¶
--apply is the only mutation request, and it does not consume the
preview that came before it. It re-reads the manifest, the four
directories, their symlink state and both targets immediately before it
writes, so a file that appeared in between is a refusal rather than an
overwrite. --apply carries no value: --apply=yes is a rejected
invocation, like an unsupported --format.
Each file is created with fopen($path, 'x+b') — a create that fails
rather than truncating whatever is already there — and every byte goes
out in a loop that treats a failed write, or one that accepts nothing,
as the end of the attempt. A flush and a close that fail are failures
too; the handle closes on every path out.
When the second file cannot be created or finished, every file this invocation created is removed, the incomplete one included. A file that was already there is never removed. A removal that fails is itself reported, with exactly the project-relative paths that may remain.
What it cannot tell you¶
Whether this project already routes GET /health somewhere else.
Orbitron reads no application source and runs no discovery, so a
conflicting route is invisible to it and the apply succeeds. The
generated test is what surfaces it: route discovery refuses two
controllers claiming one path, and the first run of the suite after the
scaffold says so — see Routing & Validation.
The workflow¶
vendor/bin/kinetis orbitron:context— once per task.vendor/bin/kinetis orbitron:inspect— the installed versions.vendor/bin/kinetis orbitron:verify— the project layout.vendor/bin/kinetis orbitron:scaffold— the health-endpoint plan, then--applywhen the plan is what you want.Route the task through Agent Workflow — over MCP, the
kinetis://docs/agent-workflowresource — then follow the matching recipe in Application Recipes.Before calling the change done, work through Agent Correctness Review and run the project’s own test suite.
Run one suite at a time against a shared database, broker or object store — One run at a time against shared state.
An MCP client runs the same steps as tool and resource reads; see Over MCP.
Over MCP¶
vendor/bin/kinetis-orbitron-mcp
A stdio MCP server speaking 2025-06-18, for any client that launches a
server as a subprocess. Register the launcher as a stdio server named
orbitron, the way that client registers any other: the server takes no
argument, needs no environment and reads nothing from the registration
but the command to run.
Equip an existing project is the same registration checked in, so every developer on the project gets it without running anything — and that is where the per-client configuration paths and discovery differences are.
Tool or resource |
What it returns |
|---|---|
|
The |
|
The |
|
The |
|
The |
|
The |
|
One page of this documentation, as Markdown. |
Warning
orbitron_scaffold_apply writes to the project. Selecting it is the
mutation request: it takes no argument, and your MCP client’s own
approval policy is what decides whether it runs. It is annotated
destructiveHint: true and idempotentHint: false — a second apply
refuses, because the targets exist by then.
All four tools publish a closed, empty input schema and refuse a call
that carries any argument at all. No message can name a project root, a
path, a source body, a URL, a template or a command: the root comes from
Composer’s own bin proxy, exactly as it does for vendor/bin/kinetis,
and every name below it is a constant. The server never boots the Kinetis
application, so running it registers no route, listener or bootstrap.
Composer’s installed-package inventory is process-cached, so the server reads it once at startup. Restart it after installing or removing a dependency; every other document is re-read on each call, and nothing about one call survives into the next.
The documentation resources¶
MCP Documentation Server owns the page catalogue and the fetch. Orbitron requires
it, holds a DocsApplication and hands every kinetis://docs/* read
straight to it, composing that server rather than copying it — so a page
added to kinetis/mcp-docs appears here without a change in Orbitron,
and registering Orbitron is the whole registration. The package stays
framework-agnostic and separately installable: a project that wants the
documentation without the harness registers vendor/bin/kinetis-mcp-docs
on its own instead.
Reading one of those resources is the one Orbitron operation that leaves the machine. The page is fetched when it is read, from the fixed main-branch origin built out of that package’s own two constants — nothing in a message chooses an origin, a ref or a path:
TLS verifies the peer and the host name;
no redirect is followed, and any status other than
200fails;a 10-second idle timeout and a 30-second total deadline bound the request;
the body is abandoned once it passes 4 MiB, rather than accumulated;
a body that is not valid UTF-8 is refused rather than encoded.
A fetch that fails is a generic MCP error naming only the URI that was asked for. The URL, the status and the transport’s own message go to the server’s stderr, where the client’s log shows them; stdout carries JSON-RPC frames and nothing else. Appendix: MCP Documentation Server Reference holds the complete fetch and protocol contract.
Those pages are published from main, so they can describe behavior
newer than this project has installed. orbitron_inspect and the
matching source under vendor/kinetis/ stay the authority for anything
version-sensitive — see Agent Workflow.
Equip an existing project¶
kinetis/skeleton ships this wiring, so a new project has it from the
first docker compose up. The same steps turn an existing Kinetis
application into one an MCP-capable agent can be pointed at, without a
global setting on anyone’s machine and without widening a single
approval.
1. The dependency¶
composer require --dev kinetis/orbitron
That one package brings the documentation with it: Orbitron requires
kinetis/mcp-docs, which it composes to serve the kinetis://docs/*
resources. Nothing else is installed or registered for them.
In a monorepo that resolves siblings through path repositories, add
kinetis/mcp-docs and kinetis/mcp-protocol to require-dev as well,
with a path repository for each. Orbitron requires both, and a root
whose minimum-stability is stable will not accept a sibling’s
dev-main from Packagist — naming them is the smallest fix, and it
beats loosening the whole project’s stability.
2. The launcher¶
An MCP client launches a server as a subprocess on the host. When PHP
and the vendor directory are on the host, the launcher is the binary
itself, ./vendor/bin/kinetis-orbitron-mcp, and this step is already
done.
When the project runs in Docker — the skeleton’s case — the server lives in the container next to the code it reports on, and a one-file bridge relays it:
#!/bin/sh
set -e
project_directory=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
exec docker compose --project-directory "$project_directory" \
exec -T app php vendor/bin/kinetis-orbitron-mcp
Save it as bin/orbitron-mcp, chmod +x it, and commit it. Three
properties are load-bearing:
the project directory comes from the script’s own location, so the checked-in configuration works in any clone, at any path, and carries no absolute user path;
-Tis required: an allocated TTY would rewrite the newline-delimited JSON-RPC frames the protocol depends on;the container must already be up. When it is not,
docker compose execfails, the client reports the server as unavailable, and the agent is expected to say so rather than proceed.
Name the service to match your own Compose file if it is not app.
3. The instructions¶
One file, AGENTS.md at the project root, is the whole agent contract.
It is what makes the harness load-bearing rather than optional: the
documents exist either way, and this is what says when they must be
read. Require, on the first application task of a session, the
preconditions under Start it, in this
order and then this handshake:
confirm the
orbitronserver is connected and its tools and resources are listed;read
kinetis://orbitron/context;read
kinetis://docs/agent-workflow, and route the task through the pages it names rather than answering from memory;call
orbitron_inspect, and treat those versions as the installed ones, in preference to any page describing Kinetismain;call
orbitron_verify.
Then require one of two outcomes, and nothing in between. On success,
with orbitron_verify reporting "status": "pass", a single readiness
line beginning Orbitron ready — naming the installed framework version
and the verified namespaces — and then the user’s actual request. On any
step that is unavailable, disconnected or failing, no application change
at all: the exact failing step and its error or document code, the
matching diagnostic from the project’s README, and then a wait for the
user.
Say which steps are not the agent’s. Bringing the stack up is something an agent with a shell can do; reconnecting the client and approving the server are the user’s, and an agent that cannot do either must say so and wait rather than work around it.
Require an orbitron_scaffold_plan call before any
orbitron_scaffold_apply, and require explicit user intent for the
apply. It is the only tool that writes.
4. The imports¶
@AGENTS.md
Both clients resolve an @path import, so the contract stays in one
file and cannot drift between three. Do not restate it in either.
5. The project MCP configuration¶
Three checked-in files, one per client, each naming the same server and the same launcher:
{
"mcpServers": {
"orbitron": {
"type": "stdio",
"command": "./bin/orbitron-mcp",
"args": []
}
}
}
[mcp_servers.orbitron]
command = "./bin/orbitron-mcp"
args = []
{
"mcpServers": {
"orbitron": {
"command": "./bin/orbitron-mcp"
}
}
}
A project with no bridge names ./vendor/bin/kinetis-orbitron-mcp in
all three instead.
Keep them this small. No env block, because the server needs no
credential and forwarding one would put it somewhere it was never meant
to go. No trust, no preapproved tool list, no auto-start of anything
else.
A client that reads none of these three file names is not excluded: it
registers ./bin/orbitron-mcp as a stdio server named orbitron the
way it registers any other, and reads AGENTS.md when you point it
there.
6. Trust and approval¶
These files register a server. They deliberately change no policy: they carry no credential, no trust override and no preapproval, so whatever trust and approval policy the developer’s client already runs under stays authoritative. Keep it that way — a repository that preapproves its own tools has moved a decision from the developer to whoever can open a pull request.
That policy belongs to the client, and so does where the client looks for the contract:
Client |
Reads instructions from |
Project MCP configuration |
|---|---|---|
Claude Code |
|
|
Codex |
|
|
Gemini CLI |
|
|
Any other MCP-capable client |
|
register |
AGENTS.md cannot grant any of this, and cannot register anything
either: a Markdown file read inside a running session adds no server to
it. The contract says so rather than letting an agent claim otherwise.
7. Start it, in this order¶
Starting the server has an order, because it runs inside the project’s
app container, the client launches one server process per session, and
the client’s own approval sits between the two. The first three are
preconditions; only the fourth is the handshake.
Bring the stack up.
bin/orbitron-mcpreaches the server throughdocker compose exec, which fails whileappis down:docker compose ps docker compose up -d
Reload, restart or reconnect the client — when the MCP configuration arrived or changed after the session started, when an earlier launch failed while the stack was down, or when the containers were recreated.
docker compose up --build, adown, or any recreation kills thedocker compose execprocess the client is holding, and the client does not relaunch it.Approve the project-local
orbitronserver, under the client’s own policy above.Run the handshake from 3. The instructions: the
orbitrontools and resources,kinetis://orbitron/context,kinetis://docs/agent-workflow,orbitron_inspect,orbitron_verify, then one readiness line.
A project running PHP on the host skips step 1: the launcher is
./vendor/bin/kinetis-orbitron-mcp and there is no container to bring
up.
8. Diagnostics¶
Write these into the project’s README, because the contract sends the agent there. These failures cover what actually happens:
Symptom |
What it means |
What to do |
|---|---|---|
The server fails to start |
|
|
The server was there and is gone |
The containers were recreated, killing the |
Restart or reconnect the client; the stack itself is healthy |
The server shows as disconnected |
The bridge or the client, not yet distinguished |
Run the launcher by hand (below). A handshake reply puts it on the client side: its trust or approval policy, or a tool catalog that has not refreshed |
The reported versions are stale |
The inventory is process-cached, and the server process predates the dependency change |
Restart the client, which launches a fresh server |
|
|
Read the |
A |
The fetch to the documentation origin failed, timed out, or returned something unusable |
Read the server’s stderr in the client’s log for the URL and reason; every other document is local and unaffected |
The launcher is an ordinary command, so a single line proves the whole path from host to server:
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"manual","version":"0"}}}' | ./bin/orbitron-mcp
A JSON-RPC result naming kinetis-orbitron-mcp means the bridge, the
container and the server are all fine.
Without MCP¶
The four commands are the same documents, so a project whose agent cannot speak MCP loses none of them:
docker compose exec app vendor/bin/kinetis orbitron:context
docker compose exec app vendor/bin/kinetis orbitron:inspect
docker compose exec app vendor/bin/kinetis orbitron:verify
docker compose exec app vendor/bin/kinetis orbitron:scaffold
Write the same handshake into AGENTS.md in terms of those commands and
their exit codes. The contract is the requirement; MCP is one way to
satisfy it.
Output and exit codes¶
Every command writes exactly one document plus a trailing newline to STDOUT, with fixed key and list order, and no progress text.
Exit |
Meaning |
|---|---|
|
The document was written; the verification reports no error, and the scaffold preview is ready or its apply completed. |
|
An unsupported |
|
|
|
A launcher or uncaught failure, from |
|
The command finished and disposing the request scope afterwards failed, which is the framework binary’s own behavior, not Orbitron’s. |
The MCP server reports the same outcomes differently: a refusal or a
failed write is an MCP result with isError: true still carrying the
document, and the binary itself exits 0 at end of input and 1 when a
write to stdout fails.
A command’s CommandArguments cannot enumerate options it never reads,
so an option no command consumes is ignored rather than rejected.
Orbitron adds no second argument parser for it, and the framework is not
changed to reject one.
Trust boundary¶
Orbitron reads three things on this machine: Composer’s
installed-package metadata, the project’s own composer.json, and — for
orbitron:scaffold — the existence and symlink state of four fixed
directories and two fixed paths. No other application source, no
configuration, no credentials. It starts no process.
It writes two files: the scaffold’s fixed targets, only on
orbitron:scaffold --apply or orbitron_scaffold_apply, each through a
create that refuses an occupied path and never truncates one. That is the
whole write set. Neither a command line nor an MCP message supplies a
path to any of it — the root comes from
Kinetis\Runtime\ProjectRoot::detect() and every name below it is a
constant — and nothing creates a directory.
One operation leaves this machine, and only over MCP: reading a
kinetis://docs/* resource, which kinetis/mcp-docs fetches over HTTPS
from its own fixed main-branch origin under the bounds described in The
documentation resources. It carries no
credential, sends nothing about the project, and no message can redirect
it: the URL is built from that package’s own constants. The four
commands and all four tools reach no network at all.
The MCP server is a local process the client launches, so that process and your filesystem permissions are the authority boundary. It has no shell and no credentials to reach.
The installed metadata is read through Composer\InstalledVersions,
which loads vendor/composer/installed.php itself, so the invocation
does touch a file inside vendor/. Orbitron reaches nothing beyond it.
The project manifest is the one fixed name composer.json under the
root Kinetis\Runtime\ProjectRoot::detect() reports, so no path a
command line supplies is ever opened. It is read through a bounded
stream read of at most 1 MiB, plus the single byte that distinguishes an
admitted manifest from an oversized one; a larger file is refused
without being read whole, and the diagnostic names the outcome rather
than quoting the file or its path.
Every command declares bootstrap: false, so neither the package
bootstrap chain nor the application’s bootstrap.php runs, and the MCP
binary boots no application at all. Nothing here runs discovery: the
scaffold’s generated test is what does that, on the project’s own next
test run.
A command invocation as a whole is still not side-effect-free, and what
remains belongs to the framework launcher rather than to Orbitron (the
MCP binary loads no .env and compiles no cache):
vendor/bin/kinetis loads .env before it dispatches any command, and
under APP_ENV=production it compiles .kinetis-cache/compiled.php
when no valid artifact is present — see Caching & AOT Compilation. bootstrap: false prevents neither.
See also¶
Agent Workflow — where a task goes after the context is read.
MCP Documentation Server — the package Orbitron composes for the
kinetis://docs/*resources, and the way to serve this documentation on its own, without the harness.CLI — how commands are discovered and what the binary does around them.