From 8377abf268c22be566af1ce95d6a477f851290bf Mon Sep 17 00:00:00 2001 From: Jem <591643+jem-computer@users.noreply.github.com> Date: Thu, 19 Jun 2025 18:41:11 -0700 Subject: [PATCH] feat: add MCP prompts support to useMcp hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Prompt type from MCP SDK - Add prompts array to UseMcpResult - Fetch prompts automatically after resources during connection - Add listPrompts() method to manually refresh prompts list - Add getPrompt(name, args) method to get specific prompt messages - Clear prompts state on disconnect 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/react/types.ts | 21 ++++++++++++++++- src/react/useMcp.ts | 55 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/src/react/types.ts b/src/react/types.ts index 20e926b..61934ea 100644 --- a/src/react/types.ts +++ b/src/react/types.ts @@ -1,4 +1,4 @@ -import { Tool, Resource, ResourceTemplate } from '@modelcontextprotocol/sdk/types.js' +import { Tool, Resource, ResourceTemplate, Prompt } from '@modelcontextprotocol/sdk/types.js' export type UseMcpOptions = { /** The /sse URL of your remote MCP server */ @@ -39,6 +39,8 @@ export type UseMcpResult = { resources: Resource[] /** List of resource templates available from the connected MCP server */ resourceTemplates: ResourceTemplate[] + /** List of prompts available from the connected MCP server */ + prompts: Prompt[] /** * The current state of the MCP connection: * - 'discovering': Checking server existence and capabilities (including auth requirements). @@ -80,6 +82,23 @@ export type UseMcpResult = { * @throws If the client is not in the 'ready' state or the read fails. */ readResource: (uri: string) => Promise<{ contents: Array<{ uri: string; mimeType?: string; text?: string; blob?: string }> }> + /** + * Function to list prompts from the MCP server. + * @returns A promise that resolves when prompts are refreshed. + * @throws If the client is not in the 'ready' state. + */ + listPrompts: () => Promise + /** + * Function to get a specific prompt from the MCP server. + * @param name The name of the prompt to get. + * @param args Optional arguments for the prompt. + * @returns A promise that resolves with the prompt messages. + * @throws If the client is not in the 'ready' state or the get fails. + */ + getPrompt: ( + name: string, + args?: Record, + ) => Promise<{ messages: Array<{ role: 'user' | 'assistant'; content: { type: string; text?: string; [key: string]: any } }> }> /** Manually attempts to reconnect if the state is 'failed'. */ retry: () => void /** Disconnects the client from the MCP server. */ diff --git a/src/react/useMcp.ts b/src/react/useMcp.ts index 6bd715d..fa5a18a 100644 --- a/src/react/useMcp.ts +++ b/src/react/useMcp.ts @@ -5,9 +5,12 @@ import { ListToolsResultSchema, ListResourcesResultSchema, ReadResourceResultSchema, + ListPromptsResultSchema, + GetPromptResultSchema, Tool, Resource, ResourceTemplate, + Prompt, } from '@modelcontextprotocol/sdk/types.js' import { useCallback, useEffect, useRef, useState } from 'react' // Import both transport types @@ -50,6 +53,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { const [tools, setTools] = useState([]) const [resources, setResources] = useState([]) const [resourceTemplates, setResourceTemplates] = useState([]) + const [prompts, setPrompts] = useState([]) const [error, setError] = useState(undefined) const [log, setLog] = useState([]) const [authUrl, setAuthUrl] = useState(undefined) @@ -108,6 +112,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { setTools([]) setResources([]) setResourceTemplates([]) + setPrompts([]) setError(undefined) setAuthUrl(undefined) } @@ -296,7 +301,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { await clientRef.current!.connect(transportInstance) // --- Success Path --- - addLog('info', `Client connected via ${transportType.toUpperCase()}. Loading tools and resources...`) + addLog('info', `Client connected via ${transportType.toUpperCase()}. Loading tools, resources, and prompts...`) successfulTransportRef.current = transportType // Store successful type setState('loading') @@ -305,14 +310,18 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { // Load resources after tools const resourcesResponse = await clientRef.current!.request({ method: 'resources/list' }, ListResourcesResultSchema) + // Load prompts after resources + const promptsResponse = await clientRef.current!.request({ method: 'prompts/list' }, ListPromptsResultSchema) + if (isMountedRef.current) { // Check mount before final state updates setTools(toolsResponse.tools) setResources(resourcesResponse.resources) setResourceTemplates(Array.isArray(resourcesResponse.resourceTemplates) ? resourcesResponse.resourceTemplates : []) + setPrompts(promptsResponse.prompts) addLog( 'info', - `Loaded ${toolsResponse.tools.length} tools, ${resourcesResponse.resources.length} resources, ${Array.isArray(resourcesResponse.resourceTemplates) ? resourcesResponse.resourceTemplates.length : 0} resource templates.`, + `Loaded ${toolsResponse.tools.length} tools, ${resourcesResponse.resources.length} resources, ${Array.isArray(resourcesResponse.resourceTemplates) ? resourcesResponse.resourceTemplates.length : 0} resource templates, ${promptsResponse.prompts.length} prompts.`, ) setState('ready') // Final success state // connectingRef will be set to false after orchestration logic @@ -668,6 +677,45 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { [state, addLog], ) // Depends on state for error message and stable addLog + // listPrompts is stable (depends on stable addLog) + const listPrompts = useCallback(async () => { + // Use stateRef for check, state for throwing error message + if (stateRef.current !== 'ready' || !clientRef.current) { + throw new Error(`MCP client is not ready (current state: ${state}). Cannot list prompts.`) + } + addLog('info', 'Listing prompts...') + try { + const promptsResponse = await clientRef.current.request({ method: 'prompts/list' }, ListPromptsResultSchema) + if (isMountedRef.current) { + setPrompts(promptsResponse.prompts) + addLog('info', `Listed ${promptsResponse.prompts.length} prompts.`) + } + } catch (err) { + addLog('error', `Error listing prompts: ${err instanceof Error ? err.message : String(err)}`, err) + throw err + } + }, [state, addLog]) // Depends on state for error message and stable addLog + + // getPrompt is stable (depends on stable addLog) + const getPrompt = useCallback( + async (name: string, args?: Record) => { + // Use stateRef for check, state for throwing error message + if (stateRef.current !== 'ready' || !clientRef.current) { + throw new Error(`MCP client is not ready (current state: ${state}). Cannot get prompt "${name}".`) + } + addLog('info', `Getting prompt: ${name}`, args) + try { + const result = await clientRef.current.request({ method: 'prompts/get', params: { name, arguments: args } }, GetPromptResultSchema) + addLog('info', `Prompt "${name}" retrieved successfully`) + return result + } catch (err) { + addLog('error', `Error getting prompt "${name}": ${err instanceof Error ? err.message : String(err)}`, err) + throw err + } + }, + [state, addLog], + ) // Depends on state for error message and stable addLog + // ===== Effects ===== // Effect for handling auth callback messages from popup (Stable dependencies) @@ -757,12 +805,15 @@ export function useMcp(options: UseMcpOptions): UseMcpResult { tools, resources, resourceTemplates, + prompts, error, log, authUrl, callTool, listResources, readResource, + listPrompts, + getPrompt, retry, disconnect, authenticate,