docs: update README files with resources and prompts documentation

- Add resources, resourceTemplates, and prompts to all feature lists
- Document new properties returned by useMcp hook
- Document new methods: listResources, readResource, listPrompts, getPrompt
- Add code examples showing how to use resources and prompts
- Update quick start examples to demonstrate all features
- Add note about graceful handling of servers with partial MCP support

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jem 2025-06-20 01:39:09 -07:00
commit b909730ae2
4 changed files with 153 additions and 63 deletions

View file

@ -25,6 +25,9 @@ yarn add use-mcp
- 🔄 Automatic connection management with reconnection and retries - 🔄 Automatic connection management with reconnection and retries
- 🔐 OAuth authentication flow handling with popup and fallback support - 🔐 OAuth authentication flow handling with popup and fallback support
- 📦 Simple React hook interface for MCP integration - 📦 Simple React hook interface for MCP integration
- 🧰 Full support for MCP tools, resources, and prompts
- 📄 Access server resources and read their contents
- 💬 Use server-provided prompt templates
- 🧰 TypeScript types for editor assistance and type checking - 🧰 TypeScript types for editor assistance and type checking
- 📝 Comprehensive logging for debugging - 📝 Comprehensive logging for debugging
- 🌐 Works with both HTTP and SSE (Server-Sent Events) transports - 🌐 Works with both HTTP and SSE (Server-Sent Events) transports
@ -38,8 +41,12 @@ function MyAIComponent() {
const { const {
state, // Connection state: 'discovering' | 'pending_auth' | 'authenticating' | 'connecting' | 'loading' | 'ready' | 'failed' state, // Connection state: 'discovering' | 'pending_auth' | 'authenticating' | 'connecting' | 'loading' | 'ready' | 'failed'
tools, // Available tools from MCP server tools, // Available tools from MCP server
resources, // Available resources from MCP server
prompts, // Available prompts from MCP server
error, // Error message if connection failed error, // Error message if connection failed
callTool, // Function to call tools on the MCP server callTool, // Function to call tools on the MCP server
readResource, // Function to read resource contents
getPrompt, // Function to get prompt messages
retry, // Retry connection manually retry, // Retry connection manually
authenticate, // Manually trigger authentication authenticate, // Manually trigger authentication
clearStorage, // Clear stored tokens and credentials clearStorage, // Clear stored tokens and credentials
@ -83,6 +90,32 @@ function MyAIComponent() {
))} ))}
</ul> </ul>
<button onClick={handleSearch}>Search</button> <button onClick={handleSearch}>Search</button>
{/* Example: Display and read resources */}
{resources.length > 0 && (
<div>
<h3>Resources: {resources.length}</h3>
<button onClick={async () => {
const content = await readResource(resources[0].uri)
console.log('Resource content:', content)
}}>
Read First Resource
</button>
</div>
)}
{/* Example: Use prompts */}
{prompts.length > 0 && (
<div>
<h3>Prompts: {prompts.length}</h3>
<button onClick={async () => {
const result = await getPrompt(prompts[0].name)
console.log('Prompt messages:', result.messages)
}}>
Get First Prompt
</button>
</div>
)}
</div> </div>
) )
} }
@ -176,10 +209,17 @@ function useMcp(options: UseMcpOptions): UseMcpResult
|----------|------|-------------| |----------|------|-------------|
| `state` | `string` | Current connection state: 'discovering', 'pending_auth', 'authenticating', 'connecting', 'loading', 'ready', 'failed' | | `state` | `string` | Current connection state: 'discovering', 'pending_auth', 'authenticating', 'connecting', 'loading', 'ready', 'failed' |
| `tools` | `Tool[]` | Available tools from the MCP server | | `tools` | `Tool[]` | Available tools from the MCP server |
| `resources` | `Resource[]` | Available resources from the MCP server |
| `resourceTemplates` | `ResourceTemplate[]` | Available resource templates from the MCP server |
| `prompts` | `Prompt[]` | Available prompts from the MCP server |
| `error` | `string \| undefined` | Error message if connection failed | | `error` | `string \| undefined` | Error message if connection failed |
| `authUrl` | `string \| undefined` | Manual authentication URL if popup is blocked | | `authUrl` | `string \| undefined` | Manual authentication URL if popup is blocked |
| `log` | `{ level: 'debug' \| 'info' \| 'warn' \| 'error'; message: string; timestamp: number }[]` | Array of log messages | | `log` | `{ level: 'debug' \| 'info' \| 'warn' \| 'error'; message: string; timestamp: number }[]` | Array of log messages |
| `callTool` | `(name: string, args?: Record<string, unknown>) => Promise<any>` | Function to call a tool on the MCP server | | `callTool` | `(name: string, args?: Record<string, unknown>) => Promise<any>` | Function to call a tool on the MCP server |
| `listResources` | `() => Promise<void>` | Refresh the list of available resources |
| `readResource` | `(uri: string) => Promise<{ contents: Array<...> }>` | Read the contents of a specific resource |
| `listPrompts` | `() => Promise<void>` | Refresh the list of available prompts |
| `getPrompt` | `(name: string, args?: Record<string, string>) => Promise<{ messages: Array<...> }>` | Get a specific prompt with optional arguments |
| `retry` | `() => void` | Manually attempt to reconnect | | `retry` | `() => void` | Manually attempt to reconnect |
| `disconnect` | `() => void` | Disconnect from the MCP server | | `disconnect` | `() => void` | Disconnect from the MCP server |
| `authenticate` | `() => void` | Manually trigger authentication | | `authenticate` | `() => void` | Manually trigger authentication |

View file

@ -6,6 +6,8 @@ A minimal demo showcasing the `use-mcp` React hook for connecting to Model Conte
- Connect to any MCP server via URL - Connect to any MCP server via URL
- View available tools and their schemas - View available tools and their schemas
- Browse and read server resources
- Interact with server-provided prompts
- Real-time connection status monitoring - Real-time connection status monitoring
- Debug logging for troubleshooting - Debug logging for troubleshooting
- Clean, minimal UI focused on MCP functionality - Clean, minimal UI focused on MCP functionality
@ -31,7 +33,7 @@ pnpm dev
3. Open your browser and navigate to the displayed local URL 3. Open your browser and navigate to the displayed local URL
4. Enter an MCP server URL to test the connection and explore available tools 4. Enter an MCP server URL to test the connection and explore available tools, resources, and prompts
## What This Demonstrates ## What This Demonstrates
@ -46,7 +48,17 @@ const connection = useMcp({
autoRetry: false autoRetry: false
}) })
// Access connection.state, connection.tools, connection.error, etc. // Access connection.state, connection.tools, connection.resources,
// connection.prompts, connection.error, etc.
``` ```
The `McpServers` component wraps this hook to provide a complete UI for server management and tool inspection. The `McpServers` component wraps this hook to provide a complete UI for server management, tool inspection, resource browsing, and prompt interaction.
## Supported MCP Features
- **Tools**: Execute server-provided tools with custom arguments and view results
- **Resources**: Browse available resources and read their contents (text or binary)
- **Resource Templates**: View dynamic resource templates with URI patterns
- **Prompts**: Interact with server prompts, provide arguments, and view generated messages
Note: Not all MCP servers implement all features. The inspector will gracefully handle servers that only support a subset of the MCP specification.

View file

@ -60,13 +60,11 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
retry: () => {}, retry: () => {},
disconnect: () => {}, disconnect: () => {},
authenticate: () => Promise.resolve(undefined), authenticate: () => Promise.resolve(undefined),
callTool: (_name: string, _args?: Record<string, unknown>) => callTool: (_name: string, _args?: Record<string, unknown>) => Promise.resolve(undefined),
Promise.resolve(undefined),
listResources: () => Promise.resolve(), listResources: () => Promise.resolve(),
readResource: (_uri: string) => Promise.resolve({ contents: [] }), readResource: (_uri: string) => Promise.resolve({ contents: [] }),
listPrompts: () => Promise.resolve(), listPrompts: () => Promise.resolve(),
getPrompt: (_name: string, _args?: Record<string, string>) => getPrompt: (_name: string, _args?: Record<string, string>) => Promise.resolve({ messages: [] }),
Promise.resolve({ messages: [] }),
clearStorage: () => {}, clearStorage: () => {},
}) })
const [toolForms, setToolForms] = useState<Record<string, Record<string, any>>>({}) const [toolForms, setToolForms] = useState<Record<string, Record<string, any>>>({})
@ -118,13 +116,11 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
retry: () => {}, retry: () => {},
disconnect: () => {}, disconnect: () => {},
authenticate: () => Promise.resolve(undefined), authenticate: () => Promise.resolve(undefined),
callTool: (_name: string, _args?: Record<string, unknown>) => callTool: (_name: string, _args?: Record<string, unknown>) => Promise.resolve(undefined),
Promise.resolve(undefined),
listResources: () => Promise.resolve(), listResources: () => Promise.resolve(),
readResource: (_uri: string) => Promise.resolve({ contents: [] }), readResource: (_uri: string) => Promise.resolve({ contents: [] }),
listPrompts: () => Promise.resolve(), listPrompts: () => Promise.resolve(),
getPrompt: (_name: string, _args?: Record<string, string>) => getPrompt: (_name: string, _args?: Record<string, string>) => Promise.resolve({ messages: [] }),
Promise.resolve({ messages: [] }),
clearStorage: () => {}, clearStorage: () => {},
}) })
} }
@ -495,9 +491,7 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
{/* Available Resources section */} {/* Available Resources section */}
<div> <div>
<h3 className="font-medium text-sm mb-3"> <h3 className="font-medium text-sm mb-3">Available Resources ({resources.length + resourceTemplates.length})</h3>
Available Resources ({resources.length + resourceTemplates.length})
</h3>
{resources.length === 0 && resourceTemplates.length === 0 ? ( {resources.length === 0 && resourceTemplates.length === 0 ? (
<div className="border border-gray-200 rounded p-4 bg-gray-50 text-center text-gray-500 text-sm"> <div className="border border-gray-200 rounded p-4 bg-gray-50 text-center text-gray-500 text-sm">
@ -515,7 +509,7 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
<div className="p-3"> <div className="p-3">
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<button <button
onClick={() => setExpandedResources(prev => ({ ...prev, [resource.uri]: !isExpanded }))} onClick={() => setExpandedResources((prev) => ({ ...prev, [resource.uri]: !isExpanded }))}
className="text-gray-500 hover:text-gray-700 mt-0.5" className="text-gray-500 hover:text-gray-700 mt-0.5"
> >
{isExpanded ? <ChevronDown size={16} /> : <ChevronRight size={16} />} {isExpanded ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
@ -525,13 +519,9 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
<FileText size={14} className="text-gray-400" /> <FileText size={14} className="text-gray-400" />
<h4 className="font-medium text-sm">{resource.name}</h4> <h4 className="font-medium text-sm">{resource.name}</h4>
</div> </div>
{resource.description && ( {resource.description && <p className="text-xs text-gray-500 mt-1">{resource.description}</p>}
<p className="text-xs text-gray-500 mt-1">{resource.description}</p>
)}
<p className="text-xs text-gray-400 mt-1 font-mono">{resource.uri}</p> <p className="text-xs text-gray-400 mt-1 font-mono">{resource.uri}</p>
{resource.mimeType && ( {resource.mimeType && <span className="text-xs text-gray-400">({resource.mimeType})</span>}
<span className="text-xs text-gray-400">({resource.mimeType})</span>
)}
</div> </div>
</div> </div>
@ -541,9 +531,9 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
onClick={async () => { onClick={async () => {
try { try {
const result = await readResource(resource.uri) const result = await readResource(resource.uri)
setResourceContents(prev => ({ ...prev, [resource.uri]: result })) setResourceContents((prev) => ({ ...prev, [resource.uri]: result }))
} catch (error: any) { } catch (error: any) {
setResourceContents(prev => ({ ...prev, [resource.uri]: { error: error.message } })) setResourceContents((prev) => ({ ...prev, [resource.uri]: { error: error.message } }))
} }
}} }}
className="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white rounded text-xs font-medium" className="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white rounded text-xs font-medium"
@ -589,13 +579,9 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
<h4 className="font-medium text-sm">{template.name}</h4> <h4 className="font-medium text-sm">{template.name}</h4>
<span className="text-xs text-blue-600 bg-blue-50 px-2 py-0.5 rounded">Template</span> <span className="text-xs text-blue-600 bg-blue-50 px-2 py-0.5 rounded">Template</span>
</div> </div>
{template.description && ( {template.description && <p className="text-xs text-gray-500 mt-1">{template.description}</p>}
<p className="text-xs text-gray-500 mt-1">{template.description}</p>
)}
<p className="text-xs text-gray-400 mt-1 font-mono">{template.uriTemplate}</p> <p className="text-xs text-gray-400 mt-1 font-mono">{template.uriTemplate}</p>
{template.mimeType && ( {template.mimeType && <span className="text-xs text-gray-400">({template.mimeType})</span>}
<span className="text-xs text-gray-400">({template.mimeType})</span>
)}
</div> </div>
</div> </div>
))} ))}
@ -605,9 +591,7 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
{/* Available Prompts section */} {/* Available Prompts section */}
<div> <div>
<h3 className="font-medium text-sm mb-3"> <h3 className="font-medium text-sm mb-3">Available Prompts ({prompts.length})</h3>
Available Prompts ({prompts.length})
</h3>
{prompts.length === 0 ? ( {prompts.length === 0 ? (
<div className="border border-gray-200 rounded p-4 bg-gray-50 text-center text-gray-500 text-sm"> <div className="border border-gray-200 rounded p-4 bg-gray-50 text-center text-gray-500 text-sm">
@ -625,7 +609,7 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
<div className="p-3"> <div className="p-3">
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<button <button
onClick={() => setExpandedPrompts(prev => ({ ...prev, [prompt.name]: !isExpanded }))} onClick={() => setExpandedPrompts((prev) => ({ ...prev, [prompt.name]: !isExpanded }))}
className="text-gray-500 hover:text-gray-700 mt-0.5" className="text-gray-500 hover:text-gray-700 mt-0.5"
> >
{isExpanded ? <ChevronDown size={16} /> : <ChevronRight size={16} />} {isExpanded ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
@ -635,9 +619,7 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
<MessageSquare size={14} className="text-purple-400" /> <MessageSquare size={14} className="text-purple-400" />
<h4 className="font-medium text-sm">{prompt.name}</h4> <h4 className="font-medium text-sm">{prompt.name}</h4>
</div> </div>
{prompt.description && ( {prompt.description && <p className="text-xs text-gray-500 mt-1">{prompt.description}</p>}
<p className="text-xs text-gray-500 mt-1">{prompt.description}</p>
)}
</div> </div>
</div> </div>
@ -652,21 +634,21 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
<label className="text-xs text-gray-600"> <label className="text-xs text-gray-600">
{arg.name} {arg.name}
{arg.required && <span className="text-red-500 ml-1">*</span>} {arg.required && <span className="text-red-500 ml-1">*</span>}
{arg.description && ( {arg.description && <span className="text-gray-400 ml-1">({arg.description})</span>}
<span className="text-gray-400 ml-1">({arg.description})</span>
)}
</label> </label>
<input <input
type="text" type="text"
className="w-full p-2 border border-gray-200 rounded text-sm" className="w-full p-2 border border-gray-200 rounded text-sm"
value={args[arg.name] || ''} value={args[arg.name] || ''}
onChange={(e) => setPromptArgs(prev => ({ onChange={(e) =>
...prev, setPromptArgs((prev) => ({
[prompt.name]: { ...prev,
...prev[prompt.name], [prompt.name]: {
[arg.name]: e.target.value ...prev[prompt.name],
} [arg.name]: e.target.value,
}))} },
}))
}
placeholder={arg.description || `Enter ${arg.name}`} placeholder={arg.description || `Enter ${arg.name}`}
/> />
</div> </div>
@ -678,9 +660,9 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
onClick={async () => { onClick={async () => {
try { try {
const result = await getPrompt(prompt.name, args) const result = await getPrompt(prompt.name, args)
setPromptResults(prev => ({ ...prev, [prompt.name]: result })) setPromptResults((prev) => ({ ...prev, [prompt.name]: result }))
} catch (error: any) { } catch (error: any) {
setPromptResults(prev => ({ ...prev, [prompt.name]: { error: error.message } })) setPromptResults((prev) => ({ ...prev, [prompt.name]: { error: error.message } }))
} }
}} }}
className="px-3 py-1 bg-purple-600 hover:bg-purple-700 text-white rounded text-xs font-medium" className="px-3 py-1 bg-purple-600 hover:bg-purple-700 text-white rounded text-xs font-medium"
@ -699,9 +681,7 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
<h5 className="text-xs font-medium text-gray-700">Messages:</h5> <h5 className="text-xs font-medium text-gray-700">Messages:</h5>
{result.messages?.map((message: any, idx: number) => ( {result.messages?.map((message: any, idx: number) => (
<div key={idx} className="border border-gray-200 rounded p-2"> <div key={idx} className="border border-gray-200 rounded p-2">
<div className="text-xs font-medium text-gray-700 mb-1"> <div className="text-xs font-medium text-gray-700 mb-1">{message.role}:</div>
{message.role}:
</div>
<pre className="text-xs font-mono bg-gray-50 p-2 rounded overflow-x-auto"> <pre className="text-xs font-mono bg-gray-50 p-2 rounded overflow-x-auto">
{message.content.text || JSON.stringify(message.content, null, 2)} {message.content.text || JSON.stringify(message.content, null, 2)}
</pre> </pre>

View file

@ -23,7 +23,7 @@ The useMcp hook manages the connection to an MCP server, handles authentication
```tsx ```tsx
import { useMcp } from 'use-mcp' import { useMcp } from 'use-mcp'
// Optional: Import types if needed // Optional: Import types if needed
import type { UseMcpOptions, UseMcpResult, Tool } from 'use-mcp' import type { UseMcpOptions, UseMcpResult, Tool, Resource, ResourceTemplate, Prompt } from 'use-mcp'
``` ```
## Usage ## Usage
@ -101,7 +101,12 @@ function MyChatComponent() {
</div> </div>
)} )}
{mcp.state === 'authenticating' && <p>Waiting for authentication...</p>} {mcp.state === 'authenticating' && <p>Waiting for authentication...</p>}
{mcp.state === 'ready' && <p>Connected! Tools available: {mcp.tools.length}</p>} {mcp.state === 'ready' && (
<p>
Connected! Tools: {mcp.tools.length}, Resources: {mcp.resources.length + mcp.resourceTemplates.length},
Prompts: {mcp.prompts.length}
</p>
)}
{/* Your Chat UI */} {/* Your Chat UI */}
<div> <div>
@ -140,6 +145,52 @@ function MyChatComponent() {
<button onClick={mcp.clearStorage} style={{ marginTop: '10px' }}> <button onClick={mcp.clearStorage} style={{ marginTop: '10px' }}>
Clear Stored Auth Clear Stored Auth
</button> </button>
{/* Example: Working with Resources */}
{mcp.state === 'ready' && mcp.resources.length > 0 && (
<div>
<h3>Available Resources:</h3>
{mcp.resources.map((resource) => (
<div key={resource.uri}>
<span>{resource.name}</span>
<button onClick={async () => {
try {
const content = await mcp.readResource(resource.uri)
console.log('Resource content:', content)
} catch (error) {
console.error('Failed to read resource:', error)
}
}}>
Read
</button>
</div>
))}
</div>
)}
{/* Example: Working with Prompts */}
{mcp.state === 'ready' && mcp.prompts.length > 0 && (
<div>
<h3>Available Prompts:</h3>
{mcp.prompts.map((prompt) => (
<div key={prompt.name}>
<span>{prompt.name}</span>
<button onClick={async () => {
try {
const result = await mcp.getPrompt(prompt.name, {
// Add any required arguments here
})
console.log('Prompt result:', result)
} catch (error) {
console.error('Failed to get prompt:', error)
}
}}>
Get Prompt
</button>
</div>
))}
</div>
)}
</div> </div>
) )
} }
@ -163,11 +214,18 @@ export default MyChatComponent
### Hook Return Value (UseMcpResult) ### Hook Return Value (UseMcpResult)
- tools: An array of Tool objects provided by the server. Empty until state is 'ready'. - tools: An array of Tool objects provided by the server. Empty until state is 'ready'.
- resources: An array of Resource objects representing data available from the server.
- resourceTemplates: An array of ResourceTemplate objects representing dynamic resources available from the server.
- prompts: An array of Prompt objects representing reusable prompt templates from the server.
- state: The current connection state ('discovering', 'authenticating', 'connecting', 'loading', 'ready', 'failed'). Use this to conditionally render UI or enable/disable features. - state: The current connection state ('discovering', 'authenticating', 'connecting', 'loading', 'ready', 'failed'). Use this to conditionally render UI or enable/disable features.
- error: An error message if state is 'failed'. - error: An error message if state is 'failed'.
- authUrl: If authentication is required and the popup was potentially blocked, this URL string can be used to let the user manually open the auth page (e.g., <a href={authUrl} target="_blank">...</a>). - authUrl: If authentication is required and the popup was potentially blocked, this URL string can be used to let the user manually open the auth page (e.g., <a href={authUrl} target="_blank">...</a>).
- log: An array of log messages { level, message, timestamp }. Useful for debugging when debug option is true. - log: An array of log messages { level, message, timestamp }. Useful for debugging when debug option is true.
- callTool(name, args): An async function to execute a tool on the MCP server. Throws an error if the client isn't ready or the call fails. - callTool(name, args): An async function to execute a tool on the MCP server. Throws an error if the client isn't ready or the call fails.
- listResources(): An async function to refresh the list of available resources. Returns void.
- readResource(uri): An async function to read the contents of a specific resource. Returns an object with a 'contents' array containing the resource data.
- listPrompts(): An async function to refresh the list of available prompts. Returns void.
- getPrompt(name, args): An async function to get a specific prompt with optional arguments. Returns an object with a 'messages' array containing the prompt messages.
- retry(): Manually triggers a reconnection attempt if the state is 'failed'. - retry(): Manually triggers a reconnection attempt if the state is 'failed'.
- disconnect(): Disconnects the client from the server. - disconnect(): Disconnects the client from the server.
- authenticate(): Manually attempts to start the authentication flow. Useful for triggering the popup via a user click if it was initially blocked. - authenticate(): Manually attempts to start the authentication flow. Useful for triggering the popup via a user click if it was initially blocked.