Server tools execute automatically when called by the LLM. They have full access to server resources like databases, APIs, and environment variables.
sequenceDiagram
participant LLM Service
participant Server
participant Tool
participant Database/API
LLM Service->>Server: tool_call chunk<br/>{name: "getUserData", args: {...}}
Server->>Server: Parse tool call<br/>arguments
Server->>Tool: execute(parsedArgs)
Tool->>Database/API: Query/Fetch data
Database/API-->>Tool: Return data
Tool-->>Server: Return result
Server->>Server: Create tool_result<br/>message
Server->>LLM Service: Continue chat with<br/>tool_result in history
Note over LLM Service: Model uses result<br/>to generate response
LLM Service-->>Server: Stream content chunks
Server-->>Server: Stream to clientsequenceDiagram
participant LLM Service
participant Server
participant Tool
participant Database/API
LLM Service->>Server: tool_call chunk<br/>{name: "getUserData", args: {...}}
Server->>Server: Parse tool call<br/>arguments
Server->>Tool: execute(parsedArgs)
Tool->>Database/API: Query/Fetch data
Database/API-->>Tool: Return data
Tool-->>Server: Return result
Server->>Server: Create tool_result<br/>message
Server->>LLM Service: Continue chat with<br/>tool_result in history
Note over LLM Service: Model uses result<br/>to generate response
LLM Service-->>Server: Stream content chunks
Server-->>Server: Stream to clientAutomatic (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().meta({ description: "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().meta({ description: "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().meta({ description: "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().meta({ description: "Search query" }),
limit: z.number().optional().meta({ description: "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().meta({ description: "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().meta({ description: "Search query" }),
limit: z.number().optional().meta({ description: "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 function:
import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { getUserData, searchProducts } from "./tools";
export async function POST(request: Request) {
const { messages } = await request.json();
const stream = chat({
adapter: openaiText("gpt-5.2"),
messages,
tools: [getUserData, searchProducts],
});
return toServerSentEventsResponse(stream);
}import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { getUserData, searchProducts } from "./tools";
export async function POST(request: Request) {
const { messages } = await request.json();
const stream = chat({
adapter: openaiText("gpt-5.2"),
messages,
tools: [getUserData, searchProducts],
});
return toServerSentEventsResponse(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 { openaiText } from "@tanstack/ai-openai";
import { getUserData, searchProducts } from "@/tools/server";
const stream = chat({
adapter: openaiText("gpt-5.2"),
messages,
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 { openaiText } from "@tanstack/ai-openai";
import { getUserData, searchProducts } from "@/tools/server";
const stream = chat({
adapter: openaiText("gpt-5.2"),
messages,
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" };
}
});If you have existing JSON Schema definitions or prefer not to use Zod, you can define tool schemas using raw JSON Schema objects:
import { toolDefinition } from "@tanstack/ai";
import type { JSONSchema } from "@tanstack/ai";
const inputSchema: JSONSchema = {
type: "object",
properties: {
userId: {
type: "string",
description: "The user ID to look up",
},
},
required: ["userId"],
};
const outputSchema: JSONSchema = {
type: "object",
properties: {
name: { type: "string" },
email: { type: "string" },
},
required: ["name", "email"],
};
const getUserDataDef = toolDefinition({
name: "get_user_data",
description: "Get user information from the database",
inputSchema,
outputSchema,
});
// When using JSON Schema, args is typed as `any`
const getUserData = getUserDataDef.server(async (args) => {
const user = await db.users.findUnique({ where: { id: args.userId } });
return { name: user.name, email: user.email };
});import { toolDefinition } from "@tanstack/ai";
import type { JSONSchema } from "@tanstack/ai";
const inputSchema: JSONSchema = {
type: "object",
properties: {
userId: {
type: "string",
description: "The user ID to look up",
},
},
required: ["userId"],
};
const outputSchema: JSONSchema = {
type: "object",
properties: {
name: { type: "string" },
email: { type: "string" },
},
required: ["name", "email"],
};
const getUserDataDef = toolDefinition({
name: "get_user_data",
description: "Get user information from the database",
inputSchema,
outputSchema,
});
// When using JSON Schema, args is typed as `any`
const getUserData = getUserDataDef.server(async (args) => {
const user = await db.users.findUnique({ where: { id: args.userId } });
return { name: user.name, email: user.email };
});Note: JSON Schema tools skip runtime validation. Zod schemas are recommended for full type safety and validation.