Using Resources
So far, you have provided access to your Neo4j database through tools, which allow the LLM to query the database dynamically based on a fixed set of parameters.
Now you'll learn about resources - a way to expose data through static URIs, similar to a REST API.
What are Resources?
Resources are data that can be loaded into the LLM's context by the client application.
Unlike tools, which the LLM decides to call, resources are application-controlled - the client decides what to load.
Tools vs Resources
Understanding when to use each is key to effective MCP server design:
Use Tools when:
- The LLM should decide when to access the data
- You need to perform computation or filtering
- The operation might have side effects
- You want the LLM to discover capabilities dynamically
Example: getMoviesByGenre(genre: string) - The LLM decides when to search
Use Resources when:
- You are exposing static reference data or documentation that has a unique identifier
- The data should be loaded into context upfront
- You want to provide specific pieces of data by ID
Example: genre://Comedy - The client directly requests a specific genre
Creating Resources
The TypeScript MCP SDK provides registerResource() to expose resources. Here is a static resource that provides reference data:
server.registerResource(
"genres",
"catalog://genres",
{ description: "All available movie genres with their counts", mimeType: "application/json" },
async () => {
const { records } = await driver.executeQuery(
`
MATCH (g:Genre)
RETURN g.name AS name,
count{ (g)<-[:IN_GENRE]-() } AS movieCount
ORDER BY g.name
`,
{},
{ database }
);
return {
contents: [{
uri: "catalog://genres",
mimeType: "application/json",
text: JSON.stringify({ genres: records.map(r => r.toObject()) }, null, 2),
}],
};
}
);The registerResource() method takes four arguments:
- Name - A human-readable name for the resource (
"genres") - URI - The static URI that clients use to request this resource (
"catalog://genres") - Options - Metadata including
descriptionandmimeType - Handler - An async function that returns the resource content
The catalog://genres URI is static - it does not need any parameters because it always returns the complete list of genres. The handler queries Neo4j for all Genre nodes and returns them as JSON.
Resources should return structured data (JSON) that clients can parse programmatically. This makes it easy for applications to:
- Process the data consistently
- Extract specific fields
- Handle the data in a type-safe way
Resource Templates
While static resources are great for reference data, sometimes you need to access specific entities. The TypeScript SDK provides ResourceTemplate for dynamic URIs with parameters.
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
server.registerResource(
"movie",
new ResourceTemplate("movie://{tmdbId}", { list: undefined }),
{ description: "Get details about a specific movie by TMDB ID", mimeType: "application/json" },
async (uri, { tmdbId }) => {
const { records } = await driver.executeQuery(
`
MATCH (m:Movie {tmdbId: $tmdbId})
RETURN m.title AS title,
m.tagline AS tagline,
m.released AS released,
m.plot AS plot,
[ (m)-[:IN_GENRE]->(g:Genre) | g.name ] AS genres
`,
{ tmdbId: String(tmdbId) },
{ database }
);
if (records.length === 0) {
return {
contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify({ error: `Movie ${tmdbId} not found` }),
}],
};
}
return {
contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(records[0].toObject(), null, 2),
}],
};
}
);The URI pattern movie://{tmdbId} introduces a dynamic element. When a client requests movie://603, the SDK extracts "603" and passes it to your handler function's second parameter as { tmdbId: "603" }.
The handler's first parameter, uri, is the parsed URL object. Use uri.href to get the full URI string for the response.
The second argument to ResourceTemplate is an options object. Setting list: undefined means the server does not provide a way to enumerate all available movies. If you wanted to support listing, you would provide a callback function that returns available resource URIs.
The dynamic pattern allows clients to:
- Request
movie://603to get The Matrix - Request
movie://605to get The Matrix Reloaded
When to Use Resources
Ideal use cases:
- Reference data - Movie details, person profiles, genre information
- Documentation - API docs, server capabilities, usage examples
- Configuration - Server settings, available options
- Static content - About pages, help text, terms of service
- Specific entities - Get one item by ID
Not ideal for:
- Dynamic searches - Use tools instead
- Filtered lists - Use tools with parameters
- Computed results - Use tools for computation
- Operations with side effects - Definitely use tools