feat: add MCP resources support to useMcp hook
- Add Resource and ResourceTemplate types from MCP SDK - Add resources and resourceTemplates arrays to UseMcpResult - Fetch resources automatically after tools during connection - Add listResources() method to manually refresh resources list - Add readResource(uri) method to read resource contents - Clear resources state on disconnect 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
25797dd4bb
commit
8373adfdf9
2 changed files with 89 additions and 4 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { Tool } from '@modelcontextprotocol/sdk/types.js'
|
||||
import { Tool, Resource, ResourceTemplate } from '@modelcontextprotocol/sdk/types.js'
|
||||
|
||||
export type UseMcpOptions = {
|
||||
/** The /sse URL of your remote MCP server */
|
||||
|
|
@ -35,6 +35,10 @@ export type UseMcpOptions = {
|
|||
export type UseMcpResult = {
|
||||
/** List of tools available from the connected MCP server */
|
||||
tools: Tool[]
|
||||
/** List of resources available from the connected MCP server */
|
||||
resources: Resource[]
|
||||
/** List of resource templates available from the connected MCP server */
|
||||
resourceTemplates: ResourceTemplate[]
|
||||
/**
|
||||
* The current state of the MCP connection:
|
||||
* - 'discovering': Checking server existence and capabilities (including auth requirements).
|
||||
|
|
@ -63,6 +67,19 @@ export type UseMcpResult = {
|
|||
* @throws If the client is not in the 'ready' state or the call fails.
|
||||
*/
|
||||
callTool: (name: string, args?: Record<string, unknown>) => Promise<any>
|
||||
/**
|
||||
* Function to list resources from the MCP server.
|
||||
* @returns A promise that resolves when resources are refreshed.
|
||||
* @throws If the client is not in the 'ready' state.
|
||||
*/
|
||||
listResources: () => Promise<void>
|
||||
/**
|
||||
* Function to read a resource from the MCP server.
|
||||
* @param uri The URI of the resource to read.
|
||||
* @returns A promise that resolves with the resource contents.
|
||||
* @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 }> }>
|
||||
/** Manually attempts to reconnect if the state is 'failed'. */
|
||||
retry: () => void
|
||||
/** Disconnects the client from the MCP server. */
|
||||
|
|
|
|||
|
|
@ -1,5 +1,14 @@
|
|||
// useMcp.ts
|
||||
import { CallToolResultSchema, JSONRPCMessage, ListToolsResultSchema, Tool } from '@modelcontextprotocol/sdk/types.js'
|
||||
import {
|
||||
CallToolResultSchema,
|
||||
JSONRPCMessage,
|
||||
ListToolsResultSchema,
|
||||
ListResourcesResultSchema,
|
||||
ReadResourceResultSchema,
|
||||
Tool,
|
||||
Resource,
|
||||
ResourceTemplate,
|
||||
} from '@modelcontextprotocol/sdk/types.js'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
// Import both transport types
|
||||
import { SSEClientTransport, SSEClientTransportOptions } from '@modelcontextprotocol/sdk/client/sse.js'
|
||||
|
|
@ -39,6 +48,8 @@ export function useMcp(options: UseMcpOptions): UseMcpResult {
|
|||
|
||||
const [state, setState] = useState<UseMcpResult['state']>('discovering')
|
||||
const [tools, setTools] = useState<Tool[]>([])
|
||||
const [resources, setResources] = useState<Resource[]>([])
|
||||
const [resourceTemplates, setResourceTemplates] = useState<ResourceTemplate[]>([])
|
||||
const [error, setError] = useState<string | undefined>(undefined)
|
||||
const [log, setLog] = useState<UseMcpResult['log']>([])
|
||||
const [authUrl, setAuthUrl] = useState<string | undefined>(undefined)
|
||||
|
|
@ -95,6 +106,8 @@ export function useMcp(options: UseMcpOptions): UseMcpResult {
|
|||
if (isMountedRef.current && !quiet) {
|
||||
setState('discovering')
|
||||
setTools([])
|
||||
setResources([])
|
||||
setResourceTemplates([])
|
||||
setError(undefined)
|
||||
setAuthUrl(undefined)
|
||||
}
|
||||
|
|
@ -283,16 +296,24 @@ export function useMcp(options: UseMcpOptions): UseMcpResult {
|
|||
await clientRef.current!.connect(transportInstance)
|
||||
|
||||
// --- Success Path ---
|
||||
addLog('info', `Client connected via ${transportType.toUpperCase()}. Loading tools...`)
|
||||
addLog('info', `Client connected via ${transportType.toUpperCase()}. Loading tools and resources...`)
|
||||
successfulTransportRef.current = transportType // Store successful type
|
||||
setState('loading')
|
||||
|
||||
const toolsResponse = await clientRef.current!.request({ method: 'tools/list' }, ListToolsResultSchema)
|
||||
|
||||
// Load resources after tools
|
||||
const resourcesResponse = await clientRef.current!.request({ method: 'resources/list' }, ListResourcesResultSchema)
|
||||
|
||||
if (isMountedRef.current) {
|
||||
// Check mount before final state updates
|
||||
setTools(toolsResponse.tools)
|
||||
addLog('info', `Loaded ${toolsResponse.tools.length} tools.`)
|
||||
setResources(resourcesResponse.resources)
|
||||
setResourceTemplates(Array.isArray(resourcesResponse.resourceTemplates) ? resourcesResponse.resourceTemplates : [])
|
||||
addLog(
|
||||
'info',
|
||||
`Loaded ${toolsResponse.tools.length} tools, ${resourcesResponse.resources.length} resources, ${Array.isArray(resourcesResponse.resourceTemplates) ? resourcesResponse.resourceTemplates.length : 0} resource templates.`,
|
||||
)
|
||||
setState('ready') // Final success state
|
||||
// connectingRef will be set to false after orchestration logic
|
||||
connectAttemptRef.current = 0 // Reset on success
|
||||
|
|
@ -604,6 +625,49 @@ export function useMcp(options: UseMcpOptions): UseMcpResult {
|
|||
}
|
||||
}, [url, addLog, disconnect]) // Depends on url and stable callbacks
|
||||
|
||||
// listResources is stable (depends on stable addLog)
|
||||
const listResources = 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 resources.`)
|
||||
}
|
||||
addLog('info', 'Listing resources...')
|
||||
try {
|
||||
const resourcesResponse = await clientRef.current.request({ method: 'resources/list' }, ListResourcesResultSchema)
|
||||
if (isMountedRef.current) {
|
||||
setResources(resourcesResponse.resources)
|
||||
setResourceTemplates(Array.isArray(resourcesResponse.resourceTemplates) ? resourcesResponse.resourceTemplates : [])
|
||||
addLog(
|
||||
'info',
|
||||
`Listed ${resourcesResponse.resources.length} resources, ${Array.isArray(resourcesResponse.resourceTemplates) ? resourcesResponse.resourceTemplates.length : 0} resource templates.`,
|
||||
)
|
||||
}
|
||||
} catch (err) {
|
||||
addLog('error', `Error listing resources: ${err instanceof Error ? err.message : String(err)}`, err)
|
||||
throw err
|
||||
}
|
||||
}, [state, addLog]) // Depends on state for error message and stable addLog
|
||||
|
||||
// readResource is stable (depends on stable addLog)
|
||||
const readResource = useCallback(
|
||||
async (uri: 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 read resource "${uri}".`)
|
||||
}
|
||||
addLog('info', `Reading resource: ${uri}`)
|
||||
try {
|
||||
const result = await clientRef.current.request({ method: 'resources/read', params: { uri } }, ReadResourceResultSchema)
|
||||
addLog('info', `Resource "${uri}" read successfully`)
|
||||
return result
|
||||
} catch (err) {
|
||||
addLog('error', `Error reading resource "${uri}": ${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)
|
||||
|
|
@ -691,10 +755,14 @@ export function useMcp(options: UseMcpOptions): UseMcpResult {
|
|||
return {
|
||||
state,
|
||||
tools,
|
||||
resources,
|
||||
resourceTemplates,
|
||||
error,
|
||||
log,
|
||||
authUrl,
|
||||
callTool,
|
||||
listResources,
|
||||
readResource,
|
||||
retry,
|
||||
disconnect,
|
||||
authenticate,
|
||||
|
|
|
|||
Loading…
Reference in a new issue