Server tools execute automatically when called by the LLM. They have full access to server resources like databases, APIs, and environment variables.
Automatic (Default):
Manual (Advanced):
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
const getUserDataDef = toolDefinition({
name: "get_user_data",
description: "Get user information from the database",
inputSchema: z.object({
userId: z.string().describe("The user ID to look up"),
}),
outputSchema: z.object({
name: z.string(),
email: z.string().email(),
createdAt: z.string(),
}),
});
const getUserData = getUserDataDef.server(async ({ userId }) => {
// This runs on the server - secure access to database
const user = await db.users.findUnique({ where: { id: userId } });
return {
name: user.name,
email: user.email,
createdAt: user.createdAt.toISOString(),
};
});
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
const getUserDataDef = toolDefinition({
name: "get_user_data",
description: "Get user information from the database",
inputSchema: z.object({
userId: z.string().describe("The user ID to look up"),
}),
outputSchema: z.object({
name: z.string(),
email: z.string().email(),
createdAt: z.string(),
}),
});
const getUserData = getUserDataDef.server(async ({ userId }) => {
// This runs on the server - secure access to database
const user = await db.users.findUnique({ where: { id: userId } });
return {
name: user.name,
email: user.email,
createdAt: user.createdAt.toISOString(),
};
});
Server tools use the isomorphic toolDefinition() API with the .server() method:
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
// Step 1: Define the tool schema
const getUserDataDef = toolDefinition({
name: "get_user_data",
description: "Get user information from the database",
inputSchema: z.object({
userId: z.string().describe("The user ID to look up"),
}),
outputSchema: z.object({
name: z.string(),
email: z.string().email(),
createdAt: z.string(),
}),
});
// Step 2: Create server implementation
const getUserData = getUserDataDef.server(async ({ userId }) => {
// This runs on the server - can access database, APIs, etc.
const user = await db.users.findUnique({ where: { id: userId } });
return {
name: user.name,
email: user.email,
createdAt: user.createdAt.toISOString(),
};
});
// Example: API call tool
const searchProductsDef = toolDefinition({
name: "search_products",
description: "Search for products in the catalog",
inputSchema: z.object({
query: z.string().describe("Search query"),
limit: z.number().optional().describe("Maximum number of results"),
}),
});
const searchProducts = searchProductsDef.server(async ({ query, limit = 10 }) => {
const response = await fetch(
`https://api.example.com/products?q=${query}&limit=${limit}`,
{
headers: {
Authorization: `Bearer ${process.env.API_KEY}`, // Server-only access
},
}
);
return await response.json();
});
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
// Step 1: Define the tool schema
const getUserDataDef = toolDefinition({
name: "get_user_data",
description: "Get user information from the database",
inputSchema: z.object({
userId: z.string().describe("The user ID to look up"),
}),
outputSchema: z.object({
name: z.string(),
email: z.string().email(),
createdAt: z.string(),
}),
});
// Step 2: Create server implementation
const getUserData = getUserDataDef.server(async ({ userId }) => {
// This runs on the server - can access database, APIs, etc.
const user = await db.users.findUnique({ where: { id: userId } });
return {
name: user.name,
email: user.email,
createdAt: user.createdAt.toISOString(),
};
});
// Example: API call tool
const searchProductsDef = toolDefinition({
name: "search_products",
description: "Search for products in the catalog",
inputSchema: z.object({
query: z.string().describe("Search query"),
limit: z.number().optional().describe("Maximum number of results"),
}),
});
const searchProducts = searchProductsDef.server(async ({ query, limit = 10 }) => {
const response = await fetch(
`https://api.example.com/products?q=${query}&limit=${limit}`,
{
headers: {
Authorization: `Bearer ${process.env.API_KEY}`, // Server-only access
},
}
);
return await response.json();
});
Pass tools to the chat method:
import { chat, toStreamResponse } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";
import { getUserData, searchProducts } from "./tools";
export async function POST(request: Request) {
const { messages } = await request.json();
const stream = chat({
adapter: openai(),
messages,
model: "gpt-4o",
tools: [getUserData, searchProducts],
});
return toStreamResponse(stream);
}
import { chat, toStreamResponse } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";
import { getUserData, searchProducts } from "./tools";
export async function POST(request: Request) {
const { messages } = await request.json();
const stream = chat({
adapter: openai(),
messages,
model: "gpt-4o",
tools: [getUserData, searchProducts],
});
return toStreamResponse(stream);
}
For better organization, define tool schemas and implementations separately:
// tools/definitions.ts
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
export const getUserDataDef = toolDefinition({
name: "get_user_data",
description: "Get user information",
inputSchema: z.object({
userId: z.string(),
}),
outputSchema: z.object({
name: z.string(),
email: z.string(),
}),
});
export const searchProductsDef = toolDefinition({
name: "search_products",
description: "Search products",
inputSchema: z.object({
query: z.string(),
}),
});
// tools/server.ts
import { getUserDataDef, searchProductsDef } from "./definitions";
import { db } from "@/lib/db";
export const getUserData = getUserDataDef.server(async ({ userId }) => {
const user = await db.users.findUnique({ where: { id: userId } });
return { name: user.name, email: user.email };
});
export const searchProducts = searchProductsDef.server(async ({ query }) => {
const products = await db.products.search(query);
return products;
});
// api/chat/route.ts
import { chat } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";
import { getUserData, searchProducts } from "@/tools/server";
const stream = chat({
adapter: openai(),
messages,
model: "gpt-4o",
tools: [getUserData, searchProducts],
});
// tools/definitions.ts
import { toolDefinition } from "@tanstack/ai";
import { z } from "zod";
export const getUserDataDef = toolDefinition({
name: "get_user_data",
description: "Get user information",
inputSchema: z.object({
userId: z.string(),
}),
outputSchema: z.object({
name: z.string(),
email: z.string(),
}),
});
export const searchProductsDef = toolDefinition({
name: "search_products",
description: "Search products",
inputSchema: z.object({
query: z.string(),
}),
});
// tools/server.ts
import { getUserDataDef, searchProductsDef } from "./definitions";
import { db } from "@/lib/db";
export const getUserData = getUserDataDef.server(async ({ userId }) => {
const user = await db.users.findUnique({ where: { id: userId } });
return { name: user.name, email: user.email };
});
export const searchProducts = searchProductsDef.server(async ({ query }) => {
const products = await db.products.search(query);
return products;
});
// api/chat/route.ts
import { chat } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";
import { getUserData, searchProducts } from "@/tools/server";
const stream = chat({
adapter: openai(),
messages,
model: "gpt-4o",
tools: [getUserData, searchProducts],
});
Server tools are automatically executed when the model calls them. The SDK:
You don't need to manually handle tool execution - it's automatic!
Tools should handle errors gracefully:
const getUserDataDef = toolDefinition({
name: "get_user_data",
description: "Get user information",
inputSchema: z.object({
userId: z.string(),
}),
outputSchema: z.object({
name: z.string().optional(),
email: z.string().optional(),
error: z.string().optional(),
}),
});
const getUserData = getUserDataDef.server(async ({ userId }) => {
try {
const user = await db.users.findUnique({ where: { id: userId } });
if (!user) {
return { error: "User not found" };
}
return { name: user.name, email: user.email };
} catch (error) {
return { error: "Failed to fetch user data" };
}
});
const getUserDataDef = toolDefinition({
name: "get_user_data",
description: "Get user information",
inputSchema: z.object({
userId: z.string(),
}),
outputSchema: z.object({
name: z.string().optional(),
email: z.string().optional(),
error: z.string().optional(),
}),
});
const getUserData = getUserDataDef.server(async ({ userId }) => {
try {
const user = await db.users.findUnique({ where: { id: userId } });
if (!user) {
return { error: "User not found" };
}
return { name: user.name, email: user.email };
} catch (error) {
return { error: "Failed to fetch user data" };
}
});
