@seed-ship/mcp-ui-solid 6.6.0 → 6.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.
@@ -12,7 +12,10 @@ const defaultExecutor = async (request) => {
12
12
  toolName: request.toolName,
13
13
  params: request.params || {},
14
14
  spaceIds: request.spaceIds,
15
- macroId: request.macroId
15
+ macroId: request.macroId,
16
+ // v6.6.1 — action kind so a window-level listener can route a
17
+ // `submit` (POST to params.submit_url) vs a tool call.
18
+ action: request.action ?? "tool-call"
16
19
  },
17
20
  bubbles: true
18
21
  });
@@ -1 +1 @@
1
- {"version":3,"file":"MCPActionContext.cjs","sources":["../../src/context/MCPActionContext.tsx"],"sourcesContent":["/**\n * MCPActionContext - Context provider for MCP action execution\n * Phase 5.0: Quick Wins - Replaces CustomEvent with typed context for Mastra integration\n */\n\nimport { createContext, createSignal, useContext, ParentComponent, Accessor } from 'solid-js'\n\n/**\n * Action request payload\n */\nexport interface ActionRequest {\n /**\n * MCP tool name to execute\n */\n toolName: string\n\n /**\n * Tool parameters\n */\n params?: Record<string, any>\n\n /**\n * Optional space IDs for multi-space context\n */\n spaceIds?: string[]\n\n /**\n * Optional macro ID for template execution\n */\n macroId?: string\n}\n\n/**\n * Action result from execution\n */\nexport interface ActionResult {\n /**\n * Whether the action was successful\n */\n success: boolean\n\n /**\n * Result data (if successful)\n */\n data?: any\n\n /**\n * Error message (if failed)\n */\n error?: string\n\n /**\n * Execution timestamp\n */\n timestamp: string\n\n /**\n * Tool that was executed\n */\n toolName: string\n}\n\n/**\n * Context value interface\n */\nexport interface MCPActionContextValue {\n /**\n * Execute an MCP action\n */\n executeAction: (request: ActionRequest) => Promise<ActionResult>\n\n /**\n * Currently available tools (from MCP server)\n */\n availableTools: Accessor<string[]>\n\n /**\n * Space IDs in current context\n */\n spaceIds: Accessor<string[]>\n\n /**\n * Current macro ID (if executing within a template)\n */\n macroId: Accessor<string | undefined>\n\n /**\n * Whether an action is currently executing\n */\n isExecuting: Accessor<boolean>\n\n /**\n * Last action result\n */\n lastResult: Accessor<ActionResult | undefined>\n}\n\n/**\n * Props for MCPActionProvider\n */\nexport interface MCPActionProviderProps {\n /**\n * Space IDs for multi-space queries\n */\n spaceIds?: string[]\n\n /**\n * Macro ID when executing within a template\n */\n macroId?: string\n\n /**\n * Available MCP tools\n */\n availableTools?: string[]\n\n /**\n * Callback for action execution (for audit logging)\n */\n onAction?: (request: ActionRequest, result: ActionResult) => void\n\n /**\n * Callback for webhook events (n8n, Zapier integration)\n */\n onWebhook?: (event: { type: string; payload: any }) => void\n\n /**\n * Custom action executor (override default)\n */\n executor?: (request: ActionRequest) => Promise<ActionResult>\n}\n\n// Create the context with undefined default\nconst MCPActionContext = createContext<MCPActionContextValue>()\n\n/**\n * Default action executor using CustomEvent fallback\n * This maintains backward compatibility while allowing Context-based usage\n */\nconst defaultExecutor = async (request: ActionRequest): Promise<ActionResult> => {\n return new Promise((resolve) => {\n const timestamp = new Date().toISOString()\n\n // Dispatch CustomEvent for backward compatibility with existing listeners\n if (typeof window !== 'undefined') {\n const event = new CustomEvent('mcp-action', {\n detail: {\n toolName: request.toolName,\n params: request.params || {},\n spaceIds: request.spaceIds,\n macroId: request.macroId,\n },\n bubbles: true,\n })\n\n // Listen for response event\n const responseHandler = (e: Event) => {\n const customEvent = e as CustomEvent\n window.removeEventListener('mcp-action-response', responseHandler)\n resolve({\n success: customEvent.detail?.success ?? true,\n data: customEvent.detail?.data,\n error: customEvent.detail?.error,\n timestamp,\n toolName: request.toolName,\n })\n }\n\n window.addEventListener('mcp-action-response', responseHandler)\n window.dispatchEvent(event)\n\n // Timeout fallback - resolve as success if no response in 100ms\n // (indicates no listener, action was dispatched)\n setTimeout(() => {\n window.removeEventListener('mcp-action-response', responseHandler)\n resolve({\n success: true,\n data: { dispatched: true },\n timestamp,\n toolName: request.toolName,\n })\n }, 100)\n } else {\n // Server-side: return immediately\n resolve({\n success: false,\n error: 'Actions not available server-side',\n timestamp,\n toolName: request.toolName,\n })\n }\n })\n}\n\n/**\n * MCPActionProvider - Provides action execution context to child components\n *\n * @example\n * ```tsx\n * <MCPActionProvider\n * spaceIds={['space-123']}\n * macroId=\"sales_overview\"\n * onAction={(req, res) => audit(req, res)}\n * >\n * <UIResourceRenderer layout={layout} />\n * </MCPActionProvider>\n * ```\n */\nexport const MCPActionProvider: ParentComponent<MCPActionProviderProps> = (props) => {\n const [isExecuting, setIsExecuting] = createSignal(false)\n const [lastResult, setLastResult] = createSignal<ActionResult>()\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const [spaceIds, _setSpaceIds] = createSignal<string[]>(props.spaceIds || [])\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const [macroId, _setMacroId] = createSignal<string | undefined>(props.macroId)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const [availableTools, _setAvailableTools] = createSignal<string[]>(props.availableTools || [])\n\n // Update signals when props change\n // Note: This is a simple approach; for more complex scenarios, consider createEffect\n\n const executeAction = async (request: ActionRequest): Promise<ActionResult> => {\n setIsExecuting(true)\n\n try {\n // Enrich request with context\n const enrichedRequest: ActionRequest = {\n ...request,\n spaceIds: request.spaceIds || spaceIds(),\n macroId: request.macroId || macroId(),\n }\n\n // Execute using custom executor or default\n const executor = props.executor || defaultExecutor\n const result = await executor(enrichedRequest)\n\n setLastResult(result)\n\n // Call audit callback if provided\n props.onAction?.(enrichedRequest, result)\n\n // Trigger webhook if provided and action was successful\n if (result.success && props.onWebhook) {\n props.onWebhook({\n type: 'action-completed',\n payload: {\n request: enrichedRequest,\n result,\n },\n })\n }\n\n return result\n } catch (error) {\n const errorResult: ActionResult = {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n timestamp: new Date().toISOString(),\n toolName: request.toolName,\n }\n\n setLastResult(errorResult)\n props.onAction?.(request, errorResult)\n\n return errorResult\n } finally {\n setIsExecuting(false)\n }\n }\n\n const contextValue: MCPActionContextValue = {\n executeAction,\n availableTools,\n spaceIds,\n macroId,\n isExecuting,\n lastResult,\n }\n\n return (\n <MCPActionContext.Provider value={contextValue}>\n {props.children}\n </MCPActionContext.Provider>\n )\n}\n\n/**\n * Hook to access MCP action context\n * Throws if used outside of MCPActionProvider\n *\n * @example\n * ```tsx\n * const { executeAction, isExecuting } = useMCPAction()\n *\n * const handleClick = async () => {\n * const result = await executeAction({\n * toolName: 'search.hub',\n * params: { query: 'revenue Q4' },\n * })\n * }\n * ```\n */\nexport function useMCPAction(): MCPActionContextValue {\n const context = useContext(MCPActionContext)\n if (!context) {\n throw new Error('useMCPAction must be used within an MCPActionProvider')\n }\n return context\n}\n\n/**\n * Hook to access MCP action context with fallback for components\n * outside of provider (uses CustomEvent fallback)\n *\n * @example\n * ```tsx\n * const { executeAction, isExecuting } = useMCPActionSafe()\n * // Works even without MCPActionProvider\n * ```\n */\nexport function useMCPActionSafe(): MCPActionContextValue {\n const context = useContext(MCPActionContext)\n\n if (context) {\n return context\n }\n\n // Fallback implementation for components outside provider\n const [isExecuting, setIsExecuting] = createSignal(false)\n const [lastResult, setLastResult] = createSignal<ActionResult>()\n\n const executeAction = async (request: ActionRequest): Promise<ActionResult> => {\n setIsExecuting(true)\n try {\n const result = await defaultExecutor(request)\n setLastResult(result)\n return result\n } finally {\n setIsExecuting(false)\n }\n }\n\n return {\n executeAction,\n availableTools: () => [],\n spaceIds: () => [],\n macroId: () => undefined,\n isExecuting,\n lastResult,\n }\n}\n\nexport { MCPActionContext }\n"],"names":["MCPActionContext","createContext","defaultExecutor","request","Promise","resolve","timestamp","Date","toISOString","window","event","CustomEvent","detail","toolName","params","spaceIds","macroId","bubbles","responseHandler","e","customEvent","removeEventListener","success","data","error","addEventListener","dispatchEvent","setTimeout","dispatched","MCPActionProvider","props","isExecuting","setIsExecuting","createSignal","lastResult","setLastResult","_setSpaceIds","_setMacroId","availableTools","_setAvailableTools","executeAction","enrichedRequest","executor","result","onAction","onWebhook","type","payload","errorResult","Error","message","contextValue","_$createComponent","Provider","value","children","useMCPAction","context","useContext","useMCPActionSafe","undefined"],"mappings":"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"}
1
+ {"version":3,"file":"MCPActionContext.cjs","sources":["../../src/context/MCPActionContext.tsx"],"sourcesContent":["/**\n * MCPActionContext - Context provider for MCP action execution\n * Phase 5.0: Quick Wins - Replaces CustomEvent with typed context for Mastra integration\n */\n\nimport { createContext, createSignal, useContext, ParentComponent, Accessor } from 'solid-js'\n\n/**\n * Action request payload\n */\nexport interface ActionRequest {\n /**\n * MCP tool name to execute. For a `submit` action with no associated\n * tool, renderers pass the sentinel `'submit'` — branch on `action`,\n * not on `toolName`, to tell a submit apart from a tool call.\n */\n toolName: string\n\n /**\n * Tool parameters\n */\n params?: Record<string, any>\n\n /**\n * Optional space IDs for multi-space context\n */\n spaceIds?: string[]\n\n /**\n * Optional macro ID for template execution\n */\n macroId?: string\n\n /**\n * Action kind (v6.6.1). Lets a host `executor` tell a tool call apart\n * from a form-style `submit`. Absent ⇒ treat as `'tool-call'` (backward\n * compatible — every pre-v6.6.1 request omits it).\n *\n * A `submit` action carries its payload in `params` (e.g. `submit_url`,\n * `connector_id`, `feedback_value`) and **must NOT** be executed as a\n * tool call — the host routes it (e.g. POST to `params.submit_url`).\n */\n action?: 'tool-call' | 'submit' | 'link'\n}\n\n/**\n * Action result from execution\n */\nexport interface ActionResult {\n /**\n * Whether the action was successful\n */\n success: boolean\n\n /**\n * Result data (if successful)\n */\n data?: any\n\n /**\n * Error message (if failed)\n */\n error?: string\n\n /**\n * Execution timestamp\n */\n timestamp: string\n\n /**\n * Tool that was executed\n */\n toolName: string\n}\n\n/**\n * Context value interface\n */\nexport interface MCPActionContextValue {\n /**\n * Execute an MCP action\n */\n executeAction: (request: ActionRequest) => Promise<ActionResult>\n\n /**\n * Currently available tools (from MCP server)\n */\n availableTools: Accessor<string[]>\n\n /**\n * Space IDs in current context\n */\n spaceIds: Accessor<string[]>\n\n /**\n * Current macro ID (if executing within a template)\n */\n macroId: Accessor<string | undefined>\n\n /**\n * Whether an action is currently executing\n */\n isExecuting: Accessor<boolean>\n\n /**\n * Last action result\n */\n lastResult: Accessor<ActionResult | undefined>\n}\n\n/**\n * Props for MCPActionProvider\n */\nexport interface MCPActionProviderProps {\n /**\n * Space IDs for multi-space queries\n */\n spaceIds?: string[]\n\n /**\n * Macro ID when executing within a template\n */\n macroId?: string\n\n /**\n * Available MCP tools\n */\n availableTools?: string[]\n\n /**\n * Callback for action execution (for audit logging)\n */\n onAction?: (request: ActionRequest, result: ActionResult) => void\n\n /**\n * Callback for webhook events (n8n, Zapier integration)\n */\n onWebhook?: (event: { type: string; payload: any }) => void\n\n /**\n * Custom action executor (override default)\n */\n executor?: (request: ActionRequest) => Promise<ActionResult>\n}\n\n// Create the context with undefined default\nconst MCPActionContext = createContext<MCPActionContextValue>()\n\n/**\n * Default action executor using CustomEvent fallback\n * This maintains backward compatibility while allowing Context-based usage\n */\nconst defaultExecutor = async (request: ActionRequest): Promise<ActionResult> => {\n return new Promise((resolve) => {\n const timestamp = new Date().toISOString()\n\n // Dispatch CustomEvent for backward compatibility with existing listeners\n if (typeof window !== 'undefined') {\n const event = new CustomEvent('mcp-action', {\n detail: {\n toolName: request.toolName,\n params: request.params || {},\n spaceIds: request.spaceIds,\n macroId: request.macroId,\n // v6.6.1 — action kind so a window-level listener can route a\n // `submit` (POST to params.submit_url) vs a tool call.\n action: request.action ?? 'tool-call',\n },\n bubbles: true,\n })\n\n // Listen for response event\n const responseHandler = (e: Event) => {\n const customEvent = e as CustomEvent\n window.removeEventListener('mcp-action-response', responseHandler)\n resolve({\n success: customEvent.detail?.success ?? true,\n data: customEvent.detail?.data,\n error: customEvent.detail?.error,\n timestamp,\n toolName: request.toolName,\n })\n }\n\n window.addEventListener('mcp-action-response', responseHandler)\n window.dispatchEvent(event)\n\n // Timeout fallback - resolve as success if no response in 100ms\n // (indicates no listener, action was dispatched)\n setTimeout(() => {\n window.removeEventListener('mcp-action-response', responseHandler)\n resolve({\n success: true,\n data: { dispatched: true },\n timestamp,\n toolName: request.toolName,\n })\n }, 100)\n } else {\n // Server-side: return immediately\n resolve({\n success: false,\n error: 'Actions not available server-side',\n timestamp,\n toolName: request.toolName,\n })\n }\n })\n}\n\n/**\n * MCPActionProvider - Provides action execution context to child components\n *\n * @example\n * ```tsx\n * <MCPActionProvider\n * spaceIds={['space-123']}\n * macroId=\"sales_overview\"\n * onAction={(req, res) => audit(req, res)}\n * >\n * <UIResourceRenderer layout={layout} />\n * </MCPActionProvider>\n * ```\n */\nexport const MCPActionProvider: ParentComponent<MCPActionProviderProps> = (props) => {\n const [isExecuting, setIsExecuting] = createSignal(false)\n const [lastResult, setLastResult] = createSignal<ActionResult>()\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const [spaceIds, _setSpaceIds] = createSignal<string[]>(props.spaceIds || [])\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const [macroId, _setMacroId] = createSignal<string | undefined>(props.macroId)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const [availableTools, _setAvailableTools] = createSignal<string[]>(props.availableTools || [])\n\n // Update signals when props change\n // Note: This is a simple approach; for more complex scenarios, consider createEffect\n\n const executeAction = async (request: ActionRequest): Promise<ActionResult> => {\n setIsExecuting(true)\n\n try {\n // Enrich request with context\n const enrichedRequest: ActionRequest = {\n ...request,\n spaceIds: request.spaceIds || spaceIds(),\n macroId: request.macroId || macroId(),\n }\n\n // Execute using custom executor or default\n const executor = props.executor || defaultExecutor\n const result = await executor(enrichedRequest)\n\n setLastResult(result)\n\n // Call audit callback if provided\n props.onAction?.(enrichedRequest, result)\n\n // Trigger webhook if provided and action was successful\n if (result.success && props.onWebhook) {\n props.onWebhook({\n type: 'action-completed',\n payload: {\n request: enrichedRequest,\n result,\n },\n })\n }\n\n return result\n } catch (error) {\n const errorResult: ActionResult = {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n timestamp: new Date().toISOString(),\n toolName: request.toolName,\n }\n\n setLastResult(errorResult)\n props.onAction?.(request, errorResult)\n\n return errorResult\n } finally {\n setIsExecuting(false)\n }\n }\n\n const contextValue: MCPActionContextValue = {\n executeAction,\n availableTools,\n spaceIds,\n macroId,\n isExecuting,\n lastResult,\n }\n\n return (\n <MCPActionContext.Provider value={contextValue}>\n {props.children}\n </MCPActionContext.Provider>\n )\n}\n\n/**\n * Hook to access MCP action context\n * Throws if used outside of MCPActionProvider\n *\n * @example\n * ```tsx\n * const { executeAction, isExecuting } = useMCPAction()\n *\n * const handleClick = async () => {\n * const result = await executeAction({\n * toolName: 'search.hub',\n * params: { query: 'revenue Q4' },\n * })\n * }\n * ```\n */\nexport function useMCPAction(): MCPActionContextValue {\n const context = useContext(MCPActionContext)\n if (!context) {\n throw new Error('useMCPAction must be used within an MCPActionProvider')\n }\n return context\n}\n\n/**\n * Hook to access MCP action context with fallback for components\n * outside of provider (uses CustomEvent fallback)\n *\n * @example\n * ```tsx\n * const { executeAction, isExecuting } = useMCPActionSafe()\n * // Works even without MCPActionProvider\n * ```\n */\nexport function useMCPActionSafe(): MCPActionContextValue {\n const context = useContext(MCPActionContext)\n\n if (context) {\n return context\n }\n\n // Fallback implementation for components outside provider\n const [isExecuting, setIsExecuting] = createSignal(false)\n const [lastResult, setLastResult] = createSignal<ActionResult>()\n\n const executeAction = async (request: ActionRequest): Promise<ActionResult> => {\n setIsExecuting(true)\n try {\n const result = await defaultExecutor(request)\n setLastResult(result)\n return result\n } finally {\n setIsExecuting(false)\n }\n }\n\n return {\n executeAction,\n availableTools: () => [],\n spaceIds: () => [],\n macroId: () => undefined,\n isExecuting,\n lastResult,\n }\n}\n\nexport { MCPActionContext }\n"],"names":["MCPActionContext","createContext","defaultExecutor","request","Promise","resolve","timestamp","Date","toISOString","window","event","CustomEvent","detail","toolName","params","spaceIds","macroId","action","bubbles","responseHandler","e","customEvent","removeEventListener","success","data","error","addEventListener","dispatchEvent","setTimeout","dispatched","MCPActionProvider","props","isExecuting","setIsExecuting","createSignal","lastResult","setLastResult","_setSpaceIds","_setMacroId","availableTools","_setAvailableTools","executeAction","enrichedRequest","executor","result","onAction","onWebhook","type","payload","errorResult","Error","message","contextValue","_$createComponent","Provider","value","children","useMCPAction","context","useContext","useMCPActionSafe","undefined"],"mappings":"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"}
@@ -8,7 +8,9 @@ import { ParentComponent, Accessor } from 'solid-js';
8
8
  */
9
9
  export interface ActionRequest {
10
10
  /**
11
- * MCP tool name to execute
11
+ * MCP tool name to execute. For a `submit` action with no associated
12
+ * tool, renderers pass the sentinel `'submit'` — branch on `action`,
13
+ * not on `toolName`, to tell a submit apart from a tool call.
12
14
  */
13
15
  toolName: string;
14
16
  /**
@@ -23,6 +25,16 @@ export interface ActionRequest {
23
25
  * Optional macro ID for template execution
24
26
  */
25
27
  macroId?: string;
28
+ /**
29
+ * Action kind (v6.6.1). Lets a host `executor` tell a tool call apart
30
+ * from a form-style `submit`. Absent ⇒ treat as `'tool-call'` (backward
31
+ * compatible — every pre-v6.6.1 request omits it).
32
+ *
33
+ * A `submit` action carries its payload in `params` (e.g. `submit_url`,
34
+ * `connector_id`, `feedback_value`) and **must NOT** be executed as a
35
+ * tool call — the host routes it (e.g. POST to `params.submit_url`).
36
+ */
37
+ action?: 'tool-call' | 'submit' | 'link';
26
38
  }
27
39
  /**
28
40
  * Action result from execution
@@ -1 +1 @@
1
- {"version":3,"file":"MCPActionContext.d.ts","sourceRoot":"","sources":["../../src/context/MCPActionContext.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAA2C,eAAe,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAE7F;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAE5B;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IAEnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,OAAO,EAAE,OAAO,CAAA;IAEhB;;OAEG;IACH,IAAI,CAAC,EAAE,GAAG,CAAA;IAEV;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd;;OAEG;IACH,SAAS,EAAE,MAAM,CAAA;IAEjB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,aAAa,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;IAEhE;;OAEG;IACH,cAAc,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;IAElC;;OAEG;IACH,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;IAE5B;;OAEG;IACH,OAAO,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IAErC;;OAEG;IACH,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;IAE9B;;OAEG;IACH,UAAU,EAAE,QAAQ,CAAC,YAAY,GAAG,SAAS,CAAC,CAAA;CAC/C;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IAEnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IAEzB;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,KAAK,IAAI,CAAA;IAEjE;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,GAAG,CAAA;KAAE,KAAK,IAAI,CAAA;IAE3D;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;CAC7D;AAGD,QAAA,MAAM,gBAAgB,+DAAyC,CAAA;AA6D/D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,iBAAiB,EAAE,eAAe,CAAC,sBAAsB,CA4ErE,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,IAAI,qBAAqB,CAMpD;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,IAAI,qBAAqB,CA8BxD;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAA"}
1
+ {"version":3,"file":"MCPActionContext.d.ts","sourceRoot":"","sources":["../../src/context/MCPActionContext.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAA2C,eAAe,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAE7F;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAE5B;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IAEnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,WAAW,GAAG,QAAQ,GAAG,MAAM,CAAA;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,OAAO,EAAE,OAAO,CAAA;IAEhB;;OAEG;IACH,IAAI,CAAC,EAAE,GAAG,CAAA;IAEV;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd;;OAEG;IACH,SAAS,EAAE,MAAM,CAAA;IAEjB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,aAAa,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;IAEhE;;OAEG;IACH,cAAc,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;IAElC;;OAEG;IACH,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;IAE5B;;OAEG;IACH,OAAO,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IAErC;;OAEG;IACH,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;IAE9B;;OAEG;IACH,UAAU,EAAE,QAAQ,CAAC,YAAY,GAAG,SAAS,CAAC,CAAA;CAC/C;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IAEnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IAEzB;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,KAAK,IAAI,CAAA;IAEjE;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,GAAG,CAAA;KAAE,KAAK,IAAI,CAAA;IAE3D;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,YAAY,CAAC,CAAA;CAC7D;AAGD,QAAA,MAAM,gBAAgB,+DAAyC,CAAA;AAgE/D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,iBAAiB,EAAE,eAAe,CAAC,sBAAsB,CA4ErE,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,IAAI,qBAAqB,CAMpD;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,IAAI,qBAAqB,CA8BxD;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAA"}
@@ -10,7 +10,10 @@ const defaultExecutor = async (request) => {
10
10
  toolName: request.toolName,
11
11
  params: request.params || {},
12
12
  spaceIds: request.spaceIds,
13
- macroId: request.macroId
13
+ macroId: request.macroId,
14
+ // v6.6.1 — action kind so a window-level listener can route a
15
+ // `submit` (POST to params.submit_url) vs a tool call.
16
+ action: request.action ?? "tool-call"
14
17
  },
15
18
  bubbles: true
16
19
  });
@@ -1 +1 @@
1
- {"version":3,"file":"MCPActionContext.js","sources":["../../src/context/MCPActionContext.tsx"],"sourcesContent":["/**\n * MCPActionContext - Context provider for MCP action execution\n * Phase 5.0: Quick Wins - Replaces CustomEvent with typed context for Mastra integration\n */\n\nimport { createContext, createSignal, useContext, ParentComponent, Accessor } from 'solid-js'\n\n/**\n * Action request payload\n */\nexport interface ActionRequest {\n /**\n * MCP tool name to execute\n */\n toolName: string\n\n /**\n * Tool parameters\n */\n params?: Record<string, any>\n\n /**\n * Optional space IDs for multi-space context\n */\n spaceIds?: string[]\n\n /**\n * Optional macro ID for template execution\n */\n macroId?: string\n}\n\n/**\n * Action result from execution\n */\nexport interface ActionResult {\n /**\n * Whether the action was successful\n */\n success: boolean\n\n /**\n * Result data (if successful)\n */\n data?: any\n\n /**\n * Error message (if failed)\n */\n error?: string\n\n /**\n * Execution timestamp\n */\n timestamp: string\n\n /**\n * Tool that was executed\n */\n toolName: string\n}\n\n/**\n * Context value interface\n */\nexport interface MCPActionContextValue {\n /**\n * Execute an MCP action\n */\n executeAction: (request: ActionRequest) => Promise<ActionResult>\n\n /**\n * Currently available tools (from MCP server)\n */\n availableTools: Accessor<string[]>\n\n /**\n * Space IDs in current context\n */\n spaceIds: Accessor<string[]>\n\n /**\n * Current macro ID (if executing within a template)\n */\n macroId: Accessor<string | undefined>\n\n /**\n * Whether an action is currently executing\n */\n isExecuting: Accessor<boolean>\n\n /**\n * Last action result\n */\n lastResult: Accessor<ActionResult | undefined>\n}\n\n/**\n * Props for MCPActionProvider\n */\nexport interface MCPActionProviderProps {\n /**\n * Space IDs for multi-space queries\n */\n spaceIds?: string[]\n\n /**\n * Macro ID when executing within a template\n */\n macroId?: string\n\n /**\n * Available MCP tools\n */\n availableTools?: string[]\n\n /**\n * Callback for action execution (for audit logging)\n */\n onAction?: (request: ActionRequest, result: ActionResult) => void\n\n /**\n * Callback for webhook events (n8n, Zapier integration)\n */\n onWebhook?: (event: { type: string; payload: any }) => void\n\n /**\n * Custom action executor (override default)\n */\n executor?: (request: ActionRequest) => Promise<ActionResult>\n}\n\n// Create the context with undefined default\nconst MCPActionContext = createContext<MCPActionContextValue>()\n\n/**\n * Default action executor using CustomEvent fallback\n * This maintains backward compatibility while allowing Context-based usage\n */\nconst defaultExecutor = async (request: ActionRequest): Promise<ActionResult> => {\n return new Promise((resolve) => {\n const timestamp = new Date().toISOString()\n\n // Dispatch CustomEvent for backward compatibility with existing listeners\n if (typeof window !== 'undefined') {\n const event = new CustomEvent('mcp-action', {\n detail: {\n toolName: request.toolName,\n params: request.params || {},\n spaceIds: request.spaceIds,\n macroId: request.macroId,\n },\n bubbles: true,\n })\n\n // Listen for response event\n const responseHandler = (e: Event) => {\n const customEvent = e as CustomEvent\n window.removeEventListener('mcp-action-response', responseHandler)\n resolve({\n success: customEvent.detail?.success ?? true,\n data: customEvent.detail?.data,\n error: customEvent.detail?.error,\n timestamp,\n toolName: request.toolName,\n })\n }\n\n window.addEventListener('mcp-action-response', responseHandler)\n window.dispatchEvent(event)\n\n // Timeout fallback - resolve as success if no response in 100ms\n // (indicates no listener, action was dispatched)\n setTimeout(() => {\n window.removeEventListener('mcp-action-response', responseHandler)\n resolve({\n success: true,\n data: { dispatched: true },\n timestamp,\n toolName: request.toolName,\n })\n }, 100)\n } else {\n // Server-side: return immediately\n resolve({\n success: false,\n error: 'Actions not available server-side',\n timestamp,\n toolName: request.toolName,\n })\n }\n })\n}\n\n/**\n * MCPActionProvider - Provides action execution context to child components\n *\n * @example\n * ```tsx\n * <MCPActionProvider\n * spaceIds={['space-123']}\n * macroId=\"sales_overview\"\n * onAction={(req, res) => audit(req, res)}\n * >\n * <UIResourceRenderer layout={layout} />\n * </MCPActionProvider>\n * ```\n */\nexport const MCPActionProvider: ParentComponent<MCPActionProviderProps> = (props) => {\n const [isExecuting, setIsExecuting] = createSignal(false)\n const [lastResult, setLastResult] = createSignal<ActionResult>()\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const [spaceIds, _setSpaceIds] = createSignal<string[]>(props.spaceIds || [])\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const [macroId, _setMacroId] = createSignal<string | undefined>(props.macroId)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const [availableTools, _setAvailableTools] = createSignal<string[]>(props.availableTools || [])\n\n // Update signals when props change\n // Note: This is a simple approach; for more complex scenarios, consider createEffect\n\n const executeAction = async (request: ActionRequest): Promise<ActionResult> => {\n setIsExecuting(true)\n\n try {\n // Enrich request with context\n const enrichedRequest: ActionRequest = {\n ...request,\n spaceIds: request.spaceIds || spaceIds(),\n macroId: request.macroId || macroId(),\n }\n\n // Execute using custom executor or default\n const executor = props.executor || defaultExecutor\n const result = await executor(enrichedRequest)\n\n setLastResult(result)\n\n // Call audit callback if provided\n props.onAction?.(enrichedRequest, result)\n\n // Trigger webhook if provided and action was successful\n if (result.success && props.onWebhook) {\n props.onWebhook({\n type: 'action-completed',\n payload: {\n request: enrichedRequest,\n result,\n },\n })\n }\n\n return result\n } catch (error) {\n const errorResult: ActionResult = {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n timestamp: new Date().toISOString(),\n toolName: request.toolName,\n }\n\n setLastResult(errorResult)\n props.onAction?.(request, errorResult)\n\n return errorResult\n } finally {\n setIsExecuting(false)\n }\n }\n\n const contextValue: MCPActionContextValue = {\n executeAction,\n availableTools,\n spaceIds,\n macroId,\n isExecuting,\n lastResult,\n }\n\n return (\n <MCPActionContext.Provider value={contextValue}>\n {props.children}\n </MCPActionContext.Provider>\n )\n}\n\n/**\n * Hook to access MCP action context\n * Throws if used outside of MCPActionProvider\n *\n * @example\n * ```tsx\n * const { executeAction, isExecuting } = useMCPAction()\n *\n * const handleClick = async () => {\n * const result = await executeAction({\n * toolName: 'search.hub',\n * params: { query: 'revenue Q4' },\n * })\n * }\n * ```\n */\nexport function useMCPAction(): MCPActionContextValue {\n const context = useContext(MCPActionContext)\n if (!context) {\n throw new Error('useMCPAction must be used within an MCPActionProvider')\n }\n return context\n}\n\n/**\n * Hook to access MCP action context with fallback for components\n * outside of provider (uses CustomEvent fallback)\n *\n * @example\n * ```tsx\n * const { executeAction, isExecuting } = useMCPActionSafe()\n * // Works even without MCPActionProvider\n * ```\n */\nexport function useMCPActionSafe(): MCPActionContextValue {\n const context = useContext(MCPActionContext)\n\n if (context) {\n return context\n }\n\n // Fallback implementation for components outside provider\n const [isExecuting, setIsExecuting] = createSignal(false)\n const [lastResult, setLastResult] = createSignal<ActionResult>()\n\n const executeAction = async (request: ActionRequest): Promise<ActionResult> => {\n setIsExecuting(true)\n try {\n const result = await defaultExecutor(request)\n setLastResult(result)\n return result\n } finally {\n setIsExecuting(false)\n }\n }\n\n return {\n executeAction,\n availableTools: () => [],\n spaceIds: () => [],\n macroId: () => undefined,\n isExecuting,\n lastResult,\n }\n}\n\nexport { MCPActionContext }\n"],"names":["MCPActionContext","createContext","defaultExecutor","request","Promise","resolve","timestamp","Date","toISOString","window","event","CustomEvent","detail","toolName","params","spaceIds","macroId","bubbles","responseHandler","e","customEvent","removeEventListener","success","data","error","addEventListener","dispatchEvent","setTimeout","dispatched","MCPActionProvider","props","isExecuting","setIsExecuting","createSignal","lastResult","setLastResult","_setSpaceIds","_setMacroId","availableTools","_setAvailableTools","executeAction","enrichedRequest","executor","result","onAction","onWebhook","type","payload","errorResult","Error","message","contextValue","_$createComponent","Provider","value","children","useMCPAction","context","useContext","useMCPActionSafe","undefined"],"mappings":"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"}
1
+ {"version":3,"file":"MCPActionContext.js","sources":["../../src/context/MCPActionContext.tsx"],"sourcesContent":["/**\n * MCPActionContext - Context provider for MCP action execution\n * Phase 5.0: Quick Wins - Replaces CustomEvent with typed context for Mastra integration\n */\n\nimport { createContext, createSignal, useContext, ParentComponent, Accessor } from 'solid-js'\n\n/**\n * Action request payload\n */\nexport interface ActionRequest {\n /**\n * MCP tool name to execute. For a `submit` action with no associated\n * tool, renderers pass the sentinel `'submit'` — branch on `action`,\n * not on `toolName`, to tell a submit apart from a tool call.\n */\n toolName: string\n\n /**\n * Tool parameters\n */\n params?: Record<string, any>\n\n /**\n * Optional space IDs for multi-space context\n */\n spaceIds?: string[]\n\n /**\n * Optional macro ID for template execution\n */\n macroId?: string\n\n /**\n * Action kind (v6.6.1). Lets a host `executor` tell a tool call apart\n * from a form-style `submit`. Absent ⇒ treat as `'tool-call'` (backward\n * compatible — every pre-v6.6.1 request omits it).\n *\n * A `submit` action carries its payload in `params` (e.g. `submit_url`,\n * `connector_id`, `feedback_value`) and **must NOT** be executed as a\n * tool call — the host routes it (e.g. POST to `params.submit_url`).\n */\n action?: 'tool-call' | 'submit' | 'link'\n}\n\n/**\n * Action result from execution\n */\nexport interface ActionResult {\n /**\n * Whether the action was successful\n */\n success: boolean\n\n /**\n * Result data (if successful)\n */\n data?: any\n\n /**\n * Error message (if failed)\n */\n error?: string\n\n /**\n * Execution timestamp\n */\n timestamp: string\n\n /**\n * Tool that was executed\n */\n toolName: string\n}\n\n/**\n * Context value interface\n */\nexport interface MCPActionContextValue {\n /**\n * Execute an MCP action\n */\n executeAction: (request: ActionRequest) => Promise<ActionResult>\n\n /**\n * Currently available tools (from MCP server)\n */\n availableTools: Accessor<string[]>\n\n /**\n * Space IDs in current context\n */\n spaceIds: Accessor<string[]>\n\n /**\n * Current macro ID (if executing within a template)\n */\n macroId: Accessor<string | undefined>\n\n /**\n * Whether an action is currently executing\n */\n isExecuting: Accessor<boolean>\n\n /**\n * Last action result\n */\n lastResult: Accessor<ActionResult | undefined>\n}\n\n/**\n * Props for MCPActionProvider\n */\nexport interface MCPActionProviderProps {\n /**\n * Space IDs for multi-space queries\n */\n spaceIds?: string[]\n\n /**\n * Macro ID when executing within a template\n */\n macroId?: string\n\n /**\n * Available MCP tools\n */\n availableTools?: string[]\n\n /**\n * Callback for action execution (for audit logging)\n */\n onAction?: (request: ActionRequest, result: ActionResult) => void\n\n /**\n * Callback for webhook events (n8n, Zapier integration)\n */\n onWebhook?: (event: { type: string; payload: any }) => void\n\n /**\n * Custom action executor (override default)\n */\n executor?: (request: ActionRequest) => Promise<ActionResult>\n}\n\n// Create the context with undefined default\nconst MCPActionContext = createContext<MCPActionContextValue>()\n\n/**\n * Default action executor using CustomEvent fallback\n * This maintains backward compatibility while allowing Context-based usage\n */\nconst defaultExecutor = async (request: ActionRequest): Promise<ActionResult> => {\n return new Promise((resolve) => {\n const timestamp = new Date().toISOString()\n\n // Dispatch CustomEvent for backward compatibility with existing listeners\n if (typeof window !== 'undefined') {\n const event = new CustomEvent('mcp-action', {\n detail: {\n toolName: request.toolName,\n params: request.params || {},\n spaceIds: request.spaceIds,\n macroId: request.macroId,\n // v6.6.1 — action kind so a window-level listener can route a\n // `submit` (POST to params.submit_url) vs a tool call.\n action: request.action ?? 'tool-call',\n },\n bubbles: true,\n })\n\n // Listen for response event\n const responseHandler = (e: Event) => {\n const customEvent = e as CustomEvent\n window.removeEventListener('mcp-action-response', responseHandler)\n resolve({\n success: customEvent.detail?.success ?? true,\n data: customEvent.detail?.data,\n error: customEvent.detail?.error,\n timestamp,\n toolName: request.toolName,\n })\n }\n\n window.addEventListener('mcp-action-response', responseHandler)\n window.dispatchEvent(event)\n\n // Timeout fallback - resolve as success if no response in 100ms\n // (indicates no listener, action was dispatched)\n setTimeout(() => {\n window.removeEventListener('mcp-action-response', responseHandler)\n resolve({\n success: true,\n data: { dispatched: true },\n timestamp,\n toolName: request.toolName,\n })\n }, 100)\n } else {\n // Server-side: return immediately\n resolve({\n success: false,\n error: 'Actions not available server-side',\n timestamp,\n toolName: request.toolName,\n })\n }\n })\n}\n\n/**\n * MCPActionProvider - Provides action execution context to child components\n *\n * @example\n * ```tsx\n * <MCPActionProvider\n * spaceIds={['space-123']}\n * macroId=\"sales_overview\"\n * onAction={(req, res) => audit(req, res)}\n * >\n * <UIResourceRenderer layout={layout} />\n * </MCPActionProvider>\n * ```\n */\nexport const MCPActionProvider: ParentComponent<MCPActionProviderProps> = (props) => {\n const [isExecuting, setIsExecuting] = createSignal(false)\n const [lastResult, setLastResult] = createSignal<ActionResult>()\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const [spaceIds, _setSpaceIds] = createSignal<string[]>(props.spaceIds || [])\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const [macroId, _setMacroId] = createSignal<string | undefined>(props.macroId)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const [availableTools, _setAvailableTools] = createSignal<string[]>(props.availableTools || [])\n\n // Update signals when props change\n // Note: This is a simple approach; for more complex scenarios, consider createEffect\n\n const executeAction = async (request: ActionRequest): Promise<ActionResult> => {\n setIsExecuting(true)\n\n try {\n // Enrich request with context\n const enrichedRequest: ActionRequest = {\n ...request,\n spaceIds: request.spaceIds || spaceIds(),\n macroId: request.macroId || macroId(),\n }\n\n // Execute using custom executor or default\n const executor = props.executor || defaultExecutor\n const result = await executor(enrichedRequest)\n\n setLastResult(result)\n\n // Call audit callback if provided\n props.onAction?.(enrichedRequest, result)\n\n // Trigger webhook if provided and action was successful\n if (result.success && props.onWebhook) {\n props.onWebhook({\n type: 'action-completed',\n payload: {\n request: enrichedRequest,\n result,\n },\n })\n }\n\n return result\n } catch (error) {\n const errorResult: ActionResult = {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n timestamp: new Date().toISOString(),\n toolName: request.toolName,\n }\n\n setLastResult(errorResult)\n props.onAction?.(request, errorResult)\n\n return errorResult\n } finally {\n setIsExecuting(false)\n }\n }\n\n const contextValue: MCPActionContextValue = {\n executeAction,\n availableTools,\n spaceIds,\n macroId,\n isExecuting,\n lastResult,\n }\n\n return (\n <MCPActionContext.Provider value={contextValue}>\n {props.children}\n </MCPActionContext.Provider>\n )\n}\n\n/**\n * Hook to access MCP action context\n * Throws if used outside of MCPActionProvider\n *\n * @example\n * ```tsx\n * const { executeAction, isExecuting } = useMCPAction()\n *\n * const handleClick = async () => {\n * const result = await executeAction({\n * toolName: 'search.hub',\n * params: { query: 'revenue Q4' },\n * })\n * }\n * ```\n */\nexport function useMCPAction(): MCPActionContextValue {\n const context = useContext(MCPActionContext)\n if (!context) {\n throw new Error('useMCPAction must be used within an MCPActionProvider')\n }\n return context\n}\n\n/**\n * Hook to access MCP action context with fallback for components\n * outside of provider (uses CustomEvent fallback)\n *\n * @example\n * ```tsx\n * const { executeAction, isExecuting } = useMCPActionSafe()\n * // Works even without MCPActionProvider\n * ```\n */\nexport function useMCPActionSafe(): MCPActionContextValue {\n const context = useContext(MCPActionContext)\n\n if (context) {\n return context\n }\n\n // Fallback implementation for components outside provider\n const [isExecuting, setIsExecuting] = createSignal(false)\n const [lastResult, setLastResult] = createSignal<ActionResult>()\n\n const executeAction = async (request: ActionRequest): Promise<ActionResult> => {\n setIsExecuting(true)\n try {\n const result = await defaultExecutor(request)\n setLastResult(result)\n return result\n } finally {\n setIsExecuting(false)\n }\n }\n\n return {\n executeAction,\n availableTools: () => [],\n spaceIds: () => [],\n macroId: () => undefined,\n isExecuting,\n lastResult,\n }\n}\n\nexport { MCPActionContext }\n"],"names":["MCPActionContext","createContext","defaultExecutor","request","Promise","resolve","timestamp","Date","toISOString","window","event","CustomEvent","detail","toolName","params","spaceIds","macroId","action","bubbles","responseHandler","e","customEvent","removeEventListener","success","data","error","addEventListener","dispatchEvent","setTimeout","dispatched","MCPActionProvider","props","isExecuting","setIsExecuting","createSignal","lastResult","setLastResult","_setSpaceIds","_setMacroId","availableTools","_setAvailableTools","executeAction","enrichedRequest","executor","result","onAction","onWebhook","type","payload","errorResult","Error","message","contextValue","_$createComponent","Provider","value","children","useMCPAction","context","useContext","useMCPActionSafe","undefined"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seed-ship/mcp-ui-solid",
3
- "version": "6.6.0",
3
+ "version": "6.6.1",
4
4
  "description": "SolidJS components for rendering MCP-generated UI resources",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -12,6 +12,7 @@ import type { UIComponent, ActionGroupParams, ActionComponentParams } from '../t
12
12
  vi.mock('../hooks/useAction', () => ({
13
13
  useAction: () => ({
14
14
  execute: vi.fn().mockResolvedValue({ success: true }),
15
+ executeAction: vi.fn().mockResolvedValue({ success: true }),
15
16
  isExecuting: () => false,
16
17
  lastResult: () => undefined,
17
18
  lastError: () => undefined,
@@ -26,7 +26,12 @@ const ActionButton: Component<{
26
26
  action: ActionComponentParams
27
27
  index: number
28
28
  }> = (props) => {
29
- const { execute, isExecuting } = useAction()
29
+ const { execute, executeAction, isExecuting } = useAction()
30
+
31
+ // tool-call and submit both go through the host executor — they show a
32
+ // loading state and gate disabled while running. link does neither.
33
+ const isExecutable = () =>
34
+ props.action.action === 'tool-call' || props.action.action === 'submit'
30
35
 
31
36
  const handleClick = async (e: MouseEvent) => {
32
37
  if (props.action.disabled) return
@@ -34,13 +39,23 @@ const ActionButton: Component<{
34
39
  if (props.action.action === 'tool-call' && props.action.toolName) {
35
40
  e.preventDefault()
36
41
  await execute(props.action.toolName, props.action.params || {})
42
+ } else if (props.action.action === 'submit') {
43
+ // submit is NOT a tool call — route it through the executor with the
44
+ // `action: 'submit'` kind preserved so the host can POST to
45
+ // params.submit_url etc. Works without a surrounding <form>.
46
+ e.preventDefault()
47
+ await executeAction({
48
+ action: 'submit',
49
+ toolName: props.action.toolName || 'submit',
50
+ params: props.action.params || {},
51
+ })
37
52
  } else if (props.action.action === 'link' && props.action.url) {
38
53
  window.open(props.action.url, '_blank', 'noopener,noreferrer')
39
54
  }
40
55
  }
41
56
 
42
57
  const isDisabled = () =>
43
- props.action.disabled || (props.action.action === 'tool-call' && isExecuting())
58
+ props.action.disabled || (isExecutable() && isExecuting())
44
59
 
45
60
  const variantClass = () => {
46
61
  switch (props.action.variant) {
@@ -98,10 +113,10 @@ const ActionButton: Component<{
98
113
  isDisabled() ? 'opacity-50 cursor-not-allowed' : ''
99
114
  }`}
100
115
  >
101
- <Show when={isExecuting() && props.action.action === 'tool-call'}>
116
+ <Show when={isExecuting() && isExecutable()}>
102
117
  <span class="animate-spin h-4 w-4 border-2 border-current border-t-transparent rounded-full" />
103
118
  </Show>
104
- <Show when={props.action.icon && !(isExecuting() && props.action.action === 'tool-call')}>
119
+ <Show when={props.action.icon && !(isExecuting() && isExecutable())}>
105
120
  <span class="text-current">{props.action.icon}</span>
106
121
  </Show>
107
122
  {props.action.label}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * v6.6.1 — `action: 'submit'` reaches the host executor outside a <form>.
3
+ *
4
+ * Before v6.6.1, `submit` actions were inert : `ActionGroupRenderer` only
5
+ * branched on `tool-call` / `link`, and the standalone `action` renderer
6
+ * emitted a native `type="submit"` button that did nothing outside a real
7
+ * `<form>`. This file pins the fix — a full integration test through the
8
+ * real `useAction` → `MCPActionContext` → host `executor` path (no mocks).
9
+ *
10
+ * Covers both render surfaces :
11
+ * - `<ActionGroupRenderer>` (action-group)
12
+ * - `<UIResourceRenderer content={{ type: 'action', ... }}>` (standalone)
13
+ */
14
+
15
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
16
+ import { render, screen, fireEvent, waitFor, cleanup } from '@solidjs/testing-library'
17
+ import { ActionGroupRenderer } from './ActionGroupRenderer'
18
+ import { UIResourceRenderer } from './UIResourceRenderer'
19
+ import { MCPActionProvider } from '../context/MCPActionContext'
20
+ import type { ActionRequest, ActionResult } from '../context/MCPActionContext'
21
+ import type { UIComponent } from '../types'
22
+
23
+ // The connector "feedback format" payload from the bug report.
24
+ const SUBMIT_PARAMS = {
25
+ submit_url: '/api/connector-render-feedback',
26
+ feedback_kind: 'presentation',
27
+ connector_id: 'clinicaltrials',
28
+ tool_name: 'clinicaltrials_search',
29
+ render_kind: 'clinical_trial_search',
30
+ preferred_layout_options: ['table', 'cards', 'bar'],
31
+ }
32
+
33
+ function makeExecutor() {
34
+ const calls: ActionRequest[] = []
35
+ const executor = vi.fn(async (req: ActionRequest): Promise<ActionResult> => {
36
+ calls.push(req)
37
+ return { success: true, timestamp: new Date().toISOString(), toolName: req.toolName }
38
+ })
39
+ return { executor, calls }
40
+ }
41
+
42
+ describe('action: submit reaches the host executor (v6.6.1)', () => {
43
+ beforeEach(() => cleanup())
44
+
45
+ it('ActionGroupRenderer routes a submit action to the executor', async () => {
46
+ const { executor, calls } = makeExecutor()
47
+ const component: UIComponent = {
48
+ id: 'ag',
49
+ type: 'action-group',
50
+ position: { colStart: 1, colSpan: 12 },
51
+ params: {
52
+ actions: [
53
+ { label: 'Feedback format', action: 'submit', params: SUBMIT_PARAMS },
54
+ ],
55
+ },
56
+ } as UIComponent
57
+
58
+ render(() => (
59
+ <MCPActionProvider executor={executor}>
60
+ <ActionGroupRenderer component={component} />
61
+ </MCPActionProvider>
62
+ ))
63
+
64
+ fireEvent.click(screen.getByRole('button', { name: 'Feedback format' }))
65
+
66
+ await waitFor(() => expect(executor).toHaveBeenCalledTimes(1))
67
+ const req = calls[0]
68
+ // The action KIND is preserved — host can tell it apart from a tool call.
69
+ expect(req.action).toBe('submit')
70
+ // The full params payload survives intact.
71
+ expect(req.params).toEqual(SUBMIT_PARAMS)
72
+ })
73
+
74
+ it('a submit action is NOT executed as a tool call', async () => {
75
+ const { executor, calls } = makeExecutor()
76
+ const component: UIComponent = {
77
+ id: 'ag',
78
+ type: 'action-group',
79
+ position: { colStart: 1, colSpan: 12 },
80
+ params: {
81
+ actions: [{ label: 'Send', action: 'submit', params: SUBMIT_PARAMS }],
82
+ },
83
+ } as UIComponent
84
+
85
+ render(() => (
86
+ <MCPActionProvider executor={executor}>
87
+ <ActionGroupRenderer component={component} />
88
+ </MCPActionProvider>
89
+ ))
90
+ fireEvent.click(screen.getByRole('button', { name: 'Send' }))
91
+
92
+ await waitFor(() => expect(executor).toHaveBeenCalledTimes(1))
93
+ // action is 'submit', never silently coerced to 'tool-call'
94
+ expect(calls[0].action).not.toBe('tool-call')
95
+ expect(calls[0].action).toBe('submit')
96
+ })
97
+
98
+ it('standalone action component (UIResourceRenderer) routes submit to the executor', async () => {
99
+ const { executor, calls } = makeExecutor()
100
+ const component: UIComponent = {
101
+ id: 'act',
102
+ type: 'action',
103
+ position: { colStart: 1, colSpan: 12 },
104
+ params: { label: 'Feedback format', action: 'submit', params: SUBMIT_PARAMS },
105
+ } as UIComponent
106
+
107
+ render(() => (
108
+ <MCPActionProvider executor={executor}>
109
+ <UIResourceRenderer content={component} />
110
+ </MCPActionProvider>
111
+ ))
112
+
113
+ fireEvent.click(screen.getByRole('button', { name: 'Feedback format' }))
114
+
115
+ await waitFor(() => expect(executor).toHaveBeenCalledTimes(1))
116
+ expect(calls[0].action).toBe('submit')
117
+ expect(calls[0].params).toEqual(SUBMIT_PARAMS)
118
+ })
119
+
120
+ it('standalone submit button is type="button" — not a native form submit', () => {
121
+ const component: UIComponent = {
122
+ id: 'act',
123
+ type: 'action',
124
+ position: { colStart: 1, colSpan: 12 },
125
+ params: { label: 'Send', action: 'submit', params: {} },
126
+ } as UIComponent
127
+
128
+ const { container } = render(() => (
129
+ <MCPActionProvider executor={makeExecutor().executor}>
130
+ <UIResourceRenderer content={component} />
131
+ </MCPActionProvider>
132
+ ))
133
+ const btn = container.querySelector('button')
134
+ // Must NOT rely on a surrounding <form> — JS-handled, type=button.
135
+ expect(btn?.getAttribute('type')).toBe('button')
136
+ })
137
+
138
+ it('tool-call actions still work unchanged', async () => {
139
+ const { executor, calls } = makeExecutor()
140
+ const component: UIComponent = {
141
+ id: 'ag',
142
+ type: 'action-group',
143
+ position: { colStart: 1, colSpan: 12 },
144
+ params: {
145
+ actions: [
146
+ { label: 'Run', action: 'tool-call', toolName: 'do_thing', params: { a: 1 } },
147
+ ],
148
+ },
149
+ } as UIComponent
150
+
151
+ render(() => (
152
+ <MCPActionProvider executor={executor}>
153
+ <ActionGroupRenderer component={component} />
154
+ </MCPActionProvider>
155
+ ))
156
+ fireEvent.click(screen.getByRole('button', { name: 'Run' }))
157
+
158
+ await waitFor(() => expect(executor).toHaveBeenCalledTimes(1))
159
+ expect(calls[0].toolName).toBe('do_thing')
160
+ expect(calls[0].params).toEqual({ a: 1 })
161
+ })
162
+
163
+ it('a disabled submit action does not call the executor', async () => {
164
+ const { executor } = makeExecutor()
165
+ const component: UIComponent = {
166
+ id: 'ag',
167
+ type: 'action-group',
168
+ position: { colStart: 1, colSpan: 12 },
169
+ params: {
170
+ actions: [
171
+ { label: 'Send', action: 'submit', params: SUBMIT_PARAMS, disabled: true },
172
+ ],
173
+ },
174
+ } as UIComponent
175
+
176
+ render(() => (
177
+ <MCPActionProvider executor={executor}>
178
+ <ActionGroupRenderer component={component} />
179
+ </MCPActionProvider>
180
+ ))
181
+ const btn = screen.getByRole('button', { name: 'Send' })
182
+ expect(btn.hasAttribute('disabled')).toBe(true)
183
+ fireEvent.click(btn)
184
+ // Give any async handler a tick — nothing should fire.
185
+ await new Promise((r) => setTimeout(r, 10))
186
+ expect(executor).not.toHaveBeenCalled()
187
+ })
188
+ })
@@ -1524,9 +1524,13 @@ function ComponentRenderer(props: {
1524
1524
  */
1525
1525
  function ActionRenderer(props: { component: UIComponent }) {
1526
1526
  const params = props.component.params as any
1527
- const { execute, isExecuting } = useAction()
1527
+ const { execute, executeAction, isExecuting } = useAction()
1528
1528
  const telemetry = useTelemetry()
1529
1529
 
1530
+ // tool-call and submit both run through the host executor — loading +
1531
+ // disabled state apply to both. link does neither.
1532
+ const isExecutable = () => params.action === 'tool-call' || params.action === 'submit'
1533
+
1530
1534
  // Telemetry: action:dispatched on click (B.5 — v5.6.0). Fires for every
1531
1535
  // click attempt (tool-call or link), regardless of execute success.
1532
1536
  // Privacy: actionName is `toolName` (tool-call) or the action kind
@@ -1549,11 +1553,20 @@ function ActionRenderer(props: { component: UIComponent }) {
1549
1553
  if (params.action === 'tool-call' && params.toolName) {
1550
1554
  e.preventDefault()
1551
1555
  await execute(params.toolName, params.params || {})
1556
+ } else if (params.action === 'submit') {
1557
+ // submit is NOT a tool call — route through the executor with the
1558
+ // `action: 'submit'` kind preserved. Works outside any <form>.
1559
+ e.preventDefault()
1560
+ await executeAction({
1561
+ action: 'submit',
1562
+ toolName: params.toolName || 'submit',
1563
+ params: params.params || {},
1564
+ })
1552
1565
  }
1553
1566
  }
1554
1567
 
1555
1568
  // Determine if button should be disabled (explicit disable or currently executing)
1556
- const isDisabled = () => params.disabled || (params.action === 'tool-call' && isExecuting())
1569
+ const isDisabled = () => params.disabled || (isExecutable() && isExecuting())
1557
1570
 
1558
1571
  if (params.type === 'link' || params.action === 'link') {
1559
1572
  return (
@@ -1579,9 +1592,9 @@ function ActionRenderer(props: { component: UIComponent }) {
1579
1592
 
1580
1593
  return (
1581
1594
  <button
1582
- type={params.action === 'submit' ? 'submit' : 'button'}
1595
+ type="button"
1583
1596
  disabled={isDisabled()}
1584
- aria-busy={isExecuting() && params.action === 'tool-call'}
1597
+ aria-busy={isExecuting() && isExecutable()}
1585
1598
  aria-label={params.ariaLabel || params.label}
1586
1599
  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
1587
1600
  ${params.variant === 'primary' ? 'bg-blue-600 text-white hover:bg-blue-700 shadow-sm' :
@@ -1594,10 +1607,10 @@ function ActionRenderer(props: { component: UIComponent }) {
1594
1607
  ${params.className || ''}`}
1595
1608
  onClick={handleClick}
1596
1609
  >
1597
- <Show when={isExecuting() && params.action === 'tool-call'}>
1610
+ <Show when={isExecuting() && isExecutable()}>
1598
1611
  <span class="animate-spin h-4 w-4 border-2 border-current border-t-transparent rounded-full" aria-hidden="true" />
1599
1612
  </Show>
1600
- <Show when={params.icon && !(isExecuting() && params.action === 'tool-call')}>
1613
+ <Show when={params.icon && !(isExecuting() && isExecutable())}>
1601
1614
  <span aria-hidden="true">{params.icon}</span>
1602
1615
  </Show>
1603
1616
  {params.label}
@@ -10,7 +10,9 @@ import { createContext, createSignal, useContext, ParentComponent, Accessor } fr
10
10
  */
11
11
  export interface ActionRequest {
12
12
  /**
13
- * MCP tool name to execute
13
+ * MCP tool name to execute. For a `submit` action with no associated
14
+ * tool, renderers pass the sentinel `'submit'` — branch on `action`,
15
+ * not on `toolName`, to tell a submit apart from a tool call.
14
16
  */
15
17
  toolName: string
16
18
 
@@ -28,6 +30,17 @@ export interface ActionRequest {
28
30
  * Optional macro ID for template execution
29
31
  */
30
32
  macroId?: string
33
+
34
+ /**
35
+ * Action kind (v6.6.1). Lets a host `executor` tell a tool call apart
36
+ * from a form-style `submit`. Absent ⇒ treat as `'tool-call'` (backward
37
+ * compatible — every pre-v6.6.1 request omits it).
38
+ *
39
+ * A `submit` action carries its payload in `params` (e.g. `submit_url`,
40
+ * `connector_id`, `feedback_value`) and **must NOT** be executed as a
41
+ * tool call — the host routes it (e.g. POST to `params.submit_url`).
42
+ */
43
+ action?: 'tool-call' | 'submit' | 'link'
31
44
  }
32
45
 
33
46
  /**
@@ -149,6 +162,9 @@ const defaultExecutor = async (request: ActionRequest): Promise<ActionResult> =>
149
162
  params: request.params || {},
150
163
  spaceIds: request.spaceIds,
151
164
  macroId: request.macroId,
165
+ // v6.6.1 — action kind so a window-level listener can route a
166
+ // `submit` (POST to params.submit_url) vs a tool call.
167
+ action: request.action ?? 'tool-call',
152
168
  },
153
169
  bubbles: true,
154
170
  })