@ggui-ai/mcp-server 0.14.0 → 0.15.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/README.md +20 -0
- package/dist/mcp-endpoint-routes.d.ts +12 -4
- package/dist/mcp-endpoint-routes.d.ts.map +1 -1
- package/dist/mcp-endpoint-routes.js +89 -30
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -43,6 +43,26 @@ This package deliberately does **not** embed cloud-specific wiring (AWS, DynamoD
|
|
|
43
43
|
Redis), and it does not implement authoring, pairing, or UI generation — those are
|
|
44
44
|
separate packages and protocol flows.
|
|
45
45
|
|
|
46
|
+
## TypeScript build
|
|
47
|
+
|
|
48
|
+
### `tsc` under `moduleResolution: NodeNext` reports errors inside `node_modules/@modelcontextprotocol/ext-apps`
|
|
49
|
+
|
|
50
|
+
Your own code is fine; the errors sit in a dependency's declarations. With `moduleResolution: "NodeNext"` (or `Node16`), `tsc` reports TS2834 and TS2339 inside `node_modules/@modelcontextprotocol/ext-apps/dist/src/app.d.ts`, and TS2305 / TS2460 on `@ggui-ai/protocol`'s `host-context.d.ts` for the three names it re-exports from there (`McpUiDisplayMode`, `McpUiHostContext`, `McpUiHostCapabilities`).
|
|
51
|
+
|
|
52
|
+
**Cause.** `@modelcontextprotocol/ext-apps` 1.7.5 ships extensionless relative specifiers in its own `.d.ts` files — a defect in the published package, tracked upstream at [modelcontextprotocol/ext-apps#704](https://github.com/modelcontextprotocol/ext-apps/issues/704). `@ggui-ai/*` re-exports those names rather than forking a frozen spec surface, so the defect surfaces on our line. Every `@ggui-ai/*` declaration itself carries explicit `.js` extensions since 0.15.0.
|
|
53
|
+
|
|
54
|
+
**Fix.** Until ext-apps ships the upstream fix, set the TypeScript switch for third-party declaration defects in your `tsconfig.json`:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"compilerOptions": {
|
|
59
|
+
"skipLibCheck": true
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`skipLibCheck` skips type-checking of `.d.ts` files only — your own sources still typecheck fully. The dependency bump follows the day the upstream fix is published; nothing in your code changes.
|
|
65
|
+
|
|
46
66
|
## License
|
|
47
67
|
|
|
48
68
|
Apache-2.0
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* POST <universalMcpPath> — DATA PLANE: agent+runtime tools
|
|
5
5
|
* (default `/mcp`)
|
|
6
|
-
* POST <pathPrefix>/:appId — data plane, per-
|
|
6
|
+
* POST <pathPrefix>/:appId — data plane, per-app variant
|
|
7
7
|
* (opt-in via `perAppRouting`)
|
|
8
8
|
* POST /control — CONTROL PLANE: design-time
|
|
9
9
|
* spec/discovery (anonymous) +
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
* (`agent` / `runtime` / `protocol` / `ops`) and the wire-name prefix
|
|
32
32
|
* rules.
|
|
33
33
|
*/
|
|
34
|
+
import { type JsonValue } from "@ggui-ai/protocol";
|
|
34
35
|
import type { AuthAdapter, AuthResult } from "@ggui-ai/mcp-server-core";
|
|
35
36
|
import type { HandlerContext, SharedHandler } from "@ggui-ai/mcp-server-handlers";
|
|
36
37
|
import type { Express } from "express";
|
|
@@ -39,7 +40,7 @@ import type { ZodRawShape } from "zod";
|
|
|
39
40
|
import { type BuildMcpServerOptions, type ServerInfo } from "./build-mcp.js";
|
|
40
41
|
import type { Logger } from "./logger.js";
|
|
41
42
|
import type { McpService } from "./mcp-mounts.js";
|
|
42
|
-
/** Per-
|
|
43
|
+
/** Per-app URL routing shape — mirrors `CreateGguiServerOptions.perAppRouting`. */
|
|
43
44
|
interface PerAppRouting {
|
|
44
45
|
readonly paramName: string;
|
|
45
46
|
readonly paramPattern: string;
|
|
@@ -61,6 +62,13 @@ export interface ErrorMapperResult {
|
|
|
61
62
|
readonly message: string;
|
|
62
63
|
/** Response headers to set before the JSON body is written. */
|
|
63
64
|
readonly headers?: Readonly<Record<string, string>>;
|
|
65
|
+
/**
|
|
66
|
+
* JSON-RPC 2.0 error `data` — any JSON value the deployment wants the
|
|
67
|
+
* client to read alongside `code` / `message` (a structured reason, a
|
|
68
|
+
* retry hint). Serialized verbatim on `error.data`; omitted from the
|
|
69
|
+
* body when absent, so a mapper that never sets it changes nothing.
|
|
70
|
+
*/
|
|
71
|
+
readonly data?: JsonValue;
|
|
64
72
|
}
|
|
65
73
|
interface MountOptions {
|
|
66
74
|
/** Express app to mount onto. */
|
|
@@ -92,9 +100,9 @@ interface MountOptions {
|
|
|
92
100
|
readonly als: AsyncLocalStorage<HandlerContext>;
|
|
93
101
|
/** Identity → appId resolution rule (SPEC §12.2). */
|
|
94
102
|
readonly appIdFromIdentity: (result: AuthResult) => string;
|
|
95
|
-
/** Universal endpoint path (default `/mcp`;
|
|
103
|
+
/** Universal endpoint path (default `/mcp`; a deployment may serve it at `/`). */
|
|
96
104
|
readonly universalMcpPath: string;
|
|
97
|
-
/** Per-
|
|
105
|
+
/** Per-app endpoint shape — absent = universal-only deployment. */
|
|
98
106
|
readonly perAppRouting?: PerAppRouting;
|
|
99
107
|
/** Whether OAuth is enabled (adds `WWW-Authenticate` on 401). */
|
|
100
108
|
readonly oauthEnabled: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-endpoint-routes.d.ts","sourceRoot":"","sources":["../src/mcp-endpoint-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;
|
|
1
|
+
{"version":3,"file":"mcp-endpoint-routes.d.ts","sourceRoot":"","sources":["../src/mcp-endpoint-routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* POST <universalMcpPath> — DATA PLANE: agent+runtime tools
|
|
5
5
|
* (default `/mcp`)
|
|
6
|
-
* POST <pathPrefix>/:appId — data plane, per-
|
|
6
|
+
* POST <pathPrefix>/:appId — data plane, per-app variant
|
|
7
7
|
* (opt-in via `perAppRouting`)
|
|
8
8
|
* POST /control — CONTROL PLANE: design-time
|
|
9
9
|
* spec/discovery (anonymous) +
|
|
@@ -31,13 +31,50 @@
|
|
|
31
31
|
* (`agent` / `runtime` / `protocol` / `ops`) and the wire-name prefix
|
|
32
32
|
* rules.
|
|
33
33
|
*/
|
|
34
|
-
import { isRecord } from "@ggui-ai/protocol";
|
|
34
|
+
import { MCP_ERROR_CODES, isRecord } from "@ggui-ai/protocol";
|
|
35
35
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
36
36
|
import { randomUUID } from "node:crypto";
|
|
37
37
|
import { resolveIdentity, UnauthenticatedError } from "./auth.js";
|
|
38
38
|
import { buildMcpServer } from "./build-mcp.js";
|
|
39
39
|
import { CONTROL_PATH, DATA_PLANE_AUDIENCES, filterHandlersByAudience, } from "./control-service.js";
|
|
40
40
|
import { buildWwwAuthenticate, resolveIssuerUrl } from "./oauth.js";
|
|
41
|
+
/** The JSON-RPC error object a mapped result becomes on the wire. */
|
|
42
|
+
function jsonRpcError(mapped) {
|
|
43
|
+
return {
|
|
44
|
+
code: mapped.code,
|
|
45
|
+
message: mapped.message,
|
|
46
|
+
...(mapped.data !== undefined ? { data: mapped.data } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** The two statuses an authorization refusal may carry — a mapper is bounded to them. */
|
|
50
|
+
const AUTHORIZATION_REFUSAL_STATUSES = new Set([401, 403]);
|
|
51
|
+
/**
|
|
52
|
+
* A deployment's error mapper may attach JSON-RPC `data` (and its own
|
|
53
|
+
* `code` / `message` / headers) to a per-app authorization refusal, so a
|
|
54
|
+
* client can read a structured reason instead of parsing prose. The
|
|
55
|
+
* refusal stays a 401 or a 403: a mapper answering any other status, or
|
|
56
|
+
* throwing, is ignored and logged, and the default-deny 403 stands
|
|
57
|
+
* byte-identical to a deployment with no mapper at all.
|
|
58
|
+
*/
|
|
59
|
+
function mapAuthorizationRefusal(err, errorMapper, log) {
|
|
60
|
+
if (!errorMapper)
|
|
61
|
+
return undefined;
|
|
62
|
+
let mapped;
|
|
63
|
+
try {
|
|
64
|
+
mapped = errorMapper(err);
|
|
65
|
+
}
|
|
66
|
+
catch (mapperErr) {
|
|
67
|
+
log.warn("error_mapper_failed", { error: String(mapperErr) });
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
if (mapped === undefined)
|
|
71
|
+
return undefined;
|
|
72
|
+
if (!AUTHORIZATION_REFUSAL_STATUSES.has(mapped.status)) {
|
|
73
|
+
log.warn("per_app_authorize_mapper_out_of_bounds", { status: mapped.status });
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
return mapped;
|
|
77
|
+
}
|
|
41
78
|
/**
|
|
42
79
|
* Resolve the resource path that `WWW-Authenticate` should point at
|
|
43
80
|
* for the current request. Per-app `/mcp` requests
|
|
@@ -113,7 +150,7 @@ export function mountMcpEndpoints(opts) {
|
|
|
113
150
|
identity = { identity: { kind: "builder" }, source: "anonymous" };
|
|
114
151
|
}
|
|
115
152
|
else {
|
|
116
|
-
// Diagnostic: did a Bearer credential actually reach the
|
|
153
|
+
// Diagnostic: did a Bearer credential actually reach the server on
|
|
117
154
|
// this request? This splits "the client/transport never sent one"
|
|
118
155
|
// from "we received it but the adapter rejected it" — otherwise
|
|
119
156
|
// indistinguishable in `auth_failed`. Only a short, non-secret
|
|
@@ -145,7 +182,7 @@ export function mountMcpEndpoints(opts) {
|
|
|
145
182
|
}
|
|
146
183
|
res.status(401).json({
|
|
147
184
|
jsonrpc: "2.0",
|
|
148
|
-
error: { code:
|
|
185
|
+
error: { code: MCP_ERROR_CODES.UNAUTHORIZED, message: err.message },
|
|
149
186
|
id: null,
|
|
150
187
|
});
|
|
151
188
|
return;
|
|
@@ -171,7 +208,10 @@ export function mountMcpEndpoints(opts) {
|
|
|
171
208
|
reqLogger.warn("federated_identity_rejected", { route: req.path });
|
|
172
209
|
res.status(403).json({
|
|
173
210
|
jsonrpc: "2.0",
|
|
174
|
-
error: {
|
|
211
|
+
error: {
|
|
212
|
+
code: MCP_ERROR_CODES.UNAUTHORIZED,
|
|
213
|
+
message: "federated identities are not permitted on this route",
|
|
214
|
+
},
|
|
175
215
|
id: null,
|
|
176
216
|
});
|
|
177
217
|
return;
|
|
@@ -196,7 +236,7 @@ export function mountMcpEndpoints(opts) {
|
|
|
196
236
|
res.status(401).json({
|
|
197
237
|
jsonrpc: "2.0",
|
|
198
238
|
error: {
|
|
199
|
-
code:
|
|
239
|
+
code: MCP_ERROR_CODES.UNAUTHORIZED,
|
|
200
240
|
message: `${opsToolName} is an operator tool and needs an authenticated caller. ` +
|
|
201
241
|
`Present a bearer token this deployment accepts, or complete the OAuth flow ` +
|
|
202
242
|
`advertised in WWW-Authenticate (universal connector keys come from the ` +
|
|
@@ -207,10 +247,10 @@ export function mountMcpEndpoints(opts) {
|
|
|
207
247
|
return;
|
|
208
248
|
}
|
|
209
249
|
}
|
|
210
|
-
// Per-
|
|
250
|
+
// Per-app URL routing. When `perAppRouting`
|
|
211
251
|
// is configured AND the request matched the per-app path
|
|
212
252
|
// `/:${paramName}/mcp`, Express populates `req.params[paramName]`
|
|
213
|
-
// with the validated
|
|
253
|
+
// with the validated app id. Use it as `ctx.appId` for this
|
|
214
254
|
// request, overriding `appIdFromIdentity`. The universal `/mcp`
|
|
215
255
|
// route doesn't have the param so it falls through to the
|
|
216
256
|
// identity-based resolution.
|
|
@@ -218,11 +258,14 @@ export function mountMcpEndpoints(opts) {
|
|
|
218
258
|
const hasUrlAppId = typeof urlAppId === "string" && urlAppId.length > 0;
|
|
219
259
|
// Per-app authorize hook — when the deployment configured
|
|
220
260
|
// `perAppRouting.authorize` AND the request matched the per-app
|
|
221
|
-
// path, invoke the callback. Throwing
|
|
222
|
-
//
|
|
223
|
-
//
|
|
261
|
+
// path, invoke the callback. Throwing refuses before the MCP
|
|
262
|
+
// handler ever sees the request, which is the boundary that
|
|
263
|
+
// prevents cross-user blueprint reads when a deployment's own tools bypass
|
|
224
264
|
// AppSync owner-auth via raw DDB. Universal-endpoint requests
|
|
225
|
-
// skip this entirely (no urlAppId).
|
|
265
|
+
// skip this entirely (no urlAppId). The deployment's `errorMapper`
|
|
266
|
+
// may give the refusal a structured JSON-RPC `data` (bounded to
|
|
267
|
+
// 401 / 403, see `mapAuthorizationRefusal`); otherwise — and for
|
|
268
|
+
// every mapping outside those bounds — the default-deny 403 stands.
|
|
226
269
|
if (hasUrlAppId && perAppRouting?.authorize) {
|
|
227
270
|
try {
|
|
228
271
|
await perAppRouting.authorize(urlAppId, identity);
|
|
@@ -232,9 +275,17 @@ export function mountMcpEndpoints(opts) {
|
|
|
232
275
|
urlAppId,
|
|
233
276
|
reason: err instanceof Error ? err.message : String(err),
|
|
234
277
|
});
|
|
235
|
-
|
|
278
|
+
const mapped = mapAuthorizationRefusal(err, errorMapper, reqLogger);
|
|
279
|
+
if (mapped?.headers)
|
|
280
|
+
res.set(mapped.headers);
|
|
281
|
+
res.status(mapped?.status ?? 403).json({
|
|
236
282
|
jsonrpc: "2.0",
|
|
237
|
-
|
|
283
|
+
// #836: a first-party server never answers -32000 — that is the SDK
|
|
284
|
+
// client's own ConnectionClosed number, so a refusal and a dropped
|
|
285
|
+
// socket would read the same. An authorization refusal is UNAUTHORIZED.
|
|
286
|
+
error: mapped
|
|
287
|
+
? jsonRpcError(mapped)
|
|
288
|
+
: { code: MCP_ERROR_CODES.UNAUTHORIZED, message: "Unauthorized" },
|
|
238
289
|
id: null,
|
|
239
290
|
});
|
|
240
291
|
return;
|
|
@@ -248,11 +299,11 @@ export function mountMcpEndpoints(opts) {
|
|
|
248
299
|
// handler should branch on the specific mechanism.
|
|
249
300
|
authSource: identity.source,
|
|
250
301
|
// Identity is the canonical source of two mutually-exclusive
|
|
251
|
-
//
|
|
252
|
-
// Threading them onto HandlerContext here means
|
|
253
|
-
// (
|
|
254
|
-
//
|
|
255
|
-
//
|
|
302
|
+
// fields: `apiKeyHash` for kind=app, `userId` for kind=user.
|
|
303
|
+
// Threading them onto HandlerContext here means a deployment's own
|
|
304
|
+
// handlers (per-key metering, per-user scoping) can read identity
|
|
305
|
+
// directly without a parallel context shape; the handlers this
|
|
306
|
+
// package ships ignore both fields.
|
|
256
307
|
...(identity.identity.kind === "app" ? { apiKeyHash: identity.identity.apiKeyHash } : {}),
|
|
257
308
|
...(identity.identity.kind === "user" ? { userId: identity.identity.userId } : {}),
|
|
258
309
|
// What the credential itself may act on, when the adapter
|
|
@@ -301,7 +352,7 @@ export function mountMcpEndpoints(opts) {
|
|
|
301
352
|
res.set(mapped.headers);
|
|
302
353
|
res.status(mapped.status).json({
|
|
303
354
|
jsonrpc: "2.0",
|
|
304
|
-
error:
|
|
355
|
+
error: jsonRpcError(mapped),
|
|
305
356
|
id: null,
|
|
306
357
|
});
|
|
307
358
|
}
|
|
@@ -316,7 +367,9 @@ export function mountMcpEndpoints(opts) {
|
|
|
316
367
|
}
|
|
317
368
|
};
|
|
318
369
|
const methodNotAllowed = (_req, res) => {
|
|
319
|
-
|
|
370
|
+
// A 405 names what IS allowed (RFC 9110 §15.5.6); every mount here is
|
|
371
|
+
// POST-only.
|
|
372
|
+
res.set("Allow", "POST").status(405).json({
|
|
320
373
|
jsonrpc: "2.0",
|
|
321
374
|
error: {
|
|
322
375
|
code: -32000,
|
|
@@ -341,24 +394,24 @@ export function mountMcpEndpoints(opts) {
|
|
|
341
394
|
anonymousOpsChallenge: controlOpsToolNames,
|
|
342
395
|
});
|
|
343
396
|
// Universal endpoint — `appId` resolved from the auth identity via
|
|
344
|
-
// `appIdFromIdentity`.
|
|
345
|
-
//
|
|
346
|
-
//
|
|
397
|
+
// `appIdFromIdentity`. A deployment's auth adapter may resolve it
|
|
398
|
+
// (e.g. to the user's default app); without one it falls through to
|
|
399
|
+
// userId / DEFAULT_BUILDER_APP_ID.
|
|
347
400
|
//
|
|
348
|
-
// Path defaults to `/mcp` (Streamable HTTP convention).
|
|
349
|
-
//
|
|
350
|
-
//
|
|
401
|
+
// Path defaults to `/mcp` (Streamable HTTP convention). A deployment
|
|
402
|
+
// whose hostname already says "mcp" may serve it at `/` so the
|
|
403
|
+
// bare-root URL is the universal endpoint — no path repeat.
|
|
351
404
|
// Exposes audience tags ['agent', 'runtime'] — runtime tools stay
|
|
352
405
|
// routable on the same endpoint but invisible to the agent's
|
|
353
406
|
// `tools/list` via the `_meta.ui.visibility: ['app']` filter.
|
|
354
407
|
app.post(universalMcpPath, agentMcpHandler);
|
|
355
|
-
// Per-
|
|
408
|
+
// Per-app endpoint — only mounted when the deployment opts in
|
|
356
409
|
// via `perAppRouting`. The same handler reads `req.params[paramName]`
|
|
357
410
|
// and uses it as `ctx.appId` for the request.
|
|
358
411
|
//
|
|
359
412
|
// When `pathPrefix` is set, the route mounts at
|
|
360
|
-
// `${pathPrefix}/:${paramName}` —
|
|
361
|
-
//
|
|
413
|
+
// `${pathPrefix}/:${paramName}` — e.g. `/apps`, so URLs read
|
|
414
|
+
// `<host>/apps/<appId>`. The prefix segments per-app traffic
|
|
362
415
|
// from system routes (`/health`, `/oauth/*`, `/.well-known/*`,
|
|
363
416
|
// `/r/*`) so an opaque appId can never shadow a future static path.
|
|
364
417
|
//
|
|
@@ -386,6 +439,12 @@ export function mountMcpEndpoints(opts) {
|
|
|
386
439
|
});
|
|
387
440
|
const route = pathPrefix !== undefined ? `${pathPrefix}/:${paramName}` : `/:${paramName}`;
|
|
388
441
|
app.post(route, agentMcpHandler);
|
|
442
|
+
// The per-app endpoint is POST-only like every other mount: GET (a
|
|
443
|
+
// client's optional SSE listener) and DELETE (session terminate) read
|
|
444
|
+
// the transport's 405, never Express's text/html 404 — a 404 says "no
|
|
445
|
+
// such resource" and a client retries it every turn (ggui#850).
|
|
446
|
+
app.get(route, methodNotAllowed);
|
|
447
|
+
app.delete(route, methodNotAllowed);
|
|
389
448
|
}
|
|
390
449
|
// /control — the control plane. Hosts every `audience: ['protocol']`
|
|
391
450
|
// tool (design-time spec/discovery, anonymous) and every
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Self-hosted MCP server for the ggui protocol. An HTTP/MCP binding layer over @ggui-ai/mcp-server-handlers and the reference adapters from @ggui-ai/mcp-server-core — run it via `ggui serve`.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ggui",
|
|
@@ -32,17 +32,17 @@
|
|
|
32
32
|
"resend": "^6.12.2",
|
|
33
33
|
"ws": "^8.20.1",
|
|
34
34
|
"zod": "^4.3.6",
|
|
35
|
-
"@ggui-ai/console": "0.
|
|
36
|
-
"@ggui-ai/design": "0.
|
|
37
|
-
"@ggui-ai/
|
|
38
|
-
"@ggui-ai/
|
|
39
|
-
"@ggui-ai/mcp-server-core": "0.
|
|
40
|
-
"@ggui-ai/negotiator": "0.
|
|
41
|
-
"@ggui-ai/preview-a2ui": "0.
|
|
42
|
-
"@ggui-ai/project-config": "0.
|
|
43
|
-
"@ggui-ai/protocol": "0.
|
|
44
|
-
"@ggui-ai/ui-registry": "0.
|
|
45
|
-
"@ggui-ai/ui-gen": "0.
|
|
35
|
+
"@ggui-ai/console": "0.15.0",
|
|
36
|
+
"@ggui-ai/design": "0.15.0",
|
|
37
|
+
"@ggui-ai/mcp-server-handlers": "0.15.0",
|
|
38
|
+
"@ggui-ai/iframe-runtime": "0.15.0",
|
|
39
|
+
"@ggui-ai/mcp-server-core": "0.15.0",
|
|
40
|
+
"@ggui-ai/negotiator": "0.15.0",
|
|
41
|
+
"@ggui-ai/preview-a2ui": "0.15.0",
|
|
42
|
+
"@ggui-ai/project-config": "0.15.0",
|
|
43
|
+
"@ggui-ai/protocol": "0.15.0",
|
|
44
|
+
"@ggui-ai/ui-registry": "0.15.0",
|
|
45
|
+
"@ggui-ai/ui-gen": "0.15.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/express": "^5.0.0",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@types/ws": "^8.5.10",
|
|
52
52
|
"typescript": "^5.0.0",
|
|
53
53
|
"vitest": "^3.2.6",
|
|
54
|
-
"@ggui-ai/protocol-conformance": "0.
|
|
54
|
+
"@ggui-ai/protocol-conformance": "0.15.0"
|
|
55
55
|
},
|
|
56
56
|
"repository": {
|
|
57
57
|
"type": "git",
|