Adding Local Resources
Register local resources to provide context data to Tambo
Register resources with Tambo to provide context data, documentation, or other information to the AI. Local resources are data sources that run in your React app. Use them for providing documentation, configuration data, or dynamic content that the AI can reference when responding to user messages.
Static Resources
The simplest way to register resources is to provide a static array via the resources prop. This is useful when you have a fixed set of resources that don't change:
import { TamboRegistryProvider, ListResourceItem } from "@tambo-ai/react";
const resources: ListResourceItem[] = [
{
uri: "docs://api-reference",
name: "API Reference",
description: "Complete API documentation",
mimeType: "text/plain",
},
{
uri: "docs://faq",
name: "Frequently Asked Questions",
description: "Common questions and answers",
mimeType: "text/plain",
},
];
export function App() {
return (
<TamboRegistryProvider resources={resources}>
<YourApp />
</TamboRegistryProvider>
);
}With static resources, you must also provide a getResource function to retrieve the content:
import { ReadResourceResult } from "@tambo-ai/react";
const getResource = async (uri: string): Promise<ReadResourceResult> => {
if (uri === "docs://api-reference") {
return {
contents: [
{
uri,
mimeType: "text/plain",
text: "API Reference: GET /api/users - Returns a list of users...",
},
],
};
}
if (uri === "docs://faq") {
return {
contents: [
{
uri,
mimeType: "text/plain",
text: "Q: How do I reset my password? A: Click the reset link...",
},
],
};
}
throw new Error(`Resource not found: ${uri}`);
};
export function App() {
return (
<TamboRegistryProvider resources={resources} getResource={getResource}>
<YourApp />
</TamboRegistryProvider>
);
}Dynamic Resources
For resources that change based on search queries or other dynamic conditions, provide listResources and getResource functions. Both functions must be provided together:
import {
TamboRegistryProvider,
ListResourceItem,
ReadResourceResult,
} from "@tambo-ai/react";
const listResources = async (search?: string): Promise<ListResourceItem[]> => {
// Fetch available resources, optionally filtered by search
const allDocs = await fetchDocumentation();
if (search) {
return allDocs.filter((doc) =>
doc.name.toLowerCase().includes(search.toLowerCase()),
);
}
return allDocs.map((doc) => ({
uri: `docs://${doc.id}`,
name: doc.title,
description: doc.summary,
mimeType: "text/plain",
}));
};
const getResource = async (uri: string): Promise<ReadResourceResult> => {
const docId = uri.replace("docs://", "");
const doc = await fetchDocument(docId);
if (!doc) {
throw new Error(`Document not found: ${uri}`);
}
return {
contents: [
{
uri,
mimeType: "text/plain",
text: doc.content,
},
],
};
};
export function App() {
return (
<TamboRegistryProvider
listResources={listResources}
getResource={getResource}
>
<YourApp />
</TamboRegistryProvider>
);
}Note:
listResourcesandgetResourcemust be provided together. If you provide one, you must provide the other. This validation ensures resources can be both discovered and retrieved.
Programmatic Registration
You can also register resources programmatically using the registry context. This is useful for adding resources based on user actions or application state:
import { useTamboRegistry } from "@tambo-ai/react";
function DocumentUploader() {
const { registerResource } = useTamboRegistry();
const handleUpload = async (file: File) => {
const content = await file.text();
// Register the uploaded document as a resource
registerResource({
uri: `user-docs://${file.name}`,
name: file.name,
description: `User uploaded: ${file.name}`,
mimeType: file.type,
});
};
return (
<input
type="file"
onChange={(e) => {
if (e.target.files?.[0]) {
handleUpload(e.target.files[0]);
}
}}
/>
);
}For batch registration, use registerResources:
import { useTamboRegistry } from "@tambo-ai/react";
function DocumentLibrary() {
const { registerResources } = useTamboRegistry();
const loadLibrary = async () => {
const docs = await fetchAllDocuments();
registerResources(
docs.map((doc) => ({
uri: `library://${doc.id}`,
name: doc.title,
description: doc.summary,
mimeType: "text/plain",
})),
);
};
// Load on mount
useEffect(() => {
loadLibrary();
}, []);
return <div>Library loaded</div>;
}Resource Content Types
Resources can return various content types through the contents array. Each content item must include a uri and mimeType:
Text Content
const getResource = async (uri: string): Promise<ReadResourceResult> => {
return {
contents: [
{
uri,
mimeType: "text/plain",
text: "This is plain text content",
},
],
};
};Binary Content (Base64)
const getResource = async (uri: string): Promise<ReadResourceResult> => {
const imageData = await fetchImageAsBase64(uri);
return {
contents: [
{
uri,
mimeType: "image/png",
blob: imageData, // Base64-encoded binary data
},
],
};
};Multiple Content Items
A single resource can return multiple content items:
const getResource = async (uri: string): Promise<ReadResourceResult> => {
return {
contents: [
{
uri: `${uri}/readme`,
mimeType: "text/plain",
text: "README content...",
},
{
uri: `${uri}/diagram`,
mimeType: "image/png",
blob: "base64-encoded-image-data...",
},
],
};
};When to Use Local Resources
Use local resources when you need to:
- Provide documentation or reference material to the AI
- Share configuration data or settings
- Make user-uploaded content available for context
- Expose application state as readable context
- Provide dynamic content that changes based on search
For server-side integrations with databases, file systems, or external APIs, consider using MCP servers instead. MCP servers provide additional capabilities like tools, prompts, and sampling.
Resource URIs and Prefixing
Local resources registered through TamboRegistryProvider are never prefixed with a server key. This distinguishes them from MCP resources, which are always prefixed with their server key to prevent URI conflicts.
// Local resource URI (no prefix)
uri: "docs://getting-started";
// MCP resource URI (always prefixed)
uri: "filesystem:file:///path/to/document";When using useTamboMcpResourceList(), local resources appear alongside MCP resources in the combined list, with local resources easily identifiable by their unprefixed URIs.
Validation and Error Handling
Tambo validates resource registrations to ensure data integrity:
- Resource objects: Must have
uri,name, andmimeTypeproperties - Function pairing:
listResourcesandgetResourcemust both be provided or both omitted - URI uniqueness: Each resource should have a unique URI within your application
// ✅ Valid - both functions provided
<TamboRegistryProvider
listResources={listResources}
getResource={getResource}
>
// ✅ Valid - neither function provided (static only)
<TamboRegistryProvider resources={staticResources}>
// ❌ Invalid - only one function provided
<TamboRegistryProvider listResources={listResources}>
// Error: Both listResources and getResource must be provided togetherIntegration with MCP Hooks
Local resources integrate seamlessly with existing MCP hooks:
Listing All Resources
import { useTamboMcpResourceList } from "@tambo-ai/react";
function ResourceBrowser() {
const { data: resources } = useTamboMcpResourceList();
return (
<ul>
{resources?.map((entry) => (
<li key={entry.resource.uri}>
{entry.resource.name}
{entry.server === null && " (local)"}
</li>
))}
</ul>
);
}Reading a Resource
import { useTamboMcpResource } from "@tambo-ai/react";
function ResourceViewer({ uri }: { uri: string }) {
const { data: resource } = useTamboMcpResource(uri);
if (!resource) return <div>Loading...</div>;
return (
<div>
{resource.contents.map((content, i) => (
<div key={i}>
{content.text && <pre>{content.text}</pre>}
{content.blob && (
<img
src={`data:${content.mimeType};base64,${content.blob}`}
alt=""
/>
)}
</div>
))}
</div>
);
}Combining Registration Methods
You can combine static resources with dynamic functions for maximum flexibility:
import { TamboRegistryProvider } from "@tambo-ai/react";
// Static resources that are always available
const staticResources = [
{
uri: "docs://privacy-policy",
name: "Privacy Policy",
mimeType: "text/plain",
},
];
// Dynamic function to list all resources (static + dynamic)
const listResources = async (search?: string) => {
const dynamicDocs = await fetchUserDocuments();
const allResources = [
...staticResources,
...dynamicDocs.map((doc) => ({
uri: `user-docs://${doc.id}`,
name: doc.title,
mimeType: "text/plain",
})),
];
if (search) {
return allResources.filter((r) =>
r.name.toLowerCase().includes(search.toLowerCase()),
);
}
return allResources;
};
const getResource = async (uri: string) => {
if (uri === "docs://privacy-policy") {
return {
contents: [
{ uri, mimeType: "text/plain", text: "Privacy policy text..." },
],
};
}
// Handle dynamic resources
const doc = await fetchDocument(uri);
return {
contents: [{ uri, mimeType: "text/plain", text: doc.content }],
};
};
<TamboRegistryProvider
resources={staticResources}
listResources={listResources}
getResource={getResource}
>
<App />
</TamboRegistryProvider>;