@uppercut-labs/agent-native 0.0.0-stage → 1.1.0
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 +164 -2
- package/agent/agent-prompt-short.md +1 -0
- package/agent/agent-prompt.md +26 -0
- package/bin/agent-native-site.mjs +269 -0
- package/bin/discover-routes.mjs +162 -0
- package/bin/verify-publication.mjs +317 -0
- package/dist/adapters/zod.d.ts +4 -0
- package/dist/adapters/zod.d.ts.map +1 -0
- package/dist/adapters/zod.js +20 -0
- package/dist/adapters/zod.js.map +1 -0
- package/dist/astro.d.ts +13 -0
- package/dist/astro.d.ts.map +1 -0
- package/dist/astro.js +41 -0
- package/dist/astro.js.map +1 -0
- package/dist/auth.d.ts +52 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +128 -0
- package/dist/auth.js.map +1 -0
- package/dist/browser.d.ts +47 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +219 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.d.ts +80 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +484 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/assert-never.d.ts +2 -0
- package/dist/core/assert-never.d.ts.map +1 -0
- package/dist/core/assert-never.js +5 -0
- package/dist/core/assert-never.js.map +1 -0
- package/dist/core/binding-internal.d.ts +10 -0
- package/dist/core/binding-internal.d.ts.map +1 -0
- package/dist/core/binding-internal.js +15 -0
- package/dist/core/binding-internal.js.map +1 -0
- package/dist/core/composition.d.ts +119 -0
- package/dist/core/composition.d.ts.map +1 -0
- package/dist/core/composition.js +545 -0
- package/dist/core/composition.js.map +1 -0
- package/dist/core/contracts.d.ts +55 -0
- package/dist/core/contracts.d.ts.map +1 -0
- package/dist/core/contracts.js +271 -0
- package/dist/core/contracts.js.map +1 -0
- package/dist/core/diagnostics.d.ts +14 -0
- package/dist/core/diagnostics.d.ts.map +1 -0
- package/dist/core/diagnostics.js +29 -0
- package/dist/core/diagnostics.js.map +1 -0
- package/dist/core/executor.d.ts +52 -0
- package/dist/core/executor.d.ts.map +1 -0
- package/dist/core/executor.js +98 -0
- package/dist/core/executor.js.map +1 -0
- package/dist/core/registry.d.ts +37 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +75 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/schema.d.ts +9 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/schema.js +36 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/discovery.d.ts +16 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +23 -0
- package/dist/discovery.js.map +1 -0
- package/dist/doctor.d.ts +200 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +287 -0
- package/dist/doctor.js.map +1 -0
- package/dist/harness-codex.d.ts +17 -0
- package/dist/harness-codex.d.ts.map +1 -0
- package/dist/harness-codex.js +485 -0
- package/dist/harness-codex.js.map +1 -0
- package/dist/harness-mcp-bridge.d.ts +14 -0
- package/dist/harness-mcp-bridge.d.ts.map +1 -0
- package/dist/harness-mcp-bridge.js +159 -0
- package/dist/harness-mcp-bridge.js.map +1 -0
- package/dist/harness.d.ts +72 -0
- package/dist/harness.d.ts.map +1 -0
- package/dist/harness.js +101 -0
- package/dist/harness.js.map +1 -0
- package/dist/http.d.ts +20 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +537 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +73 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +1039 -0
- package/dist/init.js.map +1 -0
- package/dist/mcp-apps.d.ts +9 -0
- package/dist/mcp-apps.d.ts.map +1 -0
- package/dist/mcp-apps.js +171 -0
- package/dist/mcp-apps.js.map +1 -0
- package/dist/mcp-core.d.ts +64 -0
- package/dist/mcp-core.d.ts.map +1 -0
- package/dist/mcp-core.js +358 -0
- package/dist/mcp-core.js.map +1 -0
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +2 -0
- package/dist/mcp.js.map +1 -0
- package/dist/next-browser.d.ts +14 -0
- package/dist/next-browser.d.ts.map +1 -0
- package/dist/next-browser.js +14 -0
- package/dist/next-browser.js.map +1 -0
- package/dist/next.d.ts +24 -0
- package/dist/next.d.ts.map +1 -0
- package/dist/next.js +28 -0
- package/dist/next.js.map +1 -0
- package/dist/package-version.d.ts +2 -0
- package/dist/package-version.d.ts.map +1 -0
- package/dist/package-version.js +3 -0
- package/dist/package-version.js.map +1 -0
- package/dist/publication.d.ts +115 -0
- package/dist/publication.d.ts.map +1 -0
- package/dist/publication.js +441 -0
- package/dist/publication.js.map +1 -0
- package/dist/registry.d.ts +3 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +2 -0
- package/dist/registry.js.map +1 -0
- package/docs/adopt-existing-site.md +189 -0
- package/docs/browser.md +70 -0
- package/docs/capabilities-and-bindings.md +141 -0
- package/docs/cli.md +88 -0
- package/docs/compatibility.md +32 -0
- package/docs/composition-and-versioning.md +113 -0
- package/docs/doctor-and-troubleshooting.md +58 -0
- package/docs/evidence/GHSA-ch52-4w7c-c8xp-2026-10-03.md +17 -0
- package/docs/evidence/GHSA-ch52-4w7c-c8xp-2026-10-10.md +30 -0
- package/docs/evidence/GHSA-ch52-4w7c-c8xp-2026-10-12.md +10 -0
- package/docs/evidence/UAN-002-house-style-review.md +29 -0
- package/docs/evidence/UAN-007-astro.md +43 -0
- package/docs/evidence/UAN-008-worker-sidecar.md +56 -0
- package/docs/evidence/UAN-009-init.md +43 -0
- package/docs/evidence/UAN-010-auth.md +17 -0
- package/docs/evidence/UAN-011-discovery.md +18 -0
- package/docs/evidence/UAN-012-mcp-apps.md +14 -0
- package/docs/evidence/UAN-013-astro-on-demand.md +47 -0
- package/docs/evidence/UAN-014-next.md +57 -0
- package/docs/evidence/UAN-015-node-hono.md +69 -0
- package/docs/evidence/UAN-016-composition.md +52 -0
- package/docs/evidence/UAN-017-version-migrations.md +50 -0
- package/docs/evidence/UAN-018-surface-overrides.md +30 -0
- package/docs/evidence/UAN-019-doctor.md +19 -0
- package/docs/evidence/UAN-020-negative-security-fixtures.md +61 -0
- package/docs/evidence/UAN-020-operational-failure-safety.md +49 -0
- package/docs/evidence/UAN-021-docs-coverage.md +30 -0
- package/docs/evidence/UAN-022-cross-platform-license-notices.md +39 -0
- package/docs/evidence/UAN-022-overhead-license-audit.md +233 -0
- package/docs/evidence/UAN-022-package-portability.md +54 -0
- package/docs/evidence/UAN-023-commercial-agent-hosts.md +36 -0
- package/docs/evidence/UAN-023-inspector-host-probe.md +37 -0
- package/docs/evidence/UAN-023-inspector-web-and-astro-adoption.md +44 -0
- package/docs/evidence/UAN-023-native-webmcp-chrome.md +44 -0
- package/docs/evidence/UAN-024-release-readiness-2026-10-10.md +33 -0
- package/docs/evidence/UAN-024-release-readiness.md +41 -0
- package/docs/evidence/codex-harness.md +56 -0
- package/docs/evidence/gate-2-domain-expansion.md +35 -0
- package/docs/evidence/recovery-and-selection-recipes-2026-10-15.md +63 -0
- package/docs/for-agents.md +50 -0
- package/docs/frameworks.md +62 -0
- package/docs/getting-started.md +109 -0
- package/docs/harness-codex.md +117 -0
- package/docs/harness-mcp-bridge.md +30 -0
- package/docs/http.md +80 -0
- package/docs/installation.md +95 -0
- package/docs/mcp-apps.md +57 -0
- package/docs/mcp.md +178 -0
- package/docs/next.md +73 -0
- package/docs/permissions-and-discovery.md +58 -0
- package/docs/programmatic-harness.md +88 -0
- package/docs/readability-frameworks.md +18 -0
- package/docs/reference.md +161 -0
- package/docs/release-handoff.md +41 -0
- package/docs/site-publication.md +85 -0
- package/docs/stability.md +53 -0
- package/docs/static-sites.md +141 -0
- package/docs/support-and-limitations.md +20 -0
- package/docs/surfaces.md +27 -0
- package/docs/upgrading-to-1.0.md +26 -0
- package/package.json +188 -4
- package/skill/agent-native/SKILL.md +54 -0
- package/skill/agent-native/references/adapter-decision.md +19 -0
- package/skill/agent-native/references/verification.md +25 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Codex local harness
|
|
2
|
+
|
|
3
|
+
The isolated `@uppercut-labs/agent-native/harness/codex` export controls an installed
|
|
4
|
+
Codex CLI app-server over stdio. It needs Node 22+ built-ins and a caller-installed
|
|
5
|
+
Codex executable, with no provider SDK dependency. Importing the export or creating
|
|
6
|
+
the adapter starts no process or inference. These additions require version 0.1.1. Use the versioned installation route in
|
|
7
|
+
[installation](installation.md); 0.1.0 does not include this adapter.
|
|
8
|
+
|
|
9
|
+
## Authentication and model selection
|
|
10
|
+
|
|
11
|
+
Use `codex login` and complete Codex's managed ChatGPT sign-in. The adapter checks
|
|
12
|
+
`account/read` with `refreshToken: false` before creating/resuming a thread and
|
|
13
|
+
requires account type `chatgpt` and the provider's `requiresOpenaiAuth` flag. Codex owns credential storage and renewal. Do not
|
|
14
|
+
pass a token, copy authentication files, or log account responses.
|
|
15
|
+
|
|
16
|
+
This route uses the eligible ChatGPT-plan Codex path. It does not require an OpenAI
|
|
17
|
+
Platform API key, use the OpenAI Agents API, or implement external OAuth token
|
|
18
|
+
injection. API-key accounts are rejected with `authentication-required`; there is
|
|
19
|
+
no automatic API-billed fallback. Account eligibility, usage limits and model
|
|
20
|
+
availability still belong to the provider. See [official authentication guidance](https://developers.openai.com/codex/auth/).
|
|
21
|
+
|
|
22
|
+
Select `model` explicitly; the adapter never substitutes another model. `effort`
|
|
23
|
+
defaults to `low` and may be `low`, `medium`, `high`, `xhigh`, or `max` when the selected
|
|
24
|
+
model supports it. The real acceptance fixture used the installed runtime's
|
|
25
|
+
`gpt-5.6-luna` catalog entry; this is tested evidence, not a latest-model claim.
|
|
26
|
+
|
|
27
|
+
## Lifecycle
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createCodexHarnessAdapter } from '@uppercut-labs/agent-native/harness/codex';
|
|
31
|
+
|
|
32
|
+
const adapter = createCodexHarnessAdapter({ model: 'gpt-5.6-luna' });
|
|
33
|
+
const workspace = '/absolute/path/to/disposable-workspace';
|
|
34
|
+
const session = await adapter.openSession({ target: 'local', workspace });
|
|
35
|
+
try {
|
|
36
|
+
for await (const event of adapter.runTurn({ session, prompt: 'Create a small example.' })) {
|
|
37
|
+
// Render normalized events; do not publish the opaque session/turn IDs.
|
|
38
|
+
}
|
|
39
|
+
} finally {
|
|
40
|
+
await adapter.closeSession({ session });
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`describe()` advertises local-only support with `resume`, `cancel`, and `usage`.
|
|
45
|
+
Cloud open/resume is rejected. Local workspaces must be existing absolute directories;
|
|
46
|
+
the adapter canonicalizes them and rejects resume into a different directory.
|
|
47
|
+
The app-server's `thread.id` is preserved as the opaque session ID. Resume reads
|
|
48
|
+
the persisted thread's workspace before `thread/resume`, checks the returned ID
|
|
49
|
+
and directory, and can reattach through a new adapter/process after close.
|
|
50
|
+
|
|
51
|
+
Each session owns one app-server process. It initializes the protocol connection,
|
|
52
|
+
acknowledges `initialized`, checks managed login, and starts/resumes the thread
|
|
53
|
+
with the explicitly selected model and `openai` provider. The sandbox defaults
|
|
54
|
+
to `workspace-write`; `read-only` is also supported. No full-access option is exposed.
|
|
55
|
+
Noninteractive approval policy is `never`; unexpected command/file approval requests
|
|
56
|
+
are explicitly declined, MCP elicitations are declined, and other interactive
|
|
57
|
+
requests receive a fixed unsupported response. The adapter never grants extra
|
|
58
|
+
permissions. Codex's existing user/project configuration, skills, plugins, rules,
|
|
59
|
+
and MCP settings still apply; this is not an empty provider configuration.
|
|
60
|
+
|
|
61
|
+
`runTurn` sends one text input to `turn/start`, yields `turn-started`, then streams
|
|
62
|
+
normalized tool/usage events and a terminal result. Only provider status `completed`
|
|
63
|
+
produces `completed`; `interrupted` becomes `cancelled`, and failed/unknown terminal
|
|
64
|
+
status becomes `failed`. Unauthorized terminal failures map to
|
|
65
|
+
`authentication-required`. Raw model deltas, arguments, commands, diffs, reasoning,
|
|
66
|
+
account details, stderr and provider exceptions are never forwarded.
|
|
67
|
+
|
|
68
|
+
Token usage events project `tokenUsage.last.totalTokens` as nonnegative safe-integer
|
|
69
|
+
snapshots. Several updates can occur in one turn; do not sum snapshots as a cost
|
|
70
|
+
estimate. Missing usage remains unavailable. A session allows only one active turn;
|
|
71
|
+
resume and overlapping turns are rejected until its stream is consumed/closed.
|
|
72
|
+
`cancelTurn` checks the exact active turn ID and sends `turn/interrupt`.
|
|
73
|
+
|
|
74
|
+
## Bounds and cleanup
|
|
75
|
+
|
|
76
|
+
`requestTimeoutMs` defaults to 30000 (1-300000). `turnTimeoutMs` defaults to 180000
|
|
77
|
+
(1-3600000) and stops on the provider's terminal notification, even if a consumer
|
|
78
|
+
is still rendering queued events. Prompt size is limited to 65536 characters.
|
|
79
|
+
The transport bounds its unparsed buffer to 2,097,152 UTF-16 code units and the normalized queue to 256
|
|
80
|
+
events. Malformed protocol, process exit and exhausted bounds cannot imply success.
|
|
81
|
+
|
|
82
|
+
`closeSession` is idempotent for a known session. It closes only the adapter-owned
|
|
83
|
+
stdin/process, waits `shutdownTimeoutMs` (default 2000, range 1-10000), then signals
|
|
84
|
+
that child PID with SIGTERM and finally SIGKILL if needed. Each wait is bounded;
|
|
85
|
+
cleanup failure is surfaced. No process-name search or unrelated-process kill is
|
|
86
|
+
used. Close releases runtime resources; it does not erase Codex's persisted thread.
|
|
87
|
+
Abandoning a turn stream also closes its owned runtime. Consume streams and close
|
|
88
|
+
sessions in `finally` so resources are released promptly.
|
|
89
|
+
|
|
90
|
+
`executable` defaults to `codex` on the caller's PATH; `executableArgs` optionally
|
|
91
|
+
selects a trusted launcher prefix. The adapter does not install a CLI or change
|
|
92
|
+
global configuration. A missing executable raises `CodexHarnessError` with
|
|
93
|
+
`provider-unavailable` and fixed installation guidance; missing/alternate auth has
|
|
94
|
+
fixed `codex login` guidance. Other failures use the neutral stable reasons.
|
|
95
|
+
|
|
96
|
+
## MCP boundary and acceptance
|
|
97
|
+
|
|
98
|
+
Capability execution remains a separate authorized application plane. This adapter
|
|
99
|
+
does not expose a programmatic MCP-configuration API and does not advertise `mcp`.
|
|
100
|
+
Existing Codex MCP configuration follows the application's own authorization.
|
|
101
|
+
The [loopback helper](harness-mcp-bridge.md) does not grant application permissions
|
|
102
|
+
and cannot serve cloud agents through an implicit public tunnel.
|
|
103
|
+
|
|
104
|
+
Provider-free tests cover thread mapping, cross-process resume, unavailable runtime,
|
|
105
|
+
wrong auth, malformed/timeout responses, stream failures, targeted interrupt, concurrent
|
|
106
|
+
turns, workspace mismatch, permission rejection and bounded owned cleanup.
|
|
107
|
+
To deliberately opt into real inference in a disposable workspace:
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
npm run proof:codex -- --model gpt-5.6-luna
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The proof verifies `hello world`, closes the first process, resumes the same thread
|
|
114
|
+
in a new process, then verifies `hello Codex`. It prints only normalized evidence
|
|
115
|
+
and removes its temporary workspace. Regular checks never launch real inference.
|
|
116
|
+
See [version evidence](evidence/codex-harness.md) and the
|
|
117
|
+
[official app-server protocol](https://developers.openai.com/codex/app-server/).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Local harness MCP bridge
|
|
2
|
+
|
|
3
|
+
Import `startHarnessMcpBridge` from `@uppercut-labs/agent-native/harness/mcp-bridge`
|
|
4
|
+
in Node 22+. It serves an application-supplied Web Request/Response MCP handler
|
|
5
|
+
on `127.0.0.1` at `/mcp`, using an ephemeral port by default. It refuses public
|
|
6
|
+
binds and validates Host/Origin, preserving handler authentication and capability
|
|
7
|
+
authorization. It does not import the MCP SDK or mint credentials.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
const bridge = await startHarnessMcpBridge({ handler: applicationMcpHandler });
|
|
11
|
+
try {
|
|
12
|
+
// Configure the local provider to connect to bridge.url.
|
|
13
|
+
} finally {
|
|
14
|
+
await bridge.close();
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`port` may be 0-65535. `maxRequestBytes` defaults to 32768 (1-1048576), and
|
|
19
|
+
`deadlineMs` defaults to 30000 (1-300000), including response streaming. Oversized
|
|
20
|
+
requests receive 413; deadlines abort the request and return 504 before headers
|
|
21
|
+
or close an already streaming response. Handler errors produce a fixed diagnostic
|
|
22
|
+
and 500 or stream closure. Handlers must honor AbortSignal to release their own work.
|
|
23
|
+
Idempotent `close()` aborts active requests and closes only owned connections.
|
|
24
|
+
|
|
25
|
+
Loopback is reachable by local processes and is not an authorization grant.
|
|
26
|
+
Anonymous MCP initialization may succeed while protected calls stay denied.
|
|
27
|
+
Application policy remains authoritative at discovery and execution. Cloud agents
|
|
28
|
+
need a caller-supplied reachable authenticated endpoint; this helper never creates
|
|
29
|
+
a public bind or tunnel. See [MCP policy](mcp.md) and
|
|
30
|
+
[harness contracts](programmatic-harness.md).
|
package/docs/http.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# HTTP and OpenAPI adapter
|
|
2
|
+
|
|
3
|
+
Import the HTTP adapter from its separate package entrypoint:
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { createHttpHandler } from '@uppercut-labs/agent-native/http';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
`createHttpHandler(registry, options)` returns a handler from the standard Web `Request` API to
|
|
10
|
+
`Response`. Mount that function in a framework or server of your choice. The core contract and
|
|
11
|
+
executor remain independent of HTTP, Node, and framework modules.
|
|
12
|
+
|
|
13
|
+
The default base path is `/agent-native/v1`. Public, read-only capabilities receive a deterministic
|
|
14
|
+
`POST /agent-native/v1/capabilities/{namespace}/{name}/v{major}/invoke` route. The endpoint is a
|
|
15
|
+
generic capability invocation, not a generated REST resource API. By default there is no GET
|
|
16
|
+
invocation route; a GET to a visible invocation path receives `405` with `Allow: POST`.
|
|
17
|
+
|
|
18
|
+
A definition may preserve an established read route with a contract-level override:
|
|
19
|
+
|
|
20
|
+
E10 keeps its existing search route with this override in the tested capability definition:
|
|
21
|
+
|
|
22
|
+
<!-- source:examples/e10-existing-functions-retrofit/project/src/capability.mjs#http-get-override -->
|
|
23
|
+
~~~js
|
|
24
|
+
http: {
|
|
25
|
+
path: '/api/content/search',
|
|
26
|
+
method: 'GET',
|
|
27
|
+
query: { query: 'q' },
|
|
28
|
+
},
|
|
29
|
+
~~~
|
|
30
|
+
|
|
31
|
+
[View tested source](https://github.com/uppercut-labs/agent-native/blob/main/examples/e10-existing-functions-retrofit/project/src/capability.mjs#L33)
|
|
32
|
+
<!-- /source -->
|
|
33
|
+
|
|
34
|
+
The `query` map runs from input property to query parameter. GET conversion supports string,
|
|
35
|
+
number, integer, boolean, string enum, and arrays of those scalar values; nested objects and other
|
|
36
|
+
lossy conversions fail when the HTTP adapter is created. GET overrides are rejected for write or
|
|
37
|
+
destructive capabilities. Paths must be absolute and unique across visible definitions. The same
|
|
38
|
+
override drives routing and OpenAPI parameters.
|
|
39
|
+
|
|
40
|
+
The adapter uses the shared executor for schema validation, authorization, binding selection, and
|
|
41
|
+
output validation. Its default authorization policy permits only definitions explicitly marked as
|
|
42
|
+
public reads. Supply `resolveExecutionContext` to adapt a trusted authentication boundary; this
|
|
43
|
+
does not replace authorization in the executor. Hidden/protected definitions are omitted from the
|
|
44
|
+
public OpenAPI document and route map, and unknown paths return the same `404` shape.
|
|
45
|
+
|
|
46
|
+
OpenAPI 3.1 is generated directly from the public definitions' input/output `SchemaPort` values.
|
|
47
|
+
The same document is available at `GET /agent-native/v1/openapi.json`. A health observation at
|
|
48
|
+
`GET /agent-native/v1/health` records an actual protocol request as `UAN-003.http-protocol`.
|
|
49
|
+
|
|
50
|
+
The default request body limit is 32 KiB and can be set from 1 byte to 1 MiB with
|
|
51
|
+
`maxRequestBytes`. The default execution deadline is 10 seconds and the configurable maximum is
|
|
52
|
+
300 seconds. At a deadline the handler returns `504` and sets `context.signal.aborted`. Bindings
|
|
53
|
+
must observe that signal to stop their work; JavaScript cannot forcibly stop a handler that ignores
|
|
54
|
+
cooperative cancellation.
|
|
55
|
+
|
|
56
|
+
| Result | HTTP status |
|
|
57
|
+
| --- | ---: |
|
|
58
|
+
| Validated capability result | 200 |
|
|
59
|
+
| Visible capability path with an unsupported method | 405 (Allow: POST) |
|
|
60
|
+
| Malformed JSON | 400 |
|
|
61
|
+
| Capability missing or hidden | 404 |
|
|
62
|
+
| Body exceeds limit | 413 |
|
|
63
|
+
| Unsupported content type | 415 |
|
|
64
|
+
| Input schema failure | 422 |
|
|
65
|
+
| Handler or output schema failure | 500 |
|
|
66
|
+
| Binding/auth context unavailable | 503 |
|
|
67
|
+
| Execution deadline reached | 504 |
|
|
68
|
+
|
|
69
|
+
This slice has no framework-specific route installer or authenticated OpenAPI projection,
|
|
70
|
+
rate limiter, or provider deployment integration. A host remains responsible for transport security,
|
|
71
|
+
authentication, rate limiting, and deployment.
|
|
72
|
+
|
|
73
|
+
## Reproduce the GET override
|
|
74
|
+
|
|
75
|
+
With Node.js 22 or newer, run `npm ci` and `npm run example:e10` at the root.
|
|
76
|
+
In the exported E10 project, start `npm start`, then request
|
|
77
|
+
`http://127.0.0.1:8788/api/content/search?q=night&limit=1`; the response contains
|
|
78
|
+
the existing first search match. Missing `q` or a fractional `limit` returns
|
|
79
|
+
`invalid_input` without invoking the source function. Supply a valid query and
|
|
80
|
+
integer limit, then retry. See [E10 setup](https://github.com/uppercut-labs/agent-native/blob/main/examples/e10-existing-functions-retrofit/project/README.md).
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Installing only the surfaces you use
|
|
2
|
+
|
|
3
|
+
To wrap a working site’s existing operation, follow [adopt an existing site](adopt-existing-site.md).
|
|
4
|
+
|
|
5
|
+
Agent Native `1.0.1` requires Node.js 22 or newer. Install the package and only the peers your application imports:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @uppercut-labs/agent-native@1.0.1
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
If 1.0.1 is not available from your npm registry, use its public GitHub release
|
|
12
|
+
archive. In a disposable directory, download and verify the release files before
|
|
13
|
+
installing the archive into the intended project:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
curl --fail --location --output uppercut-labs-agent-native-1.0.1.tgz \
|
|
17
|
+
https://github.com/uppercut-labs/agent-native/releases/download/v1.0.1/uppercut-labs-agent-native-1.0.1.tgz
|
|
18
|
+
curl --fail --location --output uppercut-labs-agent-native-1.0.1.tgz.sha256 \
|
|
19
|
+
https://github.com/uppercut-labs/agent-native/releases/download/v1.0.1/uppercut-labs-agent-native-1.0.1.tgz.sha256
|
|
20
|
+
shasum -a 256 -c uppercut-labs-agent-native-1.0.1.tgz.sha256
|
|
21
|
+
npm install ./uppercut-labs-agent-native-1.0.1.tgz
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
On Linux, `sha256sum -c` can replace `shasum -a 256 -c`. Stop if verification
|
|
25
|
+
fails. The checksum detects an altered download; it is not an npm provenance
|
|
26
|
+
attestation. Both release files are hosted with the tagged public source.
|
|
27
|
+
|
|
28
|
+
The public package has no global `agent-native` executable. Applications call its APIs or expose their own CLI runner. The repository examples independently install local tarballs so their checks stay tied to a source commit.
|
|
29
|
+
|
|
30
|
+
## First local capability
|
|
31
|
+
|
|
32
|
+
Add Zod for the schema adapter, then save the following as `quickstart.mjs` in your application:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npm install zod@4.6.5
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
import { bindCapability, createCapabilityRegistry, defineCapability, executeCapability } from '@uppercut-labs/agent-native';
|
|
40
|
+
import { fromZod } from '@uppercut-labs/agent-native/schema/zod';
|
|
41
|
+
import * as z from 'zod';
|
|
42
|
+
|
|
43
|
+
const greet = defineCapability({
|
|
44
|
+
identity: { namespace: 'demo', name: 'greet', majorVersion: 1 },
|
|
45
|
+
description: 'Greet one person.',
|
|
46
|
+
input: fromZod(z.object({ name: z.string().min(1) })),
|
|
47
|
+
output: fromZod(z.object({ message: z.string() })),
|
|
48
|
+
risk: 'read',
|
|
49
|
+
access: { kind: 'public' },
|
|
50
|
+
});
|
|
51
|
+
const registry = createCapabilityRegistry(
|
|
52
|
+
[greet],
|
|
53
|
+
[bindCapability(greet, {
|
|
54
|
+
id: 'local-greet',
|
|
55
|
+
targets: ['local'],
|
|
56
|
+
execute: async ({ name }) => ({ message: 'Hello, ' + name + '!' }),
|
|
57
|
+
})],
|
|
58
|
+
);
|
|
59
|
+
const result = await executeCapability(registry, {
|
|
60
|
+
identity: greet.identity,
|
|
61
|
+
runtime: 'local',
|
|
62
|
+
input: { name: 'World' },
|
|
63
|
+
caller: { kind: 'anonymous' },
|
|
64
|
+
authorization: { authorize: ({ access, risk }) => access.kind === 'public' && risk === 'read' },
|
|
65
|
+
});
|
|
66
|
+
if (result.kind !== 'success') throw new Error(result.reason);
|
|
67
|
+
console.log(result.value);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Run `node quickstart.mjs`; it prints `{ message: 'Hello, World!' }`. Every invocation supplies an authorization port, including public reads.
|
|
71
|
+
|
|
72
|
+
The root, contracts, composition, registry, executor, browser, HTTP, CLI and init subpaths need no runtime dependency from the package. Optional integrations are explicit:
|
|
73
|
+
|
|
74
|
+
| Imported subpath | Install alongside Agent Native |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
| harness | No runtime dependency; neutral contracts only |
|
|
77
|
+
| harness/mcp-bridge | Node 22+ built-ins; no MCP SDK dependency |
|
|
78
|
+
| harness/codex | Node 22+ and an installed Codex CLI with ChatGPT sign-in; no provider SDK dependency |
|
|
79
|
+
| schema/zod | zod 4.6.x (4.6.5 or later patch; tested with 4.6.5 and 4.6.6) |
|
|
80
|
+
| mcp or next | @modelcontextprotocol/server@2.3.0 |
|
|
81
|
+
| mcp-apps | @modelcontextprotocol/server@2.3.0 and @modelcontextprotocol/ext-apps@2.0.3 |
|
|
82
|
+
| astro | astro in the documented supported range |
|
|
83
|
+
|
|
84
|
+
The package does not install optional peers automatically. Application-owned routes, client libraries, framework adapters and deployment hosts remain separate. For example, the Astro on-demand fixture also installs @astrojs/node; the Next fixture installs Next and React. The browser-only E06 fixture installs Zod for its own schema and has no MCP server dependency.
|
|
85
|
+
|
|
86
|
+
Run npm run check:portability from this repository to build, pack, install in a fresh core/browser consumer, verify that optional adapters are absent, and smoke-test the application-owned CLI adapter. See the [UAN-022 measured evidence](evidence/UAN-022-package-portability.md) for the environment, sizes and Worker fixture result.
|
|
87
|
+
|
|
88
|
+
## Check a fresh consumer
|
|
89
|
+
|
|
90
|
+
Use Node.js 22 or newer. From the repository root, run `npm ci` followed by
|
|
91
|
+
`npm run check:portability`. The check packs the current source, installs
|
|
92
|
+
a core/browser consumer without optional peers, verifies imports and the packed
|
|
93
|
+
CLI, and checks every declared export in the tarball. A missing
|
|
94
|
+
`@modelcontextprotocol/server` error from `/mcp` means the application needs
|
|
95
|
+
that peer; install it only for that server integration. The independent repository examples continue to use local tarballs.
|
package/docs/mcp-apps.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# MCP Apps adapter
|
|
2
|
+
|
|
3
|
+
MCP Apps is an optional presentation layer for an existing MCP capability. Install the official SDK when using this adapter:
|
|
4
|
+
|
|
5
|
+
~~~sh
|
|
6
|
+
npm install @modelcontextprotocol/server@2.3.0 @modelcontextprotocol/ext-apps@2.0.3
|
|
7
|
+
~~~
|
|
8
|
+
|
|
9
|
+
Import the isolated subpath only in the server application:
|
|
10
|
+
|
|
11
|
+
~~~ts
|
|
12
|
+
import { createMcpAppsHandler } from '@uppercut-labs/agent-native/mcp-apps';
|
|
13
|
+
|
|
14
|
+
const handler = createMcpAppsHandler(registry, {
|
|
15
|
+
...mcpOptions,
|
|
16
|
+
resources: [{
|
|
17
|
+
capabilityId: 'example.catalog:album.lookup@1',
|
|
18
|
+
uri: 'ui://catalog/album-explorer.html',
|
|
19
|
+
name: 'Album explorer',
|
|
20
|
+
html: bundledHtml,
|
|
21
|
+
}],
|
|
22
|
+
});
|
|
23
|
+
~~~
|
|
24
|
+
|
|
25
|
+
Each resource maps to one capability already present in the registry. The adapter checks canonical capability IDs, unique mappings, bounded resource size and an exact ui:// resource URI. UI metadata is attached only after the ordinary MCP discovery rules expose the bound capability. Resource reads are registered only for those visible app tools. Tool execution still uses the same shared executor, schemas, authorization and per-surface exposure policy. The resource does not create a second API.
|
|
26
|
+
|
|
27
|
+
Resources use the official ext-apps server helpers and MIME type text/html;profile=mcp-app. CSP metadata closes connect, resource, frame and base-URI origins by default. Resource HTML is capped at 1 MiB; external resource-bearing elements and CSS origins are rejected. The view should use the official App host bridge for follow-up calls. It must not fetch the MCP endpoint directly or embed credentials. Hosts remain responsible for sandbox enforcement and identity.
|
|
28
|
+
|
|
29
|
+
Provide both content text and structuredContent on tool results. Text-only MCP clients still receive the ordinary result. Views validate structured data against the existing capability output schema and can parse the text JSON fallback. The E09 view shows loading, found, missing, denied and unavailable states, including malformed host input.
|
|
30
|
+
|
|
31
|
+
Run the standalone fixture with npm run example:e09. Its conformance fixture pairs the official ext-apps App and AppBridge classes through a deterministic in-memory channel; the outer tool calls use the official MCP client/server SDK over a real loopback HTTP socket. It verifies initial tool-result delivery, a host-mediated follow-up lookup, a missing album, authorization denial before binding effects, malformed UI input, text fallback, MIME type and undeclared resource/origin rejection.
|
|
32
|
+
|
|
33
|
+
This fixture is SDK-level evidence, not browser iframe or commercial-host acceptance. No real MCP Apps host was exercised. Verify actual host rendering, sandbox/CSP enforcement, host confirmation behavior, and authentication in UAN-023. Core and browser-only imports do not import ext-apps; the adapter requires the optional peer only when its subpath is loaded.
|
|
34
|
+
|
|
35
|
+
Official API references: [quickstart](https://github.com/modelcontextprotocol/ext-apps/blob/main/docs/quickstart.md) and [overview](https://github.com/modelcontextprotocol/ext-apps/blob/main/docs/overview.md).
|
|
36
|
+
|
|
37
|
+
## Verify host-mediated follow-up
|
|
38
|
+
|
|
39
|
+
With Node.js 22 or newer, run `npm ci` and `npm run example:e09` from the package root.
|
|
40
|
+
The E09 controller calls the host bridge for a follow-up lookup:
|
|
41
|
+
|
|
42
|
+
<!-- source:examples/e09-album-explorer/project/src/view-controller.mjs#host-mediated-lookup -->
|
|
43
|
+
~~~js
|
|
44
|
+
const result = await app.callServerTool({ name: toolName, arguments: { slug } });
|
|
45
|
+
const outcome = decodeResult(result, outputSchema);
|
|
46
|
+
show(outcome);
|
|
47
|
+
return outcome;
|
|
48
|
+
~~~
|
|
49
|
+
|
|
50
|
+
[View tested source](https://github.com/uppercut-labs/agent-native/blob/main/examples/e09-album-explorer/project/src/view-controller.mjs#L52)
|
|
51
|
+
<!-- /source -->
|
|
52
|
+
|
|
53
|
+
The SDK fixture checks found, missing, denied, malformed, and text-only fallback states.
|
|
54
|
+
If the host supplies malformed input or denies a call, the view shows an explicit
|
|
55
|
+
unavailable or denied state; it never fetches MCP credentials itself. See the
|
|
56
|
+
[E09 project instructions](https://github.com/uppercut-labs/agent-native/blob/main/examples/e09-album-explorer/project/README.md).
|
|
57
|
+
A rendered browser host remains a separate compatibility check.
|
package/docs/mcp.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Remote MCP over Streamable HTTP
|
|
2
|
+
|
|
3
|
+
For an application-owned local harness endpoint, see the
|
|
4
|
+
[loopback bridge](harness-mcp-bridge.md). Harness control does not grant capability
|
|
5
|
+
permissions; discovery and execution still use the policies below.
|
|
6
|
+
|
|
7
|
+
The MCP subpath needs the official server SDK as an explicit optional peer in the host project:
|
|
8
|
+
|
|
9
|
+
~~~sh
|
|
10
|
+
npm install @modelcontextprotocol/server@2.3.0
|
|
11
|
+
~~~
|
|
12
|
+
|
|
13
|
+
Import the adapter from its isolated package subpath:
|
|
14
|
+
|
|
15
|
+
~~~ts
|
|
16
|
+
import { createMcpHandler } from '@uppercut-labs/agent-native/mcp';
|
|
17
|
+
|
|
18
|
+
const handler = createMcpHandler(registry, {
|
|
19
|
+
resolveExecutionContext(request) {
|
|
20
|
+
// Return a caller and AuthorizationPort from the application's trusted auth boundary.
|
|
21
|
+
return { caller, authorization };
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
~~~
|
|
25
|
+
|
|
26
|
+
Mount the Web-standard Request-to-Response function at /mcp. The adapter delegates protocol
|
|
27
|
+
handling, version negotiation, tool registration and Streamable HTTP transport to the official
|
|
28
|
+
MCP server SDK. It does not implement MCP wire messages itself. The root contract and executor
|
|
29
|
+
entrypoints remain free of MCP SDK imports.
|
|
30
|
+
|
|
31
|
+
By default, the adapter registers only capabilities whose access is explicitly public and whose
|
|
32
|
+
risk is read. The optional `canDiscover` callback can narrow that set; it cannot reveal protected
|
|
33
|
+
capabilities. Protected discovery requires an explicit `discoverProtected` callback, official SDK
|
|
34
|
+
bearer verification, trusted principal resolution, and grant authorization. Destructive capabilities
|
|
35
|
+
also require an exact per-app MCP exposure entry:
|
|
36
|
+
|
|
37
|
+
~~~ts
|
|
38
|
+
surfaceExposure: { mcp: { destructive: ['account:delete@1'] } }
|
|
39
|
+
~~~
|
|
40
|
+
|
|
41
|
+
The server factory applies discovery on every protocol request, and the shared executor rechecks
|
|
42
|
+
authorization for each `tools/call`. Granting a scope alone never exposes a destructive tool. A direct call to a hidden or filtered tool name receives the SDK's normal
|
|
43
|
+
unknown-tool protocol error without running a binding. See
|
|
44
|
+
[permissions and discovery](permissions-and-discovery.md) and the
|
|
45
|
+
[E07 playlist fixture](https://github.com/uppercut-labs/agent-native/blob/main/examples/e07-playlist-permissions/README.md).
|
|
46
|
+
|
|
47
|
+
The SDK receives JSON Schema generated by each capability's SchemaPort. The shared executor
|
|
48
|
+
validates input and output again. Successful tool results place the canonical output under
|
|
49
|
+
structuredContent.result so object and scalar capability results use one predictable shape.
|
|
50
|
+
The text content carries the same JSON value. Error text is bounded and omits raw inputs, scopes,
|
|
51
|
+
handler exceptions and output values.
|
|
52
|
+
|
|
53
|
+
The default request body limit is 32 KiB and can be configured from 1 byte through 1 MiB with
|
|
54
|
+
maxRequestBytes. Tool execution defaults to a 10-second deadline, configurable up to 300 seconds.
|
|
55
|
+
A timed-out call receives an MCP tool error and its binding receives an aborted signal; bindings
|
|
56
|
+
must observe that signal to stop their work. For a write or destructive capability, the error says
|
|
57
|
+
that the write may have completed. The adapter does not retry; callers must check state before any
|
|
58
|
+
retry unless the application defines an idempotency contract. Host code remains responsible for
|
|
59
|
+
identity-provider configuration, transport security, grant persistence and rate limiting.
|
|
60
|
+
|
|
61
|
+
### Reconcile an uncertain write
|
|
62
|
+
|
|
63
|
+
The [E07 recovery recipe](https://github.com/uppercut-labs/agent-native/blob/main/examples/e07-playlist-permissions/project/src/recovery-demo.mjs)
|
|
64
|
+
connects an application-owned idempotency key to a protected status capability. Run
|
|
65
|
+
`npm run example:e07` from the repository root to build, install and execute it. To repeat
|
|
66
|
+
only the demo, run `npm run recovery` in `examples/e07-playlist-permissions/.exported`.
|
|
67
|
+
|
|
68
|
+
The original write reserves the caller's key in a file journal, deliberately ignores abort,
|
|
69
|
+
and waits on a deferred commit. After the MCP deadline warning, status returns `pending`.
|
|
70
|
+
The application then completes the original operation; the next status returns `committed`
|
|
71
|
+
with one commit. Reopening the journal preserves the outcome. No automatic retry occurs.
|
|
72
|
+
|
|
73
|
+
<!-- source:examples/e07-playlist-permissions/project/src/recovery-demo.mjs#reconcile-write -->
|
|
74
|
+
~~~js
|
|
75
|
+
const pending = await fixture.call('status', { key: 'operation-1' });
|
|
76
|
+
assert.deepEqual(pending.structuredContent.result, { state: 'pending', commits: 0 });
|
|
77
|
+
// The fixture now allows the original operation to finish; do not submit another write.
|
|
78
|
+
fixture.release.resolve();
|
|
79
|
+
await fixture.completed.promise;
|
|
80
|
+
const committed = await fixture.call('status', { key: 'operation-1' });
|
|
81
|
+
assert.deepEqual(committed.structuredContent.result, { state: 'committed', commits: 1 });
|
|
82
|
+
assert.equal(fixture.counts().writes, 1);
|
|
83
|
+
assert.equal(fixture.counts().invocations, 1);
|
|
84
|
+
~~~
|
|
85
|
+
|
|
86
|
+
[View tested source](https://github.com/uppercut-labs/agent-native/blob/main/examples/e07-playlist-permissions/project/src/recovery-demo.mjs#L19)
|
|
87
|
+
<!-- /source -->
|
|
88
|
+
|
|
89
|
+
Keep the same key and value while reconciling. A pending operation must remain pending until
|
|
90
|
+
the application can establish its outcome. A missing record is not proof that a remote write
|
|
91
|
+
never happened: investigate the application's persistence and recovery contract before
|
|
92
|
+
resubmitting. The fixture separately verifies that an explicit duplicate with the same value
|
|
93
|
+
does not start a second write, a different value conflicts, and an unauthorized status reader
|
|
94
|
+
receives no protected state. Status requires its own current grant and ownership check.
|
|
95
|
+
|
|
96
|
+
The file journal demonstrates single-process persistence, not production crash safety or
|
|
97
|
+
cross-process locking. A production application must atomically reserve the caller-scoped key
|
|
98
|
+
and persist the mutation and outcome, or reconcile with its downstream provider. Define key
|
|
99
|
+
retention and recovery for abandoned pending operations. Agent Native supplies no durable
|
|
100
|
+
operation ledger, rollback-on-timeout or exactly-once guarantee. The deterministic test advances
|
|
101
|
+
the deadline with Node's mock timers and controls commit with a deferred promise; it uses no
|
|
102
|
+
wall-clock sleeps to decide correctness.
|
|
103
|
+
|
|
104
|
+
A real local HTTP socket fixture uses the official client and server packages, both pinned to
|
|
105
|
+
version 2.3.0. The observed client session negotiated protocol version 2025-11-25. It discovers
|
|
106
|
+
and invokes the sample album capability, checks the structured result against the canonical output
|
|
107
|
+
schema, and exercises malformed transport input, oversized input, invalid tool arguments, hidden
|
|
108
|
+
definitions and a request-specific discovery filter. This is fixture-tested protocol evidence; it
|
|
109
|
+
does not establish support for every commercial MCP client or a deployed host.
|
|
110
|
+
|
|
111
|
+
Destructive definitions require an exact app surface entry, in addition to a current grant:
|
|
112
|
+
|
|
113
|
+
~~~ts
|
|
114
|
+
surfaceExposure: { mcp: { destructive: ['account:delete@1'] } }
|
|
115
|
+
~~~
|
|
116
|
+
|
|
117
|
+
The adapter filters out definitions without one unique server binding and requires current token scopes plus a matching, unexpired, unrevoked grant before consulting `discoverProtected`; that callback only narrows discovery. The shared executor still checks every call. Exposure is also checked at the call boundary so a stale listing cannot retain destructive access.
|
|
118
|
+
|
|
119
|
+
For the optional interactive MCP Apps presentation, use the isolated [/mcp-apps](mcp-apps.md) adapter and its E09 SDK conformance fixture. It keeps the regular text/structured tool result for hosts without MCP Apps rendering.
|
|
120
|
+
|
|
121
|
+
## Protected-only servers
|
|
122
|
+
|
|
123
|
+
When `bearerAuth` is configured and no capability is a public read with a server binding, a request without an `Authorization` header receives the SDK's 401 challenge with `WWW-Authenticate: Bearer`. Hosts then ask for credentials instead of connecting and seeing an empty tool list. Set `bearerAuth.required` to `true` to require a token even when public reads exist, or to `false` to keep anonymous handshakes.
|
|
124
|
+
|
|
125
|
+
## Caller identity in bindings
|
|
126
|
+
|
|
127
|
+
Every binding receives the caller the executor authorized as `context.caller`. For a verified MCP principal, `context.caller.identity` carries its `issuer`, `subject`, `tenantId` and `clientId`, so a binding can scope records to the caller without rebuilding the registry per request. Authorization still happens first: `authorizeResource` must enforce ownership before the binding runs.
|
|
128
|
+
|
|
129
|
+
## Operator failure hook
|
|
130
|
+
|
|
131
|
+
Tool errors returned to the client are deliberately generic. To see why a call failed, pass `onToolFailure`. It receives the capability ID, the tool name, a reason (an executor failure kind, or `context-unavailable`, `not-exposed`, `result-not-serializable` or `adapter-error`) and the executor's `DiagnosticObservation`. It never receives input, output, credentials or exception text. Errors thrown by the hook are ignored. To log a binding exception, catch it inside the binding.
|
|
132
|
+
|
|
133
|
+
The [E11 executor and operator recipe](https://github.com/uppercut-labs/agent-native/blob/main/examples/e11-diagnostics-fault-lab/project/src/executor-selection.mjs)
|
|
134
|
+
runs with `npm run example:e11`; repeat it with `npm run selection` in the exported project.
|
|
135
|
+
It receives `handler-failed` and `invalid-output` privately while clients receive the same
|
|
136
|
+
bounded error. Sentinel checks reject input, output, credential or exception disclosure in
|
|
137
|
+
both client results and operator events. Hidden tools remain undiscoverable and uncallable.
|
|
138
|
+
|
|
139
|
+
<!-- source:examples/e11-diagnostics-fault-lab/project/src/executor-selection.mjs#operator-hook -->
|
|
140
|
+
~~~js
|
|
141
|
+
const handler = createMcpHandler(registry, {
|
|
142
|
+
resolveExecutionContext: () => ({ caller: { kind: 'anonymous' }, authorization: publicRead }),
|
|
143
|
+
onToolFailure: (failure) => {
|
|
144
|
+
// Store only the bounded diagnostic in the application's private operator sink.
|
|
145
|
+
failures.push(failure);
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
~~~
|
|
149
|
+
|
|
150
|
+
[View tested source](https://github.com/uppercut-labs/agent-native/blob/main/examples/e11-diagnostics-fault-lab/project/src/executor-selection.mjs#L110)
|
|
151
|
+
<!-- /source -->
|
|
152
|
+
|
|
153
|
+
## Tool names
|
|
154
|
+
|
|
155
|
+
The default name, from `mcpToolName`, is collision-free but long, for example `cap_7_example_6_lookup_v1`. The `toolName` option can return shorter names. Each must be 1 to 128 characters of `A-Z a-z 0-9 _ - .` and unique within the registry; otherwise the handler is not created.
|
|
156
|
+
|
|
157
|
+
## Cloudflare Workers: keep request resources open until the response ends
|
|
158
|
+
|
|
159
|
+
A Streamable HTTP response is a stream. For a tool call, the SDK returns the `Response` first and runs the binding while the body is being written. Anything the binding uses that is scoped to the request, such as a database pool, must stay open until the body ends, not merely until the handler returns:
|
|
160
|
+
|
|
161
|
+
~~~ts
|
|
162
|
+
const response = await handler(request);
|
|
163
|
+
if (!response.body) return response;
|
|
164
|
+
const { readable, writable } = new TransformStream();
|
|
165
|
+
ctx.waitUntil(response.body.pipeTo(writable).finally(() => pool.end()));
|
|
166
|
+
return new Response(readable, response);
|
|
167
|
+
~~~
|
|
168
|
+
|
|
169
|
+
Closing the pool in a `finally` around `await handler(request)` makes every tool call fail as `handler-failed`, while discovery still works.
|
|
170
|
+
|
|
171
|
+
## Reproduce a local protocol call
|
|
172
|
+
|
|
173
|
+
Use Node.js 22 or newer, `npm ci`, then `npm run example:e04` from the repository
|
|
174
|
+
root. The exported Worker starts locally under Wrangler and the official SDK client
|
|
175
|
+
discovers and calls the public album tool through `/mcp`. A wrong configured
|
|
176
|
+
`SIDECAR_HOST` yields a 421 response; set it to the local request host before
|
|
177
|
+
retesting. The [E04 Worker instructions](https://github.com/uppercut-labs/agent-native/blob/main/examples/e04-worker-sidecar/project/README.md)
|
|
178
|
+
give the exact local settings and cleanup. This is loopback protocol evidence.
|
package/docs/next.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Next.js App Router
|
|
2
|
+
|
|
3
|
+
The experimental Next.js integration targets Next 16.3.8 App Router routes on the Node runtime. It
|
|
4
|
+
does not import Next itself: App Router supplies standard Web `Request` objects, and the adapter
|
|
5
|
+
returns standard `Response` objects from the existing HTTP and official MCP handlers.
|
|
6
|
+
|
|
7
|
+
For a public read, install the server peer and the server-boundary marker, and install the
|
|
8
|
+
client in development for protocol verification:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install @modelcontextprotocol/server@2.3.0 server-only@0.0.1
|
|
12
|
+
npm install --save-dev @modelcontextprotocol/client@2.3.0
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The app-owned server registry module exports its public-read-only authorizer, such as the
|
|
16
|
+
checked policy in [adopt an existing site](adopt-existing-site.md). Supply it in the route:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
// app/mcp/route.js
|
|
20
|
+
import { createNextMcpRoute } from '@uppercut-labs/agent-native/next';
|
|
21
|
+
import { registry, publicReadAuthorization } from '../../lib/server-registry.js';
|
|
22
|
+
|
|
23
|
+
export const runtime = 'nodejs';
|
|
24
|
+
export const { GET, POST, DELETE } = createNextMcpRoute(registry, {
|
|
25
|
+
resolveExecutionContext: () => ({
|
|
26
|
+
caller: { kind: 'anonymous' },
|
|
27
|
+
authorization: publicReadAuthorization,
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This policy permits explicitly public reads. Protected tools require the request-specific
|
|
33
|
+
verified identity and grants described in [permissions](permissions-and-discovery.md).
|
|
34
|
+
|
|
35
|
+
Mount `createNextHttpRoute` from a catch-all route covering `/agent-native/v1`. Keep server registry
|
|
36
|
+
modules marked `server-only`. The two route factories share the existing registry/executor behavior,
|
|
37
|
+
including schema checks, authorization, discovery, request bounds, and deadlines.
|
|
38
|
+
|
|
39
|
+
Browser support is isolated at `@uppercut-labs/agent-native/next/browser`. Call `sync()` from a
|
|
40
|
+
client component when `usePathname()` changes because Next exposes no stable DOM navigation event.
|
|
41
|
+
Registration remains feature-detected through the underlying WebMCP adapter. A normal page must
|
|
42
|
+
remain useful when that API is absent.
|
|
43
|
+
|
|
44
|
+
The init inspector recognizes Next from dependency and filesystem evidence without importing config
|
|
45
|
+
or application modules. It proposes manual integration and never rewrites existing Next auth,
|
|
46
|
+
layout, config, or routes. An existing `/mcp` route is a conflict for same-origin MCP mode and yields
|
|
47
|
+
no proposed edits.
|
|
48
|
+
|
|
49
|
+
See the [E03 fixture](https://github.com/uppercut-labs/agent-native/blob/main/examples/e03-next-reading-list/project/README.md) and
|
|
50
|
+
[UAN-014 local verification evidence](evidence/UAN-014-next.md).
|
|
51
|
+
|
|
52
|
+
## Verify the ownership boundary
|
|
53
|
+
|
|
54
|
+
With Node.js 22 or newer, run `npm ci` and `npm run example:e03` from the package root.
|
|
55
|
+
The fresh export installs Next, tests routes and the official MCP client, builds the production
|
|
56
|
+
app, and scans browser assets for a fake server-only sentinel. A cross-user save is denied
|
|
57
|
+
before mutation by this checked owner guard:
|
|
58
|
+
|
|
59
|
+
<!-- source:examples/e03-next-reading-list/project/lib/saved-list.js#saved-list-owner-check -->
|
|
60
|
+
~~~js
|
|
61
|
+
function authorizeOwner(identity, ownerUserId) {
|
|
62
|
+
assertFixtureIdentity(identity);
|
|
63
|
+
if (identity.userId !== ownerUserId) {
|
|
64
|
+
throw new SavedListAuthorizationError();
|
|
65
|
+
}
|
|
66
|
+
~~~
|
|
67
|
+
|
|
68
|
+
[View tested source](https://github.com/uppercut-labs/agent-native/blob/main/examples/e03-next-reading-list/project/lib/saved-list.js#L41)
|
|
69
|
+
<!-- /source -->
|
|
70
|
+
|
|
71
|
+
If the browser scan finds the sentinel, keep `server-only` modules out of client imports and
|
|
72
|
+
rerun the export. The [E03 project](https://github.com/uppercut-labs/agent-native/blob/main/examples/e03-next-reading-list/project/README.md)
|
|
73
|
+
lists the expected final smoke lines and fixture limits.
|