Use Chart Library with the Vercel AI SDK
Most agent-grounding guides are Python. This one is TypeScript. The Vercel AI SDK is the default agent stack on Next.js / Node, and Chart Library’s core loop is a plain HTTP call — so a drop-in tool is about fifteen lines, with no API key for the free keyless loop.
Your agent stops inventing technical analysis and starts citing the cohort of historical analogs: the forward-return distribution, a calibrated band, and the drivers — base rates, never a price forecast.
Step 1: Install
npm i ai @ai-sdk/openai zodai is the Vercel AI SDK, @ai-sdk/openai the model provider (swap for @ai-sdk/anthropic or any other), and zod describes the tool input. Nothing Chart Library–specific to install — the tool is just a fetch.
Step 2: Define the keyless tool
The whole integration is one tool(). It posts a (symbol, date, timeframe) subject to the public cohort_analyze endpoint and hands the model a trimmed, decision-ready result. No key — the core loop is free and keyless (add one only past 1,000 calls/day).
// chart-library-tool.ts
import { tool } from "ai";
import { z } from "zod";
export const pullComps = tool({
description:
"Pull the cohort of historical analogs for a stock setup (symbol, date, " +
"timeframe). Returns the forward-return distribution, a calibrated 80% band, " +
"and the drivers that separated winners from losers. Historical base rates, " +
"never a price forecast.",
inputSchema: z.object({
symbol: z.string().describe("Ticker, e.g. NVDA"),
date: z.string().describe("Setup date, YYYY-MM-DD"),
timeframe: z.enum(["5m", "15m", "30m", "1h", "1d"]).default("1d"),
}),
execute: async ({ symbol, date, timeframe }) => {
const res = await fetch("https://chartlibrary.io/api/v1/cohort_analyze", {
method: "POST",
headers: { "Content-Type": "application/json" },
// No Authorization header — the core loop is free + keyless.
body: JSON.stringify({
anchor: { symbol, date, timeframe },
horizons: [1, 5, 10],
}),
});
if (!res.ok) return { error: `Chart Library returned ${res.status}` };
const d = await res.json();
const horizons = Object.fromEntries(
[1, 5, 10].map((h) => {
const o = d.outcome_distribution?.[h] ?? {};
const c = d.calibration_by_horizon?.[`${h}d`] ?? {};
return [
`${h}d`,
{
median_pct: o.median,
up_rate: o.win_rate,
cohort_p10_p90: [o.p10, o.p90],
calibrated_80_band: [c.calibrated_p10, c.calibrated_p90],
},
];
}),
);
const drivers = (d.feature_importance?.["5"] ?? [])
.slice(0, 3)
.map((f: { feature: string; direction: string }) => ({
feature: f.feature,
direction: f.direction,
}));
return {
cohort_size: d.cohort_size_actual,
horizons,
drivers,
note: "Historical distribution of analogs, not a forecast.",
};
},
});Version note: this is AI SDK v5 syntax (inputSchema + execute). On v4 the field is parameters; everything else is identical.
Step 3: Run the agent
Pass the tool to generateText and let the model call it. stopWhen: stepCountIs(n) lets the agent take the tool result and write its answer in the next step.
import { generateText, stepCountIs } from "ai";
import { openai } from "@ai-sdk/openai";
import { pullComps } from "./chart-library-tool";
const { text } = await generateText({
model: openai("gpt-4o"),
tools: { pullComps },
stopWhen: stepCountIs(5),
system:
"For any stock setup, call pullComps and report the cohort distribution " +
"(median, up-rate, and the calibrated 80% band) as historical fact. Never " +
"give a single price target. If cohort_size < 30, say the sample is thin.",
prompt: "What did setups like NVDA on 2025-03-03 (1d) do next?",
});
console.log(text);That’s a grounded research agent. The model decides when to pull comps; you decide how the distribution is used.
Or connect the whole MCP surface
Prefer the full canonical toolset (search, pull_comps, cohort_introspect, cohort_members, and more) instead of hand-wrapping one endpoint? The AI SDK speaks MCP — point it at the hosted, keyless Streamable-HTTP endpoint and you get every tool at once:
import { experimental_createMCPClient as createMCPClient } from "ai";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const mcp = await createMCPClient({
transport: new StreamableHTTPClientTransport(
new URL("https://chartlibrary.io/mcp"),
),
});
const tools = await mcp.tools(); // the full canonical surface, keyless
// ...then: generateText({ model, tools, stopWhen: stepCountIs(5), prompt });
// await mcp.close() when you're done.Same engine, same keyless access. Use the single tool() when you want a tight, controlled surface; use the MCP client when you want the agent to range across the whole toolset.
Why ground the agent this way
A language model asked “what happens after a setup like this?” will produce a confident, plausible, and unverifiable answer — we measured this: even a frontier model only stays calibrated by hedging to a ~2×-wider interval. Chart Library returns the actual cohort of analogs and their realized outcomes, with a calibrated band whose coverage is audited. The agent cites history instead of inventing it.
Frequently asked questions
- Do I need an API key?
- No. The core research loop — search, cohort_analyze / pull_comps, cohort_introspect — is free and keyless, up to 1,000 calls/day. Add a key only for higher volume or paid surfaces.
- Does it work with Anthropic / other models, not just OpenAI?
- Yes. The tool is model-agnostic — swap openai('gpt-4o') for anthropic('claude-...') or any AI SDK provider. The tool() definition doesn't change.
- tool() vs the MCP client — which should I use?
- Use the single tool() for a tight, controlled surface (one endpoint, your own trimmed result shape). Use experimental_createMCPClient against https://chartlibrary.io/mcp when you want the agent to range across the full canonical toolset. Both are keyless.
- Does it forecast prices?
- Never. It returns the historical distribution of what analogous setups did next — median, up-rate, calibrated p10/p90 — as a base rate, with a coverage receipt. It does not predict a single outcome.
Wire it into your TypeScript agent now.
The core loop is free and keyless — copy the tool, run it, no signup. Add a key only when you scale.