@seed-ship/mcp-ui-solid 1.1.0 → 1.2.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/CHANGELOG.md +41 -0
- package/README.md +94 -3
- package/dist/components/FooterRenderer.cjs +75 -0
- package/dist/components/FooterRenderer.cjs.map +1 -0
- package/dist/components/FooterRenderer.js +75 -0
- package/dist/components/FooterRenderer.js.map +1 -0
- package/dist/components/GridRenderer.cjs +82 -0
- package/dist/components/GridRenderer.cjs.map +1 -0
- package/dist/components/GridRenderer.d.ts +49 -0
- package/dist/components/GridRenderer.d.ts.map +1 -0
- package/dist/components/GridRenderer.js +82 -0
- package/dist/components/GridRenderer.js.map +1 -0
- package/dist/components/UIResourceRenderer.cjs +88 -36
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +90 -38
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/dist/context/MCPActionContext.cjs +149 -0
- package/dist/context/MCPActionContext.cjs.map +1 -0
- package/dist/context/MCPActionContext.d.ts +158 -0
- package/dist/context/MCPActionContext.d.ts.map +1 -0
- package/dist/context/MCPActionContext.js +149 -0
- package/dist/context/MCPActionContext.js.map +1 -0
- package/dist/context/index.d.ts +8 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useAction.cjs +49 -0
- package/dist/hooks/useAction.cjs.map +1 -0
- package/dist/hooks/useAction.d.ts +79 -0
- package/dist/hooks/useAction.d.ts.map +1 -0
- package/dist/hooks/useAction.js +49 -0
- package/dist/hooks/useAction.js.map +1 -0
- package/dist/hooks.cjs +3 -0
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.ts +2 -0
- package/dist/hooks.js +4 -1
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +8 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +41 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types.d.ts +41 -2
- package/package.json +1 -1
- package/src/components/GridRenderer.tsx +140 -0
- package/src/components/UIResourceRenderer.tsx +65 -25
- package/src/context/MCPActionContext.tsx +350 -0
- package/src/context/index.ts +19 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useAction.ts +138 -0
- package/src/index.ts +14 -1
- package/src/types/index.ts +48 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -9,6 +9,9 @@ import { isServer } from 'solid-js/web'
|
|
|
9
9
|
import type { UIComponent, UILayout, RendererError, ComponentType } from '../types'
|
|
10
10
|
import { validateComponent, DEFAULT_RESOURCE_LIMITS } from '../services/validation'
|
|
11
11
|
import { GenerativeUIErrorBoundary } from './GenerativeUIErrorBoundary'
|
|
12
|
+
import { GridRenderer } from './GridRenderer'
|
|
13
|
+
import { FooterRenderer } from './FooterRenderer'
|
|
14
|
+
import { useAction } from '../hooks/useAction'
|
|
12
15
|
import { marked } from 'marked'
|
|
13
16
|
|
|
14
17
|
/**
|
|
@@ -554,43 +557,32 @@ function ComponentRenderer(props: {
|
|
|
554
557
|
<Show when={props.component.type === 'action'}>
|
|
555
558
|
<ActionRenderer component={props.component} />
|
|
556
559
|
</Show>
|
|
560
|
+
<Show when={props.component.type === 'grid'}>
|
|
561
|
+
<GridRenderer component={props.component} onError={props.onError} />
|
|
562
|
+
</Show>
|
|
557
563
|
</GenerativeUIErrorBoundary>
|
|
558
564
|
)
|
|
559
565
|
}
|
|
560
566
|
|
|
561
567
|
/**
|
|
562
568
|
* Render an action component (button or link)
|
|
569
|
+
* Refactored in Phase 5.0 to use useAction hook for Context-based execution
|
|
563
570
|
*/
|
|
564
571
|
function ActionRenderer(props: { component: UIComponent }) {
|
|
565
572
|
const params = props.component.params as any
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
// Initialize CustomEvent dispatcher only on client-side
|
|
569
|
-
// Use createEffect instead of onMount for SSR compatibility
|
|
570
|
-
createEffect(() => {
|
|
571
|
-
if (typeof window !== 'undefined') {
|
|
572
|
-
dispatchAction = (toolName: string, toolParams: any) => {
|
|
573
|
-
const event = new CustomEvent('mcp-action', {
|
|
574
|
-
detail: {
|
|
575
|
-
toolName,
|
|
576
|
-
params: toolParams,
|
|
577
|
-
},
|
|
578
|
-
bubbles: true,
|
|
579
|
-
})
|
|
580
|
-
window.dispatchEvent(event)
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
})
|
|
573
|
+
const { execute, isExecuting } = useAction()
|
|
584
574
|
|
|
585
|
-
// Handle click to execute tool via
|
|
586
|
-
const handleClick = (e: MouseEvent) => {
|
|
575
|
+
// Handle click to execute tool via Context (falls back to CustomEvent if no provider)
|
|
576
|
+
const handleClick = async (e: MouseEvent) => {
|
|
587
577
|
if (params.action === 'tool-call' && params.toolName) {
|
|
588
578
|
e.preventDefault()
|
|
589
|
-
|
|
590
|
-
dispatchAction?.(params.toolName, params.params || {})
|
|
579
|
+
await execute(params.toolName, params.params || {})
|
|
591
580
|
}
|
|
592
581
|
}
|
|
593
582
|
|
|
583
|
+
// Determine if button should be disabled (explicit disable or currently executing)
|
|
584
|
+
const isDisabled = () => params.disabled || (params.action === 'tool-call' && isExecuting())
|
|
585
|
+
|
|
594
586
|
if (params.type === 'link' || params.action === 'link') {
|
|
595
587
|
return (
|
|
596
588
|
<a
|
|
@@ -614,18 +606,21 @@ function ActionRenderer(props: { component: UIComponent }) {
|
|
|
614
606
|
return (
|
|
615
607
|
<button
|
|
616
608
|
type={params.action === 'submit' ? 'submit' : 'button'}
|
|
617
|
-
disabled={
|
|
609
|
+
disabled={isDisabled()}
|
|
618
610
|
class={`inline-flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
|
|
619
611
|
${params.variant === 'primary' ? 'bg-blue-600 text-white hover:bg-blue-700 shadow-sm' :
|
|
620
612
|
params.variant === 'secondary' ? 'bg-gray-100 text-gray-900 hover:bg-gray-200 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600' :
|
|
621
613
|
params.variant === 'outline' ? 'border border-gray-300 text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800' :
|
|
622
614
|
params.variant === 'danger' ? 'bg-red-600 text-white hover:bg-red-700' :
|
|
623
615
|
'bg-transparent text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800'}
|
|
624
|
-
${
|
|
616
|
+
${isDisabled() ? 'opacity-50 cursor-not-allowed' : ''}
|
|
625
617
|
${params.size === 'sm' ? 'px-3 py-1.5 text-xs' : params.size === 'lg' ? 'px-6 py-3 text-base' : ''}`}
|
|
626
618
|
onClick={handleClick}
|
|
627
619
|
>
|
|
628
|
-
<Show when={params.
|
|
620
|
+
<Show when={isExecuting() && params.action === 'tool-call'}>
|
|
621
|
+
<span class="animate-spin h-4 w-4 border-2 border-current border-t-transparent rounded-full" />
|
|
622
|
+
</Show>
|
|
623
|
+
<Show when={params.icon && !(isExecuting() && params.action === 'tool-call')}>
|
|
629
624
|
<span>{params.icon}</span>
|
|
630
625
|
</Show>
|
|
631
626
|
{params.label}
|
|
@@ -668,6 +663,46 @@ export const UIResourceRenderer: Component<UIResourceRendererProps> = (props) =>
|
|
|
668
663
|
return `grid-column: ${colStart} / span ${colSpan}; grid-row: ${rowStart ? `${rowStart} / span ${rowSpan}` : 'auto'}`
|
|
669
664
|
}
|
|
670
665
|
|
|
666
|
+
// Auto-footer logic (Phase 5.0)
|
|
667
|
+
// Automatically inject footer when metadata is present and no explicit footer exists
|
|
668
|
+
const shouldShowAutoFooter = createMemo(() => {
|
|
669
|
+
const layoutData = layout()
|
|
670
|
+
|
|
671
|
+
// Don't show if explicitly hidden
|
|
672
|
+
if (layoutData.metadata?.hideFooter) {
|
|
673
|
+
return false
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Don't show if no metadata (nothing to display)
|
|
677
|
+
if (!layoutData.metadata) {
|
|
678
|
+
return false
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Don't show if explicit footer component exists
|
|
682
|
+
const hasExplicitFooter = layoutData.components.some((c) => c.type === 'footer')
|
|
683
|
+
if (hasExplicitFooter) {
|
|
684
|
+
return false
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Show auto-footer if metadata has relevant info
|
|
688
|
+
return !!(
|
|
689
|
+
layoutData.metadata.executionTime ||
|
|
690
|
+
layoutData.metadata.sourceCount ||
|
|
691
|
+
layoutData.metadata.llmModel
|
|
692
|
+
)
|
|
693
|
+
})
|
|
694
|
+
|
|
695
|
+
// Build auto-footer params from metadata
|
|
696
|
+
const autoFooterParams = createMemo(() => {
|
|
697
|
+
const layoutData = layout()
|
|
698
|
+
return {
|
|
699
|
+
poweredBy: 'Deposium',
|
|
700
|
+
executionTime: layoutData.metadata?.executionTime,
|
|
701
|
+
model: layoutData.metadata?.llmModel,
|
|
702
|
+
sourceCount: layoutData.metadata?.sourceCount,
|
|
703
|
+
}
|
|
704
|
+
})
|
|
705
|
+
|
|
671
706
|
const layoutData = layout()
|
|
672
707
|
|
|
673
708
|
return (
|
|
@@ -681,6 +716,11 @@ export const UIResourceRenderer: Component<UIResourceRendererProps> = (props) =>
|
|
|
681
716
|
)}
|
|
682
717
|
</For>
|
|
683
718
|
</div>
|
|
719
|
+
|
|
720
|
+
{/* Auto-injected footer (Phase 5.0) */}
|
|
721
|
+
<Show when={shouldShowAutoFooter()}>
|
|
722
|
+
<FooterRenderer params={autoFooterParams()} />
|
|
723
|
+
</Show>
|
|
684
724
|
</div>
|
|
685
725
|
)
|
|
686
726
|
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCPActionContext - Context provider for MCP action execution
|
|
3
|
+
* Phase 5.0: Quick Wins - Replaces CustomEvent with typed context for Mastra integration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createContext, createSignal, useContext, ParentComponent, Accessor } from 'solid-js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Action request payload
|
|
10
|
+
*/
|
|
11
|
+
export interface ActionRequest {
|
|
12
|
+
/**
|
|
13
|
+
* MCP tool name to execute
|
|
14
|
+
*/
|
|
15
|
+
toolName: string
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Tool parameters
|
|
19
|
+
*/
|
|
20
|
+
params?: Record<string, any>
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Optional space IDs for multi-space context
|
|
24
|
+
*/
|
|
25
|
+
spaceIds?: string[]
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Optional macro ID for template execution
|
|
29
|
+
*/
|
|
30
|
+
macroId?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Action result from execution
|
|
35
|
+
*/
|
|
36
|
+
export interface ActionResult {
|
|
37
|
+
/**
|
|
38
|
+
* Whether the action was successful
|
|
39
|
+
*/
|
|
40
|
+
success: boolean
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Result data (if successful)
|
|
44
|
+
*/
|
|
45
|
+
data?: any
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Error message (if failed)
|
|
49
|
+
*/
|
|
50
|
+
error?: string
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Execution timestamp
|
|
54
|
+
*/
|
|
55
|
+
timestamp: string
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Tool that was executed
|
|
59
|
+
*/
|
|
60
|
+
toolName: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Context value interface
|
|
65
|
+
*/
|
|
66
|
+
export interface MCPActionContextValue {
|
|
67
|
+
/**
|
|
68
|
+
* Execute an MCP action
|
|
69
|
+
*/
|
|
70
|
+
executeAction: (request: ActionRequest) => Promise<ActionResult>
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Currently available tools (from MCP server)
|
|
74
|
+
*/
|
|
75
|
+
availableTools: Accessor<string[]>
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Space IDs in current context
|
|
79
|
+
*/
|
|
80
|
+
spaceIds: Accessor<string[]>
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Current macro ID (if executing within a template)
|
|
84
|
+
*/
|
|
85
|
+
macroId: Accessor<string | undefined>
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Whether an action is currently executing
|
|
89
|
+
*/
|
|
90
|
+
isExecuting: Accessor<boolean>
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Last action result
|
|
94
|
+
*/
|
|
95
|
+
lastResult: Accessor<ActionResult | undefined>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Props for MCPActionProvider
|
|
100
|
+
*/
|
|
101
|
+
export interface MCPActionProviderProps {
|
|
102
|
+
/**
|
|
103
|
+
* Space IDs for multi-space queries
|
|
104
|
+
*/
|
|
105
|
+
spaceIds?: string[]
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Macro ID when executing within a template
|
|
109
|
+
*/
|
|
110
|
+
macroId?: string
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Available MCP tools
|
|
114
|
+
*/
|
|
115
|
+
availableTools?: string[]
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Callback for action execution (for audit logging)
|
|
119
|
+
*/
|
|
120
|
+
onAction?: (request: ActionRequest, result: ActionResult) => void
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Callback for webhook events (n8n, Zapier integration)
|
|
124
|
+
*/
|
|
125
|
+
onWebhook?: (event: { type: string; payload: any }) => void
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Custom action executor (override default)
|
|
129
|
+
*/
|
|
130
|
+
executor?: (request: ActionRequest) => Promise<ActionResult>
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Create the context with undefined default
|
|
134
|
+
const MCPActionContext = createContext<MCPActionContextValue>()
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Default action executor using CustomEvent fallback
|
|
138
|
+
* This maintains backward compatibility while allowing Context-based usage
|
|
139
|
+
*/
|
|
140
|
+
const defaultExecutor = async (request: ActionRequest): Promise<ActionResult> => {
|
|
141
|
+
return new Promise((resolve) => {
|
|
142
|
+
const timestamp = new Date().toISOString()
|
|
143
|
+
|
|
144
|
+
// Dispatch CustomEvent for backward compatibility with existing listeners
|
|
145
|
+
if (typeof window !== 'undefined') {
|
|
146
|
+
const event = new CustomEvent('mcp-action', {
|
|
147
|
+
detail: {
|
|
148
|
+
toolName: request.toolName,
|
|
149
|
+
params: request.params || {},
|
|
150
|
+
spaceIds: request.spaceIds,
|
|
151
|
+
macroId: request.macroId,
|
|
152
|
+
},
|
|
153
|
+
bubbles: true,
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
// Listen for response event
|
|
157
|
+
const responseHandler = (e: Event) => {
|
|
158
|
+
const customEvent = e as CustomEvent
|
|
159
|
+
window.removeEventListener('mcp-action-response', responseHandler)
|
|
160
|
+
resolve({
|
|
161
|
+
success: customEvent.detail?.success ?? true,
|
|
162
|
+
data: customEvent.detail?.data,
|
|
163
|
+
error: customEvent.detail?.error,
|
|
164
|
+
timestamp,
|
|
165
|
+
toolName: request.toolName,
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
window.addEventListener('mcp-action-response', responseHandler)
|
|
170
|
+
window.dispatchEvent(event)
|
|
171
|
+
|
|
172
|
+
// Timeout fallback - resolve as success if no response in 100ms
|
|
173
|
+
// (indicates no listener, action was dispatched)
|
|
174
|
+
setTimeout(() => {
|
|
175
|
+
window.removeEventListener('mcp-action-response', responseHandler)
|
|
176
|
+
resolve({
|
|
177
|
+
success: true,
|
|
178
|
+
data: { dispatched: true },
|
|
179
|
+
timestamp,
|
|
180
|
+
toolName: request.toolName,
|
|
181
|
+
})
|
|
182
|
+
}, 100)
|
|
183
|
+
} else {
|
|
184
|
+
// Server-side: return immediately
|
|
185
|
+
resolve({
|
|
186
|
+
success: false,
|
|
187
|
+
error: 'Actions not available server-side',
|
|
188
|
+
timestamp,
|
|
189
|
+
toolName: request.toolName,
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* MCPActionProvider - Provides action execution context to child components
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```tsx
|
|
200
|
+
* <MCPActionProvider
|
|
201
|
+
* spaceIds={['space-123']}
|
|
202
|
+
* macroId="sales_overview"
|
|
203
|
+
* onAction={(req, res) => audit(req, res)}
|
|
204
|
+
* >
|
|
205
|
+
* <UIResourceRenderer layout={layout} />
|
|
206
|
+
* </MCPActionProvider>
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
export const MCPActionProvider: ParentComponent<MCPActionProviderProps> = (props) => {
|
|
210
|
+
const [isExecuting, setIsExecuting] = createSignal(false)
|
|
211
|
+
const [lastResult, setLastResult] = createSignal<ActionResult>()
|
|
212
|
+
const [spaceIds, setSpaceIds] = createSignal<string[]>(props.spaceIds || [])
|
|
213
|
+
const [macroId, setMacroId] = createSignal<string | undefined>(props.macroId)
|
|
214
|
+
const [availableTools, setAvailableTools] = createSignal<string[]>(props.availableTools || [])
|
|
215
|
+
|
|
216
|
+
// Update signals when props change
|
|
217
|
+
// Note: This is a simple approach; for more complex scenarios, consider createEffect
|
|
218
|
+
|
|
219
|
+
const executeAction = async (request: ActionRequest): Promise<ActionResult> => {
|
|
220
|
+
setIsExecuting(true)
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
// Enrich request with context
|
|
224
|
+
const enrichedRequest: ActionRequest = {
|
|
225
|
+
...request,
|
|
226
|
+
spaceIds: request.spaceIds || spaceIds(),
|
|
227
|
+
macroId: request.macroId || macroId(),
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Execute using custom executor or default
|
|
231
|
+
const executor = props.executor || defaultExecutor
|
|
232
|
+
const result = await executor(enrichedRequest)
|
|
233
|
+
|
|
234
|
+
setLastResult(result)
|
|
235
|
+
|
|
236
|
+
// Call audit callback if provided
|
|
237
|
+
props.onAction?.(enrichedRequest, result)
|
|
238
|
+
|
|
239
|
+
// Trigger webhook if provided and action was successful
|
|
240
|
+
if (result.success && props.onWebhook) {
|
|
241
|
+
props.onWebhook({
|
|
242
|
+
type: 'action-completed',
|
|
243
|
+
payload: {
|
|
244
|
+
request: enrichedRequest,
|
|
245
|
+
result,
|
|
246
|
+
},
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return result
|
|
251
|
+
} catch (error) {
|
|
252
|
+
const errorResult: ActionResult = {
|
|
253
|
+
success: false,
|
|
254
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
255
|
+
timestamp: new Date().toISOString(),
|
|
256
|
+
toolName: request.toolName,
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
setLastResult(errorResult)
|
|
260
|
+
props.onAction?.(request, errorResult)
|
|
261
|
+
|
|
262
|
+
return errorResult
|
|
263
|
+
} finally {
|
|
264
|
+
setIsExecuting(false)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const contextValue: MCPActionContextValue = {
|
|
269
|
+
executeAction,
|
|
270
|
+
availableTools,
|
|
271
|
+
spaceIds,
|
|
272
|
+
macroId,
|
|
273
|
+
isExecuting,
|
|
274
|
+
lastResult,
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return (
|
|
278
|
+
<MCPActionContext.Provider value={contextValue}>
|
|
279
|
+
{props.children}
|
|
280
|
+
</MCPActionContext.Provider>
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Hook to access MCP action context
|
|
286
|
+
* Throws if used outside of MCPActionProvider
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```tsx
|
|
290
|
+
* const { executeAction, isExecuting } = useMCPAction()
|
|
291
|
+
*
|
|
292
|
+
* const handleClick = async () => {
|
|
293
|
+
* const result = await executeAction({
|
|
294
|
+
* toolName: 'search.hub',
|
|
295
|
+
* params: { query: 'revenue Q4' },
|
|
296
|
+
* })
|
|
297
|
+
* }
|
|
298
|
+
* ```
|
|
299
|
+
*/
|
|
300
|
+
export function useMCPAction(): MCPActionContextValue {
|
|
301
|
+
const context = useContext(MCPActionContext)
|
|
302
|
+
if (!context) {
|
|
303
|
+
throw new Error('useMCPAction must be used within an MCPActionProvider')
|
|
304
|
+
}
|
|
305
|
+
return context
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Hook to access MCP action context with fallback for components
|
|
310
|
+
* outside of provider (uses CustomEvent fallback)
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* ```tsx
|
|
314
|
+
* const { executeAction, isExecuting } = useMCPActionSafe()
|
|
315
|
+
* // Works even without MCPActionProvider
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
318
|
+
export function useMCPActionSafe(): MCPActionContextValue {
|
|
319
|
+
const context = useContext(MCPActionContext)
|
|
320
|
+
|
|
321
|
+
if (context) {
|
|
322
|
+
return context
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Fallback implementation for components outside provider
|
|
326
|
+
const [isExecuting, setIsExecuting] = createSignal(false)
|
|
327
|
+
const [lastResult, setLastResult] = createSignal<ActionResult>()
|
|
328
|
+
|
|
329
|
+
const executeAction = async (request: ActionRequest): Promise<ActionResult> => {
|
|
330
|
+
setIsExecuting(true)
|
|
331
|
+
try {
|
|
332
|
+
const result = await defaultExecutor(request)
|
|
333
|
+
setLastResult(result)
|
|
334
|
+
return result
|
|
335
|
+
} finally {
|
|
336
|
+
setIsExecuting(false)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
executeAction,
|
|
342
|
+
availableTools: () => [],
|
|
343
|
+
spaceIds: () => [],
|
|
344
|
+
macroId: () => undefined,
|
|
345
|
+
isExecuting,
|
|
346
|
+
lastResult,
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export { MCPActionContext }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP UI Solid - Context Providers
|
|
3
|
+
*
|
|
4
|
+
* Context providers for action execution and state management
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
MCPActionProvider,
|
|
9
|
+
MCPActionContext,
|
|
10
|
+
useMCPAction,
|
|
11
|
+
useMCPActionSafe,
|
|
12
|
+
} from './MCPActionContext'
|
|
13
|
+
|
|
14
|
+
export type {
|
|
15
|
+
MCPActionContextValue,
|
|
16
|
+
MCPActionProviderProps,
|
|
17
|
+
ActionRequest,
|
|
18
|
+
ActionResult,
|
|
19
|
+
} from './MCPActionContext'
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAction - Hook for executing MCP actions from components
|
|
3
|
+
* Phase 5.0: Quick Wins - Simplified API for ActionRenderer and custom components
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createSignal, Accessor } from 'solid-js'
|
|
7
|
+
import { useMCPActionSafe, ActionRequest, ActionResult } from '../context/MCPActionContext'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Return type for useAction hook
|
|
11
|
+
*/
|
|
12
|
+
export interface UseActionReturn {
|
|
13
|
+
/**
|
|
14
|
+
* Execute a tool action
|
|
15
|
+
*/
|
|
16
|
+
execute: (toolName: string, params?: Record<string, any>) => Promise<ActionResult>
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Full execute with ActionRequest
|
|
20
|
+
*/
|
|
21
|
+
executeAction: (request: ActionRequest) => Promise<ActionResult>
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether an action is currently executing
|
|
25
|
+
*/
|
|
26
|
+
isExecuting: Accessor<boolean>
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Last result from action execution
|
|
30
|
+
*/
|
|
31
|
+
lastResult: Accessor<ActionResult | undefined>
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Last error (if any)
|
|
35
|
+
*/
|
|
36
|
+
lastError: Accessor<string | undefined>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Hook for executing MCP actions
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```tsx
|
|
44
|
+
* function MyButton() {
|
|
45
|
+
* const { execute, isExecuting, lastError } = useAction()
|
|
46
|
+
*
|
|
47
|
+
* const handleClick = async () => {
|
|
48
|
+
* const result = await execute('search.hub', { query: 'test' })
|
|
49
|
+
* if (result.success) {
|
|
50
|
+
* console.log('Data:', result.data)
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* return (
|
|
55
|
+
* <button onClick={handleClick} disabled={isExecuting()}>
|
|
56
|
+
* {isExecuting() ? 'Loading...' : 'Search'}
|
|
57
|
+
* </button>
|
|
58
|
+
* )
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function useAction(): UseActionReturn {
|
|
63
|
+
const context = useMCPActionSafe()
|
|
64
|
+
const [lastError, setLastError] = createSignal<string>()
|
|
65
|
+
|
|
66
|
+
const execute = async (toolName: string, params?: Record<string, any>): Promise<ActionResult> => {
|
|
67
|
+
setLastError(undefined)
|
|
68
|
+
|
|
69
|
+
const result = await context.executeAction({
|
|
70
|
+
toolName,
|
|
71
|
+
params,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
if (!result.success && result.error) {
|
|
75
|
+
setLastError(result.error)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const executeAction = async (request: ActionRequest): Promise<ActionResult> => {
|
|
82
|
+
setLastError(undefined)
|
|
83
|
+
|
|
84
|
+
const result = await context.executeAction(request)
|
|
85
|
+
|
|
86
|
+
if (!result.success && result.error) {
|
|
87
|
+
setLastError(result.error)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return result
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
execute,
|
|
95
|
+
executeAction,
|
|
96
|
+
isExecuting: context.isExecuting,
|
|
97
|
+
lastResult: context.lastResult,
|
|
98
|
+
lastError,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Hook for binding action to a specific tool
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```tsx
|
|
107
|
+
* function SearchButton() {
|
|
108
|
+
* const { execute, isExecuting } = useToolAction('search.hub')
|
|
109
|
+
*
|
|
110
|
+
* return (
|
|
111
|
+
* <button onClick={() => execute({ query: 'test' })} disabled={isExecuting()}>
|
|
112
|
+
* Search
|
|
113
|
+
* </button>
|
|
114
|
+
* )
|
|
115
|
+
* }
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export function useToolAction(toolName: string): {
|
|
119
|
+
execute: (params?: Record<string, any>) => Promise<ActionResult>
|
|
120
|
+
isExecuting: Accessor<boolean>
|
|
121
|
+
lastResult: Accessor<ActionResult | undefined>
|
|
122
|
+
lastError: Accessor<string | undefined>
|
|
123
|
+
} {
|
|
124
|
+
const { execute: baseExecute, isExecuting, lastResult, lastError } = useAction()
|
|
125
|
+
|
|
126
|
+
const execute = async (params?: Record<string, any>): Promise<ActionResult> => {
|
|
127
|
+
return baseExecute(toolName, params)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
execute,
|
|
132
|
+
isExecuting,
|
|
133
|
+
lastResult,
|
|
134
|
+
lastError,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export type { ActionRequest, ActionResult }
|
package/src/index.ts
CHANGED
|
@@ -38,7 +38,7 @@ export type {
|
|
|
38
38
|
} from './components'
|
|
39
39
|
|
|
40
40
|
// Hooks
|
|
41
|
-
export { useStreamingUI } from './hooks'
|
|
41
|
+
export { useStreamingUI, useAction, useToolAction } from './hooks'
|
|
42
42
|
|
|
43
43
|
export type {
|
|
44
44
|
UseStreamingUIOptions,
|
|
@@ -46,8 +46,19 @@ export type {
|
|
|
46
46
|
StreamProgress,
|
|
47
47
|
StreamError,
|
|
48
48
|
CompleteMetadata,
|
|
49
|
+
UseActionReturn,
|
|
49
50
|
} from './hooks'
|
|
50
51
|
|
|
52
|
+
// Context (Phase 5.0)
|
|
53
|
+
export { MCPActionProvider, MCPActionContext, useMCPAction, useMCPActionSafe } from './context'
|
|
54
|
+
|
|
55
|
+
export type {
|
|
56
|
+
MCPActionContextValue,
|
|
57
|
+
MCPActionProviderProps,
|
|
58
|
+
ActionRequest,
|
|
59
|
+
ActionResult,
|
|
60
|
+
} from './context'
|
|
61
|
+
|
|
51
62
|
// Types
|
|
52
63
|
export type {
|
|
53
64
|
UIComponent,
|
|
@@ -59,6 +70,8 @@ export type {
|
|
|
59
70
|
TableComponentParams,
|
|
60
71
|
MetricComponentParams,
|
|
61
72
|
TextComponentParams,
|
|
73
|
+
ActionComponentParams,
|
|
74
|
+
GridComponentParams,
|
|
62
75
|
} from './types'
|
|
63
76
|
|
|
64
77
|
// Services
|