Implementing Evaluation Framework for MCP Server Tool Selection

neon · Tech · 2025 · source Agentic: yes · promotional bias: low · source-backed · extracted with prompt 0.5.6 (schema v2)

Generated from extraction.json — do not edit by hand. Every claim cites verbatim source evidence; stated means the quote directly asserts the claim, implied means it supports it indirectly.

Problem

Domain: MCP server tool selection and safe database migration workflows

Neon needed confidence that LLMs using its MCP server could select the correct database migration tools from a large toolset, preserve state across a two-step migration workflow, and avoid directly modifying the main database branch with the wrong tool.

Constraints:

Architecture

Neon built a Braintrust-based TypeScript evaluation framework for its MCP server. The target workflow uses prepare_database_migration to apply SQL to a temporary Neon branch and return migration details, then complete_database_migration to apply changes to the main branch and delete the temporary branch. The eval harness runs realistic migration prompts through the MCP tool-calling path, captures the main branch schema before and after, records LLM call messages, deletes non-default branches, and scores the final answer with two mechanisms: a Claude 3.5 Sonnet LLM-as-judge factuality scorer and a mainBranchIntegrityCheck that verifies the main branch schema is unchanged after prepare-only calls. Repeated trials and Braintrust UI debugging guided prompt/description refinements that raised pass rate from about 60% to 100% without code changes.

Diagram confidence: explicit (topology described by the source).

flowchart LR
  LLM_runtime["LLM runtime"] --> Neon_MCP_server["Neon MCP server"]
  Neon_MCP_server --> Migration_tools["Migration tools"]
  Migration_tools --> prepare_database_migration["prepare_database_migration"]
  Migration_tools --> complete_database_migration["complete_database_migration"]
  Neon_MCP_server --> run_sql_tool["run_sql tool"]
  prepare_database_migration --> Temporary_Neon_branch["Temporary Neon branch"]
  prepare_database_migration --> Pending_migration_state["Pending migration state"]
  complete_database_migration --> Main_database_branch["Main database branch"]
  complete_database_migration --> Temporary_Neon_branch
  run_sql_tool --> Main_database_branch
  Braintrust_eval_harness["Braintrust eval harness"] --> LLM_runtime
  Braintrust_eval_harness --> Factuality_Anthropic_scorer["Factuality Anthropic scorer"]
  Braintrust_eval_harness --> Main_Branch_Integrity_Check["Main Branch Integrity Check"]
  Main_Branch_Integrity_Check --> Main_database_branch
  Braintrust_eval_harness --> Braintrust_UI["Braintrust UI"]
Drag to pan · scroll or use +/− to zoom · reconstructed from the source's described component topology

Components

Component Role Interacts with
Neon MCP server Open-source MCP server exposing database-management tools to LLM clients. LLM runtime, Migration tools, run_sql tool
LLM runtime Model client that chooses and invokes MCP tools in response to user migration requests. Neon MCP server, Braintrust eval harness
Migration tools Stateful prepare/complete database migration tools that stage SQL on a temporary branch before applying to main. Temporary Neon branch, Main database branch, Pending migration state, prepare_database_migration, complete_database_migration
prepare_database_migration Tool that starts a migration by applying SQL to a temporary branch and returning migration details. Temporary Neon branch, Pending migration state
complete_database_migration Tool that completes a migration by running it on main and deleting the temporary branch. Main database branch, Temporary Neon branch
run_sql tool General-purpose SQL execution tool that the LLM might incorrectly choose instead of the migration workflow. Main database branch
Braintrust eval harness TypeScript eval runner that defines cases, runs tasks, captures metadata, limits concurrency, and stores results. LLM runtime, Factuality Anthropic scorer, Main Branch Integrity Check, Braintrust UI
Factuality Anthropic scorer Claude 3.5 Sonnet LLM-as-judge scorer comparing submitted answers with expert expected answers. Braintrust eval harness
Main Branch Integrity Check Schema comparison check that verifies the prepare tool did not modify the main branch. Main database branch, Braintrust eval harness
Main database branch Production-like branch that should remain unchanged during prepare-only migration tests. Migration tools, Main Branch Integrity Check
Temporary Neon branch Instant Postgres branch with the same data as main where migration SQL is staged and tested. prepare_database_migration, complete_database_migration
Pending migration state Server-side state tracking migrations that have been prepared but not completed. Migration tools
Braintrust UI Managed interface for analyzing eval runs and debugging failures over time. Braintrust eval harness

Data flow

An eval case supplies a natural-language database migration request and an expert expected outcome. Before the task runs, the harness dumps the main branch schema. It then calls the LLM/MCP interaction, letting the model choose tools. The expected safe behavior is to call prepare_database_migration, create a temporary branch, apply the SQL there, and ask the user whether to commit to main. After the run, the harness dumps the main branch schema again, records LLM call messages, deletes temporary branches, and scores the final message with the LLM-as-judge factuality scorer and the main-branch integrity check.

3 supporting quotes
“This test, or eval, makes sure that the LLM generates the proper SQL for this migration and calls the “prepare_database_migration” as expected.”article, stated
“const databaseSchemaBeforeRun = await getMainBranchDatabaseSchema(); hooks.metadata.databaseSchemaBeforeRun = databaseSchemaBeforeRun;”article, stated
“We have mainBranchIntegrityCheck and factualityAnthropic.”article, stated

Why this architecture

Neon's eval design is multi-layered because an LLM can appear to answer correctly while using unsafe tools, and exact answer matching is brittle because LLMs are nondeterministic. The LLM-as-judge scorer handles semantic variations in the final response, while the integrity check proves the migration staging tool left the main branch unchanged. Braintrust supplies repeatable eval execution and debugging UI, and multiple trials account for stochastic model behavior.

3 supporting quotes
“Will the output from the LLM precisely match what we’ve written here in the “expected” field? Of course not! LLMs are not deterministic.”article, stated
“The mainBranchIntegrityCheck is just making sure that the main branch is un-modified by the first tool call from the LLM we’re testing.”article, stated
“Since we’re using Braintrust, we get access to their UI which allows us to analyze all the test/eval runs.”article, stated

Models used: Claude 3.5 Sonnet 20241022 · Claude runtime for MCP tool calling · LLM-as-a-judge factuality classifier

Scale

Alternatives considered (roads not taken)

Manual/ad hoc testing of the MCP server

Rejected because: Neon treats evals as software tests needed to know whether LLMs can pick the right tool reliably.

“we knew it’d be important for it to have tests, just like any other piece of software.”article, stated
“if you’re developing an MCP server, write tests! This is just like any other software – without tests you won’t know if it’s actually working.”article, stated

Exact string matching against expected responses

Rejected because: LLM outputs are nondeterministic, so semantic factuality scoring is needed.

“Will the output from the LLM precisely match what we’ve written here in the “expected” field? Of course not! LLMs are not deterministic.”article, stated

Use the general run_sql tool for migrations

Rejected because: The safe workflow requires preparing migrations on temporary branches before completion on main.

“the LLM could easily get confused and just apply SQL database migrations with the “run_sql” tool”article, stated

Change MCP server code to improve pass rate

Rejected because: The observed improvement came from tool descriptions/prompts alone, without code changes.

“we didn’t have to write any “code” in order to go from 60->100 – the only thing we changed were the descriptions (“prompts”) for the two MCP tools”article, stated

Patterns

tool-use-orchestration (agent-architecture)

The MCP server exposes migration tools that the LLM must choose and sequence correctly instead of falling back to arbitrary SQL execution.

“These are evaluations that we can use to make sure that an LLM is able to use our “prepare” and “complete” migrations, in the right order”article, stated
“LLMs are not very good at picking from a large list of tools. The more tools, the more confused they get.”article, stated

eval-driven-development (quality)

Neon uses repeatable evals to measure migration tool-selection behavior and guide prompt/description improvements.

“With that in mind, we decided to implement evals for our MCP server.”article, stated
“we’ve since tweaked our prompts and gotten to 100% of pass rate.”article, stated

llm-as-judge (evaluation)

Claude 3.5 Sonnet scores factual equivalence between the LLM's answer and expert expected output while ignoring nonfunctional differences.

“We use the “LLM-as-a-judge” technique to actually make sure that the actual LLM->MCP interaction we’re testing works.”article, stated
“For now, we’re using Claude for the LLM which is acting as a judge”article, stated

safety-policy-enforcement (governance)

The integrity check enforces the safety policy that prepare-only migration tests must not modify the main branch.

“The mainBranchIntegrityCheck is just making sure that the main branch is un-modified by the first tool call from the LLM we’re testing.”article, stated

observability-tracing (operations)

The eval harness records LLM call messages and uses Braintrust UI to inspect and debug individual eval runs over time.

“hooks.metadata.llmCallMessages = llmCallMessages;”article, stated
“we get access to their UI which allows us to analyze all the test/eval runs.”article, stated

Tradeoffs

LLM-as-judge scoring

Chose semantic factuality classification over exact string match against expected output.

Rationale: LLM outputs are nondeterministic but still need to be judged for factual equivalence.

“Will the output from the LLM precisely match what we’ve written here in the “expected” field? Of course not! LLMs are not deterministic.”article, stated
“This is why we’re using the “LLM-as-a-judge” scorer”article, stated

Real database integrity check

Chose before/after main branch schema comparison over only judging the final text response.

Rationale: A model can say the right thing while still using the wrong tool, so the database state itself must be checked.

“The mainBranchIntegrityCheck is just making sure that the main branch is un-modified by the first tool call from the LLM we’re testing.”article, stated

Prompt/tool-description optimization

Chose tune descriptions for prepare and complete migration tools over changing MCP implementation code.

Rationale: The pass rate improved from roughly 60% to 100% through tool-description changes alone.

“the only thing we changed were the descriptions (“prompts”) for the two MCP tools we’re testing!”article, stated

Managed eval platform

Chose Braintrust UI and SDK over only custom local eval scripts.

Rationale: The managed UI helps analyze and debug test runs over time.

“we recommend using a managed service like Braintrust in order to have a nice user interface and user experience to debug your test runs over time.”article, stated

Gotchas (what bit them)

Large MCP tool surfaces confuse LLM tool selection.

Root cause: As tool count grows, models become worse at choosing the correct tool for the job.

Resolution: Write evals specifically around high-risk tool-selection workflows before trusting the MCP server.

“LLMs are not very good at picking from a large list of tools. The more tools, the more confused they get.”article, stated
“one of the reasons why you should not do it is because you can easily get into a situation where your MCP server has too many tools!”article, stated

The migration workflow is stateful and easy to bypass.

Root cause: The server needs to track pending migrations and the model may instead choose the generic run_sql tool.

Resolution: Evaluate that prepare and complete are used in the right order and verify main branch integrity after prepare.

“it is stateful in the sense that our MCP server needs to keep state of which migrations are “pending”.”article, stated
“the LLM could easily get confused and just apply SQL database migrations with the “run_sql” tool”article, stated

LLM evals can produce false negatives from infrastructure artifacts.

Root cause: Incomplete PostgreSQL dump responses may make the integrity check think the database changed when the dump is incomplete.

Resolution: Return null/eject from the integrity check if the after-run dump does not include the completion marker.

“sometimes the pg_dump fails to deliver the full responses, which leads to false negatives”article, stated
“if (!databaseSchemaAfterRunResponseIsComplete) { return null; }”article, stated

Exact expected-output comparisons are brittle for LLM behavior.

Root cause: The model can produce valid answers with different formatting, IDs, order, or wording.

Resolution: Use an LLM-as-judge scorer that ignores non-factual implementation details while grading factual equivalence.

“Implementation details like specific IDs, or exact formatting should be considered non-factual differences.”article, stated
“Select the most appropriate option, prioritizing the core factual content over implementation specifics.”article, stated

Evaluation

The eval suite uses Braintrust's TypeScript SDK, five migration evals for the scenario, 20 trials with max concurrency 2, Claude-based factuality scoring, and a main branch schema integrity check. It compares natural-language outputs to expert expectations while also verifying that prepare-only runs do not modify the main branch.

Metrics: over 20 MCP tools · five evals for prepare migration scenario · 20 trials per eval · max concurrency 2 · factuality scores A=0.4, B=0.8, C=1, D=0, E=1 · main branch integrity score 1 or 0 · pass rate improved from around 60% to 100%

3 supporting quotes
“choiceScores: { A: 0.4, B: 0.8, C: 1, D: 0, E: 1, }”article, stated
“trialCount: 20, maxConcurrency: 2, scores: [factualityAnthropic, mainBranchIntegrityCheck],”article, stated
“we were at around 60% in terms of our “pass rate” on these tests.”article, stated

Outcomes

The article is focused on one high-risk migration workflow, but the pattern generalizes to any MCP server where tool selection has real side effects. The strongest evidence is the combination of semantic scoring with concrete database-state verification.

3 supporting quotes
“we’ve since tweaked our prompts and gotten to 100% of pass rate.”article, stated
“we didn’t have to write any “code” in order to go from 60->100”article, stated
“if you’re developing an MCP server, write tests!”article, stated