@nxisai/agent-harness 0.2.2 → 0.2.3
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/README.md +73 -2
- package/dist/providers/proxy.d.ts.map +1 -1
- package/dist/providers/proxy.js +40 -1
- package/dist/providers/proxy.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -403,10 +403,78 @@ import { connectTools } from '@nxisai/agent-harness/tools';
|
|
|
403
403
|
const { modelContext, remoteTools, warning } = await connectTools(apiKey?, { timeout?, fallback?, accept? });
|
|
404
404
|
```
|
|
405
405
|
|
|
406
|
-
Discovery reads
|
|
406
|
+
Discovery reads the site's [`.well-known` catalog](#mcp-discovery-files), follows it to the server card, and bridges the server's tools into `document.modelContext`. When the page has no WebMCP provider, a local registry is installed so bridged tools still have a home (`fallback: false` disables this). `nxis_action` is not bridged by default; it is the server-side path for external MCP clients, and the page's own form tools cover the same actions. Pass `accept` to change which tools cross.
|
|
407
407
|
|
|
408
408
|
Bridged tools are marked read-only only when their server says so. The `nxis_query` schema is flattened for the model: `{ schema_type, text, filters, sort, limit }`, where `filters` is `{ "path": ">=4.5" }` (prefixes `>=`, `>`, `<=`, `<`; a list means any-of) and `sort` is a path with `-` for descending. String and nested forms of `filters` are accepted.
|
|
409
409
|
|
|
410
|
+
### MCP discovery files
|
|
411
|
+
|
|
412
|
+
Remote tools are found by fetching two files from the **page's own origin**. The site serves them; the harness never takes an endpoint from configuration.
|
|
413
|
+
|
|
414
|
+
```
|
|
415
|
+
https://example.com/.well-known/ai-catalog.json ← tried first
|
|
416
|
+
https://example.com/.well-known/catalog.json ← tried only if the first fails
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Either file is a catalog listing one or more entries. The harness takes the first entry that has a `url` and follows it to a server card:
|
|
420
|
+
|
|
421
|
+
```json
|
|
422
|
+
{
|
|
423
|
+
"specVersion": "2025-09-16",
|
|
424
|
+
"entries": [
|
|
425
|
+
{
|
|
426
|
+
"identifier": "example-mcp",
|
|
427
|
+
"displayName": "Example",
|
|
428
|
+
"type": "mcp-server",
|
|
429
|
+
"mediaType": "application/json",
|
|
430
|
+
"url": "https://mcp.example.com/.well-known/mcp-server-card.json"
|
|
431
|
+
}
|
|
432
|
+
]
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Only `entries[].url` is read; the other fields are for human and crawler consumption. The URL may point anywhere — the card is commonly hosted on the MCP server's own origin rather than the site's.
|
|
437
|
+
|
|
438
|
+
The server card describes how to reach the server:
|
|
439
|
+
|
|
440
|
+
```json
|
|
441
|
+
{
|
|
442
|
+
"name": "com.example/mcp",
|
|
443
|
+
"title": "Example",
|
|
444
|
+
"description": "Typed retrieval and actions for example.com",
|
|
445
|
+
"websiteUrl": "https://example.com",
|
|
446
|
+
"remotes": [
|
|
447
|
+
{
|
|
448
|
+
"type": "streamable-http",
|
|
449
|
+
"url": "https://mcp.example.com/mcp",
|
|
450
|
+
"supportedProtocolVersions": ["2025-06-18"],
|
|
451
|
+
"headers": [
|
|
452
|
+
{ "name": "X-API-Key", "isSecret": true, "isRequired": false }
|
|
453
|
+
]
|
|
454
|
+
}
|
|
455
|
+
],
|
|
456
|
+
"_meta": {
|
|
457
|
+
"ai.nxis/publicTools": ["nxis_query", "nxis_get", "nxis_list_types"]
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
What each part does:
|
|
463
|
+
|
|
464
|
+
| Field | Effect |
|
|
465
|
+
|---|---|
|
|
466
|
+
| `remotes[]` | The `streamable-http` entry is used; otherwise the first entry. Its `url` is the MCP endpoint, and a missing one fails discovery. |
|
|
467
|
+
| `remotes[].headers[0]` | Names the header the `mcp-api-key` attribute is sent in. Only the first header is read. With `isRequired: true`, the harness treats calls as needing a key. |
|
|
468
|
+
| `_meta["ai.nxis/publicTools"]` | The tools usable without a key. Used as the tool list only when `tools/list` fails — normally the live list wins. |
|
|
469
|
+
| `title` | Labels the bridged toolset in the UI. |
|
|
470
|
+
|
|
471
|
+
Serving requirements:
|
|
472
|
+
|
|
473
|
+
- Both files must answer `GET` with JSON. The harness sends `Accept: application/json` and treats any non-2xx as "try the next catalog".
|
|
474
|
+
- The catalog fetch is same-origin, so it needs no CORS headers. The **server card and the MCP endpoint usually are cross-origin**, and those responses do need `Access-Control-Allow-Origin` for the site's origin.
|
|
475
|
+
- Discovery failure is never fatal. The agent keeps running with whatever the page registered itself, and `connectTools` returns the reason in `warning`.
|
|
476
|
+
- `discover="false"` skips all of this.
|
|
477
|
+
|
|
410
478
|
### A2UI
|
|
411
479
|
|
|
412
480
|
```ts
|
|
@@ -419,7 +487,7 @@ Messages are authored against A2UI v1.0 and downgraded with `toV09` for the publ
|
|
|
419
487
|
|
|
420
488
|
- WebGPU: Chrome or Edge 113+, or Safari 18+.
|
|
421
489
|
- Model weights: fetched on first use (gigabytes on a first run, then cached).
|
|
422
|
-
- Tools: `document.modelContext` from `nxis.js` or a browser extension, or a remote MCP server discoverable through `.well-known
|
|
490
|
+
- Tools: `document.modelContext` from `nxis.js` or a browser extension, or a remote MCP server discoverable through the [`.well-known` files](#mcp-discovery-files) the site serves.
|
|
423
491
|
|
|
424
492
|
## Troubleshooting and gotchas
|
|
425
493
|
|
|
@@ -432,6 +500,9 @@ Chrome's Cache Storage can fail on entries this large and kill the renderer rath
|
|
|
432
500
|
**"No tools are registered on this page yet."**
|
|
433
501
|
`document.modelContext` is empty. On a local origin the site tag does not activate, so on-page form tools are absent; remote MCP tools still arrive through discovery if `.well-known/ai-catalog.json` is served. Check that `discover` is not `"false"`.
|
|
434
502
|
|
|
503
|
+
**"No MCP server-card URL found in .well-known catalogs".**
|
|
504
|
+
Neither `/.well-known/ai-catalog.json` nor `/.well-known/catalog.json` answered with JSON containing an entry that has a `url`. Fetch both paths against the origin the page is served from, not the MCP server's origin, and check the shape against [MCP discovery files](#mcp-discovery-files). A dev server that returns `index.html` for unknown paths is a common cause: the fetch succeeds, the JSON parse does not.
|
|
505
|
+
|
|
435
506
|
**An agent answers "I cannot do that from this page".**
|
|
436
507
|
That agent's `tools` filter matched nothing among the registered tools. The slice is not widened silently. Check the names your page registers against the filter, and write an `unavailable` line per agent.
|
|
437
508
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/providers/proxy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/providers/proxy.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAc,aAAa,EAAE,MAAM,YAAY,CAAC;AAE5D,+CAA+C;AAC/C,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAyDD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,oBAAoB,GAAG,aAAa,CAkD7E"}
|
package/dist/providers/proxy.js
CHANGED
|
@@ -1,3 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A {@link ModelProvider} that forwards completions to an endpoint you host.
|
|
3
|
+
*
|
|
4
|
+
* The browser never holds a vendor credential: the page posts to your own
|
|
5
|
+
* origin, and your endpoint calls the model with a key only it can see. This is
|
|
6
|
+
* the client half of that arrangement. The server half is yours to write, and
|
|
7
|
+
* `docs/remote-providers.md` documents the contract it has to satisfy.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { createProxyProvider } from '@nxisai/agent-harness';
|
|
12
|
+
*
|
|
13
|
+
* const agent = document.querySelector('nxis-agent-fullscreen');
|
|
14
|
+
* agent.modelProvider = createProxyProvider({ endpoint: '/api/agent' });
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
import { toolSchema } from '../harness/tools/schema.js';
|
|
20
|
+
/**
|
|
21
|
+
* Reduce the page's tools to plain, serialisable data.
|
|
22
|
+
*
|
|
23
|
+
* `discoverTools` returns whatever the WebMCP implementation holds, and a
|
|
24
|
+
* native registry returns live objects that reference the page: passing those
|
|
25
|
+
* to `JSON.stringify` throws "Converting circular structure to JSON, starting
|
|
26
|
+
* at object with constructor 'Window'". The local provider never hit this
|
|
27
|
+
* because it reads the same three fields out before building its prompt.
|
|
28
|
+
*
|
|
29
|
+
* Normalising the schema here also spares every endpoint the job: WebMCP
|
|
30
|
+
* carries it under four different keys and sometimes as a JSON string, and
|
|
31
|
+
* `inputSchema` is the one the contract documents.
|
|
32
|
+
*/
|
|
33
|
+
function wireTools(tools) {
|
|
34
|
+
return tools.map((tool) => ({
|
|
35
|
+
name: tool.name,
|
|
36
|
+
description: tool.description ?? '',
|
|
37
|
+
inputSchema: toolSchema(tool),
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
1
40
|
/**
|
|
2
41
|
* Reduce a response to the single call the harness can act on.
|
|
3
42
|
*
|
|
@@ -55,7 +94,7 @@ export function createProxyProvider(opts) {
|
|
|
55
94
|
// Without this the stop control, Escape and edit-and-resend are all
|
|
56
95
|
// inert: they abort the turn through this signal and nothing else.
|
|
57
96
|
signal,
|
|
58
|
-
body: JSON.stringify({ messages, tools, maxTokens, cache }),
|
|
97
|
+
body: JSON.stringify({ messages, tools: wireTools(tools), maxTokens, cache }),
|
|
59
98
|
});
|
|
60
99
|
if (!res.ok) {
|
|
61
100
|
throw new Error(`Agent endpoint ${endpoint} returned ${res.status} ${res.statusText}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/providers/proxy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/providers/proxy.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxisai/agent-harness",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "An on-device agent for the browser: a tool-calling harness over WebMCP and remote MCP, plus a drop-in chat UI. Decides whether to run a tool, ask for what's missing, or confirm before writing.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|