feat: add MCP prompts support to useMcp hook

- 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 <noreply@anthropic.com>
This commit is contained in:
Jem 2025-06-19 18:41:11 -07:00
commit 8377abf268
2 changed files with 73 additions and 3 deletions

View file

@ -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<void>
/**
* 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<string, string>,
) => 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. */

View file

@ -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<Tool[]>([])
const [resources, setResources] = useState<Resource[]>([])
const [resourceTemplates, setResourceTemplates] = useState<ResourceTemplate[]>([])
const [prompts, setPrompts] = useState<Prompt[]>([])
const [error, setError] = useState<string | undefined>(undefined)
const [log, setLog] = useState<UseMcpResult['log']>([])
const [authUrl, setAuthUrl] = useState<string | undefined>(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<string, string>) => {
// 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,