Skip to main content

Overview

Evals test whether different LLMs call your tools correctly. They connect to your MCP server via MCP protocol, discover tools, send prompts to multiple models (GPT-4o, Claude, Gemini, etc.), and assert that each model calls the right tools with the right arguments. Eval cases can also include App Context for follow-up prompts that depend on model-visible UI state. Each eval case runs multiple times per model to measure reliability across non-deterministic LLM responses. Evals work with any MCP server. For sunpeak framework projects, the dev server starts automatically. For standalone use, point to your running server. Evals are not included in the default sunpeak test run because they cost money (API credits). Run them explicitly with --eval.

Prerequisites

  • Vercel AI SDK: install ai
  • Provider packages: Install only the ones you need:
    • @ai-sdk/openai for GPT-4o, GPT-4o-mini, o4-mini
    • @ai-sdk/anthropic for Claude Sonnet
    • @ai-sdk/google for Gemini 2.0 Flash
  • API keys: Set in tests/evals/.env (gitignored) or as environment variables

Setup

Evals are scaffolded automatically by sunpeak new and sunpeak test init. The directory structure:

Configuration

API keys are loaded automatically from tests/evals/.env. Copy .env.example to .env and fill in your keys:

Writing Evals

Each eval file exports a defineEval with an array of cases. Each case has a prompt, optional App Context, and an expectation for which tool gets called:
Use appContext when you need to test a follow-up turn that depends on state shared by the rendered MCP App, such as the selected flight, focused row, current cart, or active review decision. The shape matches updateModelContext: pass structuredContent for JSON state and content for model-visible content blocks. Each eval run exposes that context to the model before the prompt.

Assertion Levels

There are three ways to check results: Single tool checks that the first tool call matches:
Ordered sequence checks multi-step tool call order:
Custom function gives you full access to the result:
Args use partial matching. Extra keys in the actual tool call are allowed. Vitest asymmetric matchers (expect.stringMatching, expect.arrayContaining, etc.) work in args expectations.

Running Evals

Output

Each case runs N times per model. The reporter shows pass/fail counts:

Per-Eval Overrides

Individual eval files can override the global model list, run count, or pass threshold: