@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 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 `/.well-known/ai-catalog.json`, 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.
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":"AAmBA,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;AAoCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,oBAAoB,GAAG,aAAa,CAkD7E"}
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"}
@@ -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":"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"}
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.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",