@lantos1618/better-ui 0.5.0 → 0.6.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/README.md +25 -2
- package/dist/agui/index.d.mts +117 -0
- package/dist/agui/index.d.ts +117 -0
- package/dist/agui/index.js +307 -0
- package/dist/agui/index.mjs +144 -0
- package/dist/chunk-OH73K7I5.mjs +143 -0
- package/dist/mcp/index.d.mts +13 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/index.js +78 -0
- package/dist/mcp/index.mjs +81 -140
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -52,6 +52,7 @@ npm install @lantos1618/better-ui zod
|
|
|
52
52
|
|---------|------|
|
|
53
53
|
| **View integration** | Tools render their own results — no other framework does this |
|
|
54
54
|
| **MCP server** | Expose any tool registry to Claude Desktop, Cursor, VS Code |
|
|
55
|
+
| **AG-UI protocol** | Compatible with CopilotKit, LangChain, Google ADK frontends |
|
|
55
56
|
| **Multi-provider** | OpenAI, Anthropic, Google Gemini, OpenRouter |
|
|
56
57
|
| **Streaming views** | Progressive partial data rendering |
|
|
57
58
|
| **Drop-in chat** | `<Chat />` component with automatic tool view rendering |
|
|
@@ -404,6 +405,26 @@ zodToJsonSchema(z.object({
|
|
|
404
405
|
|
|
405
406
|
---
|
|
406
407
|
|
|
408
|
+
## AG-UI Protocol
|
|
409
|
+
|
|
410
|
+
Expose your tools via the [AG-UI (Agent-User Interaction Protocol)](https://docs.ag-ui.com) — compatible with CopilotKit, LangChain, Google ADK, and any AG-UI frontend.
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
import { createAGUIServer } from '@lantos1618/better-ui/agui';
|
|
414
|
+
|
|
415
|
+
const server = createAGUIServer({
|
|
416
|
+
name: 'my-tools',
|
|
417
|
+
tools: { weather, search },
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// Next.js route handler — returns SSE event stream
|
|
421
|
+
export const POST = server.handler();
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
The handler emits standard AG-UI events (`RUN_STARTED`, `TOOL_CALL_START`, `TOOL_CALL_ARGS`, `TOOL_CALL_RESULT`, `TOOL_CALL_END`, `RUN_FINISHED`) over Server-Sent Events.
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
407
428
|
## Providers
|
|
408
429
|
|
|
409
430
|
```typescript
|
|
@@ -532,8 +553,10 @@ src/
|
|
|
532
553
|
types.ts PersistenceAdapter interface
|
|
533
554
|
memory.ts In-memory adapter
|
|
534
555
|
mcp/
|
|
535
|
-
server.ts MCP server (stdio + HTTP)
|
|
556
|
+
server.ts MCP server (stdio + HTTP + SSE)
|
|
536
557
|
schema.ts Zod → JSON Schema converter
|
|
558
|
+
agui/
|
|
559
|
+
server.ts AG-UI protocol server (SSE)
|
|
537
560
|
examples/
|
|
538
561
|
nextjs-demo/ Full Next.js demo app
|
|
539
562
|
vite-demo/ Vite + Express demo app
|
|
@@ -545,7 +568,7 @@ examples/
|
|
|
545
568
|
```bash
|
|
546
569
|
npm install
|
|
547
570
|
npm run build # Build library
|
|
548
|
-
npm test # Run tests (
|
|
571
|
+
npm test # Run tests (226 tests)
|
|
549
572
|
npm run type-check # TypeScript check
|
|
550
573
|
```
|
|
551
574
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { T as Tool, c as ToolContext } from '../tool-Ca2x-VNK.mjs';
|
|
2
|
+
import 'zod';
|
|
3
|
+
import 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* AG-UI (Agent-User Interaction Protocol) Server for Better UI
|
|
7
|
+
*
|
|
8
|
+
* Implements the AG-UI protocol, allowing Better UI tools to be used with
|
|
9
|
+
* CopilotKit, LangChain, and any AG-UI compatible frontend.
|
|
10
|
+
*
|
|
11
|
+
* Protocol: Server-Sent Events (SSE) over HTTP
|
|
12
|
+
*
|
|
13
|
+
* @see https://docs.ag-ui.com
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { createAGUIServer } from '@lantos1618/better-ui/agui';
|
|
18
|
+
*
|
|
19
|
+
* const server = createAGUIServer({
|
|
20
|
+
* name: 'my-tools',
|
|
21
|
+
* tools: { weather: weatherTool, search: searchTool },
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // Next.js route handler
|
|
25
|
+
* export const POST = server.handler();
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
type AGUIEventType = 'RUN_STARTED' | 'RUN_FINISHED' | 'RUN_ERROR' | 'STEP_STARTED' | 'STEP_FINISHED' | 'TEXT_MESSAGE_START' | 'TEXT_MESSAGE_CONTENT' | 'TEXT_MESSAGE_END' | 'TOOL_CALL_START' | 'TOOL_CALL_ARGS' | 'TOOL_CALL_END' | 'TOOL_CALL_RESULT' | 'STATE_SNAPSHOT' | 'STATE_DELTA' | 'CUSTOM' | 'RAW';
|
|
30
|
+
interface AGUIEvent {
|
|
31
|
+
type: AGUIEventType;
|
|
32
|
+
timestamp?: number;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
}
|
|
35
|
+
interface RunAgentInput {
|
|
36
|
+
threadId: string;
|
|
37
|
+
runId: string;
|
|
38
|
+
/** Tool definitions from the client */
|
|
39
|
+
tools?: Array<{
|
|
40
|
+
name: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
}>;
|
|
43
|
+
/** Messages context */
|
|
44
|
+
messages?: Array<{
|
|
45
|
+
role: string;
|
|
46
|
+
content: string;
|
|
47
|
+
}>;
|
|
48
|
+
/** Single tool call to execute */
|
|
49
|
+
toolCall?: {
|
|
50
|
+
id: string;
|
|
51
|
+
name: string;
|
|
52
|
+
args: Record<string, unknown>;
|
|
53
|
+
};
|
|
54
|
+
/** Multiple tool calls to execute in sequence */
|
|
55
|
+
toolCalls?: Array<{
|
|
56
|
+
id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
args: Record<string, unknown>;
|
|
59
|
+
}>;
|
|
60
|
+
/** State context from the frontend */
|
|
61
|
+
state?: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
interface AGUIServerConfig {
|
|
64
|
+
/** Server name */
|
|
65
|
+
name: string;
|
|
66
|
+
/** Tool registry — keys are tool names */
|
|
67
|
+
tools: Record<string, Tool>;
|
|
68
|
+
/** Optional context passed to every tool execution */
|
|
69
|
+
context?: Partial<ToolContext>;
|
|
70
|
+
/** Called on errors */
|
|
71
|
+
onError?: (error: Error) => void;
|
|
72
|
+
}
|
|
73
|
+
declare class AGUIServer {
|
|
74
|
+
private config;
|
|
75
|
+
constructor(config: AGUIServerConfig);
|
|
76
|
+
/** Get available tools in AG-UI format */
|
|
77
|
+
listTools(): Array<{
|
|
78
|
+
name: string;
|
|
79
|
+
description: string;
|
|
80
|
+
parameters: Record<string, unknown>;
|
|
81
|
+
}>;
|
|
82
|
+
/**
|
|
83
|
+
* Create an HTTP handler that implements the AG-UI protocol.
|
|
84
|
+
* Returns an SSE stream of AG-UI events.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* // Next.js: app/api/agui/route.ts
|
|
89
|
+
* export const POST = server.handler();
|
|
90
|
+
*
|
|
91
|
+
* // Express:
|
|
92
|
+
* app.post('/api/agui', (req, res) => server.handler()(req));
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
handler(): (req: Request) => Promise<Response>;
|
|
96
|
+
/**
|
|
97
|
+
* Execute a tool call and emit AG-UI events.
|
|
98
|
+
*/
|
|
99
|
+
private executeToolCall;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Create an AG-UI server from a Better UI tool registry.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const server = createAGUIServer({
|
|
107
|
+
* name: 'my-app',
|
|
108
|
+
* tools: { weather: weatherTool, search: searchTool },
|
|
109
|
+
* });
|
|
110
|
+
*
|
|
111
|
+
* // Use as Next.js route handler
|
|
112
|
+
* export const POST = server.handler();
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
declare function createAGUIServer(config: AGUIServerConfig): AGUIServer;
|
|
116
|
+
|
|
117
|
+
export { type AGUIEvent, type AGUIEventType, AGUIServer, type AGUIServerConfig, type RunAgentInput, createAGUIServer };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { T as Tool, c as ToolContext } from '../tool-Ca2x-VNK.js';
|
|
2
|
+
import 'zod';
|
|
3
|
+
import 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* AG-UI (Agent-User Interaction Protocol) Server for Better UI
|
|
7
|
+
*
|
|
8
|
+
* Implements the AG-UI protocol, allowing Better UI tools to be used with
|
|
9
|
+
* CopilotKit, LangChain, and any AG-UI compatible frontend.
|
|
10
|
+
*
|
|
11
|
+
* Protocol: Server-Sent Events (SSE) over HTTP
|
|
12
|
+
*
|
|
13
|
+
* @see https://docs.ag-ui.com
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { createAGUIServer } from '@lantos1618/better-ui/agui';
|
|
18
|
+
*
|
|
19
|
+
* const server = createAGUIServer({
|
|
20
|
+
* name: 'my-tools',
|
|
21
|
+
* tools: { weather: weatherTool, search: searchTool },
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // Next.js route handler
|
|
25
|
+
* export const POST = server.handler();
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
type AGUIEventType = 'RUN_STARTED' | 'RUN_FINISHED' | 'RUN_ERROR' | 'STEP_STARTED' | 'STEP_FINISHED' | 'TEXT_MESSAGE_START' | 'TEXT_MESSAGE_CONTENT' | 'TEXT_MESSAGE_END' | 'TOOL_CALL_START' | 'TOOL_CALL_ARGS' | 'TOOL_CALL_END' | 'TOOL_CALL_RESULT' | 'STATE_SNAPSHOT' | 'STATE_DELTA' | 'CUSTOM' | 'RAW';
|
|
30
|
+
interface AGUIEvent {
|
|
31
|
+
type: AGUIEventType;
|
|
32
|
+
timestamp?: number;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
}
|
|
35
|
+
interface RunAgentInput {
|
|
36
|
+
threadId: string;
|
|
37
|
+
runId: string;
|
|
38
|
+
/** Tool definitions from the client */
|
|
39
|
+
tools?: Array<{
|
|
40
|
+
name: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
}>;
|
|
43
|
+
/** Messages context */
|
|
44
|
+
messages?: Array<{
|
|
45
|
+
role: string;
|
|
46
|
+
content: string;
|
|
47
|
+
}>;
|
|
48
|
+
/** Single tool call to execute */
|
|
49
|
+
toolCall?: {
|
|
50
|
+
id: string;
|
|
51
|
+
name: string;
|
|
52
|
+
args: Record<string, unknown>;
|
|
53
|
+
};
|
|
54
|
+
/** Multiple tool calls to execute in sequence */
|
|
55
|
+
toolCalls?: Array<{
|
|
56
|
+
id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
args: Record<string, unknown>;
|
|
59
|
+
}>;
|
|
60
|
+
/** State context from the frontend */
|
|
61
|
+
state?: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
interface AGUIServerConfig {
|
|
64
|
+
/** Server name */
|
|
65
|
+
name: string;
|
|
66
|
+
/** Tool registry — keys are tool names */
|
|
67
|
+
tools: Record<string, Tool>;
|
|
68
|
+
/** Optional context passed to every tool execution */
|
|
69
|
+
context?: Partial<ToolContext>;
|
|
70
|
+
/** Called on errors */
|
|
71
|
+
onError?: (error: Error) => void;
|
|
72
|
+
}
|
|
73
|
+
declare class AGUIServer {
|
|
74
|
+
private config;
|
|
75
|
+
constructor(config: AGUIServerConfig);
|
|
76
|
+
/** Get available tools in AG-UI format */
|
|
77
|
+
listTools(): Array<{
|
|
78
|
+
name: string;
|
|
79
|
+
description: string;
|
|
80
|
+
parameters: Record<string, unknown>;
|
|
81
|
+
}>;
|
|
82
|
+
/**
|
|
83
|
+
* Create an HTTP handler that implements the AG-UI protocol.
|
|
84
|
+
* Returns an SSE stream of AG-UI events.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* // Next.js: app/api/agui/route.ts
|
|
89
|
+
* export const POST = server.handler();
|
|
90
|
+
*
|
|
91
|
+
* // Express:
|
|
92
|
+
* app.post('/api/agui', (req, res) => server.handler()(req));
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
handler(): (req: Request) => Promise<Response>;
|
|
96
|
+
/**
|
|
97
|
+
* Execute a tool call and emit AG-UI events.
|
|
98
|
+
*/
|
|
99
|
+
private executeToolCall;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Create an AG-UI server from a Better UI tool registry.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const server = createAGUIServer({
|
|
107
|
+
* name: 'my-app',
|
|
108
|
+
* tools: { weather: weatherTool, search: searchTool },
|
|
109
|
+
* });
|
|
110
|
+
*
|
|
111
|
+
* // Use as Next.js route handler
|
|
112
|
+
* export const POST = server.handler();
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
declare function createAGUIServer(config: AGUIServerConfig): AGUIServer;
|
|
116
|
+
|
|
117
|
+
export { type AGUIEvent, type AGUIEventType, AGUIServer, type AGUIServerConfig, type RunAgentInput, createAGUIServer };
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/agui/index.ts
|
|
21
|
+
var agui_exports = {};
|
|
22
|
+
__export(agui_exports, {
|
|
23
|
+
AGUIServer: () => AGUIServer,
|
|
24
|
+
createAGUIServer: () => createAGUIServer
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(agui_exports);
|
|
27
|
+
|
|
28
|
+
// src/mcp/schema.ts
|
|
29
|
+
function zodToJsonSchema(schema) {
|
|
30
|
+
return convert(schema);
|
|
31
|
+
}
|
|
32
|
+
function convert(schema) {
|
|
33
|
+
const def = schema._def;
|
|
34
|
+
const typeName = def?.typeName;
|
|
35
|
+
switch (typeName) {
|
|
36
|
+
case "ZodString":
|
|
37
|
+
return convertString(def);
|
|
38
|
+
case "ZodNumber":
|
|
39
|
+
return convertNumber(def);
|
|
40
|
+
case "ZodBoolean":
|
|
41
|
+
return { type: "boolean" };
|
|
42
|
+
case "ZodNull":
|
|
43
|
+
return { type: "null" };
|
|
44
|
+
case "ZodLiteral":
|
|
45
|
+
return { enum: [def.value] };
|
|
46
|
+
case "ZodEnum":
|
|
47
|
+
return { type: "string", enum: def.values };
|
|
48
|
+
case "ZodNativeEnum":
|
|
49
|
+
return { enum: Object.values(def.values) };
|
|
50
|
+
case "ZodObject":
|
|
51
|
+
return convertObject(def);
|
|
52
|
+
case "ZodArray":
|
|
53
|
+
return convertArray(def);
|
|
54
|
+
case "ZodOptional":
|
|
55
|
+
return convert(def.innerType);
|
|
56
|
+
case "ZodNullable": {
|
|
57
|
+
const inner = convert(def.innerType);
|
|
58
|
+
return { anyOf: [inner, { type: "null" }] };
|
|
59
|
+
}
|
|
60
|
+
case "ZodDefault":
|
|
61
|
+
return { ...convert(def.innerType), default: def.defaultValue() };
|
|
62
|
+
case "ZodUnion":
|
|
63
|
+
return { anyOf: def.options.map((o) => convert(o)) };
|
|
64
|
+
case "ZodDiscriminatedUnion":
|
|
65
|
+
return { oneOf: [...def.options.values()].map((o) => convert(o)) };
|
|
66
|
+
case "ZodRecord":
|
|
67
|
+
return {
|
|
68
|
+
type: "object",
|
|
69
|
+
additionalProperties: convert(def.valueType)
|
|
70
|
+
};
|
|
71
|
+
case "ZodTuple": {
|
|
72
|
+
const items = def.items.map((item) => convert(item));
|
|
73
|
+
return { type: "array", items: items.length === 1 ? items[0] : void 0, prefixItems: items };
|
|
74
|
+
}
|
|
75
|
+
case "ZodEffects":
|
|
76
|
+
return convert(def.schema);
|
|
77
|
+
case "ZodPipeline":
|
|
78
|
+
return convert(def.in);
|
|
79
|
+
case "ZodLazy":
|
|
80
|
+
return convert(def.getter());
|
|
81
|
+
case "ZodAny":
|
|
82
|
+
return {};
|
|
83
|
+
case "ZodUnknown":
|
|
84
|
+
return {};
|
|
85
|
+
default:
|
|
86
|
+
return {};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function convertString(def) {
|
|
90
|
+
const schema = { type: "string" };
|
|
91
|
+
if (def.checks) {
|
|
92
|
+
for (const check of def.checks) {
|
|
93
|
+
switch (check.kind) {
|
|
94
|
+
case "min":
|
|
95
|
+
schema.minLength = check.value;
|
|
96
|
+
break;
|
|
97
|
+
case "max":
|
|
98
|
+
schema.maxLength = check.value;
|
|
99
|
+
break;
|
|
100
|
+
case "email":
|
|
101
|
+
schema.format = "email";
|
|
102
|
+
break;
|
|
103
|
+
case "url":
|
|
104
|
+
schema.format = "uri";
|
|
105
|
+
break;
|
|
106
|
+
case "uuid":
|
|
107
|
+
schema.format = "uuid";
|
|
108
|
+
break;
|
|
109
|
+
case "regex":
|
|
110
|
+
schema.pattern = check.regex.source;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (def.description) schema.description = def.description;
|
|
116
|
+
return schema;
|
|
117
|
+
}
|
|
118
|
+
function convertNumber(def) {
|
|
119
|
+
const schema = { type: "number" };
|
|
120
|
+
if (def.checks) {
|
|
121
|
+
for (const check of def.checks) {
|
|
122
|
+
switch (check.kind) {
|
|
123
|
+
case "min":
|
|
124
|
+
schema.minimum = check.value;
|
|
125
|
+
if (check.inclusive === false) schema.exclusiveMinimum = check.value;
|
|
126
|
+
break;
|
|
127
|
+
case "max":
|
|
128
|
+
schema.maximum = check.value;
|
|
129
|
+
if (check.inclusive === false) schema.exclusiveMaximum = check.value;
|
|
130
|
+
break;
|
|
131
|
+
case "int":
|
|
132
|
+
schema.type = "integer";
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (def.description) schema.description = def.description;
|
|
138
|
+
return schema;
|
|
139
|
+
}
|
|
140
|
+
function convertObject(def) {
|
|
141
|
+
const shape = def.shape();
|
|
142
|
+
const properties = {};
|
|
143
|
+
const required = [];
|
|
144
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
145
|
+
properties[key] = convert(value);
|
|
146
|
+
const fieldDef = value._def;
|
|
147
|
+
const isOptional = fieldDef?.typeName === "ZodOptional" || fieldDef?.typeName === "ZodDefault";
|
|
148
|
+
if (!isOptional) {
|
|
149
|
+
required.push(key);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const schema = { type: "object", properties };
|
|
153
|
+
if (required.length > 0) schema.required = required;
|
|
154
|
+
if (def.description) schema.description = def.description;
|
|
155
|
+
return schema;
|
|
156
|
+
}
|
|
157
|
+
function convertArray(def) {
|
|
158
|
+
const schema = {
|
|
159
|
+
type: "array",
|
|
160
|
+
items: convert(def.type)
|
|
161
|
+
};
|
|
162
|
+
if (def.minLength) schema.minItems = def.minLength.value;
|
|
163
|
+
if (def.maxLength) schema.maxItems = def.maxLength.value;
|
|
164
|
+
if (def.description) schema.description = def.description;
|
|
165
|
+
return schema;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/agui/server.ts
|
|
169
|
+
var AGUIServer = class {
|
|
170
|
+
constructor(config) {
|
|
171
|
+
this.config = config;
|
|
172
|
+
}
|
|
173
|
+
/** Get available tools in AG-UI format */
|
|
174
|
+
listTools() {
|
|
175
|
+
return Object.values(this.config.tools).map((tool) => ({
|
|
176
|
+
name: tool.name,
|
|
177
|
+
description: tool.description || tool.name,
|
|
178
|
+
parameters: zodToJsonSchema(tool.inputSchema)
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Create an HTTP handler that implements the AG-UI protocol.
|
|
183
|
+
* Returns an SSE stream of AG-UI events.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```typescript
|
|
187
|
+
* // Next.js: app/api/agui/route.ts
|
|
188
|
+
* export const POST = server.handler();
|
|
189
|
+
*
|
|
190
|
+
* // Express:
|
|
191
|
+
* app.post('/api/agui', (req, res) => server.handler()(req));
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
handler() {
|
|
195
|
+
return async (req) => {
|
|
196
|
+
let input;
|
|
197
|
+
try {
|
|
198
|
+
input = await req.json();
|
|
199
|
+
} catch {
|
|
200
|
+
return new Response("Invalid JSON", { status: 400 });
|
|
201
|
+
}
|
|
202
|
+
const { threadId, runId, toolCall } = input;
|
|
203
|
+
if (!threadId || !runId) {
|
|
204
|
+
return new Response("Missing threadId or runId", { status: 400 });
|
|
205
|
+
}
|
|
206
|
+
const encoder = new TextEncoder();
|
|
207
|
+
const self = this;
|
|
208
|
+
const stream = new ReadableStream({
|
|
209
|
+
async start(controller) {
|
|
210
|
+
const emit = (event) => {
|
|
211
|
+
const data = JSON.stringify({ ...event, timestamp: event.timestamp ?? Date.now() });
|
|
212
|
+
controller.enqueue(encoder.encode(`data: ${data}
|
|
213
|
+
|
|
214
|
+
`));
|
|
215
|
+
};
|
|
216
|
+
try {
|
|
217
|
+
emit({ type: "RUN_STARTED", threadId, runId });
|
|
218
|
+
const calls = toolCall ? [toolCall] : input.toolCalls ?? [];
|
|
219
|
+
if (calls.length > 0) {
|
|
220
|
+
for (const call of calls) {
|
|
221
|
+
await self.executeToolCall(call, emit);
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
const tools = self.listTools();
|
|
225
|
+
const messageId = `msg_${runId}`;
|
|
226
|
+
emit({ type: "TEXT_MESSAGE_START", messageId, role: "assistant" });
|
|
227
|
+
emit({
|
|
228
|
+
type: "TEXT_MESSAGE_CONTENT",
|
|
229
|
+
messageId,
|
|
230
|
+
delta: `Available tools: ${tools.map((t) => t.name).join(", ")}`
|
|
231
|
+
});
|
|
232
|
+
emit({ type: "TEXT_MESSAGE_END", messageId });
|
|
233
|
+
}
|
|
234
|
+
emit({ type: "RUN_FINISHED", threadId, runId });
|
|
235
|
+
} catch (err) {
|
|
236
|
+
emit({
|
|
237
|
+
type: "RUN_ERROR",
|
|
238
|
+
threadId,
|
|
239
|
+
runId,
|
|
240
|
+
message: err instanceof Error ? err.message : "Unknown error"
|
|
241
|
+
});
|
|
242
|
+
self.config.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
243
|
+
}
|
|
244
|
+
controller.close();
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
return new Response(stream, {
|
|
248
|
+
headers: {
|
|
249
|
+
"Content-Type": "text/event-stream",
|
|
250
|
+
"Cache-Control": "no-cache",
|
|
251
|
+
"Connection": "keep-alive"
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Execute a tool call and emit AG-UI events.
|
|
258
|
+
*/
|
|
259
|
+
async executeToolCall(toolCall, emit) {
|
|
260
|
+
const { id, name, args } = toolCall;
|
|
261
|
+
if (!Object.prototype.hasOwnProperty.call(this.config.tools, name)) {
|
|
262
|
+
emit({
|
|
263
|
+
type: "TOOL_CALL_START",
|
|
264
|
+
toolCallId: id,
|
|
265
|
+
toolCallName: name
|
|
266
|
+
});
|
|
267
|
+
emit({
|
|
268
|
+
type: "TOOL_CALL_END",
|
|
269
|
+
toolCallId: id
|
|
270
|
+
});
|
|
271
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
272
|
+
}
|
|
273
|
+
const tool = this.config.tools[name];
|
|
274
|
+
emit({
|
|
275
|
+
type: "TOOL_CALL_START",
|
|
276
|
+
toolCallId: id,
|
|
277
|
+
toolCallName: name
|
|
278
|
+
});
|
|
279
|
+
emit({
|
|
280
|
+
type: "TOOL_CALL_ARGS",
|
|
281
|
+
toolCallId: id,
|
|
282
|
+
delta: JSON.stringify(args)
|
|
283
|
+
});
|
|
284
|
+
const result = await tool.run(args, {
|
|
285
|
+
isServer: true,
|
|
286
|
+
...this.config.context
|
|
287
|
+
});
|
|
288
|
+
const resultText = typeof result === "string" ? result : JSON.stringify(result);
|
|
289
|
+
emit({
|
|
290
|
+
type: "TOOL_CALL_RESULT",
|
|
291
|
+
toolCallId: id,
|
|
292
|
+
result: resultText
|
|
293
|
+
});
|
|
294
|
+
emit({
|
|
295
|
+
type: "TOOL_CALL_END",
|
|
296
|
+
toolCallId: id
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
function createAGUIServer(config) {
|
|
301
|
+
return new AGUIServer(config);
|
|
302
|
+
}
|
|
303
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
304
|
+
0 && (module.exports = {
|
|
305
|
+
AGUIServer,
|
|
306
|
+
createAGUIServer
|
|
307
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import {
|
|
2
|
+
zodToJsonSchema
|
|
3
|
+
} from "../chunk-OH73K7I5.mjs";
|
|
4
|
+
import "../chunk-Y6FXYEAI.mjs";
|
|
5
|
+
|
|
6
|
+
// src/agui/server.ts
|
|
7
|
+
var AGUIServer = class {
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.config = config;
|
|
10
|
+
}
|
|
11
|
+
/** Get available tools in AG-UI format */
|
|
12
|
+
listTools() {
|
|
13
|
+
return Object.values(this.config.tools).map((tool) => ({
|
|
14
|
+
name: tool.name,
|
|
15
|
+
description: tool.description || tool.name,
|
|
16
|
+
parameters: zodToJsonSchema(tool.inputSchema)
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create an HTTP handler that implements the AG-UI protocol.
|
|
21
|
+
* Returns an SSE stream of AG-UI events.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* // Next.js: app/api/agui/route.ts
|
|
26
|
+
* export const POST = server.handler();
|
|
27
|
+
*
|
|
28
|
+
* // Express:
|
|
29
|
+
* app.post('/api/agui', (req, res) => server.handler()(req));
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
handler() {
|
|
33
|
+
return async (req) => {
|
|
34
|
+
let input;
|
|
35
|
+
try {
|
|
36
|
+
input = await req.json();
|
|
37
|
+
} catch {
|
|
38
|
+
return new Response("Invalid JSON", { status: 400 });
|
|
39
|
+
}
|
|
40
|
+
const { threadId, runId, toolCall } = input;
|
|
41
|
+
if (!threadId || !runId) {
|
|
42
|
+
return new Response("Missing threadId or runId", { status: 400 });
|
|
43
|
+
}
|
|
44
|
+
const encoder = new TextEncoder();
|
|
45
|
+
const self = this;
|
|
46
|
+
const stream = new ReadableStream({
|
|
47
|
+
async start(controller) {
|
|
48
|
+
const emit = (event) => {
|
|
49
|
+
const data = JSON.stringify({ ...event, timestamp: event.timestamp ?? Date.now() });
|
|
50
|
+
controller.enqueue(encoder.encode(`data: ${data}
|
|
51
|
+
|
|
52
|
+
`));
|
|
53
|
+
};
|
|
54
|
+
try {
|
|
55
|
+
emit({ type: "RUN_STARTED", threadId, runId });
|
|
56
|
+
const calls = toolCall ? [toolCall] : input.toolCalls ?? [];
|
|
57
|
+
if (calls.length > 0) {
|
|
58
|
+
for (const call of calls) {
|
|
59
|
+
await self.executeToolCall(call, emit);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
const tools = self.listTools();
|
|
63
|
+
const messageId = `msg_${runId}`;
|
|
64
|
+
emit({ type: "TEXT_MESSAGE_START", messageId, role: "assistant" });
|
|
65
|
+
emit({
|
|
66
|
+
type: "TEXT_MESSAGE_CONTENT",
|
|
67
|
+
messageId,
|
|
68
|
+
delta: `Available tools: ${tools.map((t) => t.name).join(", ")}`
|
|
69
|
+
});
|
|
70
|
+
emit({ type: "TEXT_MESSAGE_END", messageId });
|
|
71
|
+
}
|
|
72
|
+
emit({ type: "RUN_FINISHED", threadId, runId });
|
|
73
|
+
} catch (err) {
|
|
74
|
+
emit({
|
|
75
|
+
type: "RUN_ERROR",
|
|
76
|
+
threadId,
|
|
77
|
+
runId,
|
|
78
|
+
message: err instanceof Error ? err.message : "Unknown error"
|
|
79
|
+
});
|
|
80
|
+
self.config.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
81
|
+
}
|
|
82
|
+
controller.close();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
return new Response(stream, {
|
|
86
|
+
headers: {
|
|
87
|
+
"Content-Type": "text/event-stream",
|
|
88
|
+
"Cache-Control": "no-cache",
|
|
89
|
+
"Connection": "keep-alive"
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Execute a tool call and emit AG-UI events.
|
|
96
|
+
*/
|
|
97
|
+
async executeToolCall(toolCall, emit) {
|
|
98
|
+
const { id, name, args } = toolCall;
|
|
99
|
+
if (!Object.prototype.hasOwnProperty.call(this.config.tools, name)) {
|
|
100
|
+
emit({
|
|
101
|
+
type: "TOOL_CALL_START",
|
|
102
|
+
toolCallId: id,
|
|
103
|
+
toolCallName: name
|
|
104
|
+
});
|
|
105
|
+
emit({
|
|
106
|
+
type: "TOOL_CALL_END",
|
|
107
|
+
toolCallId: id
|
|
108
|
+
});
|
|
109
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
110
|
+
}
|
|
111
|
+
const tool = this.config.tools[name];
|
|
112
|
+
emit({
|
|
113
|
+
type: "TOOL_CALL_START",
|
|
114
|
+
toolCallId: id,
|
|
115
|
+
toolCallName: name
|
|
116
|
+
});
|
|
117
|
+
emit({
|
|
118
|
+
type: "TOOL_CALL_ARGS",
|
|
119
|
+
toolCallId: id,
|
|
120
|
+
delta: JSON.stringify(args)
|
|
121
|
+
});
|
|
122
|
+
const result = await tool.run(args, {
|
|
123
|
+
isServer: true,
|
|
124
|
+
...this.config.context
|
|
125
|
+
});
|
|
126
|
+
const resultText = typeof result === "string" ? result : JSON.stringify(result);
|
|
127
|
+
emit({
|
|
128
|
+
type: "TOOL_CALL_RESULT",
|
|
129
|
+
toolCallId: id,
|
|
130
|
+
result: resultText
|
|
131
|
+
});
|
|
132
|
+
emit({
|
|
133
|
+
type: "TOOL_CALL_END",
|
|
134
|
+
toolCallId: id
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
function createAGUIServer(config) {
|
|
139
|
+
return new AGUIServer(config);
|
|
140
|
+
}
|
|
141
|
+
export {
|
|
142
|
+
AGUIServer,
|
|
143
|
+
createAGUIServer
|
|
144
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// src/mcp/schema.ts
|
|
2
|
+
function zodToJsonSchema(schema) {
|
|
3
|
+
return convert(schema);
|
|
4
|
+
}
|
|
5
|
+
function convert(schema) {
|
|
6
|
+
const def = schema._def;
|
|
7
|
+
const typeName = def?.typeName;
|
|
8
|
+
switch (typeName) {
|
|
9
|
+
case "ZodString":
|
|
10
|
+
return convertString(def);
|
|
11
|
+
case "ZodNumber":
|
|
12
|
+
return convertNumber(def);
|
|
13
|
+
case "ZodBoolean":
|
|
14
|
+
return { type: "boolean" };
|
|
15
|
+
case "ZodNull":
|
|
16
|
+
return { type: "null" };
|
|
17
|
+
case "ZodLiteral":
|
|
18
|
+
return { enum: [def.value] };
|
|
19
|
+
case "ZodEnum":
|
|
20
|
+
return { type: "string", enum: def.values };
|
|
21
|
+
case "ZodNativeEnum":
|
|
22
|
+
return { enum: Object.values(def.values) };
|
|
23
|
+
case "ZodObject":
|
|
24
|
+
return convertObject(def);
|
|
25
|
+
case "ZodArray":
|
|
26
|
+
return convertArray(def);
|
|
27
|
+
case "ZodOptional":
|
|
28
|
+
return convert(def.innerType);
|
|
29
|
+
case "ZodNullable": {
|
|
30
|
+
const inner = convert(def.innerType);
|
|
31
|
+
return { anyOf: [inner, { type: "null" }] };
|
|
32
|
+
}
|
|
33
|
+
case "ZodDefault":
|
|
34
|
+
return { ...convert(def.innerType), default: def.defaultValue() };
|
|
35
|
+
case "ZodUnion":
|
|
36
|
+
return { anyOf: def.options.map((o) => convert(o)) };
|
|
37
|
+
case "ZodDiscriminatedUnion":
|
|
38
|
+
return { oneOf: [...def.options.values()].map((o) => convert(o)) };
|
|
39
|
+
case "ZodRecord":
|
|
40
|
+
return {
|
|
41
|
+
type: "object",
|
|
42
|
+
additionalProperties: convert(def.valueType)
|
|
43
|
+
};
|
|
44
|
+
case "ZodTuple": {
|
|
45
|
+
const items = def.items.map((item) => convert(item));
|
|
46
|
+
return { type: "array", items: items.length === 1 ? items[0] : void 0, prefixItems: items };
|
|
47
|
+
}
|
|
48
|
+
case "ZodEffects":
|
|
49
|
+
return convert(def.schema);
|
|
50
|
+
case "ZodPipeline":
|
|
51
|
+
return convert(def.in);
|
|
52
|
+
case "ZodLazy":
|
|
53
|
+
return convert(def.getter());
|
|
54
|
+
case "ZodAny":
|
|
55
|
+
return {};
|
|
56
|
+
case "ZodUnknown":
|
|
57
|
+
return {};
|
|
58
|
+
default:
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function convertString(def) {
|
|
63
|
+
const schema = { type: "string" };
|
|
64
|
+
if (def.checks) {
|
|
65
|
+
for (const check of def.checks) {
|
|
66
|
+
switch (check.kind) {
|
|
67
|
+
case "min":
|
|
68
|
+
schema.minLength = check.value;
|
|
69
|
+
break;
|
|
70
|
+
case "max":
|
|
71
|
+
schema.maxLength = check.value;
|
|
72
|
+
break;
|
|
73
|
+
case "email":
|
|
74
|
+
schema.format = "email";
|
|
75
|
+
break;
|
|
76
|
+
case "url":
|
|
77
|
+
schema.format = "uri";
|
|
78
|
+
break;
|
|
79
|
+
case "uuid":
|
|
80
|
+
schema.format = "uuid";
|
|
81
|
+
break;
|
|
82
|
+
case "regex":
|
|
83
|
+
schema.pattern = check.regex.source;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (def.description) schema.description = def.description;
|
|
89
|
+
return schema;
|
|
90
|
+
}
|
|
91
|
+
function convertNumber(def) {
|
|
92
|
+
const schema = { type: "number" };
|
|
93
|
+
if (def.checks) {
|
|
94
|
+
for (const check of def.checks) {
|
|
95
|
+
switch (check.kind) {
|
|
96
|
+
case "min":
|
|
97
|
+
schema.minimum = check.value;
|
|
98
|
+
if (check.inclusive === false) schema.exclusiveMinimum = check.value;
|
|
99
|
+
break;
|
|
100
|
+
case "max":
|
|
101
|
+
schema.maximum = check.value;
|
|
102
|
+
if (check.inclusive === false) schema.exclusiveMaximum = check.value;
|
|
103
|
+
break;
|
|
104
|
+
case "int":
|
|
105
|
+
schema.type = "integer";
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (def.description) schema.description = def.description;
|
|
111
|
+
return schema;
|
|
112
|
+
}
|
|
113
|
+
function convertObject(def) {
|
|
114
|
+
const shape = def.shape();
|
|
115
|
+
const properties = {};
|
|
116
|
+
const required = [];
|
|
117
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
118
|
+
properties[key] = convert(value);
|
|
119
|
+
const fieldDef = value._def;
|
|
120
|
+
const isOptional = fieldDef?.typeName === "ZodOptional" || fieldDef?.typeName === "ZodDefault";
|
|
121
|
+
if (!isOptional) {
|
|
122
|
+
required.push(key);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const schema = { type: "object", properties };
|
|
126
|
+
if (required.length > 0) schema.required = required;
|
|
127
|
+
if (def.description) schema.description = def.description;
|
|
128
|
+
return schema;
|
|
129
|
+
}
|
|
130
|
+
function convertArray(def) {
|
|
131
|
+
const schema = {
|
|
132
|
+
type: "array",
|
|
133
|
+
items: convert(def.type)
|
|
134
|
+
};
|
|
135
|
+
if (def.minLength) schema.minItems = def.minLength.value;
|
|
136
|
+
if (def.maxLength) schema.maxItems = def.maxLength.value;
|
|
137
|
+
if (def.description) schema.description = def.description;
|
|
138
|
+
return schema;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export {
|
|
142
|
+
zodToJsonSchema
|
|
143
|
+
};
|
package/dist/mcp/index.d.mts
CHANGED
|
@@ -96,6 +96,19 @@ declare class MCPServer {
|
|
|
96
96
|
* ```
|
|
97
97
|
*/
|
|
98
98
|
httpHandler(): (req: Request) => Promise<Response>;
|
|
99
|
+
/**
|
|
100
|
+
* Create a Streamable HTTP handler (MCP spec 2025-03-26).
|
|
101
|
+
* Supports both single JSON-RPC requests and SSE streaming for long-running operations.
|
|
102
|
+
* Compatible with Next.js route handlers, Deno, Bun, Cloudflare Workers.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* // Next.js route: app/api/mcp/route.ts
|
|
107
|
+
* import { server } from '@/lib/mcp';
|
|
108
|
+
* export const POST = server.streamableHttpHandler();
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
streamableHttpHandler(): (req: Request) => Promise<Response>;
|
|
99
112
|
}
|
|
100
113
|
/**
|
|
101
114
|
* Create an MCP server from a Better UI tool registry.
|
package/dist/mcp/index.d.ts
CHANGED
|
@@ -96,6 +96,19 @@ declare class MCPServer {
|
|
|
96
96
|
* ```
|
|
97
97
|
*/
|
|
98
98
|
httpHandler(): (req: Request) => Promise<Response>;
|
|
99
|
+
/**
|
|
100
|
+
* Create a Streamable HTTP handler (MCP spec 2025-03-26).
|
|
101
|
+
* Supports both single JSON-RPC requests and SSE streaming for long-running operations.
|
|
102
|
+
* Compatible with Next.js route handlers, Deno, Bun, Cloudflare Workers.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* // Next.js route: app/api/mcp/route.ts
|
|
107
|
+
* import { server } from '@/lib/mcp';
|
|
108
|
+
* export const POST = server.streamableHttpHandler();
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
streamableHttpHandler(): (req: Request) => Promise<Response>;
|
|
99
112
|
}
|
|
100
113
|
/**
|
|
101
114
|
* Create an MCP server from a Better UI tool registry.
|
package/dist/mcp/index.js
CHANGED
|
@@ -394,6 +394,84 @@ var MCPServer = class {
|
|
|
394
394
|
return Response.json(response);
|
|
395
395
|
};
|
|
396
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* Create a Streamable HTTP handler (MCP spec 2025-03-26).
|
|
399
|
+
* Supports both single JSON-RPC requests and SSE streaming for long-running operations.
|
|
400
|
+
* Compatible with Next.js route handlers, Deno, Bun, Cloudflare Workers.
|
|
401
|
+
*
|
|
402
|
+
* @example
|
|
403
|
+
* ```typescript
|
|
404
|
+
* // Next.js route: app/api/mcp/route.ts
|
|
405
|
+
* import { server } from '@/lib/mcp';
|
|
406
|
+
* export const POST = server.streamableHttpHandler();
|
|
407
|
+
* ```
|
|
408
|
+
*/
|
|
409
|
+
streamableHttpHandler() {
|
|
410
|
+
return async (req) => {
|
|
411
|
+
const accept = req.headers.get("accept") || "";
|
|
412
|
+
const contentType = req.headers.get("content-type") || "";
|
|
413
|
+
if (!contentType.includes("application/json")) {
|
|
414
|
+
return Response.json(
|
|
415
|
+
{ jsonrpc: "2.0", id: null, error: { code: PARSE_ERROR, message: "Content-Type must be application/json" } },
|
|
416
|
+
{ status: 400 }
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
let message;
|
|
420
|
+
try {
|
|
421
|
+
message = await req.json();
|
|
422
|
+
} catch {
|
|
423
|
+
return Response.json(
|
|
424
|
+
{ jsonrpc: "2.0", id: null, error: { code: PARSE_ERROR, message: "Parse error" } },
|
|
425
|
+
{ status: 400 }
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
if (!message.jsonrpc || message.jsonrpc !== "2.0") {
|
|
429
|
+
return Response.json(
|
|
430
|
+
{ jsonrpc: "2.0", id: message.id ?? null, error: { code: INVALID_REQUEST, message: "Invalid JSON-RPC version" } },
|
|
431
|
+
{ status: 400 }
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
if (accept.includes("text/event-stream")) {
|
|
435
|
+
const encoder = new TextEncoder();
|
|
436
|
+
const self = this;
|
|
437
|
+
const sseStream = new ReadableStream({
|
|
438
|
+
async start(controller) {
|
|
439
|
+
try {
|
|
440
|
+
const response2 = await self.handleMessage(message);
|
|
441
|
+
if (response2) {
|
|
442
|
+
controller.enqueue(encoder.encode(`event: message
|
|
443
|
+
data: ${JSON.stringify(response2)}
|
|
444
|
+
|
|
445
|
+
`));
|
|
446
|
+
}
|
|
447
|
+
} catch (err) {
|
|
448
|
+
const errorResponse = {
|
|
449
|
+
jsonrpc: "2.0",
|
|
450
|
+
id: message.id ?? null,
|
|
451
|
+
error: { code: INTERNAL_ERROR, message: err instanceof Error ? err.message : "Internal error" }
|
|
452
|
+
};
|
|
453
|
+
controller.enqueue(encoder.encode(`event: message
|
|
454
|
+
data: ${JSON.stringify(errorResponse)}
|
|
455
|
+
|
|
456
|
+
`));
|
|
457
|
+
}
|
|
458
|
+
controller.close();
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
return new Response(sseStream, {
|
|
462
|
+
headers: {
|
|
463
|
+
"Content-Type": "text/event-stream",
|
|
464
|
+
"Cache-Control": "no-cache"
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
const response = await this.handleMessage(message);
|
|
469
|
+
if (!response) {
|
|
470
|
+
return new Response(null, { status: 204 });
|
|
471
|
+
}
|
|
472
|
+
return Response.json(response);
|
|
473
|
+
};
|
|
474
|
+
}
|
|
397
475
|
};
|
|
398
476
|
var McpError = class extends Error {
|
|
399
477
|
constructor(code, message) {
|
package/dist/mcp/index.mjs
CHANGED
|
@@ -1,145 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
zodToJsonSchema
|
|
3
|
+
} from "../chunk-OH73K7I5.mjs";
|
|
1
4
|
import "../chunk-Y6FXYEAI.mjs";
|
|
2
5
|
|
|
3
|
-
// src/mcp/schema.ts
|
|
4
|
-
function zodToJsonSchema(schema) {
|
|
5
|
-
return convert(schema);
|
|
6
|
-
}
|
|
7
|
-
function convert(schema) {
|
|
8
|
-
const def = schema._def;
|
|
9
|
-
const typeName = def?.typeName;
|
|
10
|
-
switch (typeName) {
|
|
11
|
-
case "ZodString":
|
|
12
|
-
return convertString(def);
|
|
13
|
-
case "ZodNumber":
|
|
14
|
-
return convertNumber(def);
|
|
15
|
-
case "ZodBoolean":
|
|
16
|
-
return { type: "boolean" };
|
|
17
|
-
case "ZodNull":
|
|
18
|
-
return { type: "null" };
|
|
19
|
-
case "ZodLiteral":
|
|
20
|
-
return { enum: [def.value] };
|
|
21
|
-
case "ZodEnum":
|
|
22
|
-
return { type: "string", enum: def.values };
|
|
23
|
-
case "ZodNativeEnum":
|
|
24
|
-
return { enum: Object.values(def.values) };
|
|
25
|
-
case "ZodObject":
|
|
26
|
-
return convertObject(def);
|
|
27
|
-
case "ZodArray":
|
|
28
|
-
return convertArray(def);
|
|
29
|
-
case "ZodOptional":
|
|
30
|
-
return convert(def.innerType);
|
|
31
|
-
case "ZodNullable": {
|
|
32
|
-
const inner = convert(def.innerType);
|
|
33
|
-
return { anyOf: [inner, { type: "null" }] };
|
|
34
|
-
}
|
|
35
|
-
case "ZodDefault":
|
|
36
|
-
return { ...convert(def.innerType), default: def.defaultValue() };
|
|
37
|
-
case "ZodUnion":
|
|
38
|
-
return { anyOf: def.options.map((o) => convert(o)) };
|
|
39
|
-
case "ZodDiscriminatedUnion":
|
|
40
|
-
return { oneOf: [...def.options.values()].map((o) => convert(o)) };
|
|
41
|
-
case "ZodRecord":
|
|
42
|
-
return {
|
|
43
|
-
type: "object",
|
|
44
|
-
additionalProperties: convert(def.valueType)
|
|
45
|
-
};
|
|
46
|
-
case "ZodTuple": {
|
|
47
|
-
const items = def.items.map((item) => convert(item));
|
|
48
|
-
return { type: "array", items: items.length === 1 ? items[0] : void 0, prefixItems: items };
|
|
49
|
-
}
|
|
50
|
-
case "ZodEffects":
|
|
51
|
-
return convert(def.schema);
|
|
52
|
-
case "ZodPipeline":
|
|
53
|
-
return convert(def.in);
|
|
54
|
-
case "ZodLazy":
|
|
55
|
-
return convert(def.getter());
|
|
56
|
-
case "ZodAny":
|
|
57
|
-
return {};
|
|
58
|
-
case "ZodUnknown":
|
|
59
|
-
return {};
|
|
60
|
-
default:
|
|
61
|
-
return {};
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
function convertString(def) {
|
|
65
|
-
const schema = { type: "string" };
|
|
66
|
-
if (def.checks) {
|
|
67
|
-
for (const check of def.checks) {
|
|
68
|
-
switch (check.kind) {
|
|
69
|
-
case "min":
|
|
70
|
-
schema.minLength = check.value;
|
|
71
|
-
break;
|
|
72
|
-
case "max":
|
|
73
|
-
schema.maxLength = check.value;
|
|
74
|
-
break;
|
|
75
|
-
case "email":
|
|
76
|
-
schema.format = "email";
|
|
77
|
-
break;
|
|
78
|
-
case "url":
|
|
79
|
-
schema.format = "uri";
|
|
80
|
-
break;
|
|
81
|
-
case "uuid":
|
|
82
|
-
schema.format = "uuid";
|
|
83
|
-
break;
|
|
84
|
-
case "regex":
|
|
85
|
-
schema.pattern = check.regex.source;
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (def.description) schema.description = def.description;
|
|
91
|
-
return schema;
|
|
92
|
-
}
|
|
93
|
-
function convertNumber(def) {
|
|
94
|
-
const schema = { type: "number" };
|
|
95
|
-
if (def.checks) {
|
|
96
|
-
for (const check of def.checks) {
|
|
97
|
-
switch (check.kind) {
|
|
98
|
-
case "min":
|
|
99
|
-
schema.minimum = check.value;
|
|
100
|
-
if (check.inclusive === false) schema.exclusiveMinimum = check.value;
|
|
101
|
-
break;
|
|
102
|
-
case "max":
|
|
103
|
-
schema.maximum = check.value;
|
|
104
|
-
if (check.inclusive === false) schema.exclusiveMaximum = check.value;
|
|
105
|
-
break;
|
|
106
|
-
case "int":
|
|
107
|
-
schema.type = "integer";
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (def.description) schema.description = def.description;
|
|
113
|
-
return schema;
|
|
114
|
-
}
|
|
115
|
-
function convertObject(def) {
|
|
116
|
-
const shape = def.shape();
|
|
117
|
-
const properties = {};
|
|
118
|
-
const required = [];
|
|
119
|
-
for (const [key, value] of Object.entries(shape)) {
|
|
120
|
-
properties[key] = convert(value);
|
|
121
|
-
const fieldDef = value._def;
|
|
122
|
-
const isOptional = fieldDef?.typeName === "ZodOptional" || fieldDef?.typeName === "ZodDefault";
|
|
123
|
-
if (!isOptional) {
|
|
124
|
-
required.push(key);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
const schema = { type: "object", properties };
|
|
128
|
-
if (required.length > 0) schema.required = required;
|
|
129
|
-
if (def.description) schema.description = def.description;
|
|
130
|
-
return schema;
|
|
131
|
-
}
|
|
132
|
-
function convertArray(def) {
|
|
133
|
-
const schema = {
|
|
134
|
-
type: "array",
|
|
135
|
-
items: convert(def.type)
|
|
136
|
-
};
|
|
137
|
-
if (def.minLength) schema.minItems = def.minLength.value;
|
|
138
|
-
if (def.maxLength) schema.maxItems = def.maxLength.value;
|
|
139
|
-
if (def.description) schema.description = def.description;
|
|
140
|
-
return schema;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
6
|
// src/mcp/server.ts
|
|
144
7
|
var PARSE_ERROR = -32700;
|
|
145
8
|
var INVALID_REQUEST = -32600;
|
|
@@ -368,6 +231,84 @@ var MCPServer = class {
|
|
|
368
231
|
return Response.json(response);
|
|
369
232
|
};
|
|
370
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Create a Streamable HTTP handler (MCP spec 2025-03-26).
|
|
236
|
+
* Supports both single JSON-RPC requests and SSE streaming for long-running operations.
|
|
237
|
+
* Compatible with Next.js route handlers, Deno, Bun, Cloudflare Workers.
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```typescript
|
|
241
|
+
* // Next.js route: app/api/mcp/route.ts
|
|
242
|
+
* import { server } from '@/lib/mcp';
|
|
243
|
+
* export const POST = server.streamableHttpHandler();
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
streamableHttpHandler() {
|
|
247
|
+
return async (req) => {
|
|
248
|
+
const accept = req.headers.get("accept") || "";
|
|
249
|
+
const contentType = req.headers.get("content-type") || "";
|
|
250
|
+
if (!contentType.includes("application/json")) {
|
|
251
|
+
return Response.json(
|
|
252
|
+
{ jsonrpc: "2.0", id: null, error: { code: PARSE_ERROR, message: "Content-Type must be application/json" } },
|
|
253
|
+
{ status: 400 }
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
let message;
|
|
257
|
+
try {
|
|
258
|
+
message = await req.json();
|
|
259
|
+
} catch {
|
|
260
|
+
return Response.json(
|
|
261
|
+
{ jsonrpc: "2.0", id: null, error: { code: PARSE_ERROR, message: "Parse error" } },
|
|
262
|
+
{ status: 400 }
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
if (!message.jsonrpc || message.jsonrpc !== "2.0") {
|
|
266
|
+
return Response.json(
|
|
267
|
+
{ jsonrpc: "2.0", id: message.id ?? null, error: { code: INVALID_REQUEST, message: "Invalid JSON-RPC version" } },
|
|
268
|
+
{ status: 400 }
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
if (accept.includes("text/event-stream")) {
|
|
272
|
+
const encoder = new TextEncoder();
|
|
273
|
+
const self = this;
|
|
274
|
+
const sseStream = new ReadableStream({
|
|
275
|
+
async start(controller) {
|
|
276
|
+
try {
|
|
277
|
+
const response2 = await self.handleMessage(message);
|
|
278
|
+
if (response2) {
|
|
279
|
+
controller.enqueue(encoder.encode(`event: message
|
|
280
|
+
data: ${JSON.stringify(response2)}
|
|
281
|
+
|
|
282
|
+
`));
|
|
283
|
+
}
|
|
284
|
+
} catch (err) {
|
|
285
|
+
const errorResponse = {
|
|
286
|
+
jsonrpc: "2.0",
|
|
287
|
+
id: message.id ?? null,
|
|
288
|
+
error: { code: INTERNAL_ERROR, message: err instanceof Error ? err.message : "Internal error" }
|
|
289
|
+
};
|
|
290
|
+
controller.enqueue(encoder.encode(`event: message
|
|
291
|
+
data: ${JSON.stringify(errorResponse)}
|
|
292
|
+
|
|
293
|
+
`));
|
|
294
|
+
}
|
|
295
|
+
controller.close();
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
return new Response(sseStream, {
|
|
299
|
+
headers: {
|
|
300
|
+
"Content-Type": "text/event-stream",
|
|
301
|
+
"Cache-Control": "no-cache"
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
const response = await this.handleMessage(message);
|
|
306
|
+
if (!response) {
|
|
307
|
+
return new Response(null, { status: 204 });
|
|
308
|
+
}
|
|
309
|
+
return Response.json(response);
|
|
310
|
+
};
|
|
311
|
+
}
|
|
371
312
|
};
|
|
372
313
|
var McpError = class extends Error {
|
|
373
314
|
constructor(code, message) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lantos1618/better-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "A minimal, type-safe AI-first UI framework for building tools",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -36,6 +36,11 @@
|
|
|
36
36
|
"import": "./dist/mcp/index.mjs",
|
|
37
37
|
"require": "./dist/mcp/index.js"
|
|
38
38
|
},
|
|
39
|
+
"./agui": {
|
|
40
|
+
"types": "./dist/agui/index.d.ts",
|
|
41
|
+
"import": "./dist/agui/index.mjs",
|
|
42
|
+
"require": "./dist/agui/index.js"
|
|
43
|
+
},
|
|
39
44
|
"./theme.css": "./src/theme.css"
|
|
40
45
|
},
|
|
41
46
|
"files": [
|
|
@@ -50,7 +55,11 @@
|
|
|
50
55
|
"ai",
|
|
51
56
|
"react",
|
|
52
57
|
"tools",
|
|
53
|
-
"typescript"
|
|
58
|
+
"typescript",
|
|
59
|
+
"mcp",
|
|
60
|
+
"ag-ui",
|
|
61
|
+
"model-context-protocol",
|
|
62
|
+
"ai-tools"
|
|
54
63
|
],
|
|
55
64
|
"author": "Lyndon Leong",
|
|
56
65
|
"license": "MIT",
|
|
@@ -60,7 +69,7 @@
|
|
|
60
69
|
},
|
|
61
70
|
"scripts": {
|
|
62
71
|
"build": "npm run build:lib",
|
|
63
|
-
"build:lib": "tsup src/index.ts src/react/index.ts src/components/index.ts src/auth/index.ts src/persistence/index.ts src/mcp/index.ts --format cjs,esm --dts --clean --tsconfig tsconfig.lib.json",
|
|
72
|
+
"build:lib": "tsup src/index.ts src/react/index.ts src/components/index.ts src/auth/index.ts src/persistence/index.ts src/mcp/index.ts src/agui/index.ts --format cjs,esm --dts --clean --tsconfig tsconfig.lib.json",
|
|
64
73
|
"test": "jest",
|
|
65
74
|
"type-check": "tsc --noEmit",
|
|
66
75
|
"prepublishOnly": "npm run build:lib"
|