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:
parent
d4220ec5f2
commit
b909730ae2
4 changed files with 153 additions and 63 deletions
40
README.md
40
README.md
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue