Documentation

Build with the DFO / MCP server

What you can ask

Once the DFO is connected to your AI assistant (see Connect), you can ask it questions about development finance in plain language, such as "What is health spending per person in Kenya?" or "How much health aid did Nigeria receive between 2020 and 2024?" The DFO covers countries, subregions, and continents. It can look up a figure or trend, compare places, rank them, or calculate per-person amounts, and every answer names the dataset it came from. You'll get the best results by naming the place, years, and unit you want. The DFO can't handle very large requests in one go. If you ask for something it can't do, or for years it doesn't have data for, the assistant will tell you and suggest an alternative.

For developers

The DFO MCP server answers questions about any series it holds for a country, subregion, or continent. You can compare, rank, aggregate, or divide the results. Every answer carries the dataset's source and a frame reference that names the exact data used.

One question, start to finish

The example below follows one question from start to finish in DFO Studio, which calls the same tools. The session is trimmed to the tool calls and the answer.

Looking into health spending in Kenya.

search    Resolve the question against the data graph
          resolved: definite
get_data  Fetch the data
          24 observations · 2000–2023 · WorldDevelopmentIndicators
…

"provenance": {
  "inputs": [{"spec_id": "spec_0002907ee44bf25fc98f343372614a84",
              "content_version": "cv_bec21df8ebf432401c160f10858e61fb"}],
  "sources": [{"id": "dc/base/WorldDevelopmentIndicators",
               "name": "WorldDevelopmentIndicators",
               "licence": "not stated by the platform"}],
  "freshness": {"data_through": "2023", "last_refreshed": "2026-08-29", "partial_period": false},
  …
  "citation": "WorldDevelopmentIndicators"
}

Based on data from the World Development Indicators, healthcare expenditure per person
in Kenya spanned from 2000 to 2023 across 24 observations:

* Healthcare Expenditure per Person: Started at 75.5 International Dollars in 2000 and
  increased to 274.0 International Dollars by 2023.

The omitted calls are Studio's chart-drawing tools.

search turned the question into one dataset definition with a spec_id. The id identifies what is measured, where, and when. It stays the same when the data is refreshed.

get_data returns the data for that id and a frame reference containing spec_id and content_version. The content_version identifies this retrieval. When the World Bank revises the series, the same spec_id returns a new content_version. An answer that quotes both ids can be checked against the data it used.

A direct MCP call to get_data with the same id returned the pair and a plain-text citation:

Retrieved the requested data for observationAbout country/KEN.
Coverage: 24 rows · 1 observationAbout, 24 years · 2000-2023
Frame: spec_id=spec_0002907ee44bf25fc98f343372614a84, content_version=cv_bec21df8ebf432401c160f10858e61fb -- pass both to downstream tools.
Source: WorldDevelopmentIndicators (WorldDevelopmentIndicators).
…

Kenya's series is available in international dollars and US dollars. The server chose international dollars because the question specified no unit. To request US dollars, the model passes scope with a facets choice on its next get_data call.

Comparing two series

compare accepts two frame references, or one frame reference and two entities. It compares values over the periods both sides cover. To answer "How does Kenya's debt service compare to its climate finance receipts?", run search with two parts, call get_data once per part, then call compare with both frame references.

The analysis tools operate on frame references. aggregate groups and reduces, growth measures change across a window, rank orders entities, share_of gives one entity's share of another in one unit, quotient_of divides one measure by another in any two units, sum_of adds series, filter keeps matching rows, and join combines two frames.

Dividing one measure by another

quotient_of divides one series by another at each period both report, whatever their units, and states the result in the unit the two compose. To answer "What was Kenya's debt service per head of population?", run search with two parts, one for debt service and one for population, call get_data once per part, then call quotient_of with both frame references. The population frame declares no unit, so the result's unit names it by its statistical variable (StatVar) identifier: CurrencyUSD per Count_Person.

To get one figure per entity, such as aid per head for each of several recipients, set numerator_by to the numerator's role column and denominator_by to the column the population frame holds the same entities on. A period only one side reports is left out and recorded, and so is a key with no counterpart or a period where the denominator is zero. The call refuses when no period is shared, when no key pairs, or when every matched period has a zero denominator. share_of divides a part by its whole in one unit and refuses two sides in different units.

Skills

A search result can name skills that apply to it, each with an id and a one-line description, beneath the part blocks: "Skills for these results; get_skill loads one by id:". get_skill(skill_id) loads one as a SKILL.md document, front matter followed by its body, cut past 32,000 characters with a line giving the full length. An id no skill carries refuses skill_not_found.

Limits and refusals

A frame holds at most 250,000 rows. get_data returns over_ceiling for a larger retrieval, and the analysis tools return frame_too_large for a larger input frame. Both refusals ask the model to narrow the request and retry.

A refusal arrives as a tool result marked as an error. Its text explains the problem and what to do. Structured content carries a code, a retry_mode and a fix. A Studio session asked for French inflation from 2015 to 2024, although the series ended in 2022. The growth call returned:

growth_gap_year: {'window_start': '2015', 'window_end': '2024', 'entity_count': 1, 'missing_start': 0, 'missing_end': 1, 'suggested_window': ['2015', '2022'], 'suggested_window_entities': 1}

Growth reads each series' value in the first and last year of the window, and this series has no 2024 value. The fix suggests 2015 to 2022, a window in which the series has values in both the first and last year.

The retry_mode tells the model what to do next:

  • correct_and_retry: change an argument and call again.
  • refresh_then_retry: fetch fresh state, such as a new get_data call, then retry.
  • retry_after_delay: retry the same call later, after a service outage or while a large dataset is still being prepared.
  • do_not_retry: this call cannot succeed on this server.

The refusals a reader meets most often:

  • The DFO resolves countries, subregions, continents, continental unions, places below a country, organisations, DAC aggregates and membership groups such as the G7, whose members it scopes in place of the group. "World" resolves to the world as one place. An ambiguous name such as "Congo" returns entity_ambiguous with up to five candidates to choose from. A name with no match returns entity_not_found.
  • An unknown spec_id returns definition_not_held, and the model runs search again.
  • A large retrieval, such as aid from every donor to one country, can take dfo-apis longer than one call allows. get_data then returns operation_still_processing. The same call made again picks up the same background operation while the data's coverage is unchanged.
  • A mid-size retrieval that dfo-apis runs in the foreground can still take longer than the roughly 30 seconds this server gives a synchronous retrieval inside its 45-second answer window. get_data then returns service_unavailable, naming the retrieval as possibly still running upstream and suggesting a narrower scope, such as fewer entities or a shorter window, which finishes faster.
  • compare requires a common year and the same unit on both sides. It returns comparison_periods_disjoint when periods do not overlap and cross_frame_unit_mismatch when units differ.
  • growth returns growth_gap_year when no series has values in both the first and last year of the window, as above. A series missing only one of those years is left out of the result and listed as excluded, and missing years between them never refuse.
  • The server returns capability_unavailable with do_not_retry for a feature this deployment has disabled.

The ordinary search results no_data and unsupported can resemble refusals. no_data means the DFO holds nothing for that part of the question and may suggest nearby series. unsupported means the question is outside the DFO's scope and gives the reason.

A single search call takes up to six parts, one per measured variable, and describe takes up to eight ids.

Next

  • Connect shows how to add the server to Claude, ChatGPT, Gemini, Claude Code, Codex, or Gemini CLI.
  • The Tools reference lists every argument, error code and retry mode.

View as markdown