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
- 🔐 OAuth authentication flow handling with popup and fallback support
- 📦 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
- 📝 Comprehensive logging for debugging
- 🌐 Works with both HTTP and SSE (Server-Sent Events) transports
@ -38,8 +41,12 @@ function MyAIComponent() {
const {
state, // Connection state: 'discovering' | 'pending_auth' | 'authenticating' | 'connecting' | 'loading' | 'ready' | 'failed'
tools, // Available tools from MCP server
resources, // Available resources from MCP server
prompts, // Available prompts from MCP server
error, // Error message if connection failed
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
authenticate, // Manually trigger authentication
clearStorage, // Clear stored tokens and credentials
@ -83,6 +90,32 @@ function MyAIComponent() {
))}
</ul>
<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>
)
}
@ -176,10 +209,17 @@ function useMcp(options: UseMcpOptions): UseMcpResult
|----------|------|-------------|
| `state` | `string` | Current connection state: 'discovering', 'pending_auth', 'authenticating', 'connecting', 'loading', 'ready', 'failed' |
| `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 |
| `authUrl` | `string \| undefined` | Manual authentication URL if popup is blocked |
| `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 |
| `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 |
| `disconnect` | `() => void` | Disconnect from the MCP server |
| `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
- View available tools and their schemas
- Browse and read server resources
- Interact with server-provided prompts
- Real-time connection status monitoring
- Debug logging for troubleshooting
- Clean, minimal UI focused on MCP functionality
@ -31,7 +33,7 @@ pnpm dev
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
@ -46,7 +48,17 @@ const connection = useMcp({
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: () => {},
disconnect: () => {},
authenticate: () => Promise.resolve(undefined),
callTool: (_name: string, _args?: Record<string, unknown>) =>
Promise.resolve(undefined),
callTool: (_name: string, _args?: Record<string, unknown>) => Promise.resolve(undefined),
listResources: () => Promise.resolve(),
readResource: (_uri: string) => Promise.resolve({ contents: [] }),
listPrompts: () => Promise.resolve(),
getPrompt: (_name: string, _args?: Record<string, string>) =>
Promise.resolve({ messages: [] }),
getPrompt: (_name: string, _args?: Record<string, string>) => Promise.resolve({ messages: [] }),
clearStorage: () => {},
})
const [toolForms, setToolForms] = useState<Record<string, Record<string, any>>>({})
@ -118,13 +116,11 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
retry: () => {},
disconnect: () => {},
authenticate: () => Promise.resolve(undefined),
callTool: (_name: string, _args?: Record<string, unknown>) =>
Promise.resolve(undefined),
callTool: (_name: string, _args?: Record<string, unknown>) => Promise.resolve(undefined),
listResources: () => Promise.resolve(),
readResource: (_uri: string) => Promise.resolve({ contents: [] }),
listPrompts: () => Promise.resolve(),
getPrompt: (_name: string, _args?: Record<string, string>) =>
Promise.resolve({ messages: [] }),
getPrompt: (_name: string, _args?: Record<string, string>) => Promise.resolve({ messages: [] }),
clearStorage: () => {},
})
}
@ -495,9 +491,7 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
{/* Available Resources section */}
<div>
<h3 className="font-medium text-sm mb-3">
Available Resources ({resources.length + resourceTemplates.length})
</h3>
<h3 className="font-medium text-sm mb-3">Available Resources ({resources.length + resourceTemplates.length})</h3>
{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">
@ -515,7 +509,7 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
<div className="p-3">
<div className="flex items-start gap-2">
<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"
>
{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" />
<h4 className="font-medium text-sm">{resource.name}</h4>
</div>
{resource.description && (
<p className="text-xs text-gray-500 mt-1">{resource.description}</p>
)}
{resource.description && <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>
{resource.mimeType && (
<span className="text-xs text-gray-400">({resource.mimeType})</span>
)}
{resource.mimeType && <span className="text-xs text-gray-400">({resource.mimeType})</span>}
</div>
</div>
@ -541,9 +531,9 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
onClick={async () => {
try {
const result = await readResource(resource.uri)
setResourceContents(prev => ({ ...prev, [resource.uri]: result }))
setResourceContents((prev) => ({ ...prev, [resource.uri]: result }))
} 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"
@ -589,13 +579,9 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
<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>
</div>
{template.description && (
<p className="text-xs text-gray-500 mt-1">{template.description}</p>
)}
{template.description && <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>
{template.mimeType && (
<span className="text-xs text-gray-400">({template.mimeType})</span>
)}
{template.mimeType && <span className="text-xs text-gray-400">({template.mimeType})</span>}
</div>
</div>
))}
@ -605,9 +591,7 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
{/* Available Prompts section */}
<div>
<h3 className="font-medium text-sm mb-3">
Available Prompts ({prompts.length})
</h3>
<h3 className="font-medium text-sm mb-3">Available Prompts ({prompts.length})</h3>
{prompts.length === 0 ? (
<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="flex items-start gap-2">
<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"
>
{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" />
<h4 className="font-medium text-sm">{prompt.name}</h4>
</div>
{prompt.description && (
<p className="text-xs text-gray-500 mt-1">{prompt.description}</p>
)}
{prompt.description && <p className="text-xs text-gray-500 mt-1">{prompt.description}</p>}
</div>
</div>
@ -652,21 +634,21 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
<label className="text-xs text-gray-600">
{arg.name}
{arg.required && <span className="text-red-500 ml-1">*</span>}
{arg.description && (
<span className="text-gray-400 ml-1">({arg.description})</span>
)}
{arg.description && <span className="text-gray-400 ml-1">({arg.description})</span>}
</label>
<input
type="text"
className="w-full p-2 border border-gray-200 rounded text-sm"
value={args[arg.name] || ''}
onChange={(e) => setPromptArgs(prev => ({
onChange={(e) =>
setPromptArgs((prev) => ({
...prev,
[prompt.name]: {
...prev[prompt.name],
[arg.name]: e.target.value
[arg.name]: e.target.value,
},
}))
}
}))}
placeholder={arg.description || `Enter ${arg.name}`}
/>
</div>
@ -678,9 +660,9 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
onClick={async () => {
try {
const result = await getPrompt(prompt.name, args)
setPromptResults(prev => ({ ...prev, [prompt.name]: result }))
setPromptResults((prev) => ({ ...prev, [prompt.name]: result }))
} 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"
@ -699,9 +681,7 @@ export function McpServers({ onToolsUpdate }: { onToolsUpdate?: (tools: Tool[])
<h5 className="text-xs font-medium text-gray-700">Messages:</h5>
{result.messages?.map((message: any, idx: number) => (
<div key={idx} className="border border-gray-200 rounded p-2">
<div className="text-xs font-medium text-gray-700 mb-1">
{message.role}:
</div>
<div className="text-xs font-medium text-gray-700 mb-1">{message.role}:</div>
<pre className="text-xs font-mono bg-gray-50 p-2 rounded overflow-x-auto">
{message.content.text || JSON.stringify(message.content, null, 2)}
</pre>

View file

@ -23,7 +23,7 @@ The useMcp hook manages the connection to an MCP server, handles authentication
```tsx
import { useMcp } from 'use-mcp'
// 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
@ -101,7 +101,12 @@ function MyChatComponent() {
</div>
)}
{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 */}
<div>
@ -140,6 +145,52 @@ function MyChatComponent() {
<button onClick={mcp.clearStorage} style={{ marginTop: '10px' }}>
Clear Stored Auth
</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>
)
}
@ -163,11 +214,18 @@ export default MyChatComponent
### Hook Return Value (UseMcpResult)
- 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.
- 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>).
- 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.
- 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'.
- 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.