Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive guide for building production-ready MCP servers with tools, resources, prompts, and React widgets using mcp-use.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/server/resources.md
1# Resources23Resources expose read-only data that clients can fetch. They don't take input parameters (use resource templates for that).45**Use resources for:** Configuration, static data, documentation, listings, app state67---89## Basic Resource1011```typescript12import { MCPServer, object, text, markdown } from "mcp-use/server";1314const server = new MCPServer({15name: "my-server",16version: "1.0.0"17});1819server.resource(20{21name: "app_settings",22uri: "config://settings",23title: "Application Settings",24description: "Current server configuration"25},26async () => object({27theme: "dark",28version: "1.0.0",29language: "en",30features: {31notifications: true,32analytics: false33}34})35);36```3738**Key points:**39- First argument: resource configuration (uri, name, description, mimeType)40- Second argument: async handler function (no input parameters)41- Handler returns response helper (`object()`, `text()`, `markdown()`, etc.)42- URI scheme is arbitrary (use meaningful prefixes: `config://`, `docs://`, `data://`)4344---4546## Resource Definition4748### URI49Use a scheme-based format for organization:5051```typescript52✅ "config://settings" // Configuration data53✅ "docs://user-guide" // Documentation54✅ "data://available-cities" // Static data55✅ "state://current-user" // Current state5657❌ "settings" // Missing scheme58❌ "http://example.com/data" // Don't use http:// (reserved)59```6061### Name62Machine-readable identifier (kebab-case):63```typescript64✅ "app_settings"65✅ "user_guide"66✅ "available_cities"67```6869### Title70Human-readable name shown to users:71```typescript72✅ "Application Settings"73✅ "User Guide"74✅ "Available Cities"75```7677### Description78Optional but recommended. Explains what the resource contains:79```typescript80description: "Current server configuration including theme, language, and feature flags"81```8283### MIME Type84Indicates content format:8586| Content Type | MIME Type |87|--------------|-----------|88| JSON object | `application/json` |89| Plain text | `text/plain` |90| Markdown | `text/markdown` |91| HTML | `text/html` |92| Image | `image/png`, `image/jpeg` |93| Binary | `application/octet-stream` |9495---9697## Static Resources9899Resources that return fixed data:100101```typescript102server.resource(103{104name: "supported_languages",105uri: "data://supported-languages",106title: "Supported Languages",107description: "List of supported language codes"108},109async () => object({110languages: ["en", "es", "fr", "de", "ja"],111default: "en"112})113);114115server.resource(116{117name: "api_guide",118uri: "docs://api-guide",119title: "API Documentation",120description: "Complete API reference and examples"121},122async () => markdown(`123# API Guide124125## Authentication126Use Bearer token in Authorization header...127128## Endpoints129- POST /api/users - Create user130- GET /api/users/:id - Get user131`)132);133```134135---136137## Dynamic Resources138139Resources that fetch or compute data at request time:140141```typescript142server.resource(143{144name: "current_stats",145uri: "stats://current",146title: "Current Statistics",147description: "Real-time server statistics"148},149async () => {150const stats = await calculateStats();151152return object({153users: stats.totalUsers,154requests: stats.requestCount,155uptime: process.uptime(),156timestamp: new Date().toISOString()157});158}159);160161server.resource(162{163name: "active_sessions",164uri: "state://active-sessions",165title: "Active Sessions",166description: "Currently active user sessions"167},168async () => {169const sessions = await getActiveSessions();170171return object({172count: sessions.length,173sessions: sessions.map(s => ({174id: s.id,175user: s.userId,176started: s.startTime177}))178});179}180);181```182183**When to use dynamic resources:**184- Data changes over time185- Data is expensive to compute (compute on demand)186- Data reflects current server state187188---189190## Resource Templates191192When you need parameters, use resource templates with URI placeholders:193194```typescript195server.resourceTemplate(196{197name: "user_profile",198uriTemplate: "user://{userId}/profile",199title: "User Profile",200description: "Get user profile by ID"201},202async (uri: URL, params: Record<string, string>) => {203// Extract parameters from params object204const { userId } = params;205206const user = await fetchUser(userId);207208if (!user) {209return error(`User not found: ${userId}`);210}211212return object({213id: user.id,214name: user.name,215email: user.email,216createdAt: user.createdAt217});218}219);220```221222**URI template syntax:**223- `{param}` - Single path segment224- `{param*}` - Multiple path segments (greedy)225226**Examples:**227```typescript228// Single parameter229"user://{userId}/profile" // Matches: user://123/profile230"docs://{section}" // Matches: docs://getting-started231232// Multiple parameters233"files://{folder}/{filename}" // Matches: files://documents/report.pdf234"api://{version}/users/{id}" // Matches: api://v1/users/42235236// Greedy parameter (matches multiple segments)237"docs://{path*}" // Matches: docs://guides/api/authentication238```239240**Handler signature:**241```typescript242async (uri: URL, params: Record<string, string>) => {243// uri: URL object of the matched URI244// params: Record<string, string> with extracted template parameters245246// Extract the parameters you need247const { userId } = params;248}249```250251**⚠️ TypeScript Best Practice:**252- **Recommended:** Use explicit types and extract params inside the function body (as shown above)253- **Not recommended:** Parameter destructuring like `async (uri, { userId }) => {}` works at runtime but TypeScript has trouble matching it against the `Record<string, string>` type, potentially causing compilation errors254255---256257## Completion (Autocomplete) for Templates258259Add autocomplete suggestions for resource template variables using the `callbacks.complete` option:260261```typescript262// Static list of suggestions per variable263server.resourceTemplate(264{265uriTemplate: "docs://{docId}",266name: "Documentation",267description: "Get documentation by ID",268callbacks: {269complete: {270docId: ["getting-started", "api-reference", "faq", "changelog"]271}272}273},274async (uri: URL, params: Record<string, string>) => {275const { docId } = params;276return markdown(await fetchDoc(docId));277}278);279280// Dynamic suggestions via callback281server.resourceTemplate(282{283uriTemplate: "user://{userId}/profile",284name: "User Profile",285callbacks: {286complete: {287userId: async (value: string) => {288const users = await searchUsers(value);289return users.map(u => u.id);290}291}292}293},294async (uri: URL, params: Record<string, string>) => {295const { userId } = params;296return object(await fetchUser(userId));297}298);299```300301**Key points:**302- `complete` maps each template variable to either a `string[]` (prefix-matched automatically) or a callback `(value: string) => Promise<string[]>`303- Clients request suggestions via MCP `completion/complete`304305---306307## Error Handling308309Resources can fail - handle errors gracefully with `error()` helper:310311```typescript312server.resource({ uri: "data://external-api", ... }, async () => {313try {314const data = await fetch("https://api.example.com/data");315if (!data.ok) return error(`API returned status ${data.status}`);316return object(await data.json());317} catch (err) {318return error(`Failed to fetch: ${err.message}`);319}320});321```322323See [tools.md](tools.md#error-handling) for comprehensive error handling patterns.324325---326327## Listing Resources328329Clients can list all available resources. Organize resources by URI scheme for discoverability:330331```typescript332// Good organization333server.resource({ uri: "config://settings", ... });334server.resource({ uri: "config://features", ... });335server.resource({ uri: "config://limits", ... });336337server.resource({ uri: "docs://guide", ... });338server.resource({ uri: "docs://api", ... });339server.resource({ uri: "docs://faq", ... });340341server.resource({ uri: "data://cities", ... });342server.resource({ uri: "data://countries", ... });343```344345When a client lists resources, they see:346```json347{348"resources": [349{ "uri": "config://settings", "name": "Application Settings", ... },350{ "uri": "config://features", "name": "Feature Flags", ... },351{ "uri": "docs://guide", "name": "User Guide", ... },352...353]354}355```356357---358359## Resource vs Tool360361**Use a resource when:**362- ✅ Read-only data363- ✅ No input parameters (or use resource templates)364- ✅ Data that clients might browse or list365- ✅ Configuration, docs, static data366367**Use a tool when:**368- ✅ Action with side effects369- ✅ Complex input validation needed370- ✅ Needs Zod schema for structured input371- ✅ May return visual UI (widgets)372373**Example:**374```typescript375// ❌ Bad - Use resource instead376server.tool(377{ name: "get-settings", schema: z.object({}) },378async () => object({ theme: "dark" })379);380381// ✅ Good382server.resource(383{ uri: "config://settings", ... },384async () => object({ theme: "dark" })385);386387// ✅ Good - Tool appropriate here (has input)388server.tool(389{ name: "update-settings", schema: z.object({ theme: z.string() }) },390async ({ theme }) => {391await saveSettings({ theme });392return text("Settings updated");393}394);395```396397---398399## Caching Resources400401Since resources are read-only, caching is often beneficial:402403```typescript404const cache = new Map<string, { data: any; expires: number }>();405406server.resource(407{408uri: "data://expensive-computation",409name: "Expensive Data",410mimeType: "application/json"411},412async () => {413const cacheKey = "expensive-computation";414const cached = cache.get(cacheKey);415416// Return cached data if not expired417if (cached && cached.expires > Date.now()) {418return object(cached.data);419}420421// Compute fresh data422const data = await expensiveComputation();423424// Cache for 10 minutes425cache.set(cacheKey, {426data,427expires: Date.now() + 10 * 60 * 1000428});429430return object(data);431}432);433```434435---436437## Complete Example438439```typescript440import { MCPServer, object, markdown, error } from "mcp-use/server";441442const server = new MCPServer({443name: "docs-server",444version: "1.0.0"445});446447// Static configuration448server.resource(449{450uri: "config://settings",451name: "Server Settings",452mimeType: "application/json"453},454async () => object({455version: "1.0.0",456environment: process.env.NODE_ENV || "development"457})458);459460// Dynamic list461server.resource(462{463uri: "data://available-docs",464name: "Available Documentation",465mimeType: "application/json"466},467async () => {468const docs = await listDocuments();469return object({ docs });470}471);472473// Parameterized access474server.resourceTemplate(475{476uriTemplate: "docs://{docId}",477name: "Documentation",478description: "Get documentation by ID",479mimeType: "text/markdown"480},481async (uri: URL, params: Record<string, string>) => {482const { docId } = params;483const doc = await fetchDocument(docId);484485if (!doc) {486return error(`Document not found: ${docId}`);487}488489return markdown(doc.content);490}491);492493server.listen();494```495496---497498## Next Steps499500- **Format responses** → [response-helpers.md](response-helpers.md)501- **Create tools** → [tools.md](tools.md)502- **See examples** → [../patterns/common-patterns.md](../patterns/common-patterns.md)503