@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,189 @@
|
|
|
1
|
+
# Make an existing website agent-native
|
|
2
|
+
|
|
3
|
+
**Target:** Agent Native 1.0.1 · Node.js 22+ · an existing JavaScript/TypeScript website.
|
|
4
|
+
**Result:** A real operation the website already supports, available to an agent over an actual HTTP and/or MCP server surface, with the same input checks and explicit authorization the application expects.
|
|
5
|
+
|
|
6
|
+
Agent Native is not a chatbot widget or an automatic site conversion tool. It defines reusable operations that software—including suitable agents—can discover and invoke through selected adapters. Your existing pages, functions and ownership rules remain your responsibility.
|
|
7
|
+
|
|
8
|
+
## 1. Identify one operation your website already provides
|
|
9
|
+
|
|
10
|
+
Choose something low risk: search public content, retrieve an album, list public catalog entries, or convert a value. Find the *existing function* your website already calls. Prefer a public read on your first pass, not user-specific private data or an update/delete. Record the input/output shape and whether an ordinary visitor is entitled to the result.
|
|
11
|
+
|
|
12
|
+
A representative complete example is [E10 existing functions retrofit](https://github.com/uppercut-labs/agent-native/tree/main/examples/e10-existing-functions-retrofit). It leaves an existing `searchContent({query, limit})` function intact, defines a typed Agent Native capability around it, and preserves the site's established `GET /api/content/search` shape. It is much closer to a real integration than inventing a new `greet` operation.
|
|
13
|
+
|
|
14
|
+
Compare any user-named stack or host with the package manifest, lockfile, actual entry point, and build/deployment configuration. Explain a mismatch and follow the explicit request; then choose an adapter for the website's actual execution model:
|
|
15
|
+
|
|
16
|
+
| Your website | How remote agents call it | Example and limitation |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| Existing Node/Hono server | Mount a standard Web `Request`/`Response` HTTP or MCP handler in your current router | [E05](https://github.com/uppercut-labs/agent-native/tree/main/examples/e05-node-cli); app must provide TLS/auth/rate limits |
|
|
19
|
+
| Next.js App Router | Use package `/next` route factory on Node runtime | [E03](https://github.com/uppercut-labs/agent-native/tree/main/examples/e03-next-reading-list); keep registry server-only |
|
|
20
|
+
| Astro on-demand | Add app-owned endpoints with an existing compatible server adapter | [E02](https://github.com/uppercut-labs/agent-native/tree/main/examples/e02-astro-on-demand-catalog); do not silently change output modes |
|
|
21
|
+
| Astro fully static | Use a separate Worker/sidecar server for HTTP/MCP while leaving site prerendered | [E01/E04](https://github.com/uppercut-labs/agent-native/tree/main/examples); static HTML cannot answer a dynamic remote MCP request by itself |
|
|
22
|
+
| Browser-local only | Optional WebMCP browser registration with ordinary human fallback | [E06](https://github.com/uppercut-labs/agent-native/tree/main/examples/e06-browser-only-theme-controls); draft browser API, not universal host support |
|
|
23
|
+
|
|
24
|
+
## 2. Install the version you can actually test
|
|
25
|
+
|
|
26
|
+
In the consuming website repository with Node 22 or later, install the public package:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npm install @uppercut-labs/agent-native@1.0.1
|
|
30
|
+
npm install zod@4.6.5
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Zod is only needed if you use the `/schema/zod` adapter. Check the app’s current pin
|
|
34
|
+
first; an incompatible schema peer can stay isolated in the separate sidecar instead
|
|
35
|
+
of silently changing the existing website’s dependencies. To serve MCP, add the tested official server SDK peer separately:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
npm install @modelcontextprotocol/server@2.3.0
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
For official client verification, install `npm install --save-dev @modelcontextprotocol/client@2.3.0`.
|
|
42
|
+
Import `Client` and `StreamableHTTPClientTransport` from `@modelcontextprotocol/client`;
|
|
43
|
+
connect the transport to your actual mounted MCP URL, list tools, and call the expected
|
|
44
|
+
capability with its schema-valid input. For Next, also install `server-only@0.0.1` and
|
|
45
|
+
follow the [authorized route example](next.md).
|
|
46
|
+
|
|
47
|
+
No package-level `agent-native` executable ships. The `@uppercut-labs/agent-native/init` import is an experimental, narrowly recognized programmatic Astro static retrofit; it does **not** automatically discover your business functions, publish a Worker or install a global `init` command. See [installation](installation.md) and [Astro init details](getting-started.md).
|
|
48
|
+
|
|
49
|
+
## 3. Define and bind your **existing** function
|
|
50
|
+
|
|
51
|
+
E10's [capability module](https://github.com/uppercut-labs/agent-native/blob/main/examples/e10-existing-functions-retrofit/project/src/capability.mjs) is a runnable reference. It uses:
|
|
52
|
+
|
|
53
|
+
- `defineCapability({identity, description, input, output, risk:'read', access:{kind:'public'}})` from `/contracts`;
|
|
54
|
+
- `fromZod(z.object(...))` from `/schema/zod` for input and output contracts;
|
|
55
|
+
- `bindCapability(capability, {id, targets:['local','server'], execute: existingFunction})` from `/registry`;
|
|
56
|
+
- `createCapabilityRegistry([capability], [binding])` to check definitions and bindings.
|
|
57
|
+
|
|
58
|
+
The tested definition, binding and public-read-only authorizer are:
|
|
59
|
+
|
|
60
|
+
<!-- source:examples/e10-existing-functions-retrofit/project/src/capability.mjs#adoption-contract -->
|
|
61
|
+
~~~js
|
|
62
|
+
const contentItem = z.object({
|
|
63
|
+
slug: z.string(),
|
|
64
|
+
title: z.string(),
|
|
65
|
+
summary: z.string(),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export const searchContentCapability = defineCapability({
|
|
69
|
+
identity: { namespace: 'content', name: 'search', majorVersion: 1 },
|
|
70
|
+
description: 'Search the existing public content collection.',
|
|
71
|
+
input: fromZod(
|
|
72
|
+
z.object({
|
|
73
|
+
query: z.string().trim().min(1).max(80),
|
|
74
|
+
limit: z.number().int().min(1).max(10).optional(),
|
|
75
|
+
}),
|
|
76
|
+
),
|
|
77
|
+
output: fromZod(
|
|
78
|
+
z.object({
|
|
79
|
+
query: z.string(),
|
|
80
|
+
results: z.array(contentItem),
|
|
81
|
+
}),
|
|
82
|
+
),
|
|
83
|
+
risk: 'read',
|
|
84
|
+
access: { kind: 'public' },
|
|
85
|
+
surfaces: {
|
|
86
|
+
// docs:start http-get-override
|
|
87
|
+
http: {
|
|
88
|
+
path: '/api/content/search',
|
|
89
|
+
method: 'GET',
|
|
90
|
+
query: { query: 'q' },
|
|
91
|
+
},
|
|
92
|
+
// docs:end http-get-override
|
|
93
|
+
// docs:start cli-command-override
|
|
94
|
+
cli: {
|
|
95
|
+
command: 'content-search',
|
|
96
|
+
aliases: ['search'],
|
|
97
|
+
},
|
|
98
|
+
// docs:end cli-command-override
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
export const searchContentBinding = bindCapability(searchContentCapability, {
|
|
103
|
+
id: 'existing-content-search',
|
|
104
|
+
targets: ['local', 'server'],
|
|
105
|
+
execute: searchContent,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
export const registry = createCapabilityRegistry([searchContentCapability], [searchContentBinding]);
|
|
109
|
+
|
|
110
|
+
export const publicReadAuthorization = {
|
|
111
|
+
authorize(request) {
|
|
112
|
+
return request.risk === 'read' && request.access.kind === 'public';
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
~~~
|
|
116
|
+
|
|
117
|
+
[View tested source](https://github.com/uppercut-labs/agent-native/blob/main/examples/e10-existing-functions-retrofit/project/src/capability.mjs#L8)
|
|
118
|
+
<!-- /source -->
|
|
119
|
+
|
|
120
|
+
The example preserves an established GET route with the same contract, instead of inventing a duplicate endpoint:
|
|
121
|
+
|
|
122
|
+
<!-- source:examples/e10-existing-functions-retrofit/project/src/capability.mjs#http-get-override -->
|
|
123
|
+
~~~js
|
|
124
|
+
http: {
|
|
125
|
+
path: '/api/content/search',
|
|
126
|
+
method: 'GET',
|
|
127
|
+
query: { query: 'q' },
|
|
128
|
+
},
|
|
129
|
+
~~~
|
|
130
|
+
|
|
131
|
+
[View tested source](https://github.com/uppercut-labs/agent-native/blob/main/examples/e10-existing-functions-retrofit/project/src/capability.mjs#L33)
|
|
132
|
+
<!-- /source -->
|
|
133
|
+
|
|
134
|
+
Identity namespace and name use lower-case slug segments separated by dots or hyphens,
|
|
135
|
+
such as `{ namespace: 'blog', name: 'list-posts', majorVersion: 1 }`.
|
|
136
|
+
|
|
137
|
+
For your own site, adapt the namespace/operation names and input/output schemas from the actual operation. A registry is not a permission policy: supply an application authorization port **on every execution**, even for explicitly public reads. The E10 sample authorizer permits exactly public reads. Do not copy it to a protected write or assume an unauthenticated visitor owns someone else's data. [Capabilities and bindings](capabilities-and-bindings.md) explains this boundary.
|
|
138
|
+
|
|
139
|
+
## 4. Mount the agent surface in an actual server
|
|
140
|
+
|
|
141
|
+
E10's [HTTP mount](https://github.com/uppercut-labs/agent-native/blob/main/examples/e10-existing-functions-retrofit/project/src/http.mjs) wraps the same registry in `createHttpHandler` from `@uppercut-labs/agent-native/http`, supplying a caller and explicit `authorization` through `resolveExecutionContext`. It adapts the Web-standard handler to its pre-existing Node HTTP server and leaves unrelated `/api/health` and content routes intact.
|
|
142
|
+
|
|
143
|
+
<!-- source:examples/e10-existing-functions-retrofit/project/src/http.mjs#adoption-http -->
|
|
144
|
+
~~~js
|
|
145
|
+
const generatedHandler = createHttpHandler(registry, {
|
|
146
|
+
resolveExecutionContext: () => ({
|
|
147
|
+
caller: { kind: 'anonymous' },
|
|
148
|
+
authorization: publicReadAuthorization,
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
~~~
|
|
152
|
+
|
|
153
|
+
[View tested source](https://github.com/uppercut-labs/agent-native/blob/main/examples/e10-existing-functions-retrofit/project/src/http.mjs#L7)
|
|
154
|
+
<!-- /source -->
|
|
155
|
+
|
|
156
|
+
By default, public read capabilities may be invoked by `POST /agent-native/v1/capabilities/{namespace}/{name}/v{major}/invoke` and are described at `GET /agent-native/v1/openapi.json`; E10 demonstrates an approved GET override at the original path. The HTTP adapter in 1.0.1 exposes **only public reads**; user-specific or destructive operations are not automatically routable. See [HTTP/OpenAPI](http.md).
|
|
157
|
+
|
|
158
|
+
For agent-native MCP clients, add the isolated `/mcp` adapter and mount it at a real `/mcp` route, with the host's request-specific authorization context. Use the official MCP server SDK and a compatible MCP client. Successful structured results appear under `structuredContent.result`. MCP protected discovery, scopes, revocable grants and per-resource authorization are covered in [remote MCP](mcp.md) and [permissions](permissions-and-discovery.md). **Do not turn on protected MCP in the beginner read-only sample without an actual verified identity source.**
|
|
159
|
+
|
|
160
|
+
If your website has no server, select a separately deployed Worker or equivalent server to host those routes, wire CORS/host/origin carefully and keep the static site untouched. Local workerd evidence is not a claim that your DNS or production Worker was deployed. See [static sites](static-sites.md).
|
|
161
|
+
|
|
162
|
+
## 5. Prove the behavior, including a refusal
|
|
163
|
+
|
|
164
|
+
For the checked E10 example, run this in the *public package source repository* to export and exercise its independent copy:
|
|
165
|
+
|
|
166
|
+
```sh
|
|
167
|
+
npm ci
|
|
168
|
+
npm run example:e10
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
To verify the published registry package independently, run `npm run check:adoption`.
|
|
172
|
+
It installs the 1.0.0 compatibility baseline in a clean consumer, preserves the E10 domain function, runs its HTTP/CLI
|
|
173
|
+
tests, and uses the official MCP client over an actual local socket. The client validates
|
|
174
|
+
`structuredContent.result`, rejects invalid input before the function, and cannot discover
|
|
175
|
+
or call a protected canary. This is maintainer loopback proof, not an outsider pilot.
|
|
176
|
+
|
|
177
|
+
Inside the generated E10 project, the [README](https://github.com/uppercut-labs/agent-native/blob/main/examples/e10-existing-functions-retrofit/project/README.md) documents `npm ci`, `npm test`, `npm run search -- --query night --limit 1`, `npm start` and `curl 'http://127.0.0.1:8788/api/content/search?q=night&limit=1'`. In this fixture, that URL returns its first search match. Missing query, a fractional limit or an empty CLI query fails input validation before executing the existing function. POST to the preserved read GET route returns `405` with `Allow: GET`; unrelated health and catalog routes remain available.
|
|
178
|
+
|
|
179
|
+
For **your site**, perform the equivalent on its actual local server, not the package fixture. Record the operation name and version, URL/host, expected schema, valid result, malformed input failure, denied or hidden operation, and unchanged human website flow. For MCP, use a compatible client to list tools and call one (not a hand-built success assertion). If only local route tests passed, describe that honestly; deployed provider and live commercial-host claims require separate verification. See [compatibility](compatibility.md) and [support limits](support-and-limitations.md).
|
|
180
|
+
|
|
181
|
+
## 6. Take it from local proof to a deployed project
|
|
182
|
+
|
|
183
|
+
Your application owns deployment routing, TLS, Origin/Host/CORS rules, rate limits, credential verification, resource authorization, persistence, secret management, observability and rollback. Never put server SDKs, credential stores or private schemas in browser assets. Calls that time out may still have completed writes; don't retry a mutation blindly without a real idempotency contract and state check.
|
|
184
|
+
|
|
185
|
+
Use [release/provider handoff](release-handoff.md) to plan actual host acceptance, and [doctor and troubleshooting](doctor-and-troubleshooting.md) if a binding/route is unavailable. Treat the current [stability](stability.md) and [support matrix](support-and-limitations.md) as release-specific.
|
|
186
|
+
|
|
187
|
+
## Hand this to a coding agent
|
|
188
|
+
|
|
189
|
+
For a repo-aware AI assistant, read [the agent integration guide](for-agents.md) and give it the portable skill or copyable prompt. Neither automatically grants access to the customer's site, deployment credentials or private operations.
|
package/docs/browser.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Browser-local WebMCP
|
|
2
|
+
|
|
3
|
+
Import the browser-only subpath so application browser bundles do not load HTTP or MCP server
|
|
4
|
+
adapters:
|
|
5
|
+
|
|
6
|
+
~~~ts
|
|
7
|
+
import { createBrowserCapabilityAdapter } from '@uppercut-labs/agent-native/browser';
|
|
8
|
+
|
|
9
|
+
const adapter = createBrowserCapabilityAdapter(document);
|
|
10
|
+
const report = await adapter.sync(registry);
|
|
11
|
+
~~~
|
|
12
|
+
|
|
13
|
+
The adapter feature-detects the current draft API at document.modelContext. It registers
|
|
14
|
+
capabilities only when the registry has exactly one browser binding. The WebMCP tool name is a
|
|
15
|
+
deterministic length-coded projection of the capability identity and follows the draft's allowed
|
|
16
|
+
ASCII character set and 128-character limit.
|
|
17
|
+
|
|
18
|
+
By default, discovery includes only public read capabilities. An application may explicitly opt
|
|
19
|
+
in to a protected page-local tool with canExpose; every execution still goes through the shared
|
|
20
|
+
executor and the application's AuthorizationPort. resolveExecutionContext is read for each tool
|
|
21
|
+
call, so authorization and identity are not captured as browser secrets. Resync after route or
|
|
22
|
+
authentication changes to abort stale registrations and register the current projection.
|
|
23
|
+
|
|
24
|
+
The adapter owns each registration with the WebMCP registerTool(tool, { signal }) AbortSignal.
|
|
25
|
+
Calling dispose() or receiving pagehide on the document's owning Window aborts those
|
|
26
|
+
registrations. Repeated adapter creation for the same Document returns the same adapter instance.
|
|
27
|
+
The registration signal controls availability; WebMCP passes a separate execution signal to the
|
|
28
|
+
tool callback. Revoked callbacks also reject retained references after a resync or dispose.
|
|
29
|
+
|
|
30
|
+
The normal human interface should work when WebMCP is absent. E06 demonstrates this with a
|
|
31
|
+
page-local theme action and no server fallback. E12 uses the same converter function through local
|
|
32
|
+
and browser bindings. The lifecycle fixtures use a simulated API and are labeled as such. On
|
|
33
|
+
2026-10-10, the built E06 and E01 pages also passed against native WebMCP in Chrome 155 with the
|
|
34
|
+
opt-in `WebMCP` feature. That run covered host-side tool listing and invocation, invalid input,
|
|
35
|
+
`pagehide` cleanup and Astro client navigation. See the
|
|
36
|
+
[native Chrome evidence](evidence/UAN-023-native-webmcp-chrome.md). No shipping browser agent was tested.
|
|
37
|
+
|
|
38
|
+
The API shape follows the current WebMCP draft at https://webmachinelearning.github.io/webmcp/.
|
|
39
|
+
That draft exposes Document.modelContext.registerTool() in secure contexts, and the registration
|
|
40
|
+
options accept an AbortSignal. Tool annotations follow the draft: read capabilities set
|
|
41
|
+
`readOnlyHint` and destructive capabilities set `consequentialHint`. The only verified browser is
|
|
42
|
+
Chrome 155 with WebMCP enabled through `chrome://flags/#enable-webmcp-testing` or an origin
|
|
43
|
+
trial. Default Chrome does not expose the API, and no agent host is certified.
|
|
44
|
+
|
|
45
|
+
## Verify the browser-only binding
|
|
46
|
+
|
|
47
|
+
With Node.js 22 or newer, run `npm ci` and `npm run example:e06` from the package root.
|
|
48
|
+
The exported page binds its theme action only to `browser`:
|
|
49
|
+
|
|
50
|
+
<!-- source:examples/e06-browser-only-theme-controls/project/src/theme.mjs#browser-theme-binding -->
|
|
51
|
+
~~~js
|
|
52
|
+
const binding = bindCapability(capability, {
|
|
53
|
+
id: 'page-theme',
|
|
54
|
+
targets: ['browser'],
|
|
55
|
+
execute: async ({ theme }) => {
|
|
56
|
+
document.documentElement.dataset.theme = theme;
|
|
57
|
+
return { theme };
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
~~~
|
|
61
|
+
|
|
62
|
+
[View tested source](https://github.com/uppercut-labs/agent-native/blob/main/examples/e06-browser-only-theme-controls/project/src/theme.mjs#L23)
|
|
63
|
+
<!-- /source -->
|
|
64
|
+
|
|
65
|
+
The simulated lifecycle test checks registration, protected invocation, disposal, and the
|
|
66
|
+
ordinary button fallback. A server invocation fails with `binding-unavailable`; use a
|
|
67
|
+
separate server binding only if the application actually implements one. See the
|
|
68
|
+
[E06 standalone instructions](https://github.com/uppercut-labs/agent-native/blob/main/examples/e06-browser-only-theme-controls/project/README.md).
|
|
69
|
+
The simulated fixture alone implies no native result; `node test/uan023-native-webmcp.mjs`
|
|
70
|
+
repeats E06 and E01 in a locally installed Chrome with the feature enabled.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Capabilities and bindings
|
|
2
|
+
|
|
3
|
+
Identity namespace and name are lower-case slug segments separated by dots or hyphens,
|
|
4
|
+
with at most 64 characters each; majorVersion is a positive safe integer.
|
|
5
|
+
|
|
6
|
+
A capability definition describes a versioned outcome and its runtime input/output contracts. It
|
|
7
|
+
does not need an implementation. A binding supplies that implementation for named runtime
|
|
8
|
+
targets. Definitions and bindings compose into an immutable checked registry; requests execute
|
|
9
|
+
through the shared gateway.
|
|
10
|
+
|
|
11
|
+
## Frozen authoring signatures
|
|
12
|
+
|
|
13
|
+
The v0 authoring surface is exercised in the installable examples under `examples/`. Definitions
|
|
14
|
+
require identity, description, input and output schema ports, risk, and access. Public access is
|
|
15
|
+
allowed only for read capabilities. Protected access carries one or more scopes; it does not issue
|
|
16
|
+
or persist grants.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
defineCapability({
|
|
20
|
+
identity: { namespace: 'example.catalog', name: 'album.lookup', majorVersion: 1 },
|
|
21
|
+
description: 'Look up a public sample album by its slug.',
|
|
22
|
+
input: albumInput,
|
|
23
|
+
output: albumOutput,
|
|
24
|
+
risk: 'read',
|
|
25
|
+
access: { kind: 'public' },
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Each binding has a unique slug and one or more explicit targets: `browser`, `server`, or `local`.
|
|
30
|
+
No runtime is inferred. A server-only binding is unavailable to a browser request. If multiple
|
|
31
|
+
bindings match one capability and runtime, the caller must specify `bindingId`; otherwise execution
|
|
32
|
+
returns a `binding-ambiguous` failure.
|
|
33
|
+
|
|
34
|
+
Every execution request provides a runtime, caller, and `AuthorizationPort`. The binding receives that caller as `context.caller`, after authorization, so it can scope records to it. The executor validates
|
|
35
|
+
input first, awaits `authorize` for every invocation, invokes only the selected binding after an
|
|
36
|
+
allow result, then validates output. Authorization errors deny execution. Public-read examples
|
|
37
|
+
implement their own explicit policy port; there is no implicit allow-all default. The port is the
|
|
38
|
+
adopter's policy boundary, not a durable grant store or identity integration.
|
|
39
|
+
|
|
40
|
+
The result is a discriminated `success` or `failure`. Failure reasons include invalid identity,
|
|
41
|
+
missing capability, unavailable or ambiguous binding, invalid input, denied authorization,
|
|
42
|
+
authorization error, invalid output, and handler failure. Each failure includes a stable
|
|
43
|
+
`DiagnosticObservation` without input, output, credentials, or exception text.
|
|
44
|
+
|
|
45
|
+
## Exact identity and explicit binding selection
|
|
46
|
+
|
|
47
|
+
Run `npm run example:e11` at the repository root for an installed, executable
|
|
48
|
+
[selection and diagnostics recipe](https://github.com/uppercut-labs/agent-native/blob/main/examples/e11-diagnostics-fault-lab/project/src/executor-selection.mjs).
|
|
49
|
+
After exporting it, `npm run selection` in `examples/e11-diagnostics-fault-lab/.exported`
|
|
50
|
+
repeats the sequence. Failed requests invoke no handler:
|
|
51
|
+
|
|
52
|
+
| Request | Outcome |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| `name: 'example.selection binding.read'` | `invalid-identity` |
|
|
55
|
+
| Valid identity with `name: 'missing'` | `capability-missing` |
|
|
56
|
+
| Browser runtime with local-only bindings | `binding-unavailable` |
|
|
57
|
+
| Local runtime with two bindings, no selection | `binding-ambiguous` |
|
|
58
|
+
| Exact identity, local runtime, `bindingId: 'primary'` | Success, one handler call |
|
|
59
|
+
|
|
60
|
+
<!-- source:examples/e11-diagnostics-fault-lab/project/src/executor-selection.mjs#select-binding -->
|
|
61
|
+
~~~js
|
|
62
|
+
const selected = await executeCapability(registry, {
|
|
63
|
+
identity: { namespace: 'example.selection', name: 'binding.read', majorVersion: 1 },
|
|
64
|
+
runtime: 'local',
|
|
65
|
+
bindingId: 'primary',
|
|
66
|
+
input: {},
|
|
67
|
+
caller: { kind: 'anonymous' },
|
|
68
|
+
authorization: publicRead,
|
|
69
|
+
});
|
|
70
|
+
assert.equal(selected.kind, 'success');
|
|
71
|
+
assert.equal(selected.bindingId, 'primary');
|
|
72
|
+
assert.deepEqual(selected.value, { value: 'primary' });
|
|
73
|
+
~~~
|
|
74
|
+
|
|
75
|
+
[View tested source](https://github.com/uppercut-labs/agent-native/blob/main/examples/e11-diagnostics-fault-lab/project/src/executor-selection.mjs#L65)
|
|
76
|
+
<!-- /source -->
|
|
77
|
+
|
|
78
|
+
Namespace, name and major version are separate fields. Never pass a display label or a
|
|
79
|
+
space-separated phrase as the name, normalize it into another identity, or silently select
|
|
80
|
+
the first binding. If an application exposes an executor wrapper with a fixed namespace,
|
|
81
|
+
state that namespace in both its schema descriptions and tool description, give the valid
|
|
82
|
+
name example `binding.read`, and reject combined labels. Expose a `bindingId` selection only
|
|
83
|
+
when the application's contract permits it. The MCP adapter exposes capabilities with one
|
|
84
|
+
server binding; it does not select between ambiguous bindings on behalf of a client.
|
|
85
|
+
|
|
86
|
+
The same recipe exercises the [MCP operator failure hook](mcp.md#operator-failure-hook).
|
|
87
|
+
Keep detailed reasons in a private operator sink; do not expose hidden capabilities or
|
|
88
|
+
unredacted exceptions to diagnose generic client errors.
|
|
89
|
+
|
|
90
|
+
## Runnable examples
|
|
91
|
+
|
|
92
|
+
- [E01 domain execution slice](https://github.com/uppercut-labs/agent-native/blob/main/examples/e01-album-catalog/project/README.md) proves found and
|
|
93
|
+
missing album outcomes plus malformed-slug rejection through local execution.
|
|
94
|
+
- [E12 shared unit converter](https://github.com/uppercut-labs/agent-native/blob/main/examples/e12-shared-unit-converter/project/README.md) proves the
|
|
95
|
+
converter through an explicit local binding and the same executor.
|
|
96
|
+
|
|
97
|
+
Run `npm run example:e01` and `npm run example:e12` at the repository root for fresh tarball
|
|
98
|
+
exports, independent installs, tests, and CLI output. These examples do not implement the full E01
|
|
99
|
+
Astro retrofit or claim browser, MCP, HTTP/OpenAPI, or real-host support.
|
|
100
|
+
|
|
101
|
+
## Tested source and first run
|
|
102
|
+
|
|
103
|
+
With Node.js 22 or newer, run `npm ci` in the repository root, then
|
|
104
|
+
`npm run example:e01` and `npm run example:e12`. E01 returns a found or missing album through
|
|
105
|
+
the local executor; a malformed slug fails input validation before its handler runs. E12 prints
|
|
106
|
+
a versioned JSON conversion result with `30.48 cm`; an unsupported unit returns
|
|
107
|
+
`invalid-input` before the shared converter runs. See the [E01 project](https://github.com/uppercut-labs/agent-native/blob/main/examples/e01-album-catalog/project/README.md)
|
|
108
|
+
and [E12 project](https://github.com/uppercut-labs/agent-native/blob/main/examples/e12-shared-unit-converter/project/README.md) for independent exports.
|
|
109
|
+
|
|
110
|
+
E01 executes a registered album lookup with an explicit public-read policy:
|
|
111
|
+
|
|
112
|
+
<!-- source:examples/e01-album-catalog/project/src/catalog.mjs#local-album-execution -->
|
|
113
|
+
~~~js
|
|
114
|
+
/** @param {unknown} input */
|
|
115
|
+
export function runAlbumLookup(input) {
|
|
116
|
+
return executeCapability(registry, {
|
|
117
|
+
identity: getAlbumCapability.identity,
|
|
118
|
+
runtime: 'local',
|
|
119
|
+
input,
|
|
120
|
+
caller: { kind: 'anonymous' },
|
|
121
|
+
authorization: publicReadAuthorization,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
~~~
|
|
125
|
+
|
|
126
|
+
[View tested source](https://github.com/uppercut-labs/agent-native/blob/main/examples/e01-album-catalog/project/src/catalog.mjs#L36)
|
|
127
|
+
<!-- /source -->
|
|
128
|
+
|
|
129
|
+
E12 binds one conversion function to local and browser runtimes:
|
|
130
|
+
|
|
131
|
+
<!-- source:examples/e12-shared-unit-converter/project/src/converter.mjs#shared-converter-bindings -->
|
|
132
|
+
~~~js
|
|
133
|
+
export const localConversionBinding = bindCapability(convertDistanceCapability, {
|
|
134
|
+
id: 'local-converter',
|
|
135
|
+
targets: ['local'],
|
|
136
|
+
execute: convertDistance,
|
|
137
|
+
});
|
|
138
|
+
~~~
|
|
139
|
+
|
|
140
|
+
[View tested source](https://github.com/uppercut-labs/agent-native/blob/main/examples/e12-shared-unit-converter/project/src/converter.mjs#L44)
|
|
141
|
+
<!-- /source -->
|
package/docs/cli.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Generated CLI
|
|
2
|
+
|
|
3
|
+
This slice exports a runner from the separate `@uppercut-labs/agent-native/cli` entrypoint and includes runnable installed application CLIs in E05/E12. Applications own their capability executable. The development branch also provides a separate [site publication command](site-publication.md). Applications provide
|
|
4
|
+
a capability registry, caller, authorization port, and stream adapter. The library does not
|
|
5
|
+
discover application capabilities or credentials implicitly.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { runCapabilityCli } from '@uppercut-labs/agent-native/cli';
|
|
9
|
+
|
|
10
|
+
const status = await runCapabilityCli(process.argv.slice(2), {
|
|
11
|
+
registry,
|
|
12
|
+
caller: { kind: 'anonymous' },
|
|
13
|
+
authorization,
|
|
14
|
+
credentialProfiles,
|
|
15
|
+
}, {
|
|
16
|
+
writeStdout: (value) => process.stdout.write(value),
|
|
17
|
+
writeStderr: (value) => process.stderr.write(value),
|
|
18
|
+
readStdin: async () => readStdin(),
|
|
19
|
+
});
|
|
20
|
+
process.exitCode = status;
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The help list filters to public reads by default, and a selected hidden or unknown identity gets the same generic unavailable message. Applications may provide an async `canDiscover` callback for protected help metadata and `surfaceExposure: { cli: { destructive: ['account:delete@1'] } }` for exact destructive exposure. A destructive entry still needs the app's `canDiscover` approval; direct execution is independently checked by the shared executor.
|
|
24
|
+
|
|
25
|
+
The registry generates the application CLI help list and simple field flags from each input JSON Schema. String,
|
|
26
|
+
number, integer, boolean, and enum properties accept typed flags; nested objects and arrays require
|
|
27
|
+
`--input-json JSON`. Use `--input-json -` to read one JSON document from the provided stdin port.
|
|
28
|
+
Capability identity must match the complete `namespace:name@major` form. There is no fallback to a
|
|
29
|
+
different version or binding.
|
|
30
|
+
|
|
31
|
+
Definitions may replace that invocation name with an established command and explicit aliases:
|
|
32
|
+
|
|
33
|
+
E10 binds its existing command names in the same tested definition:
|
|
34
|
+
|
|
35
|
+
<!-- source:examples/e10-existing-functions-retrofit/project/src/capability.mjs#cli-command-override -->
|
|
36
|
+
~~~js
|
|
37
|
+
cli: {
|
|
38
|
+
command: 'content-search',
|
|
39
|
+
aliases: ['search'],
|
|
40
|
+
},
|
|
41
|
+
~~~
|
|
42
|
+
|
|
43
|
+
[View tested source](https://github.com/uppercut-labs/agent-native/blob/main/examples/e10-existing-functions-retrofit/project/src/capability.mjs#L40)
|
|
44
|
+
<!-- /source -->
|
|
45
|
+
|
|
46
|
+
Commands and aliases are lowercase slugs and must be unique across the registry. They resolve to
|
|
47
|
+
the same canonical capability identity used for execution and result envelopes; an override does
|
|
48
|
+
not change the contract identity.
|
|
49
|
+
|
|
50
|
+
Every invocation names `--mode local` or `--mode remote`. Local mode uses the shared executor and
|
|
51
|
+
requires the configured authorization port; an optional `--binding-id` selects one exact local
|
|
52
|
+
binding. Remote mode requires both `--profile NAME` and a configured own-property credential entry.
|
|
53
|
+
The profile includes an HTTPS base URL and bearer token. HTTP is permitted only for localhost and
|
|
54
|
+
loopback addresses for local fixtures. The CLI rejects URL userinfo and does not print tokens.
|
|
55
|
+
Remote mode rejects `--binding-id`; runtime binding selection belongs to the remote host.
|
|
56
|
+
|
|
57
|
+
A remote `--timeout-ms` races the request against a deadline and also signals abort to cooperative
|
|
58
|
+
fetchers. A fetcher that ignores abort cannot hold the CLI past that timeout. The generated remote
|
|
59
|
+
HTTP surface exposes public reads only; protected writes are rejected before any network request.
|
|
60
|
+
Local mode does not apply `--timeout-ms` to a binding. Remote JSON is checked against the selected
|
|
61
|
+
capability output schema before it can be reported as success.
|
|
62
|
+
|
|
63
|
+
For invocations, stdout contains exactly one JSON envelope using `uan.cli-result/v1`; stderr carries
|
|
64
|
+
effective target and redacted failure diagnostics. Exit codes are 0 for success, 2 for invalid input,
|
|
65
|
+
3 for denied authorization, and 1 for other failures. Help and version commands are human-readable.
|
|
66
|
+
The CLI runner itself never spawns a shell. Applications that launch it should pass argv as an array
|
|
67
|
+
and disable shell interpretation.
|
|
68
|
+
|
|
69
|
+
See the installed E05 and E12 application examples for local and loopback HTTP fixtures, positive
|
|
70
|
+
output, timeout, credential, malformed-input, and handler-failure paths. E05 mounts the generated
|
|
71
|
+
HTTP and MCP handlers into one pinned Node/Hono application and checks the MCP surface with the
|
|
72
|
+
official client SDK. It remains a local fixture, not a deployment or external credential provider.
|
|
73
|
+
|
|
74
|
+
## Discovery filtering
|
|
75
|
+
|
|
76
|
+
Help lists public reads by default. Applications may provide an async `canDiscover` callback for protected metadata and `surfaceExposure: { cli: { destructive: ['account:delete@1'] } }` for exact destructive exposure. Local help requires one unique local binding; remote help lists only public reads with one server binding. Direct destructive local invocation requires the explicit CLI exposure entry before input reaches a binding, and the shared executor independently checks authorization. Close `canDiscover` over a trusted caller from the application's auth boundary; do not infer identity from argv.
|
|
77
|
+
|
|
78
|
+
## Exact runner options
|
|
79
|
+
|
|
80
|
+
The [generated API and CLI reference](reference.md) lists every package import
|
|
81
|
+
and each argument recognized by the current parser. `npm run docs:check`
|
|
82
|
+
compares that page with the package manifest and CLI source.
|
|
83
|
+
|
|
84
|
+
With Node.js 22 or newer, run `npm ci` then `npm run example:e10` at the repository
|
|
85
|
+
root. In its exported project, `npm run search -- --query night --limit 1` yields the
|
|
86
|
+
same versioned result envelope as the existing function. An empty query exits 2 with
|
|
87
|
+
`invalid-input`; pass a nonempty query to repair it. See the
|
|
88
|
+
[E10 CLI fixture](https://github.com/uppercut-labs/agent-native/blob/main/examples/e10-existing-functions-retrofit/project/README.md).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Compatibility and evidence
|
|
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`, with the same runtime and API as `1.0.0`. The table describes the exact local fixtures and hosts
|
|
6
|
+
that were tested; it is not a claim for all releases of a runtime or every client.
|
|
7
|
+
|
|
8
|
+
| Surface | Runnable check | Verified boundary |
|
|
9
|
+
| --- | --- | --- |
|
|
10
|
+
| Static Astro | `npm run example:e01` | Astro 7.3.5 static build and simulated WebMCP lifecycle; native Chrome 155 WebMCP (opt-in feature) [evidence](evidence/UAN-023-native-webmcp-chrome.md) |
|
|
11
|
+
| Astro on-demand | `npm run example:e02` | Astro 7.3.5 with its existing Node adapter, HTTP route and official MCP SDK client |
|
|
12
|
+
| Next App Router | `npm run example:e03` | Next 16.3.8 on Node, production route smoke and browser artifact scan |
|
|
13
|
+
| Cloudflare Worker | `npm run example:e04` | Wrangler 4.147.0 local workerd, HTTP/OpenAPI and official MCP SDK client |
|
|
14
|
+
| Node/Hono | `npm run example:e05` | Hono 4.13.12 and Node adapter 2.1.3 on loopback |
|
|
15
|
+
| Browser-only WebMCP | `npm run example:e06` | Simulated `document.modelContext` and native Chrome 155 with the opt-in `WebMCP` feature; ordinary button fallback in default Chrome |
|
|
16
|
+
| Deployed protected MCP | [Domain Expansion evidence](evidence/gate-2-domain-expansion.md) | Cloudflare Worker with Neon in production: official MCP SDK client 2.3.0 and Claude Code 2.1.285 against `https://domains.uppercut-labs.com/mcp`, with bearer tokens, grants, typed outcomes and a rollback drill |
|
|
17
|
+
| Commercial agent hosts over MCP | `node test/uan023-agent-host.mjs agy` or `claude` (opt-in inference) | Antigravity CLI 1.2.14/1.3.3 and Claude Code 2.1.286 called the E04 flagship tool, with found and invalid cases ([evidence](evidence/UAN-023-commercial-agent-hosts.md)) |
|
|
18
|
+
| MCP Apps | `npm run example:e09` | Official ext-apps App/AppBridge fixture and local MCP client; no commercial host claim |
|
|
19
|
+
| Codex local harness | `npm run proof:codex -- --model gpt-5.6-luna` (explicit inference opt-in) | CLI 0.145.0, managed ChatGPT login, two file writes and same-thread resume across processes; [evidence](evidence/codex-harness.md) |
|
|
20
|
+
|
|
21
|
+
Use Node.js 22 or newer and `npm ci` at the repository root before a listed command.
|
|
22
|
+
Each command packs the package into an independent exported project; the root
|
|
23
|
+
`npm run check` runs all twelve examples on Node 22 and 24 in CI.
|
|
24
|
+
The [example coverage manifest](https://github.com/uppercut-labs/agent-native/blob/main/docs-site/coverage.json) maps all E01-E12 scripts, source,
|
|
25
|
+
guides, negative probes, requirements and the CI gate. A malformed input or denied request
|
|
26
|
+
is part of each example's test, not a successful operation to copy into an application.
|
|
27
|
+
|
|
28
|
+
For a missing optional peer, install the exact adapter dependency in
|
|
29
|
+
[installation](installation.md). For an inert route, check the application's adapter, runtime
|
|
30
|
+
and route mapping in [frameworks](frameworks.md). Native browser support is verified only in
|
|
31
|
+
Chrome 155 with WebMCP opted in; a simulated `modelContext` cannot prove other browsers. See
|
|
32
|
+
[support and limitations](support-and-limitations.md) for the release boundary.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Composition and versioning
|
|
2
|
+
|
|
3
|
+
Reusable capability packs distribute contracts, not providers. A pack owns an authority and a
|
|
4
|
+
namespace; its definitions use the combined namespace `<authority>.<namespace>`. Definitions remain
|
|
5
|
+
ordinary `CapabilityDefinition` values, and a consumer binds the exact definition it implements.
|
|
6
|
+
Composition does not wrap handlers, grant scopes, or create a privileged execution path.
|
|
7
|
+
|
|
8
|
+
## Exact major selection and aliases
|
|
9
|
+
|
|
10
|
+
`majorVersion` is part of the canonical identity. A pack can therefore export v1 and v2 together,
|
|
11
|
+
and `composeCapabilityPacks()` keeps both definitions. The E08 v2 consumer selects one exact major:
|
|
12
|
+
|
|
13
|
+
<!-- source:examples/e08-reusable-versioned-pack/project/src/demo-b.mjs#select-v2-major -->
|
|
14
|
+
~~~js
|
|
15
|
+
const selected = composition.select({
|
|
16
|
+
namespace: 'example.org.catalog',
|
|
17
|
+
name: 'album.lookup',
|
|
18
|
+
majorVersion: 2,
|
|
19
|
+
});
|
|
20
|
+
if (selected === undefined) throw new Error('v2 contract is not installed');
|
|
21
|
+
~~~
|
|
22
|
+
|
|
23
|
+
[View tested source](https://github.com/uppercut-labs/agent-native/blob/main/examples/e08-reusable-versioned-pack/project/src/demo-b.mjs#L13)
|
|
24
|
+
<!-- /source -->
|
|
25
|
+
|
|
26
|
+
`composition.resolve()` accepts either a canonical ID or an explicitly configured alias. Every
|
|
27
|
+
import must choose `{ kind: 'none' }` or `{ kind: 'explicit', aliases: [...] }`. An alias stores one
|
|
28
|
+
full canonical ID; it never means "latest" and never falls forward. Thus an `album-v1` alias that
|
|
29
|
+
targets `example.org.catalog:album.lookup@1` continues to select v1 when v2 is installed. Missing
|
|
30
|
+
targets, duplicate aliases, and duplicate canonical identities fail composition with source
|
|
31
|
+
locations.
|
|
32
|
+
|
|
33
|
+
The E08 v1 consumer pins an alias explicitly instead of inferring a latest version:
|
|
34
|
+
|
|
35
|
+
<!-- source:examples/e08-reusable-versioned-pack/project/src/demo.mjs#compose-v1-alias -->
|
|
36
|
+
~~~js
|
|
37
|
+
const composition = composeCapabilityPacks([
|
|
38
|
+
{
|
|
39
|
+
pack: catalogPack,
|
|
40
|
+
source: 'src/demo.mjs:consumer-import',
|
|
41
|
+
aliasPolicy: {
|
|
42
|
+
kind: 'explicit',
|
|
43
|
+
aliases: [{ name: 'album-v1', capabilityId: 'example.org.catalog:album.lookup@1' }],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
]);
|
|
47
|
+
const selected = composition.resolve('album-v1');
|
|
48
|
+
if (selected === undefined) throw new Error('v1 alias is not configured');
|
|
49
|
+
~~~
|
|
50
|
+
|
|
51
|
+
[View tested source](https://github.com/uppercut-labs/agent-native/blob/main/examples/e08-reusable-versioned-pack/project/src/demo.mjs#L10)
|
|
52
|
+
<!-- /source -->
|
|
53
|
+
|
|
54
|
+
## Contract migration reports
|
|
55
|
+
|
|
56
|
+
`compareCapabilityDefinitions(previous, next)` returns a structural report over input and output
|
|
57
|
+
JSON Schema plus risk, access, and HTTP/CLI surface metadata. Reports identify paths and before/after values for
|
|
58
|
+
property additions/removals, likely one-to-one property renames, required-field changes, defaults,
|
|
59
|
+
unit annotations such as `x-unit`, and other represented schema values. Rename detection is a
|
|
60
|
+
structural aid, not proof of author intent.
|
|
61
|
+
|
|
62
|
+
`assertCompatibleCapabilityReplacement(previous, next)` is for an unchanged canonical identity. It
|
|
63
|
+
allows an implementation or description change when input/output schemas and risk/access metadata
|
|
64
|
+
remain compatible. It rejects a breaking definition replacement, so a v1 ID cannot silently acquire
|
|
65
|
+
v2 requirements or output shape.
|
|
66
|
+
|
|
67
|
+
For a new major, use `defineCapabilityMigration({ previous, next, semanticReview })`. The semantic
|
|
68
|
+
note and named reviewer are mandatory even when the two schemas are identical. JSON Schema can show
|
|
69
|
+
represented structural changes; it cannot prove behavioral equivalence, units outside the schema,
|
|
70
|
+
side effects, ordering, freshness, or other semantics. A pack that contains multiple majors of one
|
|
71
|
+
capability family must attach the reviewed adjacent-major migrations and lifecycle policy; pack
|
|
72
|
+
definition fails when either is missing.
|
|
73
|
+
|
|
74
|
+
## Deprecation and removal
|
|
75
|
+
|
|
76
|
+
Lifecycle is application policy, not npm semver inference. `defineCapabilityLifecyclePolicy()`
|
|
77
|
+
records each canonical major as `supported`, `deprecated`, or `removed`. Deprecation needs an
|
|
78
|
+
operator-facing note. Removal needs a note and reviewer.
|
|
79
|
+
|
|
80
|
+
`validateCapabilityPackMigration()` compares an earlier pack to a later pack. It checks unchanged
|
|
81
|
+
IDs with the replacement guard, requires reviewed migration records for added majors of an existing
|
|
82
|
+
capability family, and reports additions/removals. A supported major cannot disappear implicitly. A
|
|
83
|
+
major may be removed only when the previous policy already marked it deprecated and the new policy
|
|
84
|
+
records an explicit reviewed removal. Package version changes do not alter these states.
|
|
85
|
+
|
|
86
|
+
## Generated surfaces
|
|
87
|
+
|
|
88
|
+
`capabilitySurfaceNames(identity)` maps each exact major identity deterministically:
|
|
89
|
+
|
|
90
|
+
| Surface | Mapping |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| CLI | `<namespace>:<name>@<major>` |
|
|
93
|
+
| HTTP | `/capabilities/<namespace>/<name>/v<major>/invoke` |
|
|
94
|
+
| MCP | length-encoded `cap_..._v<major>` tool name |
|
|
95
|
+
| OpenAPI | length-encoded `invoke_..._v<major>` operation ID |
|
|
96
|
+
|
|
97
|
+
HTTP segments are encoded separately. MCP names over 128 characters receive a visible
|
|
98
|
+
`_fnv1a64_<digest>` qualifier rather than silent truncation. Aliases are composition lookup names;
|
|
99
|
+
they do not replace canonical CLI IDs, HTTP paths, MCP names, or OpenAPI operations. Adapters expose
|
|
100
|
+
only definitions present in the registry and keep their existing authorization/discovery policies.
|
|
101
|
+
|
|
102
|
+
Run `npm run example:e08` for the independently installed contract-only package. It exports v1 and
|
|
103
|
+
v2 album lookup contracts, keeps a stable v1 alias, uses two consumer bindings, checks old/new output
|
|
104
|
+
fixtures, and exercises migration failures. E12 retains its established canonical unit-converter ID.
|
|
105
|
+
|
|
106
|
+
## Reproduce coexistence
|
|
107
|
+
|
|
108
|
+
With Node.js 22 or newer, run `npm ci` and `npm run example:e08` from the
|
|
109
|
+
repository root. The standalone package installs v1 and v2 contracts and each
|
|
110
|
+
consumer selects its intended major; the v1 alias remains pinned. A breaking
|
|
111
|
+
same-ID replacement fails composition. Give the changed contract a new major,
|
|
112
|
+
add a reviewed migration record, and keep the earlier major until its lifecycle
|
|
113
|
+
policy explicitly permits removal. See [E08 setup](https://github.com/uppercut-labs/agent-native/blob/main/examples/e08-reusable-versioned-pack/project/README.md).
|