@ggui-ai/protocol-conformance 0.1.0-rc.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/LICENSE +201 -0
- package/README.md +89 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +193 -0
- package/dist/conformance-host.d.ts +155 -0
- package/dist/conformance-host.d.ts.map +1 -0
- package/dist/conformance-host.js +45 -0
- package/dist/fixtures/bootstrap-protocol/bootstrap-bundle-fetch-failed.json +26 -0
- package/dist/fixtures/bootstrap-protocol/bootstrap-meta-missing.json +32 -0
- package/dist/fixtures/bootstrap-protocol/bootstrap-success.json +17 -0
- package/dist/fixtures/bootstrap-protocol/index.d.ts +5 -0
- package/dist/fixtures/bootstrap-protocol/index.d.ts.map +1 -0
- package/dist/fixtures/bootstrap-protocol/index.js +24 -0
- package/dist/fixtures/canvas-mode-wire-shapes/canvas-bootstrap-mutual-exclusion.json +25 -0
- package/dist/fixtures/canvas-mode-wire-shapes/canvas-lifecycle-channel-emits-handshake-started.json +30 -0
- package/dist/fixtures/canvas-mode-wire-shapes/canvas-navigated-updates-active-stack-item.json +30 -0
- package/dist/fixtures/canvas-mode-wire-shapes/host-context-observed-persists.json +22 -0
- package/dist/fixtures/canvas-mode-wire-shapes/index.d.ts +5 -0
- package/dist/fixtures/canvas-mode-wire-shapes/index.d.ts.map +1 -0
- package/dist/fixtures/canvas-mode-wire-shapes/index.js +47 -0
- package/dist/fixtures/index.d.ts +78 -0
- package/dist/fixtures/index.d.ts.map +1 -0
- package/dist/fixtures/index.js +87 -0
- package/dist/fixtures/observability-events/index.d.ts +5 -0
- package/dist/fixtures/observability-events/index.d.ts.map +1 -0
- package/dist/fixtures/observability-events/index.js +22 -0
- package/dist/fixtures/observability-events/observability-contract-error-emitted.json +29 -0
- package/dist/fixtures/observability-events/observability-wired-tool-invoked.json +28 -0
- package/dist/fixtures/refresh-semantics/index.d.ts +5 -0
- package/dist/fixtures/refresh-semantics/index.d.ts.map +1 -0
- package/dist/fixtures/refresh-semantics/index.js +19 -0
- package/dist/fixtures/refresh-semantics/stream-refresh-success.json +40 -0
- package/dist/fixtures/reserved-channel-authority/index.d.ts +4 -0
- package/dist/fixtures/reserved-channel-authority/index.d.ts.map +1 -0
- package/dist/fixtures/reserved-channel-authority/index.js +21 -0
- package/dist/fixtures/reserved-channel-authority/props-update-roundtrip.json +34 -0
- package/dist/fixtures/reserved-channel-authority/stream-schema-violation.json +45 -0
- package/dist/fixtures/schema-version-handshake/index.d.ts +4 -0
- package/dist/fixtures/schema-version-handshake/index.d.ts.map +1 -0
- package/dist/fixtures/schema-version-handshake/index.js +20 -0
- package/dist/fixtures/schema-version-handshake/version-match.json +16 -0
- package/dist/fixtures/schema-version-handshake/version-mismatch.json +26 -0
- package/dist/fixtures/wired-action-dispatch/index.d.ts +5 -0
- package/dist/fixtures/wired-action-dispatch/index.d.ts.map +1 -0
- package/dist/fixtures/wired-action-dispatch/index.js +29 -0
- package/dist/fixtures/wired-action-dispatch/wired-action-success.json +36 -0
- package/dist/fixtures/wired-action-dispatch/wired-action-tool-not-found.json +31 -0
- package/dist/fixtures/wired-action-dispatch/wired-action-tool-threw.json +32 -0
- package/dist/fixtures/wired-action-dispatch/wired-action-tool-timeout.json +32 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/loader.d.ts +19 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +46 -0
- package/dist/match-behavior.d.ts +121 -0
- package/dist/match-behavior.d.ts.map +1 -0
- package/dist/match-behavior.js +385 -0
- package/dist/registration-conformance/cases/gate-export-not-registered.json +19 -0
- package/dist/registration-conformance/cases/gate-no-gadgets-accept.json +13 -0
- package/dist/registration-conformance/cases/gate-package-mismatch.json +19 -0
- package/dist/registration-conformance/cases/gate-priority-not-registered-outranks-mismatch.json +20 -0
- package/dist/registration-conformance/cases/gate-registered-resolves-accept.json +25 -0
- package/dist/registration-conformance/cases/gate-stdlib-hook-accept.json +27 -0
- package/dist/registration-conformance/cases/gate-typo-classifies-as-not-registered.json +27 -0
- package/dist/registration-conformance/index.d.ts +114 -0
- package/dist/registration-conformance/index.d.ts.map +1 -0
- package/dist/registration-conformance/index.js +63 -0
- package/dist/reporter.d.ts +32 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +151 -0
- package/dist/resolution-conformance/cases/resolve-bundle-host-computed.json +13 -0
- package/dist/resolution-conformance/cases/resolve-default-host.json +12 -0
- package/dist/resolution-conformance/cases/resolve-explicit-bundle-and-style-url.json +14 -0
- package/dist/resolution-conformance/cases/resolve-explicit-bundle-url.json +12 -0
- package/dist/resolution-conformance/cases/resolve-loopback-host-http-scheme.json +13 -0
- package/dist/resolution-conformance/cases/resolve-package-only-no-version.json +8 -0
- package/dist/resolution-conformance/cases/resolve-stdlib-no-urls.json +10 -0
- package/dist/resolution-conformance/index.d.ts +76 -0
- package/dist/resolution-conformance/index.d.ts.map +1 -0
- package/dist/resolution-conformance/index.js +81 -0
- package/dist/run-conformance.d.ts +58 -0
- package/dist/run-conformance.d.ts.map +1 -0
- package/dist/run-conformance.js +286 -0
- package/dist/schema-conformance/cases/wire-bad-export-name-reject.json +11 -0
- package/dist/schema-conformance/cases/wire-bad-package-name-reject.json +11 -0
- package/dist/schema-conformance/cases/wire-component-export-accept.json +10 -0
- package/dist/schema-conformance/cases/wire-empty-package-reject.json +11 -0
- package/dist/schema-conformance/cases/wire-minimal-accept.json +10 -0
- package/dist/schema-conformance/cases/wire-multi-package-accept.json +14 -0
- package/dist/schema-conformance/cases/wire-prose-accept.json +15 -0
- package/dist/schema-conformance/cases/wire-registry-metadata-reject.json +13 -0
- package/dist/schema-conformance/cases/wire-stale-libraries-reject.json +14 -0
- package/dist/schema-conformance/cases/wire-stdlib-accept.json +10 -0
- package/dist/schema-conformance/cases/wire-transport-field-reject.json +15 -0
- package/dist/schema-conformance/cases/wire-version-field-reject.json +13 -0
- package/dist/schema-conformance/index.d.ts +71 -0
- package/dist/schema-conformance/index.d.ts.map +1 -0
- package/dist/schema-conformance/index.js +103 -0
- package/dist/types.d.ts +434 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +62 -0
- package/dist/ws-transport.d.ts +42 -0
- package/dist/ws-transport.d.ts.map +1 -0
- package/dist/ws-transport.js +135 -0
- package/package.json +83 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal WebSocket transport for the conformance runner.
|
|
3
|
+
*
|
|
4
|
+
* Opens a live-channel connection to a ggui-protocol server (SPEC §12.2),
|
|
5
|
+
* sends subscribe + action frames verbatim, and exposes an async
|
|
6
|
+
* `observe()` that collects inbound frames into a buffer for the
|
|
7
|
+
* matcher.
|
|
8
|
+
*
|
|
9
|
+
* Scope discipline: this file is the `kind: 'ws'` arm of
|
|
10
|
+
* `TransportConfig`. Post-v1.1 transports (stdio MCP, HTTP long-poll)
|
|
11
|
+
* land as sibling modules with the same `Transport` shape. The
|
|
12
|
+
* transport is NOT a full MCP client — it does not try to be
|
|
13
|
+
* compatible with the MCP SDK, and it does not implement stack
|
|
14
|
+
* management. The kit runs against an already-provisioned session
|
|
15
|
+
* (the host's `create-session` setup step ran first) and treats the
|
|
16
|
+
* wire as opaque after that.
|
|
17
|
+
*
|
|
18
|
+
* Auth:
|
|
19
|
+
* - `bearer` token → `Authorization: Bearer <token>` header on
|
|
20
|
+
* the upgrade request.
|
|
21
|
+
* - `session-cookie` → `Cookie: <cookie>` header.
|
|
22
|
+
*/
|
|
23
|
+
import WebSocket from 'ws';
|
|
24
|
+
/**
|
|
25
|
+
* Open a WS transport against `config.url`, wait for the socket to
|
|
26
|
+
* reach `OPEN` state, and return the transport handle. Throws if the
|
|
27
|
+
* socket errors or closes before reaching OPEN.
|
|
28
|
+
*/
|
|
29
|
+
export async function openWsTransport(config) {
|
|
30
|
+
const headers = authHeaders(config.auth);
|
|
31
|
+
const socket = new WebSocket(config.url, { headers });
|
|
32
|
+
const buffer = [];
|
|
33
|
+
let flushWaiter = null;
|
|
34
|
+
socket.on('message', (raw) => {
|
|
35
|
+
const text = raw.toString('utf8');
|
|
36
|
+
const parsed = safeParse(text);
|
|
37
|
+
const frame = parsed.ok === true
|
|
38
|
+
? { kind: 'frame', raw: text, parsed: parsed.value }
|
|
39
|
+
: { kind: 'unparseable', raw: text, error: parsed.error };
|
|
40
|
+
buffer.push(frame);
|
|
41
|
+
// Fire any pending observer synchronously so predicate-driven
|
|
42
|
+
// early-return works without a polling loop.
|
|
43
|
+
const waiter = flushWaiter;
|
|
44
|
+
if (waiter !== null) {
|
|
45
|
+
flushWaiter = null;
|
|
46
|
+
waiter(frame);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
await new Promise((done, fail) => {
|
|
50
|
+
const onOpen = () => {
|
|
51
|
+
socket.off('error', onError);
|
|
52
|
+
socket.off('close', onClose);
|
|
53
|
+
done();
|
|
54
|
+
};
|
|
55
|
+
const onError = (err) => {
|
|
56
|
+
socket.off('open', onOpen);
|
|
57
|
+
socket.off('close', onClose);
|
|
58
|
+
fail(err);
|
|
59
|
+
};
|
|
60
|
+
const onClose = (code, reason) => {
|
|
61
|
+
socket.off('open', onOpen);
|
|
62
|
+
socket.off('error', onError);
|
|
63
|
+
fail(new Error(`protocol-conformance: ws closed before open — code=${code} reason=${reason.toString('utf8')}`));
|
|
64
|
+
};
|
|
65
|
+
socket.once('open', onOpen);
|
|
66
|
+
socket.once('error', onError);
|
|
67
|
+
socket.once('close', onClose);
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
70
|
+
send(frame) {
|
|
71
|
+
socket.send(JSON.stringify(frame));
|
|
72
|
+
},
|
|
73
|
+
async observe({ timeoutMs, predicate }) {
|
|
74
|
+
// Drain any already-buffered frames first; the timeout only
|
|
75
|
+
// bounds the wait for NEW frames.
|
|
76
|
+
if (predicate !== undefined) {
|
|
77
|
+
const existing = buffer.find((f) => predicate(f));
|
|
78
|
+
if (existing !== undefined)
|
|
79
|
+
return snapshot(buffer);
|
|
80
|
+
}
|
|
81
|
+
await new Promise((done) => {
|
|
82
|
+
const timer = setTimeout(() => {
|
|
83
|
+
flushWaiter = null;
|
|
84
|
+
done();
|
|
85
|
+
}, timeoutMs);
|
|
86
|
+
const onFrame = (frame) => {
|
|
87
|
+
if (predicate !== undefined && predicate(frame)) {
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
done();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
flushWaiter = onFrame;
|
|
93
|
+
};
|
|
94
|
+
flushWaiter = onFrame;
|
|
95
|
+
});
|
|
96
|
+
return snapshot(buffer);
|
|
97
|
+
},
|
|
98
|
+
async close() {
|
|
99
|
+
if (socket.readyState === WebSocket.CLOSED ||
|
|
100
|
+
socket.readyState === WebSocket.CLOSING) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
await new Promise((done) => {
|
|
104
|
+
socket.once('close', () => done());
|
|
105
|
+
socket.close();
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// =============================================================================
|
|
111
|
+
// Helpers
|
|
112
|
+
// =============================================================================
|
|
113
|
+
function authHeaders(auth) {
|
|
114
|
+
switch (auth.kind) {
|
|
115
|
+
case 'bearer':
|
|
116
|
+
return { Authorization: `Bearer ${auth.token}` };
|
|
117
|
+
case 'session-cookie':
|
|
118
|
+
return { Cookie: auth.cookie };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function safeParse(text) {
|
|
122
|
+
try {
|
|
123
|
+
const parsed = JSON.parse(text);
|
|
124
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
125
|
+
return { ok: false, error: 'frame is not a JSON object' };
|
|
126
|
+
}
|
|
127
|
+
return { ok: true, value: parsed };
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
return { ok: false, error: String(err.message ?? err) };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function snapshot(buffer) {
|
|
134
|
+
return buffer.slice();
|
|
135
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ggui-ai/protocol-conformance",
|
|
3
|
+
"version": "0.1.0-rc.1",
|
|
4
|
+
"description": "Conformance test kit for the ggui protocol. Drives a behavioral fixture catalog against a live implementation over WebSocket, plus three pure-function catalogs (schema, registration, resolution) for the gadget obligations, and reports a scorecard-style pass / fail / skip. MCP builders adopt this kit to prove their implementation is protocol-conformant.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"ggui",
|
|
8
|
+
"mcp",
|
|
9
|
+
"protocol",
|
|
10
|
+
"conformance",
|
|
11
|
+
"test-kit"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "dist/index.js",
|
|
19
|
+
"types": "dist/index.d.ts",
|
|
20
|
+
"bin": {
|
|
21
|
+
"ggui-protocol-conformance": "./dist/cli.js"
|
|
22
|
+
},
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"import": "./dist/index.js",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./fixtures": {
|
|
30
|
+
"types": "./dist/fixtures/index.d.ts",
|
|
31
|
+
"import": "./dist/fixtures/index.js",
|
|
32
|
+
"default": "./dist/fixtures/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./schema-conformance": {
|
|
35
|
+
"types": "./dist/schema-conformance/index.d.ts",
|
|
36
|
+
"import": "./dist/schema-conformance/index.js",
|
|
37
|
+
"default": "./dist/schema-conformance/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./registration-conformance": {
|
|
40
|
+
"types": "./dist/registration-conformance/index.d.ts",
|
|
41
|
+
"import": "./dist/registration-conformance/index.js",
|
|
42
|
+
"default": "./dist/registration-conformance/index.js"
|
|
43
|
+
},
|
|
44
|
+
"./resolution-conformance": {
|
|
45
|
+
"types": "./dist/resolution-conformance/index.d.ts",
|
|
46
|
+
"import": "./dist/resolution-conformance/index.js",
|
|
47
|
+
"default": "./dist/resolution-conformance/index.js"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"ws": "^8.20.1",
|
|
52
|
+
"@ggui-ai/protocol": "0.1.0-rc.1"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^22.0.0",
|
|
56
|
+
"@types/ws": "^8.5.10",
|
|
57
|
+
"typescript": "^5.0.0",
|
|
58
|
+
"vitest": "^3.0.0"
|
|
59
|
+
},
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "https://github.com/ggui-ai/ggui.git",
|
|
63
|
+
"directory": "packages/protocol-conformance"
|
|
64
|
+
},
|
|
65
|
+
"homepage": "https://github.com/ggui-ai/ggui/tree/main/packages/protocol-conformance",
|
|
66
|
+
"bugs": {
|
|
67
|
+
"url": "https://github.com/ggui-ai/ggui/issues"
|
|
68
|
+
},
|
|
69
|
+
"publishConfig": {
|
|
70
|
+
"access": "public"
|
|
71
|
+
},
|
|
72
|
+
"engines": {
|
|
73
|
+
"node": ">=20.0.0"
|
|
74
|
+
},
|
|
75
|
+
"author": "ggui contributors <hello@ggui.ai>",
|
|
76
|
+
"scripts": {
|
|
77
|
+
"build": "tsc -p tsconfig.build.json && node ../scripts/fix-esm-imports.mjs dist && chmod +x dist/cli.js",
|
|
78
|
+
"dev": "tsc --watch",
|
|
79
|
+
"typecheck": "tsc --noEmit",
|
|
80
|
+
"test": "vitest run --passWithNoTests",
|
|
81
|
+
"test:watch": "vitest"
|
|
82
|
+
}
|
|
83
|
+
}
|