@passioncode-ai/fabric-agent-adapter 0.8.0 → 0.8.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.1 - 2026-10-08
4
+
5
+ The contract pin moves to Fabric Agent Contract `main` `623bf61` (DEC-0025 through DEC-0031); bundles issued
6
+ under the DEC-0024, DEC-0021, DEC-0020 and DEC-0019 revisions stay valid. And a fix for `Mcp-Name` values outside
7
+ plain ASCII.
8
+
9
+ ### Changed
10
+
11
+ - **Default contract pin `23f9fda4` → `623bf61358c339cb10297807b3f024b5d9f1f327`** (FAA-14). `23f9fda4` (DEC-0024)
12
+ joins `SUPPORTED_CONTRACT_COMMITS`. The revisions between add only optional surfaces: settings backups and the
13
+ feed token header (DEC-0025), runner routes (DEC-0026, DEC-0029), spending limits in the usage report
14
+ (DEC-0027), activity telemetry (DEC-0030) and devices (DEC-0031). The kits emit none of them yet, which stays
15
+ valid; adopting DEC-0025 and DEC-0027 is FAA-13, runner routes FAA-11. Checked: `npm test`; both compiled-schema
16
+ arms against clean checkouts at `2ce3922` and `623bf61` (11/11); a kit usage report validates against the new
17
+ `service-usage.schema.json`.
18
+
19
+ ### Fixed
20
+
21
+ - **`Mcp-Name` values outside plain ASCII follow the spec's Base64 sentinel.** MCP 2026-07-28
22
+ Streamable HTTP ("Value Encoding") requires a name or URI with non-ASCII characters, control
23
+ characters, leading or trailing spaces, or the sentinel's own shape to travel as
24
+ `=?base64?<UTF-8 base64>?=`, and a server to decode it before comparing it with the body. Both kits
25
+ sent the raw value and compared the raw header, so such a call was refused by a conforming server
26
+ and a conforming client was refused by ours. `encode_header_value` / `encodeHeaderValue` and
27
+ `decode_header_value` / `decodeHeaderValue` now do both; a malformed sentinel or a raw non-ASCII
28
+ header is refused as `-32020`. Tested with the spec's own five examples in both kits. This is the
29
+ part of PR #24 that FAA-08 had not already shipped.
30
+
3
31
  ## 0.8.0 - 2026-10-04
4
32
 
5
33
  Agents report what they spend (Fabric Agent Contract DEC-0021), and a service may keep its MCP
@@ -5,7 +5,7 @@ This repository is open source under the [GNU Affero General Public License v3.0
5
5
  meet the AGPL's terms — for example inside a closed-source product, or as a modified hosted
6
6
  service whose source is not published.
7
7
 
8
- To obtain a commercial license, write to **contact@passioncode.ai**.
8
+ To obtain a commercial license, tell us about your use at **https://passioncode.ai/business/**, or write to **commercial@passioncode.ai**.
9
9
 
10
10
  SPDX: `AGPL-3.0-only OR LicenseRef-PassionCode-Commercial`. Copyright (c) 2026 Siarhei Sheleh.
11
11
  Contributions are accepted under [CLA.md](CLA.md), which allows this dual licence.
package/README.md CHANGED
@@ -191,7 +191,7 @@ Scaffolding is non-destructive by default. It writes only the paths declared in
191
191
  The default pin for new bundles is [`fabric-contract.lock.json`](fabric-contract.lock.json):
192
192
 
193
193
  - version: `0.1.0`
194
- - commit: `23f9fda4c05f8a3852246ee98d4f2adf74ed0875`
194
+ - commit: `623bf61358c339cb10297807b3f024b5d9f1f327`
195
195
 
196
196
  Every other mention of the default contract revision — this section, the skills' metadata, the
197
197
  skill card, `adapt_project.py` — must equal it; `python3 test/validate.py` fails a tree
@@ -218,13 +218,13 @@ FABRIC_CONTRACT_OLD=/path/to/legacy-contract FABRIC_CONTRACT_NEW=/path/to/curren
218
218
  ```
219
219
 
220
220
  Without both environment variables, those tests explicitly skip as `NOT_RUN`.
221
- This source checkout is 0.8.0 pending release (0.7.0 was never released); the verified published GitHub/npm version
222
- on 2026-10-04 is 0.6.3. Source delivery does not update installed skills or live sessions.
221
+ This source checkout is 0.8.1. 0.8.0 is the latest published GitHub/npm release (2026-10-05; 0.7.0 was never
222
+ released); 0.8.1 is published by its `v0.8.1` tag. Source delivery does not update installed skills or live sessions.
223
223
 
224
224
  ## License
225
225
 
226
226
  Open source under the [GNU AGPL-3.0](LICENSE). A [commercial license](COMMERCIAL-LICENSE.md) is
227
- available for use that does not meet the AGPL's terms — contact@passioncode.ai.
227
+ available for use that does not meet the AGPL's terms — [passioncode.ai/business](https://passioncode.ai/business/).
228
228
  Versions before 0.5.3 were released under PolyForm Noncommercial or Internal Use (v0.4.3 to
229
229
  v0.5.2) and MIT (v0.4.2 and earlier); each keeps the licence it was released under.
230
230
  Contributions are accepted under [CLA.md](CLA.md) ([CONTRIBUTING.md](CONTRIBUTING.md)).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passioncode-ai/fabric-agent-adapter",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Create Fabric-compatible agents, run them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract. This package is the installer CLI.",
5
5
  "scripts": {
6
6
  "test": "python3 test/validate.py && python3 -m unittest discover -s test -q && node --test 'test/*.test.mjs'"
@@ -3,7 +3,7 @@
3
3
  "name": "fabric-agent-adapter",
4
4
  "displayName": "Fabric Agent Adapter",
5
5
  "description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles.",
6
- "version": "0.8.0",
6
+ "version": "0.8.1",
7
7
  "author": {
8
8
  "name": "PassionCode.ai",
9
9
  "url": "https://passioncode.ai/"
@@ -5,9 +5,9 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
5
5
  compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode.
6
6
  metadata:
7
7
  author: PassionCode.ai
8
- version: "0.8.0"
8
+ version: "0.8.1"
9
9
  contract-version: "0.1.0"
10
- contract-commit: "23f9fda4c05f8a3852246ee98d4f2adf74ed0875"
10
+ contract-commit: "623bf61358c339cb10297807b3f024b5d9f1f327"
11
11
  ---
12
12
 
13
13
  # Adapting projects to Fabric
@@ -70,7 +70,7 @@ Use exactly:
70
70
 
71
71
  - contract version `0.1.0`;
72
72
  - repository `https://github.com/passioncode-ai/fabric-agent-contract`;
73
- - commit `23f9fda4c05f8a3852246ee98d4f2adf74ed0875`.
73
+ - commit `623bf61358c339cb10297807b3f024b5d9f1f327`.
74
74
 
75
75
  This is the default for new bundles. When checking an issued legacy bundle, preserve its
76
76
  explicit supported lock and use that exact clean checkout. Never silently upgrade or
@@ -16,10 +16,10 @@ from urllib.parse import urlparse
16
16
 
17
17
  CONTRACT_VERSION = "0.1.0"
18
18
  CONTRACT_REPOSITORY = "https://github.com/passioncode-ai/fabric-agent-contract"
19
- CONTRACT_COMMIT = "23f9fda4c05f8a3852246ee98d4f2adf74ed0875"
19
+ CONTRACT_COMMIT = "623bf61358c339cb10297807b3f024b5d9f1f327"
20
20
  # #region supported-contract-revisions — docs: README.md#contract-pin
21
21
  # Only this declaration may name a legacy revision in live files.
22
- SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
22
+ SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
23
23
  # #endregion supported-contract-revisions
24
24
  INTEROP_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/interop/0.1"
25
25
  MCP_REVISION = "2026-07-28"
@@ -16,10 +16,10 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
16
16
  compatibility: Python 3.9+ or Node.js 20+ for the kits; the probe needs Python 3.9+. launchd steps are macOS-only (Linux services use lifecycle manager none until a systemd adapter exists). Dashboard handoff optionally uses Fabric Dashboards MCP link/host_status/open; without it, report unresolved host capability. The contract checkout is optional.
17
17
  metadata:
18
18
  author: PassionCode.ai
19
- version: "0.8.0"
19
+ version: "0.8.1"
20
20
  contract-version: "0.1.0"
21
21
  extension: "fabric-service/0.1"
22
- extension-commit: "23f9fda4c05f8a3852246ee98d4f2adf74ed0875"
22
+ extension-commit: "623bf61358c339cb10297807b3f024b5d9f1f327"
23
23
  ---
24
24
 
25
25
  # Building Fabric services
@@ -1,6 +1,6 @@
1
1
  # fabric-service/0.1 — wire reference
2
2
 
3
- Pinned to `fabric-agent-contract` commit `23f9fda4c05f8a3852246ee98d4f2adf74ed0875`
3
+ Pinned to `fabric-agent-contract` commit `623bf61358c339cb10297807b3f024b5d9f1f327`
4
4
  (`docs/specification/service.md`, DEC-0015; the usage report DEC-0021). The contract's schemas are normative;
5
5
  this page is the working summary. Extension key:
6
6
  `https://fabric.passioncode.ai/agent-contract/extensions/service/0.1`.
@@ -226,6 +226,37 @@ export class JobStore {
226
226
  export const NAMED_METHODS = { 'tools/call': 'name', 'prompts/get': 'name', 'resources/read': 'uri' };
227
227
  export const HEADER_MISMATCH = -32020; // the code the official 2026-07-28 server SDK answers a header/body mismatch with
228
228
 
229
+ const SENTINEL = ['=?base64?', '?='];
230
+
231
+ /**
232
+ * A body value as an HTTP header value, by MCP 2026-07-28 Streamable HTTP "Value Encoding": as it
233
+ * is when it is printable ASCII without leading or trailing whitespace, otherwise — and for a plain
234
+ * value that itself looks like the sentinel — `=?base64?<base64 of its UTF-8>?=`.
235
+ */
236
+ export function encodeHeaderValue(value) {
237
+ const s = String(value);
238
+ const plain = /^[\x20-\x7e]*$/.test(s) && s === s.replace(/^ +| +$/g, '') && !(s.startsWith(SENTINEL[0]) && s.endsWith(SENTINEL[1]));
239
+ if (plain) return s;
240
+ let binary = '';
241
+ for (const byte of new TextEncoder().encode(s)) binary += String.fromCharCode(byte);
242
+ return SENTINEL[0] + btoa(binary) + SENTINEL[1];
243
+ }
244
+
245
+ /** The body value a header carries, decoding the Base64 sentinel; null for invalid characters or a
246
+ * sentinel that is not valid Base64 of UTF-8 (a server rejects both). */
247
+ export function decodeHeaderValue(value) {
248
+ const s = String(value);
249
+ if (s.startsWith(SENTINEL[0]) && s.endsWith(SENTINEL[1]) && s.length >= SENTINEL[0].length + SENTINEL[1].length) {
250
+ const body = s.slice(SENTINEL[0].length, -SENTINEL[1].length);
251
+ if (body.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(body)) return null;
252
+ try {
253
+ const bytes = Uint8Array.from(atob(body), (c) => c.charCodeAt(0));
254
+ return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
255
+ } catch { return null; }
256
+ }
257
+ return /^[\x20-\x7e\t]*$/.test(s) ? s : null;
258
+ }
259
+
229
260
  /**
230
261
  * MCP 2026-07-28 Streamable HTTP mirrors the body in `Mcp-Method` (every request and
231
262
  * notification) and `Mcp-Name` (`params.name` or `params.uri` of tools/call, prompts/get,
@@ -250,7 +281,11 @@ export function mcpHeaderProblem(message, headers) {
250
281
  if (field) {
251
282
  const named = header('Mcp-Name');
252
283
  if (named === null && declared) return `the body carries params.${field} but the required Mcp-Name header is absent`;
253
- if (named !== null && named !== String(message.params?.[field])) return `Mcp-Name disagrees with the body's params.${field}`;
284
+ if (named !== null) {
285
+ const decoded = decodeHeaderValue(named);
286
+ if (decoded === null) return 'Mcp-Name is malformed: invalid characters or a broken =?base64?…?= value';
287
+ if (decoded !== String(message.params?.[field])) return `Mcp-Name disagrees with the body's params.${field}`;
288
+ }
254
289
  }
255
290
  return null;
256
291
  }
@@ -259,6 +294,6 @@ export function mcpHeaderProblem(message, headers) {
259
294
  export function mcpRequestHeaders(method, params = {}) {
260
295
  const out = { 'MCP-Protocol-Version': MCP_REVISION, 'Mcp-Method': method };
261
296
  const field = NAMED_METHODS[method];
262
- if (field && params[field] !== undefined && params[field] !== null) out['Mcp-Name'] = String(params[field]);
297
+ if (field && params[field] !== undefined && params[field] !== null) out['Mcp-Name'] = encodeHeaderValue(params[field]);
263
298
  return out;
264
299
  }
@@ -17,6 +17,7 @@ Normative source: fabric-agent-contract docs/specification/interop.md (DEC-0016,
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
+ import base64
20
21
  import json
21
22
  from pathlib import Path
22
23
  import re
@@ -386,6 +387,33 @@ NAMED_METHODS = {"tools/call": "name", "prompts/get": "name", "resources/read":
386
387
  HEADER_MISMATCH = -32020 # the code the official 2026-07-28 server SDK answers a header/body mismatch with
387
388
 
388
389
 
390
+ _SENTINEL = ("=?base64?", "?=")
391
+
392
+
393
+ def encode_header_value(value: str) -> str:
394
+ """A body value as an HTTP header value, by MCP 2026-07-28 Streamable HTTP "Value Encoding": as
395
+ it is when it is printable ASCII without leading or trailing whitespace, otherwise — and for a
396
+ plain value that itself looks like the sentinel — `=?base64?<base64 of its UTF-8>?=`."""
397
+ plain = (all(0x20 <= ord(c) <= 0x7E for c in value) and value == value.strip(" ")
398
+ and not (value.startswith(_SENTINEL[0]) and value.endswith(_SENTINEL[1])))
399
+ if plain:
400
+ return value
401
+ return _SENTINEL[0] + base64.b64encode(value.encode("utf-8")).decode("ascii") + _SENTINEL[1]
402
+
403
+
404
+ def decode_header_value(value: str) -> Optional[str]:
405
+ """The body value a header carries, decoding the Base64 sentinel; None for a header value with
406
+ invalid characters or a sentinel that is not valid Base64 of UTF-8 (a server rejects both)."""
407
+ if value.startswith(_SENTINEL[0]) and value.endswith(_SENTINEL[1]) and len(value) >= len(_SENTINEL[0]) + len(_SENTINEL[1]):
408
+ try:
409
+ return base64.b64decode(value[len(_SENTINEL[0]):-len(_SENTINEL[1])], validate=True).decode("utf-8")
410
+ except ValueError: # binascii.Error and UnicodeDecodeError are both ValueErrors
411
+ return None
412
+ if any(not (0x20 <= ord(c) <= 0x7E or c == "\t") for c in value):
413
+ return None
414
+ return value
415
+
416
+
389
417
  def mcp_header_problem(message: Any, headers: Any) -> Optional[str]:
390
418
  """MCP 2026-07-28 Streamable HTTP mirrors the body in `Mcp-Method` (every request and
391
419
  notification) and `Mcp-Name` (`params.name` or `params.uri` of tools/call, prompts/get,
@@ -414,8 +442,12 @@ def mcp_header_problem(message: Any, headers: Any) -> Optional[str]:
414
442
  named = header("Mcp-Name")
415
443
  if named is None and declared:
416
444
  return "the body carries params.%s but the required Mcp-Name header is absent" % field
417
- if named is not None and named != str(value):
418
- return "Mcp-Name disagrees with the body's params.%s" % field
445
+ if named is not None:
446
+ decoded = decode_header_value(named)
447
+ if decoded is None:
448
+ return "Mcp-Name is malformed: invalid characters or a broken =?base64?…?= value"
449
+ if decoded != str(value):
450
+ return "Mcp-Name disagrees with the body's params.%s" % field
419
451
  return None
420
452
 
421
453
 
@@ -424,7 +456,7 @@ def mcp_request_headers(method: str, params: Dict[str, Any]) -> Dict[str, str]:
424
456
  out = {"MCP-Protocol-Version": MCP_REVISION, "Mcp-Method": method}
425
457
  field = NAMED_METHODS.get(method)
426
458
  if field and params.get(field) is not None:
427
- out["Mcp-Name"] = str(params[field])
459
+ out["Mcp-Name"] = encode_header_value(str(params[field]))
428
460
  return out
429
461
 
430
462
 
@@ -5,9 +5,9 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
5
5
  compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode. Ships in one plugin with adapting-projects-to-fabric, whose scripts it reuses.
6
6
  metadata:
7
7
  author: PassionCode.ai
8
- version: "0.8.0"
8
+ version: "0.8.1"
9
9
  contract-version: "0.1.0"
10
- contract-commit: "23f9fda4c05f8a3852246ee98d4f2adf74ed0875"
10
+ contract-commit: "623bf61358c339cb10297807b3f024b5d9f1f327"
11
11
  ---
12
12
 
13
13
  # Creating Fabric-compatible agents
@@ -119,7 +119,7 @@ python3 <plugin-dir>/skills/adapting-projects-to-fabric/scripts/adapt_project.py
119
119
  --schema-base <immutable-base-uri>
120
120
  ```
121
121
 
122
- Pin exactly contract `0.1.0` at commit `23f9fda4c05f8a3852246ee98d4f2adf74ed0875` and
122
+ Pin exactly contract `0.1.0` at commit `623bf61358c339cb10297807b3f024b5d9f1f327` and
123
123
  read the pinned guide before implementing protocol details. If this skill is installed
124
124
  without its sibling, the scaffolder is absent: create the bundle by hand from the pinned
125
125
  contract's `docs/guides/connecting-compatible-agents.md` and mark the structural check