AI Tools Registry
Install AI SDK tools into your project with the shadcn CLI. Some tools include UI components to render the result in your chat view.
To learn how to use tools, check out the AI SDK docs.
To add your own tools to the AI Tools Registry, file a PR on our public GitHub repository.
Public Stats
Global M5+ earthquakes — last 30 days
Code
import { tool } from "ai"
import { z } from "zod"
// Fetch global earthquake counts (per day) from USGS for the last N days
export const publicStatsTool = tool({
description:
"Fetch daily counts of global earthquakes from USGS for the last N days.",
inputSchema: z.object({
daysBack: z
.number()
.int()
.min(1)
.max(365)
.default(30)
.describe("How many days back from today (UTC) to include"),
minMagnitude: z
.number()
.min(0)
.max(10)
.default(5)
.describe("Minimum magnitude to include"),
}),
execute: async ({ daysBack, minMagnitude }): Promise<PublicStatsResult> => {
const end = new Date()
const start = new Date(end.getTime() - daysBack * 24 * 60 * 60 * 1000)
const fmt = (d: Date) => d.toISOString().slice(0, 10)
const params = new URLSearchParams({
format: "geojson",
starttime: fmt(start),
endtime: fmt(end),
minmagnitude: String(minMagnitude),
})
const url = `https://earthquake.usgs.gov/fdsnws/event/1/query?${params.toString()}`
const res = await fetch(url)
if (!res.ok) throw new Error(`USGS API failed: ${res.status}`)
const data = (await res.json()) as {
features?: Array<{ properties?: { time?: number } }>
}
const counts = new Map<string, number>()
for (const f of data.features ?? []) {
const t = f?.properties?.time
if (!Number.isFinite(t)) continue
const day = new Date(Number(t)).toISOString().slice(0, 10)
counts.set(day, (counts.get(day) || 0) + 1)
}
const series: StatsSeriesPoint[] = []
for (let i = daysBack; i >= 0; i--) {
const d = new Date(end.getTime() - i * 24 * 60 * 60 * 1000)
const day = d.toISOString().slice(0, 10)
series.push({ date: day, count: counts.get(day) || 0 })
}
return { title: `Global M${minMagnitude}+ earthquakes`, series }
},
})
export interface StatsSeriesPoint {
date: string // YYYY-MM-DD
count: number
}
export interface PublicStatsResult {
title: string
series: StatsSeriesPoint[]
}
export default publicStatsTool
Public Stats
Source: USGS Earthquake Catalog
Get Weather
Returns weather for a location
Code
import { tool } from "ai"
import { z } from "zod"
// Tool definition first
export const getWeatherTool = tool({
description: "Get the current weather for a location.",
inputSchema: z.object({
location: z.string().describe("City name, address or coordinates"),
unit: z.enum(["C", "F"]).default("C"),
}),
execute: async ({ location, unit }) => {
const { latitude, longitude, name } = await geocodeLocation(location)
const params = new URLSearchParams({
latitude: String(latitude),
longitude: String(longitude),
current: [
"temperature_2m",
"relative_humidity_2m",
"wind_speed_10m",
"weather_code",
].join(","),
daily: ["temperature_2m_max", "temperature_2m_min"].join(","),
timezone: "auto",
temperature_unit: unit === "F" ? "fahrenheit" : "celsius",
wind_speed_unit: "kmh",
})
const url = `https://api.open-meteo.com/v1/forecast?${params.toString()}`
const res = await fetch(url)
if (!res.ok) throw new Error(`Weather API failed: ${res.status}`)
const data = (await res.json()) as ForecastResponse
const current = data?.current
const daily = data?.daily
if (!current || !daily) throw new Error("Malformed weather API response")
const weatherCode = Number(current.weather_code)
const mapped = mapWeatherCode(weatherCode)
const result: GetWeatherResult = {
location: name,
unit,
temperature: Math.round(Number(current.temperature_2m)),
condition: mapped.condition,
high: Math.round(Number(daily.temperature_2m_max?.[0])),
low: Math.round(Number(daily.temperature_2m_min?.[0])),
humidity: Math.max(
0,
Math.min(1, Number(current.relative_humidity_2m) / 100)
),
windKph: Math.round(Number(current.wind_speed_10m)),
icon: mapped.icon,
}
return result
},
})
// Re-export shape for result rendering components
export interface GetWeatherResult {
location: string
unit: "C" | "F"
temperature: number
condition: string
high: number
low: number
humidity: number // 0..1
windKph: number
icon?: string
}
// API response types (from Open-Meteo)
interface GeocodeItem {
id: number
name: string
latitude: number
longitude: number
elevation?: number
country_code?: string
admin1?: string
timezone?: string
}
interface GeocodeResponse {
results?: GeocodeItem[]
}
interface ForecastCurrent {
time: string
interval: number
temperature_2m: number
relative_humidity_2m: number
wind_speed_10m: number
weather_code: number
}
interface ForecastDaily {
time: string[]
temperature_2m_max: number[]
temperature_2m_min: number[]
}
interface ForecastResponse {
current: ForecastCurrent
daily: ForecastDaily
}
// Helper functions (hoisted)
async function geocodeLocation(location: string): Promise<{
latitude: number
longitude: number
name: string
}> {
// Allow "lat,lon" inputs without geocoding
const coordMatch = location
.trim()
.match(/^\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*$/)
if (coordMatch) {
const latitude = parseFloat(coordMatch[1])
const longitude = parseFloat(coordMatch[2])
return {
latitude,
longitude,
name: `${latitude.toFixed(3)}, ${longitude.toFixed(3)}`,
}
}
const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(
location
)}&count=1&language=en&format=json`
const res = await fetch(url)
if (!res.ok) throw new Error(`Geocoding failed: ${res.status}`)
const data = (await res.json()) as GeocodeResponse
const first = data?.results?.[0]
if (!first) throw new Error(`Location not found: ${location}`)
const nameParts = [first.name, first.admin1, first.country_code].filter(
Boolean
)
return {
latitude: first.latitude,
longitude: first.longitude,
name: nameParts.join(", "),
}
}
function mapWeatherCode(code: number): { condition: string; icon?: string } {
switch (code) {
case 0:
return { condition: "Clear sky", icon: "weather-sun" }
case 1:
return { condition: "Mainly clear", icon: "weather-sun" }
case 2:
return { condition: "Partly cloudy", icon: "weather-partly" }
case 3:
return { condition: "Overcast", icon: "weather-cloud" }
case 45:
case 48:
return { condition: "Fog", icon: "weather-fog" }
case 51:
case 53:
case 55:
case 56:
case 57:
return { condition: "Drizzle", icon: "weather-drizzle" }
case 61:
case 63:
case 65:
case 66:
case 67:
return { condition: "Rain", icon: "weather-rain" }
case 71:
case 73:
case 75:
case 77:
return { condition: "Snow", icon: "weather-snow" }
case 80:
case 81:
case 82:
return { condition: "Showers", icon: "weather-showers" }
case 85:
case 86:
return { condition: "Snow showers", icon: "weather-snow" }
case 95:
case 96:
case 99:
return { condition: "Thunderstorm", icon: "weather-thunder" }
default:
return { condition: "Unknown" }
}
}
export default getWeatherTool
Weather
Powered by your tool
San Francisco, California, US
14°C
Overcast
High
18°C
Low
14°C
Humidity
95%
Wind: 8 kph
News Search
Returns headlines for a topic
Code
import { tool } from "ai"
import { z } from "zod"
// Tool first
export const newsSearchTool = tool({
description: "Return recent headlines related to a topic.",
inputSchema: z.object({
topic: z.string().min(1),
limit: z.number().min(1).max(20).default(5),
}),
execute: async ({ topic, limit }) => {
// Use Hacker News Algolia Search API (no API key required)
const url = `https://hn.algolia.com/api/v1/search?${new URLSearchParams({
query: topic,
tags: "story",
hitsPerPage: String(limit),
}).toString()}`
const res = await fetch(url)
if (!res.ok) throw new Error(`News API failed: ${res.status}`)
const data = (await res.json()) as AlgoliaSearchResponse
const items: NewsItem[] = (data.hits || []).map((h) => ({
id: String(h.objectID),
title: h.title || h.story_title || "(untitled)",
url: h.url || h.story_url || undefined,
publishedAt: h.created_at || undefined,
}))
return { topic, items }
},
})
export default newsSearchTool
// Public result shapes for UI
export interface NewsItem {
id: string
title: string
url?: string
publishedAt?: string
}
export interface NewsSearchResult {
topic: string
items: NewsItem[]
}
// Response types from Algolia API
interface AlgoliaHit {
objectID: string
title?: string
story_title?: string
url?: string
story_url?: string
created_at?: string
}
interface AlgoliaSearchResponse {
hits: AlgoliaHit[]
}
News
Topic: AI
- Airfoil2/27/2024, 4:32:49 PM
- Open source AI is the path forward7/23/2024, 3:08:41 PM
- My AI skeptic friends are all nuts6/2/2025, 9:09:53 PM
- Gemini AI12/6/2023, 3:03:47 PM
- Bypassing airport security via SQL injection8/29/2024, 3:53:08 PM
Calculator
Basic arithmetic
Code
import { tool } from "ai"
import { z } from "zod"
export const calculatorTool = tool({
description: "Simple calculator for basic arithmetic.",
inputSchema: z.object({
a: z.number(),
b: z.number(),
operator: z.enum(["+", "-", "*", "/"]).default("+"),
}),
execute: async ({ a, b, operator }) => {
let result: number
switch (operator) {
case "+":
result = a + b
break
case "-":
result = a - b
break
case "*":
result = a * b
break
case "/":
result = a / b
break
default:
result = a + b
}
return { a, b, operator, result }
},
})
export interface CalculatorResult {
a: number
b: number
operator: "+" | "-" | "*" | "/"
result: number
}
export default calculatorTool
{
"a": 7,
"b": 3,
"operator": "+",
"result": 10
}
Translate
Translate text (mock)
Code
import { tool } from "ai"
import { z } from "zod"
export const translateTool = tool({
description: "Translate a given text into a target language.",
inputSchema: z.object({
text: z.string().min(1),
targetLanguage: z
.string()
.default("en")
.describe("Target language code e.g. es, fr, de"),
sourceLanguage: z
.string()
.default("en")
.describe("Source language code e.g. en, fr, de"),
}),
execute: async ({ text, targetLanguage, sourceLanguage }) => {
// Use MyMemory Translation API (free, no key). Requires explicit langpair.
const url = `https://api.mymemory.translated.net/get?${new URLSearchParams({
q: text,
langpair: `${sourceLanguage}|${targetLanguage}`,
}).toString()}`
const res = await fetch(url)
if (!res.ok) throw new Error(`Translate API failed: ${res.status}`)
const data = (await res.json()) as MyMemoryResponse
const translated = data?.responseData?.translatedText || text
return { text, targetLanguage, translated }
},
})
export interface TranslateResult {
text: string
targetLanguage: string
translated: string
}
// MyMemory response type
interface MyMemoryResponse {
responseData: { translatedText: string; match?: number }
responseStatus: number
matches?: unknown[]
}
export default translateTool
{
"text": "Hello, world!",
"targetLanguage": "es",
"translated": "¡Hola, mundo!"
}
Time Now
Current time for timezone
Code
import { tool } from "ai"
import { z } from "zod"
export interface TimeNowResult {
timeZone: string
iso: string
formatted: string
}
export const timeNowTool = tool({
description: "Get the current time for a given IANA timezone.",
inputSchema: z.object({
timeZone: z.string().default("UTC"),
locale: z.string().default("en-US"),
}),
execute: async ({ timeZone, locale }) => {
const now = new Date()
return {
timeZone,
iso: now.toISOString(),
formatted: now.toLocaleString(locale, { timeZone }),
}
},
})
export default timeNowTool
{
"timeZone": "UTC",
"iso": "2025-09-04T13:57:29.413Z",
"formatted": "Thu, 04 Sep 2025 13:57:29 GMT"
}
Web Search
Search the web and show results
Code
import { tool } from "ai"
import { z } from "zod"
// Tool first
export const webSearchTool = tool({
description: "Search the web and return relevant results.",
inputSchema: z.object({
query: z.string().min(1),
limit: z.number().min(1).max(20).default(5),
lang: z.string().optional(),
country: z.string().optional(),
}),
execute: async ({ query, limit, lang, country }) => {
// Prefer Brave Search API if a token is provided, else fall back to DuckDuckGo IA API
// You can get up to 2k queries per month for free - https://brave.com/search/api/
const braveToken = process.env.BRAVE_SEARCH_API_KEY
if (braveToken) {
try {
const params = new URLSearchParams({
q: query,
count: String(limit),
})
// Pass through optional hints where possible (Brave will ignore unknown params)
if (country) params.set("country", country)
if (lang) params.set("search_lang", lang)
const url = `https://api.search.brave.com/res/v1/web/search?${params.toString()}`
const res = await fetch(url, {
headers: {
Accept: "application/json",
"X-Subscription-Token": braveToken,
},
})
if (!res.ok) throw new Error(`Brave API failed: ${res.status}`)
const data = BraveSearchSchema.parse(await res.json())
const items = Array.isArray(data.web?.results)
? data.web!.results!
: Array.isArray(data.results)
? data.results!
: []
const results: WebSearchItem[] = items
.slice(0, limit)
.map((r) => {
const title = r.title || r.url || "Untitled"
const url = r.url || ""
let source: string | undefined
try {
source =
r.profile?.long_name ||
(url ? new URL(url).hostname : undefined)
} catch {
source = r.profile?.long_name || undefined
}
return {
title,
url,
snippet: r.description || undefined,
source: source || "Brave",
}
})
.filter((r) => !!r.url)
if (results.length > 0) {
return { query, results }
}
// If Brave returned no results, fall through to DDG
} catch (err) {
if (err instanceof Error) {
console.error(err.message)
}
// Swallow and fall back to DDG
}
} else {
console.info("Brave Search API key not found, falling back to DuckDuckGo")
}
// DuckDuckGo Instant Answer API (free, no key). Not full web search
const url = `https://api.duckduckgo.com/?${new URLSearchParams({
q: query,
format: "json",
no_redirect: "1",
no_html: "1",
t: "ai-tools-registry",
kl: lang ? `${lang}-en` : "",
}).toString()}`
const res = await fetch(url)
if (!res.ok) throw new Error(`Search API failed: ${res.status}`)
const data = DDGResponseSchema.parse(await res.json())
const flatten = (
items: DDGRelated[] = [],
acc: DDGTopic[] = []
): DDGTopic[] => {
for (const it of items) {
// If group
if ((it as DDGRelatedGroup).Topics)
flatten((it as DDGRelatedGroup).Topics, acc)
else if ((it as DDGTopic).FirstURL && (it as DDGTopic).Text)
acc.push(it as DDGTopic)
}
return acc
}
const related = flatten(data.RelatedTopics)
const results: WebSearchItem[] = related.slice(0, limit).map((r) => {
let hostname: string | undefined
try {
hostname = new URL(r.FirstURL).hostname
} catch {
hostname = undefined
}
return {
title: r.Text,
url: r.FirstURL,
snippet: undefined,
source: hostname || "DuckDuckGo",
}
})
return { query, results }
},
})
export default webSearchTool
// Public result shapes for UI
export interface WebSearchItem {
title: string
url: string
snippet?: string
source?: string
}
export interface WebSearchResult {
query: string
results: WebSearchItem[]
}
// DuckDuckGo Instant Answer types
// DuckDuckGo Instant Answer types (runtime-validated)
export const DDGTopicSchema = z
.object({
FirstURL: z.string().url(),
Text: z.string(),
})
.passthrough()
export type DDGTopic = z.infer<typeof DDGTopicSchema>
export const DDGRelatedGroupSchema = z
.object({
Name: z.string().optional(),
Topics: z.array(DDGTopicSchema),
})
.passthrough()
export type DDGRelatedGroup = z.infer<typeof DDGRelatedGroupSchema>
export const DDGRelatedSchema = z.union([DDGTopicSchema, DDGRelatedGroupSchema])
export type DDGRelated = z.infer<typeof DDGRelatedSchema>
export const DDGResponseSchema = z
.object({
Results: z.array(DDGTopicSchema).optional(),
RelatedTopics: z.array(DDGRelatedSchema).optional(),
})
.passthrough()
export type DDGResponse = z.infer<typeof DDGResponseSchema>
// Brave Search Web API types (minimal, aligned to used fields)
export const BraveProfileSchema = z
.object({
name: z.string().optional(),
url: z.string().url().optional(),
long_name: z.string().optional(),
img: z.string().url().optional(),
})
.passthrough()
export const BraveWebResultSchema = z
.object({
title: z.string().optional(),
url: z.string().url().optional(),
description: z.string().optional(),
profile: BraveProfileSchema.optional(),
})
.passthrough()
export const BraveWebSchema = z
.object({
results: z.array(BraveWebResultSchema).optional(),
})
.passthrough()
export const BraveSearchSchema = z
.object({
type: z.string().optional(),
web: BraveWebSchema.optional(),
results: z.array(BraveWebResultSchema).optional(),
})
.passthrough()
export type BraveProfile = z.infer<typeof BraveProfileSchema>
export type BraveWebResult = z.infer<typeof BraveWebResultSchema>
export type BraveWeb = z.infer<typeof BraveWebSchema>
export type BraveSearchResponse = z.infer<typeof BraveSearchSchema>
Web Search
Query: chatgpt
Markdown
Render markdown in your chat view
Code
import { tool } from "ai"
import { z } from "zod"
export interface MarkdownResult {
markdown: string
}
export const markdownTool = tool({
description: "Render Markdown content (recommended in UI).",
inputSchema: z.object({
markdown: z.string().min(1),
}),
execute: async ({ markdown }) => {
return { markdown }
},
})
export default markdownTool
Markdown
Rendered with react-markdown
Hello World
This is markdown.
- Item one
- Item two
Tip: You can copy the tool code from the left.
QR Code Generator
Generate QR codes for text or URLs
Code
import { tool } from "ai"
import { z } from "zod"
import QRCode from "qrcode"
export const qrCodeTool = tool({
description: "Generate QR codes for text, URLs, or other data.",
inputSchema: z.object({
data: z
.string()
.min(1)
.describe("The text or URL to encode in the QR code"),
size: z
.number()
.min(100)
.max(500)
.default(300)
.describe("Size of the QR code in pixels"),
}),
execute: async ({ data, size }) => {
const output = await QRCode.toDataURL(data, {
width: size,
margin: 4,
})
const result: QRCodeResult = {
data,
size,
output,
}
return result
},
})
export interface QRCodeResult {
data: string
size: number
output: string
}
export default qrCodeTool
QR Code
https://ai-tools-registry.vercel.app
Size: 300px
All Tools
Get Weather
AI SDK tool that returns mock weather for a location. Includes a WeatherCard renderer.
QR Code Generator
AI SDK tool that generates QR codes for text or URLs. Includes a QR code display component.