@leanmcp/ui 0.3.3 → 0.3.5
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 +285 -285
- package/dist/{chunk-2HRO6CFU.js → chunk-57VDL5UC.js} +2 -2
- package/dist/chunk-57VDL5UC.js.map +1 -0
- package/dist/{chunk-KX75VCMM.mjs → chunk-5RGEQPF3.mjs} +2 -2
- package/dist/chunk-5RGEQPF3.mjs.map +1 -0
- package/dist/globals.css +138 -0
- package/dist/index.css.map +1 -1
- package/dist/index.js +192 -192
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/server.js +13 -13
- package/dist/server.mjs +1 -1
- package/dist/styles.css +4 -0
- package/dist/theme.css +102 -0
- package/package.json +111 -111
- package/dist/chunk-2HRO6CFU.js.map +0 -1
- package/dist/chunk-KX75VCMM.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,285 +1,285 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<img
|
|
3
|
-
src="https://raw.githubusercontent.com/LeanMCP/leanmcp-sdk/refs/heads/main/assets/logo.png"
|
|
4
|
-
alt="LeanMCP Logo"
|
|
5
|
-
width="400"
|
|
6
|
-
/>
|
|
7
|
-
</p>
|
|
8
|
-
|
|
9
|
-
<p align="center">
|
|
10
|
-
<strong>@leanmcp/ui</strong><br/>
|
|
11
|
-
MCP-Native UI SDK for React — Build rich, interactive MCP Apps with first-class tool integration.
|
|
12
|
-
</p>
|
|
13
|
-
|
|
14
|
-
<p align="center">
|
|
15
|
-
<a href="https://www.npmjs.com/package/@leanmcp/ui">
|
|
16
|
-
<img src="https://img.shields.io/npm/v/@leanmcp/ui" alt="npm version" />
|
|
17
|
-
</a>
|
|
18
|
-
<a href="https://www.npmjs.com/package/@leanmcp/ui">
|
|
19
|
-
<img src="https://img.shields.io/npm/dm/@leanmcp/ui" alt="npm downloads" />
|
|
20
|
-
</a>
|
|
21
|
-
<a href="https://docs.leanmcp.com/sdk/ui">
|
|
22
|
-
<img src="https://img.shields.io/badge/Docs-leanmcp-0A66C2?" />
|
|
23
|
-
</a>
|
|
24
|
-
<a href="https://discord.com/invite/DsRcA3GwPy">
|
|
25
|
-
<img src="https://img.shields.io/badge/Discord-Join-5865F2?logo=discord&logoColor=white" />
|
|
26
|
-
</a>
|
|
27
|
-
<a href="https://x.com/LeanMcp">
|
|
28
|
-
<img src="https://img.shields.io/badge/@LeanMCP-f5f5f5?logo=x&logoColor=000000" />
|
|
29
|
-
</a>
|
|
30
|
-
</p>
|
|
31
|
-
|
|
32
|
-
## Features
|
|
33
|
-
|
|
34
|
-
- **MCP-Native Components** — ToolButton, ToolSelect, ToolForm, ToolDataGrid, and more
|
|
35
|
-
- **First-Class Tool Integration** — Components that natively call MCP tools
|
|
36
|
-
- **ChatGPT Apps Support** — Build apps that work inside ChatGPT with `@GPTApp`
|
|
37
|
-
- **Streaming Support** — Handle partial/streaming tool responses
|
|
38
|
-
- **Theming** — Automatic host theme adaptation (light/dark)
|
|
39
|
-
- **Testing Utilities** — `MockAppProvider` for unit testing
|
|
40
|
-
|
|
41
|
-
## Installation
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
npm install @leanmcp/ui
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Quick Start
|
|
48
|
-
|
|
49
|
-
```tsx
|
|
50
|
-
import { AppProvider, ToolButton } from '@leanmcp/ui';
|
|
51
|
-
import '@leanmcp/ui/styles.css';
|
|
52
|
-
|
|
53
|
-
function MyApp() {
|
|
54
|
-
return (
|
|
55
|
-
<AppProvider appInfo={{ name: 'MyApp', version: '1.0.0' }}>
|
|
56
|
-
<ToolButton tool="refresh-data" resultDisplay="toast">
|
|
57
|
-
Refresh
|
|
58
|
-
</ToolButton>
|
|
59
|
-
</AppProvider>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
## Components
|
|
65
|
-
|
|
66
|
-
### MCP-Native Components
|
|
67
|
-
|
|
68
|
-
| Component | Description |
|
|
69
|
-
|-----------|-------------|
|
|
70
|
-
| `ToolButton` | Button with tool execution, confirmation, result display |
|
|
71
|
-
| `ToolSelect` | Select with tool-based options and selection callbacks |
|
|
72
|
-
| `ToolInput` | Input with debounced search and autocomplete |
|
|
73
|
-
| `ToolForm` | Form with multiple field types (text, select, checkbox, slider) |
|
|
74
|
-
| `ToolDataGrid` | Table with server-side pagination, sorting, row actions |
|
|
75
|
-
| `ResourceView` | Display MCP server resources with auto-refresh |
|
|
76
|
-
| `StreamingContent` | Render streaming/partial tool data |
|
|
77
|
-
|
|
78
|
-
### Utility Components
|
|
79
|
-
|
|
80
|
-
| Component | Description |
|
|
81
|
-
|-----------|-------------|
|
|
82
|
-
| `RequireConnection` | Guard wrapper for MCP connection state |
|
|
83
|
-
| `ToolErrorBoundary` | Error boundary with retry for tool errors |
|
|
84
|
-
| `ToolProvider` | Scoped configuration context |
|
|
85
|
-
|
|
86
|
-
## Hooks
|
|
87
|
-
|
|
88
|
-
| Hook | Description |
|
|
89
|
-
|------|-------------|
|
|
90
|
-
| `useTool` | Call tools with retry, abort, transformation |
|
|
91
|
-
| `useToolStream` | Handle streaming tool input |
|
|
92
|
-
| `useResource` | Read MCP resources with auto-refresh |
|
|
93
|
-
| `useMessage` | Send messages to host chat |
|
|
94
|
-
| `useHostContext` | Access host theme and viewport |
|
|
95
|
-
|
|
96
|
-
### GPT Apps SDK Hooks
|
|
97
|
-
|
|
98
|
-
These hooks provide access to the ChatGPT Apps SDK globals (compatible with OpenAI's `window.openai` API):
|
|
99
|
-
|
|
100
|
-
| Hook | Description |
|
|
101
|
-
|------|-------------|
|
|
102
|
-
| `useToolOutput` | Access `structuredContent` from the tool response |
|
|
103
|
-
| `useToolInput` | Access input arguments passed to the tool |
|
|
104
|
-
| `useWidgetState` | Read/write persistent widget state across sessions |
|
|
105
|
-
| `useToolResponseMetadata` | Access `_meta` from the tool response |
|
|
106
|
-
| `useOpenAiGlobal` | Low-level hook to subscribe to any `window.openai` property |
|
|
107
|
-
|
|
108
|
-
## Examples
|
|
109
|
-
|
|
110
|
-
### ToolButton with Confirmation
|
|
111
|
-
|
|
112
|
-
```tsx
|
|
113
|
-
<ToolButton
|
|
114
|
-
tool="delete-item"
|
|
115
|
-
args={{ id: item.id }}
|
|
116
|
-
confirm={{
|
|
117
|
-
title: 'Delete Item?',
|
|
118
|
-
description: 'This cannot be undone.'
|
|
119
|
-
}}
|
|
120
|
-
variant="destructive"
|
|
121
|
-
>
|
|
122
|
-
Delete
|
|
123
|
-
</ToolButton>
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### ToolSelect with Dynamic Options
|
|
127
|
-
|
|
128
|
-
```tsx
|
|
129
|
-
<ToolSelect
|
|
130
|
-
optionsTool="list-categories"
|
|
131
|
-
transformOptions={(r) => r.categories.map(c => ({
|
|
132
|
-
value: c.id,
|
|
133
|
-
label: c.name
|
|
134
|
-
}))}
|
|
135
|
-
onSelectTool="set-category"
|
|
136
|
-
argName="categoryId"
|
|
137
|
-
/>
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### ToolDataGrid
|
|
141
|
-
|
|
142
|
-
```tsx
|
|
143
|
-
<ToolDataGrid
|
|
144
|
-
dataTool="list-users"
|
|
145
|
-
columns={[
|
|
146
|
-
{ key: 'name', header: 'Name', sortable: true },
|
|
147
|
-
{ key: 'email', header: 'Email' },
|
|
148
|
-
{ key: 'status', header: 'Status' }
|
|
149
|
-
]}
|
|
150
|
-
transformData={(r) => ({ rows: r.users, total: r.total })}
|
|
151
|
-
rowActions={[
|
|
152
|
-
{ label: 'Edit', tool: 'edit-user' }
|
|
153
|
-
]}
|
|
154
|
-
pagination
|
|
155
|
-
/>
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
### ToolForm
|
|
159
|
-
|
|
160
|
-
```tsx
|
|
161
|
-
<ToolForm
|
|
162
|
-
toolName="create-item"
|
|
163
|
-
fields={[
|
|
164
|
-
{ name: 'title', label: 'Title', required: true },
|
|
165
|
-
{ name: 'priority', label: 'Priority', type: 'select',
|
|
166
|
-
options: [
|
|
167
|
-
{ value: 'low', label: 'Low' },
|
|
168
|
-
{ value: 'high', label: 'High' }
|
|
169
|
-
]
|
|
170
|
-
},
|
|
171
|
-
{ name: 'notify', label: 'Send notifications', type: 'switch' }
|
|
172
|
-
]}
|
|
173
|
-
showSuccessToast
|
|
174
|
-
/>
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
## Server-Side Integration
|
|
178
|
-
|
|
179
|
-
Use `@UIApp` decorator to register your React component as an MCP resource.
|
|
180
|
-
|
|
181
|
-
> **Note:** Use a relative path string for the `component` property, not an imported component. This avoids importing React components on the server side.
|
|
182
|
-
|
|
183
|
-
```typescript
|
|
184
|
-
// mcp/dashboard/index.ts
|
|
185
|
-
import { UIApp } from '@leanmcp/core';
|
|
186
|
-
|
|
187
|
-
export class DashboardService {
|
|
188
|
-
@UIApp({
|
|
189
|
-
component: './Dashboard', // Path relative to this file
|
|
190
|
-
name: 'dashboard',
|
|
191
|
-
title: 'Analytics Dashboard'
|
|
192
|
-
})
|
|
193
|
-
dashboard() {}
|
|
194
|
-
}
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
## ChatGPT Integration
|
|
198
|
-
|
|
199
|
-
Use `@GPTApp` for ChatGPT-specific apps:
|
|
200
|
-
|
|
201
|
-
```typescript
|
|
202
|
-
import { GPTApp } from '@leanmcp/ui';
|
|
203
|
-
|
|
204
|
-
export class SlackService {
|
|
205
|
-
@GPTApp({
|
|
206
|
-
component: './SlackApp', // Path relative to this file
|
|
207
|
-
name: 'slack-composer'
|
|
208
|
-
})
|
|
209
|
-
slackComposer() {}
|
|
210
|
-
}
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### GPT Apps SDK Hooks Usage
|
|
214
|
-
|
|
215
|
-
Access tool output (structuredContent) without making additional API calls:
|
|
216
|
-
|
|
217
|
-
```tsx
|
|
218
|
-
import { useToolOutput, useWidgetState } from '@leanmcp/ui';
|
|
219
|
-
|
|
220
|
-
function ChannelsView() {
|
|
221
|
-
// Access the structured data from the tool response
|
|
222
|
-
const toolOutput = useToolOutput<{ channels: Channel[] }>();
|
|
223
|
-
|
|
224
|
-
// Persist state across the session
|
|
225
|
-
const [state, setState] = useWidgetState({ selectedChannel: null });
|
|
226
|
-
|
|
227
|
-
if (!toolOutput?.channels) return <div>Loading...</div>;
|
|
228
|
-
|
|
229
|
-
return (
|
|
230
|
-
<ul>
|
|
231
|
-
{toolOutput.channels.map(ch => (
|
|
232
|
-
<li key={ch.id} onClick={() => setState({ selectedChannel: ch.id })}>
|
|
233
|
-
{ch.name}
|
|
234
|
-
</li>
|
|
235
|
-
))}
|
|
236
|
-
</ul>
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
## Theming
|
|
242
|
-
|
|
243
|
-
The SDK uses CSS variables compatible with MCP host theming. Import the styles:
|
|
244
|
-
|
|
245
|
-
```tsx
|
|
246
|
-
import '@leanmcp/ui/styles.css';
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
The styles automatically adapt to the host's theme (light/dark).
|
|
250
|
-
|
|
251
|
-
## Testing
|
|
252
|
-
|
|
253
|
-
Use `MockAppProvider` for unit testing:
|
|
254
|
-
|
|
255
|
-
```tsx
|
|
256
|
-
import { MockAppProvider } from '@leanmcp/ui/testing';
|
|
257
|
-
|
|
258
|
-
test('renders tool result', () => {
|
|
259
|
-
render(
|
|
260
|
-
<MockAppProvider
|
|
261
|
-
toolResult={{ data: 'test' }}
|
|
262
|
-
callTool={async () => ({ content: [{ type: 'text', text: '{}' }] })}
|
|
263
|
-
>
|
|
264
|
-
<MyComponent />
|
|
265
|
-
</MockAppProvider>
|
|
266
|
-
);
|
|
267
|
-
});
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
## Documentation
|
|
271
|
-
|
|
272
|
-
- [Full Documentation](https://docs.leanmcp.com/sdk/ui)
|
|
273
|
-
- [Component Reference](https://docs.leanmcp.com/sdk/ui-components)
|
|
274
|
-
- [Hooks Reference](https://docs.leanmcp.com/sdk/ui-hooks)
|
|
275
|
-
- [ChatGPT Apps Guide](https://docs.leanmcp.com/sdk/ui-gpt-apps)
|
|
276
|
-
|
|
277
|
-
## Related Packages
|
|
278
|
-
|
|
279
|
-
- [@leanmcp/core](https://www.npmjs.com/package/@leanmcp/core) — Core MCP server functionality
|
|
280
|
-
- [@leanmcp/cli](https://www.npmjs.com/package/@leanmcp/cli) — CLI for project scaffolding
|
|
281
|
-
- [@leanmcp/auth](https://www.npmjs.com/package/@leanmcp/auth) — Authentication decorators
|
|
282
|
-
|
|
283
|
-
## License
|
|
284
|
-
|
|
285
|
-
MIT
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img
|
|
3
|
+
src="https://raw.githubusercontent.com/LeanMCP/leanmcp-sdk/refs/heads/main/assets/logo.png"
|
|
4
|
+
alt="LeanMCP Logo"
|
|
5
|
+
width="400"
|
|
6
|
+
/>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<strong>@leanmcp/ui</strong><br/>
|
|
11
|
+
MCP-Native UI SDK for React — Build rich, interactive MCP Apps with first-class tool integration.
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="https://www.npmjs.com/package/@leanmcp/ui">
|
|
16
|
+
<img src="https://img.shields.io/npm/v/@leanmcp/ui" alt="npm version" />
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://www.npmjs.com/package/@leanmcp/ui">
|
|
19
|
+
<img src="https://img.shields.io/npm/dm/@leanmcp/ui" alt="npm downloads" />
|
|
20
|
+
</a>
|
|
21
|
+
<a href="https://docs.leanmcp.com/sdk/ui">
|
|
22
|
+
<img src="https://img.shields.io/badge/Docs-leanmcp-0A66C2?" />
|
|
23
|
+
</a>
|
|
24
|
+
<a href="https://discord.com/invite/DsRcA3GwPy">
|
|
25
|
+
<img src="https://img.shields.io/badge/Discord-Join-5865F2?logo=discord&logoColor=white" />
|
|
26
|
+
</a>
|
|
27
|
+
<a href="https://x.com/LeanMcp">
|
|
28
|
+
<img src="https://img.shields.io/badge/@LeanMCP-f5f5f5?logo=x&logoColor=000000" />
|
|
29
|
+
</a>
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **MCP-Native Components** — ToolButton, ToolSelect, ToolForm, ToolDataGrid, and more
|
|
35
|
+
- **First-Class Tool Integration** — Components that natively call MCP tools
|
|
36
|
+
- **ChatGPT Apps Support** — Build apps that work inside ChatGPT with `@GPTApp`
|
|
37
|
+
- **Streaming Support** — Handle partial/streaming tool responses
|
|
38
|
+
- **Theming** — Automatic host theme adaptation (light/dark)
|
|
39
|
+
- **Testing Utilities** — `MockAppProvider` for unit testing
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install @leanmcp/ui
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { AppProvider, ToolButton } from '@leanmcp/ui';
|
|
51
|
+
import '@leanmcp/ui/styles.css';
|
|
52
|
+
|
|
53
|
+
function MyApp() {
|
|
54
|
+
return (
|
|
55
|
+
<AppProvider appInfo={{ name: 'MyApp', version: '1.0.0' }}>
|
|
56
|
+
<ToolButton tool="refresh-data" resultDisplay="toast">
|
|
57
|
+
Refresh
|
|
58
|
+
</ToolButton>
|
|
59
|
+
</AppProvider>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Components
|
|
65
|
+
|
|
66
|
+
### MCP-Native Components
|
|
67
|
+
|
|
68
|
+
| Component | Description |
|
|
69
|
+
|-----------|-------------|
|
|
70
|
+
| `ToolButton` | Button with tool execution, confirmation, result display |
|
|
71
|
+
| `ToolSelect` | Select with tool-based options and selection callbacks |
|
|
72
|
+
| `ToolInput` | Input with debounced search and autocomplete |
|
|
73
|
+
| `ToolForm` | Form with multiple field types (text, select, checkbox, slider) |
|
|
74
|
+
| `ToolDataGrid` | Table with server-side pagination, sorting, row actions |
|
|
75
|
+
| `ResourceView` | Display MCP server resources with auto-refresh |
|
|
76
|
+
| `StreamingContent` | Render streaming/partial tool data |
|
|
77
|
+
|
|
78
|
+
### Utility Components
|
|
79
|
+
|
|
80
|
+
| Component | Description |
|
|
81
|
+
|-----------|-------------|
|
|
82
|
+
| `RequireConnection` | Guard wrapper for MCP connection state |
|
|
83
|
+
| `ToolErrorBoundary` | Error boundary with retry for tool errors |
|
|
84
|
+
| `ToolProvider` | Scoped configuration context |
|
|
85
|
+
|
|
86
|
+
## Hooks
|
|
87
|
+
|
|
88
|
+
| Hook | Description |
|
|
89
|
+
|------|-------------|
|
|
90
|
+
| `useTool` | Call tools with retry, abort, transformation |
|
|
91
|
+
| `useToolStream` | Handle streaming tool input |
|
|
92
|
+
| `useResource` | Read MCP resources with auto-refresh |
|
|
93
|
+
| `useMessage` | Send messages to host chat |
|
|
94
|
+
| `useHostContext` | Access host theme and viewport |
|
|
95
|
+
|
|
96
|
+
### GPT Apps SDK Hooks
|
|
97
|
+
|
|
98
|
+
These hooks provide access to the ChatGPT Apps SDK globals (compatible with OpenAI's `window.openai` API):
|
|
99
|
+
|
|
100
|
+
| Hook | Description |
|
|
101
|
+
|------|-------------|
|
|
102
|
+
| `useToolOutput` | Access `structuredContent` from the tool response |
|
|
103
|
+
| `useToolInput` | Access input arguments passed to the tool |
|
|
104
|
+
| `useWidgetState` | Read/write persistent widget state across sessions |
|
|
105
|
+
| `useToolResponseMetadata` | Access `_meta` from the tool response |
|
|
106
|
+
| `useOpenAiGlobal` | Low-level hook to subscribe to any `window.openai` property |
|
|
107
|
+
|
|
108
|
+
## Examples
|
|
109
|
+
|
|
110
|
+
### ToolButton with Confirmation
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
<ToolButton
|
|
114
|
+
tool="delete-item"
|
|
115
|
+
args={{ id: item.id }}
|
|
116
|
+
confirm={{
|
|
117
|
+
title: 'Delete Item?',
|
|
118
|
+
description: 'This cannot be undone.'
|
|
119
|
+
}}
|
|
120
|
+
variant="destructive"
|
|
121
|
+
>
|
|
122
|
+
Delete
|
|
123
|
+
</ToolButton>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### ToolSelect with Dynamic Options
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
<ToolSelect
|
|
130
|
+
optionsTool="list-categories"
|
|
131
|
+
transformOptions={(r) => r.categories.map(c => ({
|
|
132
|
+
value: c.id,
|
|
133
|
+
label: c.name
|
|
134
|
+
}))}
|
|
135
|
+
onSelectTool="set-category"
|
|
136
|
+
argName="categoryId"
|
|
137
|
+
/>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### ToolDataGrid
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
<ToolDataGrid
|
|
144
|
+
dataTool="list-users"
|
|
145
|
+
columns={[
|
|
146
|
+
{ key: 'name', header: 'Name', sortable: true },
|
|
147
|
+
{ key: 'email', header: 'Email' },
|
|
148
|
+
{ key: 'status', header: 'Status' }
|
|
149
|
+
]}
|
|
150
|
+
transformData={(r) => ({ rows: r.users, total: r.total })}
|
|
151
|
+
rowActions={[
|
|
152
|
+
{ label: 'Edit', tool: 'edit-user' }
|
|
153
|
+
]}
|
|
154
|
+
pagination
|
|
155
|
+
/>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### ToolForm
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<ToolForm
|
|
162
|
+
toolName="create-item"
|
|
163
|
+
fields={[
|
|
164
|
+
{ name: 'title', label: 'Title', required: true },
|
|
165
|
+
{ name: 'priority', label: 'Priority', type: 'select',
|
|
166
|
+
options: [
|
|
167
|
+
{ value: 'low', label: 'Low' },
|
|
168
|
+
{ value: 'high', label: 'High' }
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
{ name: 'notify', label: 'Send notifications', type: 'switch' }
|
|
172
|
+
]}
|
|
173
|
+
showSuccessToast
|
|
174
|
+
/>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Server-Side Integration
|
|
178
|
+
|
|
179
|
+
Use `@UIApp` decorator to register your React component as an MCP resource.
|
|
180
|
+
|
|
181
|
+
> **Note:** Use a relative path string for the `component` property, not an imported component. This avoids importing React components on the server side.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// mcp/dashboard/index.ts
|
|
185
|
+
import { UIApp } from '@leanmcp/core';
|
|
186
|
+
|
|
187
|
+
export class DashboardService {
|
|
188
|
+
@UIApp({
|
|
189
|
+
component: './Dashboard', // Path relative to this file
|
|
190
|
+
name: 'dashboard',
|
|
191
|
+
title: 'Analytics Dashboard'
|
|
192
|
+
})
|
|
193
|
+
dashboard() {}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## ChatGPT Integration
|
|
198
|
+
|
|
199
|
+
Use `@GPTApp` for ChatGPT-specific apps:
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { GPTApp } from '@leanmcp/ui';
|
|
203
|
+
|
|
204
|
+
export class SlackService {
|
|
205
|
+
@GPTApp({
|
|
206
|
+
component: './SlackApp', // Path relative to this file
|
|
207
|
+
name: 'slack-composer'
|
|
208
|
+
})
|
|
209
|
+
slackComposer() {}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### GPT Apps SDK Hooks Usage
|
|
214
|
+
|
|
215
|
+
Access tool output (structuredContent) without making additional API calls:
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
import { useToolOutput, useWidgetState } from '@leanmcp/ui';
|
|
219
|
+
|
|
220
|
+
function ChannelsView() {
|
|
221
|
+
// Access the structured data from the tool response
|
|
222
|
+
const toolOutput = useToolOutput<{ channels: Channel[] }>();
|
|
223
|
+
|
|
224
|
+
// Persist state across the session
|
|
225
|
+
const [state, setState] = useWidgetState({ selectedChannel: null });
|
|
226
|
+
|
|
227
|
+
if (!toolOutput?.channels) return <div>Loading...</div>;
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<ul>
|
|
231
|
+
{toolOutput.channels.map(ch => (
|
|
232
|
+
<li key={ch.id} onClick={() => setState({ selectedChannel: ch.id })}>
|
|
233
|
+
{ch.name}
|
|
234
|
+
</li>
|
|
235
|
+
))}
|
|
236
|
+
</ul>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Theming
|
|
242
|
+
|
|
243
|
+
The SDK uses CSS variables compatible with MCP host theming. Import the styles:
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
import '@leanmcp/ui/styles.css';
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The styles automatically adapt to the host's theme (light/dark).
|
|
250
|
+
|
|
251
|
+
## Testing
|
|
252
|
+
|
|
253
|
+
Use `MockAppProvider` for unit testing:
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
import { MockAppProvider } from '@leanmcp/ui/testing';
|
|
257
|
+
|
|
258
|
+
test('renders tool result', () => {
|
|
259
|
+
render(
|
|
260
|
+
<MockAppProvider
|
|
261
|
+
toolResult={{ data: 'test' }}
|
|
262
|
+
callTool={async () => ({ content: [{ type: 'text', text: '{}' }] })}
|
|
263
|
+
>
|
|
264
|
+
<MyComponent />
|
|
265
|
+
</MockAppProvider>
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Documentation
|
|
271
|
+
|
|
272
|
+
- [Full Documentation](https://docs.leanmcp.com/sdk/ui)
|
|
273
|
+
- [Component Reference](https://docs.leanmcp.com/sdk/ui-components)
|
|
274
|
+
- [Hooks Reference](https://docs.leanmcp.com/sdk/ui-hooks)
|
|
275
|
+
- [ChatGPT Apps Guide](https://docs.leanmcp.com/sdk/ui-gpt-apps)
|
|
276
|
+
|
|
277
|
+
## Related Packages
|
|
278
|
+
|
|
279
|
+
- [@leanmcp/core](https://www.npmjs.com/package/@leanmcp/core) — Core MCP server functionality
|
|
280
|
+
- [@leanmcp/cli](https://www.npmjs.com/package/@leanmcp/cli) — CLI for project scaffolding
|
|
281
|
+
- [@leanmcp/auth](https://www.npmjs.com/package/@leanmcp/auth) — Authentication decorators
|
|
282
|
+
|
|
283
|
+
## License
|
|
284
|
+
|
|
285
|
+
MIT
|
|
@@ -134,5 +134,5 @@ exports.getGPTAppMetadata = getGPTAppMetadata;
|
|
|
134
134
|
exports.getGPTAppUri = getGPTAppUri;
|
|
135
135
|
exports.getUIAppMetadata = getUIAppMetadata;
|
|
136
136
|
exports.getUIAppUri = getUIAppUri;
|
|
137
|
-
//# sourceMappingURL=chunk-
|
|
138
|
-
//# sourceMappingURL=chunk-
|
|
137
|
+
//# sourceMappingURL=chunk-57VDL5UC.js.map
|
|
138
|
+
//# sourceMappingURL=chunk-57VDL5UC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/decorator/UIApp.ts","../src/decorator/GPTApp.ts"],"names":["UI_APP_COMPONENT_KEY","UI_APP_URI_KEY","UI_APP_OPTIONS_KEY","UIApp","options","target","propertyKey","descriptor","methodName","String","className","name","toLowerCase","replace","uri","Reflect","defineMetadata","component","value","existingMeta","getMetadata","ui","resourceUri","getUIAppMetadata","undefined","getUIAppUri","GPT_APP_COMPONENT_KEY","GPT_APP_URI_KEY","GPT_APP_OPTIONS_KEY","GPTApp","uiMeta","visibility","openAiMeta","widgetAccessible","prefersBorder","widgetDomain","widgetDescription","csp","fileParams","invocation","invoking","invoked","getGPTAppMetadata","getGPTAppUri"],"mappings":"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","file":"chunk-57VDL5UC.js","sourcesContent":["/**\n * @UIApp Decorator\n * \n * Links an MCP tool to a React UI component.\n * When applied to a tool method, it:\n * 1. Adds _meta.ui/resourceUri to the tool definition\n * 2. Auto-registers a resource that renders the component to HTML\n * \n * This decorator is designed to work with @leanmcp/core decorators.\n */\nimport 'reflect-metadata';\nimport type React from 'react';\n\n// Metadata keys\nexport const UI_APP_COMPONENT_KEY = 'ui:app:component';\nexport const UI_APP_URI_KEY = 'ui:app:uri';\nexport const UI_APP_OPTIONS_KEY = 'ui:app:options';\n\n/**\n * Options for @UIApp decorator\n */\nexport interface UIAppOptions {\n /** \n * React component or path to component file (relative to service file).\n * - Use path string (e.g., './WeatherCard') for CLI build - avoids importing browser code in server\n * - Use component reference for direct SSR rendering\n */\n component: React.ComponentType<any> | string;\n /** Custom resource URI (auto-generated if not provided) */\n uri?: string;\n /** HTML document title */\n title?: string;\n /** Additional CSS styles */\n styles?: string;\n}\n\n\n/**\n * Decorator that links a tool to a UI component.\n * \n * When the tool is called, the host will fetch the UI resource\n * which returns the component rendered as HTML.\n * \n * @example\n * ```typescript\n * import { Tool } from '@leanmcp/core';\n * import { UIApp } from '@leanmcp/ui';\n * import { WeatherCard } from './WeatherCard';\n * \n * class WeatherService {\n * @Tool({ description: 'Get weather for a city' })\n * @UIApp({ component: WeatherCard })\n * async getWeather(args: { city: string }) {\n * return { city: args.city, temp: 22 };\n * }\n * }\n * ```\n */\nexport function UIApp(options: UIAppOptions): MethodDecorator {\n return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {\n const methodName = String(propertyKey);\n // Use same pattern as @Resource in @leanmcp/core: className without 'service' suffix\n const className = target.constructor.name.toLowerCase().replace('service', '');\n\n // Generate URI using ui:// scheme (required by ext-apps hosts)\n const uri = options.uri ?? `ui://${className}/${methodName}`;\n\n // Store metadata on the method\n Reflect.defineMetadata(UI_APP_COMPONENT_KEY, options.component, descriptor.value!);\n Reflect.defineMetadata(UI_APP_URI_KEY, uri, descriptor.value!);\n Reflect.defineMetadata(UI_APP_OPTIONS_KEY, options, descriptor.value!);\n\n // Also store the resource URI for the @Tool decorator to pick up\n // The @Tool decorator in @leanmcp/core should check for this\n const existingMeta = Reflect.getMetadata('tool:meta', descriptor.value) || {};\n Reflect.defineMetadata('tool:meta', {\n ...existingMeta,\n // New nested format (preferred by ext-apps 0.2.2)\n ui: {\n resourceUri: uri,\n },\n // Legacy flat format (for backwards compatibility)\n 'ui/resourceUri': uri,\n }, descriptor.value!);\n\n return descriptor;\n };\n}\n\n/**\n * Helper to get UIApp metadata from a method\n */\nexport function getUIAppMetadata(target: Function): UIAppOptions | undefined {\n const component = Reflect.getMetadata(UI_APP_COMPONENT_KEY, target);\n if (!component) return undefined;\n\n return {\n component,\n uri: Reflect.getMetadata(UI_APP_URI_KEY, target),\n ...Reflect.getMetadata(UI_APP_OPTIONS_KEY, target),\n };\n}\n\n/**\n * Helper to get the resource URI from a method\n */\nexport function getUIAppUri(target: Function): string | undefined {\n return Reflect.getMetadata(UI_APP_URI_KEY, target);\n}\n","/**\n * @GPTApp Decorator\n * \n * Links an MCP tool to a React UI component compliant with ChatGPT Apps SDK.\n * \n * When applied to a tool method, it:\n * 1. Adds _meta.ui/resourceUri to the tool definition\n * 2. Adds OpenAI-specific metadata (widgetAccessible, outputTemplate, etc.)\n * 3. Auto-registers a resource that renders the component to HTML with 'text/html+skybridge' mimeType\n */\nimport 'reflect-metadata';\nimport type React from 'react';\n\n// Metadata keys\nexport const GPT_APP_COMPONENT_KEY = 'gptapp:component';\nexport const GPT_APP_URI_KEY = 'gptapp:uri';\nexport const GPT_APP_OPTIONS_KEY = 'gptapp:options';\n\n/**\n * Options for @GPTApp decorator\n */\nexport interface GPTAppOptions {\n /** \n * React component or path to component file (relative to service file).\n * - Use path string (e.g., './WeatherCard') for CLI build - avoids importing browser code in server\n * - Use component reference for direct SSR rendering\n */\n component: React.ComponentType<any> | string;\n\n /** Custom resource URI (auto-generated ui://... if not provided) */\n uri?: string;\n\n /** HTML document title */\n title?: string;\n\n /** Additional CSS styles */\n styles?: string;\n\n // ========== GPT-specific options ==========\n\n /** \n * Allow widget to call tools via window.openai.callTool.\n * Maps to _meta[\"openai/widgetAccessible\"]\n */\n widgetAccessible?: boolean;\n\n /** \n * Tool visibility: 'public' (default) or 'private'.\n * 'private' hides the tool from the model but keeps it callable by the widget.\n * Maps to _meta[\"openai/visibility\"]\n */\n visibility?: 'public' | 'private';\n\n /** \n * Widget prefers border around iframe.\n * Maps to _meta[\"openai/widgetPrefersBorder\"]\n */\n prefersBorder?: boolean;\n\n /** \n * Widget domain for API allowlists.\n * Maps to _meta[\"openai/widgetDomain\"]\n */\n widgetDomain?: string;\n\n /** \n * Widget description for the model.\n * Maps to _meta[\"openai/widgetDescription\"]\n */\n widgetDescription?: string;\n\n /** \n * Content Security Policy configuration.\n * Maps to _meta[\"openai/widgetCSP\"]\n */\n csp?: {\n connect_domains?: string[];\n resource_domains?: string[];\n redirect_domains?: string[];\n frame_domains?: string[];\n };\n\n /** \n * File parameters (fields treated as file uploads).\n * Maps to _meta[\"openai/fileParams\"]\n */\n fileParams?: string[];\n\n /** \n * Invocation messages shown during tool call.\n * Maps to _meta[\"openai/toolInvocation\"]\n */\n invocation?: {\n invoking?: string; // e.g., \"Preparing the board…\"\n invoked?: string; // e.g., \"Board ready.\"\n };\n}\n\n\n/**\n * Decorator that links a tool to a UI component for ChatGPT Apps.\n * \n * When the tool is called, the host will fetch the UI resource\n * which returns the component rendered as HTML with 'text/html+skybridge'.\n * \n * @example\n * ```typescript\n * import { Tool } from '@leanmcp/core';\n * import { GPTApp } from '@leanmcp/ui';\n * import { KanbanBoard } from './KanbanBoard';\n * \n * class KanbanService {\n * @Tool({ description: 'Show Kanban Board' })\n * @GPTApp({ \n * component: KanbanBoard,\n * widgetAccessible: true,\n * invocation: { invoking: 'Loading board...' }\n * })\n * async showBoard() { ... }\n * }\n * ```\n */\nexport function GPTApp(options: GPTAppOptions): MethodDecorator {\n return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {\n const methodName = String(propertyKey);\n // Use same pattern as @Resource in @leanmcp/core: className without 'service' suffix\n const className = target.constructor.name.toLowerCase().replace('service', '');\n\n // Generate URI using ui:// scheme (required by ext-apps hosts)\n const uri = options.uri ?? `ui://${className}/${methodName}`;\n\n // Store metadata on the method\n Reflect.defineMetadata(GPT_APP_COMPONENT_KEY, options.component, descriptor.value!);\n Reflect.defineMetadata(GPT_APP_URI_KEY, uri, descriptor.value!);\n Reflect.defineMetadata(GPT_APP_OPTIONS_KEY, options, descriptor.value!);\n\n // Also store the resource URI and OpenAI-specific metadata for the @Tool decorator to pick up\n const existingMeta = Reflect.getMetadata('tool:meta', descriptor.value) || {};\n\n // Build nested ui metadata (preferred by ext-apps 0.2.2)\n const uiMeta: Record<string, any> = {\n resourceUri: uri,\n };\n\n // Map visibility to ext-apps format\n if (options.visibility) {\n uiMeta.visibility = options.visibility === 'private' ? ['app'] : ['model', 'app'];\n }\n\n const openAiMeta: Record<string, any> = {\n // New nested format (preferred by ext-apps 0.2.2)\n ui: uiMeta,\n // Legacy flat format (for backwards compatibility)\n 'ui/resourceUri': uri,\n 'openai/outputTemplate': uri,\n };\n\n if (options.widgetAccessible !== undefined) {\n openAiMeta['openai/widgetAccessible'] = options.widgetAccessible;\n }\n\n if (options.visibility) {\n openAiMeta['openai/visibility'] = options.visibility;\n }\n\n if (options.prefersBorder !== undefined) {\n openAiMeta['openai/widgetPrefersBorder'] = options.prefersBorder;\n }\n\n if (options.widgetDomain) {\n openAiMeta['openai/widgetDomain'] = options.widgetDomain;\n }\n\n if (options.widgetDescription) {\n openAiMeta['openai/widgetDescription'] = options.widgetDescription;\n }\n\n if (options.csp) {\n openAiMeta['openai/widgetCSP'] = options.csp;\n }\n\n if (options.fileParams) {\n openAiMeta['openai/fileParams'] = options.fileParams;\n }\n\n if (options.invocation) {\n if (options.invocation.invoking) openAiMeta['openai/toolInvocation/invoking'] = options.invocation.invoking;\n if (options.invocation.invoked) openAiMeta['openai/toolInvocation/invoked'] = options.invocation.invoked;\n }\n\n Reflect.defineMetadata('tool:meta', {\n ...existingMeta,\n ...openAiMeta,\n }, descriptor.value!);\n\n return descriptor;\n };\n}\n\n/**\n * Helper to get GPTApp metadata from a method\n */\nexport function getGPTAppMetadata(target: Function): GPTAppOptions | undefined {\n const component = Reflect.getMetadata(GPT_APP_COMPONENT_KEY, target);\n if (!component) return undefined;\n\n return {\n component,\n uri: Reflect.getMetadata(GPT_APP_URI_KEY, target),\n ...Reflect.getMetadata(GPT_APP_OPTIONS_KEY, target),\n };\n}\n\n/**\n * Helper to get the resource URI from a method\n */\nexport function getGPTAppUri(target: Function): string | undefined {\n return Reflect.getMetadata(GPT_APP_URI_KEY, target);\n}\n"]}
|
|
@@ -120,5 +120,5 @@ function getGPTAppUri(target) {
|
|
|
120
120
|
__name(getGPTAppUri, "getGPTAppUri");
|
|
121
121
|
|
|
122
122
|
export { GPTApp, GPT_APP_COMPONENT_KEY, GPT_APP_OPTIONS_KEY, GPT_APP_URI_KEY, UIApp, UI_APP_COMPONENT_KEY, UI_APP_OPTIONS_KEY, UI_APP_URI_KEY, __name, getGPTAppMetadata, getGPTAppUri, getUIAppMetadata, getUIAppUri };
|
|
123
|
-
//# sourceMappingURL=chunk-
|
|
124
|
-
//# sourceMappingURL=chunk-
|
|
123
|
+
//# sourceMappingURL=chunk-5RGEQPF3.mjs.map
|
|
124
|
+
//# sourceMappingURL=chunk-5RGEQPF3.mjs.map
|