@octane-xplat/mcp 0.0.0-nightly.g2e22171
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +340 -0
- package/package.json +47 -0
- package/src/actions/bridge.mjs +2 -0
- package/src/contracts/actions.d.ts +209 -0
- package/src/contracts/actions.mjs +3 -0
- package/src/contracts/boundaries.d.ts +6 -0
- package/src/contracts/boundaries.mjs +3 -0
- package/src/contracts/capabilities.d.ts +106 -0
- package/src/contracts/capabilities.mjs +3 -0
- package/src/contracts/code.d.ts +134 -0
- package/src/contracts/code.mjs +3 -0
- package/src/contracts/errors.d.ts +75 -0
- package/src/contracts/errors.mjs +3 -0
- package/src/contracts/evidence.d.ts +115 -0
- package/src/contracts/evidence.mjs +3 -0
- package/src/contracts/identity.d.ts +127 -0
- package/src/contracts/identity.mjs +3 -0
- package/src/contracts/index.d.ts +11 -0
- package/src/contracts/index.mjs +3 -0
- package/src/contracts/inspection.d.ts +45 -0
- package/src/contracts/inspection.mjs +3 -0
- package/src/contracts/policy.d.ts +86 -0
- package/src/contracts/policy.mjs +3 -0
- package/src/contracts/sessions.d.ts +160 -0
- package/src/contracts/sessions.mjs +3 -0
- package/src/contracts/status.d.ts +40 -0
- package/src/contracts/status.mjs +3 -0
- package/src/discovery.mjs +2 -0
- package/src/docs.mjs +84 -0
- package/src/inspection/service.mjs +5 -0
- package/src/inspection/tools.mjs +53 -0
- package/src/runtime/logs.mjs +2 -0
- package/src/runtime/process.mjs +2 -0
- package/src/runtime/service.mjs +2 -0
- package/src/runtime/targets.mjs +2 -0
- package/src/runtime/tools.mjs +126 -0
- package/src/server.mjs +565 -0
- package/src/status.mjs +2 -0
- package/src/xplat-mcp.mjs +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alec Larson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# @octane-xplat/mcp
|
|
2
|
+
|
|
3
|
+
MCP server for agents working on Octane Xplat consumer apps. One entry point
|
|
4
|
+
gives the agent detected project identity, framework versions, guidance,
|
|
5
|
+
capability discovery, and the shared permission / session / evidence contracts
|
|
6
|
+
that every later subsystem (docs, code search, runtime, inspection,
|
|
7
|
+
development actions) plugs into.
|
|
8
|
+
|
|
9
|
+
Published as a normal lockstep package on npm. The qualified surface today is
|
|
10
|
+
the Chromium consumer lane (status/docs/code/build/launch/inspection/
|
|
11
|
+
interaction/development actions); native-platform inspection and input
|
|
12
|
+
adapters report honest `unavailable` results until they are qualified — see
|
|
13
|
+
[Remaining acceptance](#remaining-acceptance).
|
|
14
|
+
|
|
15
|
+
## Install and run
|
|
16
|
+
|
|
17
|
+
Add the server to a consumer app and point the agent's MCP client at the
|
|
18
|
+
installed `xplat-mcp` bin:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
pnpm add -D @octane-xplat/mcp
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"octane-xplat": {
|
|
28
|
+
"command": "xplat-mcp",
|
|
29
|
+
"args": ["--workspace", "/path/to/app-workspace"]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
A one-off run without a devDependency also works:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
pnpm dlx @octane-xplat/mcp --workspace /path/to/app-workspace
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
For development inside this repository:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
pnpm --filter @octane-xplat/mcp start -- --workspace <path>
|
|
45
|
+
pnpm --filter @octane-xplat/mcp test
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Transport is stdio; stdout is protocol-clean (diagnostics go to stderr).
|
|
49
|
+
Options: `--workspace` / `XPLAT_MCP_WORKSPACE` (default cwd), `--app` /
|
|
50
|
+
`XPLAT_MCP_APP` (pre-select an app id), `--permissions` /
|
|
51
|
+
`XPLAT_MCP_PERMISSIONS` (comma-separated scope grants), `--evidence-dir` /
|
|
52
|
+
`XPLAT_MCP_EVIDENCE_DIR`.
|
|
53
|
+
|
|
54
|
+
## Tools
|
|
55
|
+
|
|
56
|
+
| Tool | Purpose |
|
|
57
|
+
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
58
|
+
| `xplat_project_status` | Detected apps, selection state, framework version binding, guidance sources, docs/code-index status, declared platforms, runtime prerequisites, capability summary, permissions, boundaries, next steps. |
|
|
59
|
+
| `xplat_select_app` | Explicitly bind an app by id/path/name. Ambiguous or unknown selections fail with the candidate list — the server never picks silently. |
|
|
60
|
+
| `xplat_capability_matrix` | Per-platform capability entries: build, launch, logs, inspection, screenshot, interaction, and the rest are separate rows with status, detail, and alternatives. |
|
|
61
|
+
| `xplat_permissions` | Grant states per scope plus the action-time check result and recovery path. |
|
|
62
|
+
| `xplat_sessions` | Bound sessions: owned vs attached user-owned, state, build identity. |
|
|
63
|
+
| `xplat_targets` | Launchable targets per platform (simulators, devices, host targets) with availability, missing-toolchain reasons, and busy/ownership state. |
|
|
64
|
+
| `xplat_build` | Build the bound app for one platform as a job — same commands `xplat build` runs. `requestKey` dedupes retries. |
|
|
65
|
+
| `xplat_launch` | Owned dev session on one target as a job; completes at confirmed readiness, session owns the processes until `xplat_session_end`. |
|
|
66
|
+
| `xplat_attach` | Bind a compatible already-running dev session (user-owned, preserved). |
|
|
67
|
+
| `xplat_logs` | Bounded ordered logs for a session or build job: cursor, limit, stream filter, truncation/reset indicators, `historical` flag for dead sessions. |
|
|
68
|
+
| `xplat_dev_actions` | The bound app's live development-action registry: instance identity, revision, declarations (contracts, effects, availability). Empty vs disconnected vs no-bridge are distinct states. |
|
|
69
|
+
| `xplat_dev_action_invoke` | Invoke an action against an exact app instance + registry revision. Pre-validates input, availability, grants and queue bounds; returns a job (`requestKey` dedupes retries). |
|
|
70
|
+
| `xplat_dev_action_status` | Invocation status/result — same job data restricted to dev-action jobs, with instance/request identity. |
|
|
71
|
+
| `xplat_job_status` / `xplat_job_cancel` | Bounded job status and cooperative cancellation. |
|
|
72
|
+
| `xplat_session_end` | Ownership-safe cleanup: owned sessions release resources; attached sessions are preserved (our log taps still stop). |
|
|
73
|
+
| `xplat_evidence` | `list` / `retain` / `delete` evidence; retain takes an optional ISO expiration. |
|
|
74
|
+
| `xplat_code_search` | Exact identifier/token search over current source (`mode: 'exact'`), optional local semantic index (`mode: 'semantic'`), or both (`'auto'`). Corpus filters: `app`, `framework`; platform and package filters. |
|
|
75
|
+
| `xplat_code_read` | Bounded read of current source by search ref (`xplat-code://…`) or explicit corpus + path; results carry freshness markers and continuation offsets. |
|
|
76
|
+
| `xplat_docs_search` / `xplat_docs_read` | Installed-version docs search and bounded page reads, with snapshot/version provenance and unavailable/conflict results. |
|
|
77
|
+
| `xplat_guidance` | Tailored guidance for the selected app's installed packages. |
|
|
78
|
+
|
|
79
|
+
## Retrieve the app's documentation
|
|
80
|
+
|
|
81
|
+
Select your app first. Search defaults to maintained guides for its actual
|
|
82
|
+
installed versions; design notes and recipes require an explicit corpus.
|
|
83
|
+
The provider validates release tags and preserves cached snapshot commits.
|
|
84
|
+
Missing matching docs return an installed-source fallback instead of latest docs.
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"name": "xplat_docs_search",
|
|
89
|
+
"arguments": { "query": "testID", "package": "@octane-xplat/ui", "platform": "web" }
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Read a result using its `ref`. A ref from an older installation remains tied
|
|
94
|
+
to that version and is labeled outside the installed binding after an upgrade.
|
|
95
|
+
`xplat_project_status` reports provider readiness; each search reports actual
|
|
96
|
+
snapshot availability, so readiness alone is not a successful retrieval.
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"name": "xplat_docs_read",
|
|
101
|
+
"arguments": {
|
|
102
|
+
"package": "@octane-xplat/ui",
|
|
103
|
+
"path": "docs/verify/test-identifiers.md",
|
|
104
|
+
"limit": 2000
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Code search reads the bound application's source plus its installed
|
|
110
|
+
`@octane-xplat/*` package sources — corpora stay labeled and distinguishable.
|
|
111
|
+
The semantic index is local TF-IDF (`local-tfidf`): it builds in the
|
|
112
|
+
background, reports `preparing` / `ready` / `stale` / `unavailable`, and stale
|
|
113
|
+
hits are revalidated against current file content before serving. Nothing
|
|
114
|
+
leaves the machine; there is no remote embedding path. Secrets, private
|
|
115
|
+
configuration, generated output, and paths outside the bound scope are
|
|
116
|
+
excluded and unreadable.
|
|
117
|
+
|
|
118
|
+
Every result is `{ ok: true, data } | { ok: false, error }` where `error` is a
|
|
119
|
+
`ContractError` from the shared vocabulary.
|
|
120
|
+
|
|
121
|
+
The service layer lives in `packages/cli/src/mcp/` — the `xplat` CLI projects
|
|
122
|
+
the same contracts through its agent commands (`xplat catalog --json` is the
|
|
123
|
+
entry point). `packages/mcp/src/` holds the stdio transport adapter: tool
|
|
124
|
+
registration wraps the shared services, and the published `.d.ts` contract
|
|
125
|
+
surface stays here.
|
|
126
|
+
|
|
127
|
+
## Contract surface (`@octane-xplat/mcp/contracts/*`)
|
|
128
|
+
|
|
129
|
+
Later work packages integrate against these modules; the `.d.ts` files are the
|
|
130
|
+
documented surface and the `.mjs` files carry the runtime values and stores.
|
|
131
|
+
|
|
132
|
+
- `errors` — `ContractErrorCode` vocabulary (`invalid_selection`,
|
|
133
|
+
`ambiguous_selection`, `permission_denied`, `unsupported_capability`,
|
|
134
|
+
`prerequisite_missing`, `target_busy`, `stale_identity`,
|
|
135
|
+
`disconnected_runtime`, `execution_failure`, `not_found`, `cancelled`,
|
|
136
|
+
`timeout`, `unavailable`) with `recovery` (smallest corrective action) and
|
|
137
|
+
`retryable` semantics.
|
|
138
|
+
- `identity` — `Platform`, `AppRef`, `FrameworkBinding` (installed / workspace /
|
|
139
|
+
lockfile / provisional version sources), `GuidanceStatus`,
|
|
140
|
+
`RuntimePrerequisite`, `AppSelection` (`bound` / `ambiguous` / `none`).
|
|
141
|
+
- `capabilities` — `CapabilityId`, `CapabilityStatus`, `CapabilityEntry`,
|
|
142
|
+
`PlatformCapabilityMatrix`, and the `CapabilityProvider` / registry seam
|
|
143
|
+
adapters use to report real support.
|
|
144
|
+
- `policy` — `PermissionScope` grants (`workspace`, `runtime-actions`,
|
|
145
|
+
`visual-capture`, `evidence-retain`, `external-effects`), `PolicyStore` with
|
|
146
|
+
task-authorization baseline, action-time checks, expiry, revocation, and an
|
|
147
|
+
elicitation channel for explicit developer grants. `visual-capture` is never
|
|
148
|
+
baseline-granted.
|
|
149
|
+
- `sessions` — `SessionBinding` (`owned` / `attached`, `starting` / `ready` /
|
|
150
|
+
`disconnected` / `stopped`), `Job` (`queued` → `running` → terminal, with
|
|
151
|
+
`cancelling` / `cancelled`), request-key deduplication, and ownership-safe
|
|
152
|
+
`end` / `shutdown`.
|
|
153
|
+
- `evidence` — `EvidenceRecord` with provenance (app, platform, target,
|
|
154
|
+
session, build identity, time, interaction mechanism), session-scoped
|
|
155
|
+
default retention, `retain` with expiration, ownership-safe deletion, and
|
|
156
|
+
`TRANSCRIPT_LIMITATION_NOTE`. `isOsInputEvidence` is the guard that keeps
|
|
157
|
+
handler-dispatch results from being labeled OS-input verification.
|
|
158
|
+
- `status` — `ProjectStatusReport`, the shape `xplat_project_status` returns.
|
|
159
|
+
- `code` — the `CodeProvider` seam the context's `code` slot consumes, plus
|
|
160
|
+
`CodeHit` / `CodeSearchResult` / provenance and freshness shapes.
|
|
161
|
+
- `boundaries` — `PRODUCT_BOUNDARIES`, the advertised out-of-scope list.
|
|
162
|
+
- `actions` — the app-declared development-action wire contract (package G):
|
|
163
|
+
`DevActionDeclaration` (stable id, title, description, runtime-validatable
|
|
164
|
+
input/result field contracts, disclosed effects, availability, execution
|
|
165
|
+
policy), `DevActionRegistryAnnouncement` (instance identity + revision +
|
|
166
|
+
declarations), invocation request/outcome types, and the field-subset
|
|
167
|
+
validators both sides share. The app announces its registry through a
|
|
168
|
+
dev-bridge (`xplatDevBridge` vite middleware in `@octane-xplat/cli`, included
|
|
169
|
+
in the `xplatNative` preset); the server scans the app's bridge session
|
|
170
|
+
files under `node_modules/.cache/xplat/dev-bridge/` and polls live
|
|
171
|
+
announcements — an empty registry is never confused with a disconnected app.
|
|
172
|
+
|
|
173
|
+
## Runtime (work item D)
|
|
174
|
+
|
|
175
|
+
The shared `runtime/` service (in `packages/cli/src/mcp/runtime/`, re-exported
|
|
176
|
+
here) implements build/launch/attach/logs/cleanup for consumer apps
|
|
177
|
+
on all six platforms. Adapters (`runtime/adapters/*`) own platform
|
|
178
|
+
mechanics and always spawn the app's own toolchain — `pnpm exec vite`,
|
|
179
|
+
`pnpm exec ns run …`, the app's installed `@octane-xplat/cli` dev entry, or
|
|
180
|
+
its `dev` script — so a session behaves like the app's installed framework
|
|
181
|
+
version. `service.mjs` owns the shared contract behavior: explicit
|
|
182
|
+
app+target binding, `target_busy` contention, job dedupe/cooperative
|
|
183
|
+
cancel, ownership-safe cleanup, bounded logs, and capability-matrix entries.
|
|
184
|
+
|
|
185
|
+
| Platform | Launch | Attach | Host gate |
|
|
186
|
+
| -------------- | ----------------------------------------------------------------- | -------------------------------------------------- | ---------- |
|
|
187
|
+
| web / Chromium | `pnpm exec vite` + headless Chromium (devtools endpoint reported) | probe common dev ports — identity heuristic | none |
|
|
188
|
+
| iOS | `ns run ios --device <udid>` | booted sim + app running → `simctl log stream` tap | macOS |
|
|
189
|
+
| Android | `ns run android --device <serial>` | package running → `adb logcat --pid` tap | none (adb) |
|
|
190
|
+
| macOS | `xplat.targets.macos.dev` → CLI dev entry, else `pnpm run dev` | pgrep on app root — no scrollback | macOS |
|
|
191
|
+
| Linux | `pnpm run dev` (app-owned script) | pgrep on app root | Linux |
|
|
192
|
+
| Windows | `ns run windows` | not yet qualified | Windows |
|
|
193
|
+
|
|
194
|
+
Readiness is probe/marker-confirmed (`ready` session state) — HTTP 200 for
|
|
195
|
+
web, the host-start marker for macOS, `Successfully synced application` /
|
|
196
|
+
first `CONSOLE` line for `ns run`. A timeout or pre-ready exit fails the job
|
|
197
|
+
and ends the owned session honestly. Chromium is located via
|
|
198
|
+
`XPLAT_MCP_CHROMIUM`, the Playwright browser cache, or system installs;
|
|
199
|
+
without it the web session still reports the server URL with an explicit
|
|
200
|
+
`browser.launched: false`.
|
|
201
|
+
|
|
202
|
+
Physical iOS devices, Windows attach detection, and non-simulator iOS log
|
|
203
|
+
taps are reported limitations, not silent gaps — the capability matrix
|
|
204
|
+
carries the detail and an alternative.
|
|
205
|
+
|
|
206
|
+
## Inspect and interact with Chromium
|
|
207
|
+
|
|
208
|
+
This development package adds `xplat_inspect`, `xplat_interact`, and
|
|
209
|
+
`xplat_screenshot` for the Chromium browser started by `xplat_launch`.
|
|
210
|
+
Select a DOM `id` or shared `testID` explicitly; duplicate names fail.
|
|
211
|
+
Inspection returns bounded text/value/geometry, a document identity, and
|
|
212
|
+
session/build provenance. Main-document DOM hosts are the qualified surface;
|
|
213
|
+
shadow roots, frames, component ownership and signals are excluded.
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"name": "xplat_inspect",
|
|
218
|
+
"arguments": {
|
|
219
|
+
"sessionId": "SESSION_FROM_LAUNCH",
|
|
220
|
+
"control": { "kind": "testID", "name": "counter.increment" }
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Pass the returned `documentId` when interacting. A `dom-event` interaction
|
|
226
|
+
clicks the control center through CDP. `os-input` sends Space or Enter through
|
|
227
|
+
the macOS event system to the owned headed Chromium process. It requires
|
|
228
|
+
Swift, OS event-posting permission, and a desktop-input reservation. The
|
|
229
|
+
server does not open permission prompts. Inspect the resulting app state
|
|
230
|
+
separately; sending input alone is not an assertion.
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"name": "xplat_interact",
|
|
235
|
+
"arguments": {
|
|
236
|
+
"sessionId": "SESSION_FROM_LAUNCH",
|
|
237
|
+
"documentId": "DOCUMENT_FROM_INSPECTION",
|
|
238
|
+
"control": { "kind": "testID", "name": "counter.increment" },
|
|
239
|
+
"mechanism": "os-input",
|
|
240
|
+
"key": "Space"
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Screenshot requests require an explicit current-task `visual-capture` grant
|
|
246
|
+
before connecting or sending a capture command. No grant means no capture.
|
|
247
|
+
Password fields, hidden inputs and hosts marked `data-xplat-private` are
|
|
248
|
+
redacted in nonvisual inspection; this does not guarantee complete redaction
|
|
249
|
+
of arbitrary application data.
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{ "name": "xplat_screenshot", "arguments": { "sessionId": "SESSION_FROM_LAUNCH" } }
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Run the consumer acceptance lane
|
|
256
|
+
|
|
257
|
+
Use a fresh directory outside your existing app. These commands scaffold the
|
|
258
|
+
normal web starter and add one app-owned development action. The in-repo lane
|
|
259
|
+
links framework packages to this checkout; other dependencies
|
|
260
|
+
install normally. The runner checks docs and current code, build/readiness,
|
|
261
|
+
the counter's `0 → 1` transition, action discovery/invocation, screenshot
|
|
262
|
+
denial, logs, and owned browser/server/profile cleanup. It also checks that
|
|
263
|
+
the fixture declaration and handler are absent from production artifacts.
|
|
264
|
+
|
|
265
|
+
```sh
|
|
266
|
+
node packages/mcp/tests/prepare-consumer.mjs /tmp/new-xplat-mcp-app
|
|
267
|
+
pnpm --dir /tmp/new-xplat-mcp-app install
|
|
268
|
+
node packages/mcp/tests/consumer-workflow.mjs /tmp/new-xplat-mcp-app
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The default run supplies browser-event evidence. On macOS, reserve the full
|
|
272
|
+
resource set and add `--os-input` for a headed Chromium OS keyboard journey.
|
|
273
|
+
Only the owned browser receives the events; the helper attempts to restore the
|
|
274
|
+
previously active application and reports when restoration is unconfirmed.
|
|
275
|
+
The runner captures no images.
|
|
276
|
+
|
|
277
|
+
```sh
|
|
278
|
+
goddard-agent resource run --json '{"resources":{"native_builds":1,"desktop_input":1},"purpose":"MCP consumer OS keyboard acceptance"}' -- node packages/mcp/tests/consumer-workflow.mjs /tmp/new-xplat-mcp-app --os-input
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The packed-artifact lane qualifies the same checks against release-evidence
|
|
282
|
+
packaging: `prepare-packed-consumer.mjs` packs the needed packages, scaffolds
|
|
283
|
+
through the packed `create-octane-xplat` bin, installs `file:` tarballs (no
|
|
284
|
+
registry/latest resolution for framework packages), and tags the framework
|
|
285
|
+
HEAD as `v<installed version>` in a local bare release fixture. The runner
|
|
286
|
+
spawns the consumer's own installed `xplat-mcp` bin over stdin/stdout and
|
|
287
|
+
redirects the canonical release-repo URL to the fixture through a per-process
|
|
288
|
+
`url.insteadOf` (never user git config). Snapshot cache and evidence roots are
|
|
289
|
+
isolated under the work dir. OS keyboard input additionally requires an
|
|
290
|
+
unlocked console — the window server refuses activation while the screen is
|
|
291
|
+
locked, and the run reports that block honestly.
|
|
292
|
+
|
|
293
|
+
```sh
|
|
294
|
+
goddard-agent resource run --json '{"resources":{"native_builds":1,"desktop_input":1},"purpose":"packed MCP stdio consumer acceptance"}' -- bash -c '
|
|
295
|
+
node packages/mcp/tests/prepare-packed-consumer.mjs /tmp/xplat-packed-mcp &&
|
|
296
|
+
node packages/mcp/tests/packed-stdio-consumer.mjs /tmp/xplat-packed-mcp'
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
For an existing consumer, declare actions behind the development build gate.
|
|
300
|
+
Registration automatically starts the bridge when the dev-server endpoint is
|
|
301
|
+
present. The action's contracts and effects appear in `xplat_dev_actions`;
|
|
302
|
+
no action-specific MCP tool definition is needed.
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
if (import.meta.env.DEV) {
|
|
306
|
+
const { registerDevAction } = await import('@octane-xplat/platform/dev-actions')
|
|
307
|
+
registerDevAction({
|
|
308
|
+
id: 'fixture.describe-counter',
|
|
309
|
+
title: 'Describe the counter',
|
|
310
|
+
description: 'Identify this test fixture without external effects.',
|
|
311
|
+
result: { fields: { label: { type: 'string' } } },
|
|
312
|
+
effects: { summary: 'Read fixture identity.' },
|
|
313
|
+
run: () => ({ label: 'starter-counter' }),
|
|
314
|
+
})
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Remaining acceptance
|
|
319
|
+
|
|
320
|
+
The Chromium lane is an incremental milestone. All six platforms remain
|
|
321
|
+
required by the original plan. Native inspection/input adapters, attached
|
|
322
|
+
Chromium inspection, approved screenshot qualification, and the complete
|
|
323
|
+
platform-by-capability acceptance matrix remain unfinished. Runtime adapters
|
|
324
|
+
and honest unavailable responses do not establish native behavior.
|
|
325
|
+
|
|
326
|
+
| Target | Inspection/input qualification boundary |
|
|
327
|
+
| --------------- | ------------------------------------------------------------------------------ |
|
|
328
|
+
| Chromium | Owned main-document DOM; browser events plus macOS scoped OS keyboard path |
|
|
329
|
+
| iOS / Android | Native host inspection and OS-input adapters still required |
|
|
330
|
+
| macOS | AppKit host inspection/input still required; Chromium on macOS is web evidence |
|
|
331
|
+
| Linux / Windows | Target-host adapters and independent consumer qualification still required |
|
|
332
|
+
|
|
333
|
+
## Boundaries
|
|
334
|
+
|
|
335
|
+
This server complements the agent's editing tools. It is not an autonomous
|
|
336
|
+
code editor or deployment system: no arbitrary shell execution, no arbitrary
|
|
337
|
+
runtime expression evaluation, no universal component tree, no hook/signal
|
|
338
|
+
inspection, no profiling API, no cloud indexing by default, no app-store
|
|
339
|
+
operations, no credential management. Screenshots are permission-gated —
|
|
340
|
+
capability existence never implies capture approval.
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@octane-xplat/mcp",
|
|
3
|
+
"version": "0.0.0-nightly.g2e22171",
|
|
4
|
+
"description": "MCP server for Octane Xplat consumer apps — project identity, capability discovery, permission and evidence contracts.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/octane-xplat/octane-xplat",
|
|
9
|
+
"directory": "packages/mcp"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"xplat-mcp": "./src/xplat-mcp.mjs"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"exports": {
|
|
20
|
+
"./contracts": {
|
|
21
|
+
"types": "./src/contracts/index.d.ts",
|
|
22
|
+
"default": "./src/contracts/index.mjs"
|
|
23
|
+
},
|
|
24
|
+
"./contracts/*": {
|
|
25
|
+
"types": "./src/contracts/*.d.ts",
|
|
26
|
+
"default": "./src/contracts/*.mjs"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "1.31.0",
|
|
34
|
+
"@octane-xplat/cli": "0.0.0-nightly.g2e22171",
|
|
35
|
+
"yaml": "2.9.1",
|
|
36
|
+
"zod": "4.6.5"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"typescript": "5.9.3"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"start": "node src/xplat-mcp.mjs",
|
|
43
|
+
"test": "node --test tests/*.test.mjs",
|
|
44
|
+
"test:consumer": "node tests/consumer-workflow.mjs",
|
|
45
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App-declared development-action contract (design: plans/octane-xplat-mcp.md,
|
|
3
|
+
* "App-declared development actions").
|
|
4
|
+
*
|
|
5
|
+
* The app owns action semantics; the framework owns discovery and invocation.
|
|
6
|
+
* A dev-mode app announces a registry snapshot (identity + revision + typed
|
|
7
|
+
* declarations) over a platform bridge; this server lists the live registry
|
|
8
|
+
* and invokes actions through that same bridge. The declaration is the single
|
|
9
|
+
* source of truth: handler, runtime-validatable input/result contracts, and
|
|
10
|
+
* metadata all travel in one announcement — there are no MCP-side per-action
|
|
11
|
+
* definitions.
|
|
12
|
+
*
|
|
13
|
+
* Wire invariants:
|
|
14
|
+
* - Every registry carries `appInstanceId` — each running app realm (tab,
|
|
15
|
+
* window, device process) is a distinct invocation destination. Calls are
|
|
16
|
+
* never silently shared or redirected.
|
|
17
|
+
* - `revision` increments on every registry change (add/change/remove, owner
|
|
18
|
+
* disposal, hot reload). Invocation presents the revision it read; a stale
|
|
19
|
+
* revision is rejected with `stale_identity` before anything runs.
|
|
20
|
+
* - Announcements are heartbeats: a stopped heartbeat marks the instance
|
|
21
|
+
* `disconnected` — a live app with zero actions still announces, so an
|
|
22
|
+
* empty registry is always distinguishable from a disconnected runtime.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type { ContractError } from './errors'
|
|
26
|
+
import type { Platform } from './identity'
|
|
27
|
+
|
|
28
|
+
export const DEV_ACTIONS_PROTOCOL = 'xplat-dev-actions'
|
|
29
|
+
export const DEV_ACTIONS_PROTOCOL_VERSION = 1
|
|
30
|
+
/** Heartbeat interval the app-side bridge uses; instances quieter than this ×3 are stale. */
|
|
31
|
+
export const DEV_ACTIONS_ANNOUNCE_INTERVAL_MS: number
|
|
32
|
+
export const DEV_ACTIONS_STALE_AFTER_MS: number
|
|
33
|
+
/** Bounded pending-invocation queue per instance; saturation returns `target_busy`. */
|
|
34
|
+
export const DEV_ACTIONS_QUEUE_LIMIT: number
|
|
35
|
+
/** Cap on overlapping invocations per instance (declared `overlap` actions). */
|
|
36
|
+
export const DEV_ACTIONS_OVERLAP_LIMIT: number
|
|
37
|
+
/** Default and maximum execution timeouts. */
|
|
38
|
+
export const DEV_ACTIONS_DEFAULT_TIMEOUT_MS: number
|
|
39
|
+
export const DEV_ACTIONS_MAX_TIMEOUT_MS: number
|
|
40
|
+
|
|
41
|
+
// ---------- declaration contract ----------
|
|
42
|
+
|
|
43
|
+
/** Runtime-validatable field subset. `fields`/`items` allow one level of structure. */
|
|
44
|
+
export interface DevActionField {
|
|
45
|
+
type: 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array'
|
|
46
|
+
description?: string
|
|
47
|
+
/** Applied when the caller omits the field. */
|
|
48
|
+
default?: unknown
|
|
49
|
+
/** Closed value set. */
|
|
50
|
+
enum?: readonly unknown[]
|
|
51
|
+
minLength?: number
|
|
52
|
+
maxLength?: number
|
|
53
|
+
/** Regular expression source (must compile). */
|
|
54
|
+
pattern?: string
|
|
55
|
+
minimum?: number
|
|
56
|
+
maximum?: number
|
|
57
|
+
minItems?: number
|
|
58
|
+
maxItems?: number
|
|
59
|
+
/** For `object`: named nested fields. Defaults apply to top-level fields only. */
|
|
60
|
+
fields?: Record<string, DevActionField>
|
|
61
|
+
/** For `array`: element contract. */
|
|
62
|
+
items?: DevActionField
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Named typed fields. `{}` (or omitted) means the action takes no arguments. */
|
|
66
|
+
export interface DevActionFields {
|
|
67
|
+
fields?: Record<string, DevActionField>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Disclosed effect categories — the agent reads these to decide whether an
|
|
72
|
+
* action fits the existing authorization. `externalEffects` maps to the
|
|
73
|
+
* `external-effects` permission scope and is checked at invocation time.
|
|
74
|
+
*/
|
|
75
|
+
export interface DevActionEffects {
|
|
76
|
+
/** Plain-language summary of what the action changes. */
|
|
77
|
+
summary: string
|
|
78
|
+
auth?: boolean
|
|
79
|
+
navigation?: boolean
|
|
80
|
+
appState?: boolean
|
|
81
|
+
testData?: boolean
|
|
82
|
+
/** Effects outside the app itself — external test services, destructive resets. */
|
|
83
|
+
externalEffects?: boolean
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type DevActionConcurrency =
|
|
87
|
+
/** Default: actions for one app instance run one at a time through a bounded queue. */
|
|
88
|
+
| 'serialized'
|
|
89
|
+
/** The app declares this action safe to overlap with other executions. */
|
|
90
|
+
| 'overlap'
|
|
91
|
+
|
|
92
|
+
export interface DevActionExecution {
|
|
93
|
+
concurrency?: DevActionConcurrency
|
|
94
|
+
/** Cooperative cancellation supported by the handler. */
|
|
95
|
+
cancellable?: boolean
|
|
96
|
+
/** Safe to retry (idempotent). Non-retryable actions never auto-rerun. */
|
|
97
|
+
retryable?: boolean
|
|
98
|
+
/** Per-action execution deadline; bounded by DEV_ACTIONS_MAX_TIMEOUT_MS. */
|
|
99
|
+
timeoutMs?: number
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface DevActionDeclaration {
|
|
103
|
+
/** Stable machine identity, e.g. `auth.login-as-test-admin`. */
|
|
104
|
+
id: string
|
|
105
|
+
title: string
|
|
106
|
+
/** When to use it and what successful completion means. */
|
|
107
|
+
description: string
|
|
108
|
+
input?: DevActionFields
|
|
109
|
+
result?: DevActionFields
|
|
110
|
+
effects: DevActionEffects
|
|
111
|
+
availability?: {
|
|
112
|
+
/** Platforms this action applies to; absent = all. */
|
|
113
|
+
platforms?: Platform[]
|
|
114
|
+
/** Readiness conditions outside platform selection (free text). */
|
|
115
|
+
readyWhen?: string
|
|
116
|
+
}
|
|
117
|
+
execution?: DevActionExecution
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Declared action plus its live availability, as announced by the app. */
|
|
121
|
+
export interface DevActionListing extends DevActionDeclaration {
|
|
122
|
+
availability?: {
|
|
123
|
+
platforms?: Platform[]
|
|
124
|
+
readyWhen?: string
|
|
125
|
+
/** Live readiness verdict from the app. */
|
|
126
|
+
ready: boolean
|
|
127
|
+
/** Why it is unavailable right now. */
|
|
128
|
+
reason?: string
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---------- registry announcement ----------
|
|
133
|
+
|
|
134
|
+
export interface DevActionRegistryAnnouncement {
|
|
135
|
+
protocol: typeof DEV_ACTIONS_PROTOCOL
|
|
136
|
+
version: number
|
|
137
|
+
/** Unique per app realm — survives hot reload, never crosses processes. */
|
|
138
|
+
appInstanceId: string
|
|
139
|
+
/** App-supplied label for the developer, e.g. package name or window title. */
|
|
140
|
+
appLabel?: string
|
|
141
|
+
platform: Platform
|
|
142
|
+
revision: number
|
|
143
|
+
announcedAt: string
|
|
144
|
+
actions: DevActionListing[]
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ---------- invocation ----------
|
|
148
|
+
|
|
149
|
+
export interface DevActionInvocationRequest {
|
|
150
|
+
/** Stable request identity — retries with the same requestId must not re-run the effect. */
|
|
151
|
+
requestId: string
|
|
152
|
+
appInstanceId: string
|
|
153
|
+
actionId: string
|
|
154
|
+
/** The registry revision the caller discovered; rejected when stale. */
|
|
155
|
+
registryRevision: number
|
|
156
|
+
input: Record<string, unknown>
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export type DevActionInvocationStatus =
|
|
160
|
+
| 'succeeded'
|
|
161
|
+
| 'failed'
|
|
162
|
+
| 'cancelled'
|
|
163
|
+
/** The app may or may not have run the action; reported, never silently retried. */
|
|
164
|
+
| 'unknown'
|
|
165
|
+
|
|
166
|
+
export interface DevActionOutcome {
|
|
167
|
+
requestId: string
|
|
168
|
+
status: DevActionInvocationStatus
|
|
169
|
+
/** Structured result validated against the declared result contract. */
|
|
170
|
+
result?: Record<string, unknown>
|
|
171
|
+
/** Machine-readable failure detail; mapped to ContractError codes server-side. */
|
|
172
|
+
error?: { code: string; message: string }
|
|
173
|
+
/** What the app knows changed before a failure/cancellation; absent = unknown. */
|
|
174
|
+
partialEffects?: string[]
|
|
175
|
+
completedAt: string
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** What `validateActionInput` returns. */
|
|
179
|
+
export type DevActionInputResult =
|
|
180
|
+
| { ok: true; value: Record<string, unknown> }
|
|
181
|
+
| { ok: false; errors: string[] }
|
|
182
|
+
|
|
183
|
+
// ---------- validators (shared with the app-side implementation) ----------
|
|
184
|
+
|
|
185
|
+
/** Declaration sanity: id shape, required metadata, compilable field patterns. */
|
|
186
|
+
export declare function validateActionDeclaration(action: unknown): string[]
|
|
187
|
+
|
|
188
|
+
/** Type-check + apply defaults + reject undeclared fields. */
|
|
189
|
+
export declare function validateActionInput(
|
|
190
|
+
contract: DevActionFields | undefined,
|
|
191
|
+
input: unknown,
|
|
192
|
+
): DevActionInputResult
|
|
193
|
+
|
|
194
|
+
/** Result-contract validation. An invalid output fails explicitly — it does not undo a ran action. */
|
|
195
|
+
export declare function validateActionOutput(
|
|
196
|
+
contract: DevActionFields | undefined,
|
|
197
|
+
output: unknown,
|
|
198
|
+
): DevActionInputResult
|
|
199
|
+
|
|
200
|
+
/** Structural check on an announcement payload. */
|
|
201
|
+
export declare function isDevActionAnnouncement(
|
|
202
|
+
value: unknown,
|
|
203
|
+
): value is DevActionRegistryAnnouncement
|
|
204
|
+
|
|
205
|
+
/** Live availability for an announced action on a given platform. */
|
|
206
|
+
export declare function actionAvailability(
|
|
207
|
+
action: DevActionListing,
|
|
208
|
+
platform: Platform,
|
|
209
|
+
): { ready: boolean; reason?: string }
|