nomis-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@nomis-mcpClaimant count for the West of England authorities, latest month"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
nomis-mcp
Query the NOMIS API — the ONS labour market and census data service — from Claude. Two halves that are meant to be installed together:
an MCP server, six tools shaped around the discovery chain NOMIS actually requires rather than a one-to-one mapping of its endpoints;
the
nomis-extractskill, which drives those tools: it turns a vague data request into a confirmed spec before it fetches anything, carries the West of England geography codes, and can emit the finished query as a reproduciblenomisdataR script.
The server without the skill works, but every session then re-derives the same geography vintages and dataset quirks.
Why hand-written tools
NOMIS publishes 1,617 datasets sharing 482 distinct dimension concepts. Only
measures and freq appear in all of them, geography in 1,586. No fixed
OpenAPI parameter list covers that, so generating tools from a spec cannot work.
The useful unit of work is a four-step chain: search datasets, read the
dimensions of one, resolve codes, fetch.
Related MCP server: Australian Bureau of Statistics
Getting started
1. Install the server
git clone https://github.com/stevecrawshaw/nomis-mcp.git ~/projects/nomis-mcp
cd ~/projects/nomis-mcp
uv sync2. Get an API key
Sign in at nomisweb.co.uk, open My Account, and
copy the unique ID — it starts with 0x.
This matters more than it looks. Without a key you are an anonymous user capped at 25,000 cells, and NOMIS enforces that cap by returning HTTP 200 with a body cut short and no error of any kind. A query matching 1.7 million records comes back as 25,000 rows that look complete.
cp .env.example .env # then paste the key into NOMIS_UIDEither variable works:
NOMIS_UID="0x..." # quote it, see below
NOMIS_CONFIG_FILE=~/projects/config.yml # or reuse the R project's configNOMIS_CONFIG_FILE reads the same layout nomis_codes.R uses:
nomis:
uid: "0x1a2b3c"Quote the value. YAML 1.1 reads unquoted 0x1a2b3c as the integer 1715004,
which would send a mangled key and silently demote you to anonymous. The loader
recovers the literal token anyway, and there is a test for it, but quoting is
clearer.
3. Register the server with Claude Code
Copy .mcp.json.example to .mcp.json in whichever project should have NOMIS
access, then set the absolute path and your key:
{
"mcpServers": {
"nomis": {
"command": "uv",
"args": ["run", "--directory", "/home/you/projects/nomis-mcp",
"python", "-m", "nomis_mcp.server"],
"env": {
"NOMIS_UID": "0x...",
"NOMIS_OUTPUT_DIR": "~/nomis-downloads"
}
}
}
}.mcp.json is git-ignored here because it holds the key. For access from every
project instead of one, run claude mcp add --scope user with the same command.
4. Install the skill
bash scripts/install-skill.sh # symlink into ~/.claude/skills
bash scripts/install-skill.sh --copy # snapshot instead of symlink
bash scripts/install-skill.sh --project ~/work/x # one project onlyThe symlink is the default so a git pull here updates the skill in place. The
script also merges the nomis-extract entry into your skill-rules.json,
backing up the existing file, so its trigger keywords fire without you naming the
skill.
5. Check it works
Restart Claude Code, then ask it to run check_auth. That tool probes a known
large query and reports whether the cap is actually lifted, rather than trusting
that a key is present. Then try a real request:
Claimant count for the West of England authorities, latest month
The skill should ask about topic, geography and timescale, show you a written spec, and fetch only after you confirm it.
Tools
Tool | Purpose |
| Report key status and empirically test the 25,000-cell cap |
| Find datasets by keyword, with status and last-updated |
| List a dataset's filterable dimensions and geography types |
| Resolve names to the opaque numeric codes fetches need |
| Fetch observations, capped and truncation-checked |
| Stream an unrestricted query to CSV on disc |
Worked example
Claimant count for Bristol, latest month:
search_datasets("claimant") # -> NM_1_1
get_dataset_dimensions("NM_1_1") # -> geography, time, sex, item, measures
search_codes("NM_1_1", "geography") # -> TYPE424 = local authorities (April 2023)
search_codes("NM_1_1", "geography", "bristol", "TYPE424")
# -> 1778384919, E06000023
fetch_data("NM_1_1", {"geography": "1778384919", "sex": "7",
"item": "1", "measures": "20100", "time": "latest"})
# -> 626, July 2026Geography takes two steps deliberately. Searching the geography codelist without
a type returns nothing and reports success, because the top of that hierarchy
holds only a few country nodes. search_codes routes around this by returning
the type list instead.
The skill
skills/nomis-extract/ is the canonical copy.
File | Loaded |
| Always. The seven-step chain: scope, dataset, columns, filters, confirm, fetch, R script |
| On geography, |
| On step 7, when writing the |
reference.md is where the local knowledge lives: the four West of England
authority codes, why no single NOMIS code covers that footprint, the latest
boundary vintage per geography level, and the per-dataset traps (NM_2014_1
returns duplicate "All Ages" rows; NM_162_1 rounds to the nearest 5). Add a
dataset quirk there each time you find one.
Silent failures this server guards against
NOMIS answers many bad requests with HTTP 200. Each of these is handled explicitly and has a test or an error path:
Input | NOMIS response | Handling |
Oversized query | 200, body truncated at the cap |
|
Unknown dataset id | 200, | Absence of a name raises |
Unknown dimension name | 200, empty body | Empty body raises |
Search without wildcards | 200, no matches | Wildcards added automatically |
Geography search above a type | 200, empty codelist | Returns the geography type list instead |
One it does not: a search_datasets query matching nothing throws
'NoneType' object has no attribute 'get' from the API rather than returning an
empty list. The skill treats that error as "no results", not "bad query".
Development
uv run pytest # 20 tests, fixtures captured from the live API 2026-08-29
uv run ruff check src tests
uv run mypy srcparse.py holds no network calls, so the SDMX and CSV handling is tested against
fixtures in tests/fixtures/.
Scope
Exploratory querying inside Claude. The nomisdata R package remains the
analysis path; fetch_data_to_file and the generated R script are the handover
points. Not implemented: jsonstat output, spatial/KML fetch (1,000-cell cap),
response caching.
Licence
MIT. See LICENSE.
Last reviewed: 2026-08-29
Available Tools
6 toolscheck_authA
Report whether the server holds a NOMIS API key, and test the cell limit.
Without a key, every data request is silently capped at 25,000 records: NOMIS answers with HTTP 200 and a truncated body, no error. With a valid unique ID the cap is lifted.
Run this when a fetch reports truncation, or to confirm setup. The probe downloads one narrow column of a large query, so it takes a few seconds.
To obtain a key: sign in at nomisweb.co.uk, open My Account, and copy the
unique ID (it starts with 0x). Supply it to this server as the
NOMIS_UID environment variable, or point NOMIS_CONFIG_FILE at a
YAML file with nomis: {uid: 0x...}. The key is a password: it is never
exposed as a tool parameter and is redacted from every URL returned here.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does so thoroughly: it discloses the silent 25,000-record cap, the misleading HTTP 200 behavior, the probe's narrow-column download with a few-second runtime, and the key's redaction from URLs.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is longer than average, but every sentence earns its place: purpose, silent cap warning, run conditions, setup steps, and security considerations are all operationally relevant and front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter diagnostic tool with an output schema, this is complete: it states what is checked, why it matters, when to invoke it, how long it takes, and how to configure the prerequisite. Nothing an agent needs to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the schema already exhaustively covers inputs. The description adds useful context by clarifying that the key is supplied via NOMIS_UID or NOMIS_CONFIG_FILE, not as a tool parameter.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and object: 'Report whether the server holds a NOMIS API key, and test the cell limit.' This clearly distinguishes check_auth from sibling data-access tools such as fetch_data and search_datasets.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly states when to run: 'Run this when a fetch reports truncation, or to confirm setup.' It also explains the consequence of a missing key and how to supply one, giving an agent unambiguous context for invocation.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fetch_dataA
Fetch observations from a dataset. Codes must come from search_codes.
Returns rows plus 'total', the number of records matching the query, and 'truncated'. Read them. NOMIS answers an oversized query with HTTP 200 and a body cut short with no error, so a result can look complete while holding a small fraction of the data. Never total, average or rank a truncated result; narrow the filters, or use fetch_data_to_file.
A worked example, claimant count for Bristol in the latest month:
fetch_data('NM_1_1', {'geography': '1778384919', 'sex': '7',
'item': '1', 'measures': '20100',
'time': 'latest'})Requesting a whole geography type (e.g. 'TYPE151' for 2021 LSOAs) across several dimensions produces millions of records. Use fetch_data_to_file.
| Name | Required | Description | Default |
|---|---|---|---|
| select | No | Columns to return. Defaults to date, geography name and code, and value. Fewer columns means a smaller result. | |
| filters | Yes | Dimension filters, keyed by the concept names from get_dataset_dimensions, with values from search_codes. Comma-separate multiple codes. Examples: {'geography': '1778384919', 'sex': '7', 'item': '1', 'measures': '20100', 'time': 'latest'}. geography also accepts a range '1774190614...1774190617', a whole type 'TYPE424', 'POSTCODE|bs1 5tr;424', 'LATLONG|51.45;-2.58;424', or 'MAKE|my area|2013265921;2013265922'. time accepts 'latest', 'latestMINUS12', 'previous', 'prevyear', 'first', or a date '2026-07'. | |
| dataset_id | Yes | Dataset id, e.g. NM_1_1 | |
| record_limit | No | Maximum rows to return | |
| record_offset | No | Rows to skip |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full behavioral disclosure burden. It excellently warns that NOMIS returns HTTP 200 with silently truncated bodies, instructs agents to read the 'truncated' flag, and warns against aggregating incomplete results. This is high-value, non-obvious behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but every section earns its place: purpose, return fields, a critical truncation warning, a concrete example, and a clear boundary with fetch_data_to_file. The most important safety and usage information is front-loaded before the example.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity, absent annotations, and detailed schema, the description covers purpose, return values, failure modes, usage constraints, alternatives, and a realistic example. An output schema exists, so return-value details are not strictly required, but the description still provides them where they matter.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3; the schema already documents filter syntax, geography special values, time options, defaults, and constraints. The description adds a worked example and reiterates that codes come from search_codes, but it does not materially extend the parameter semantics beyond what the schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description immediately states 'Fetch observations from a dataset', giving a specific verb and resource. It further clarifies scope with a worked example and distinguishes the tool from the sibling fetch_data_to_file by describing when that alternative should be used.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage guidance is explicit: codes must come from search_codes, truncated results must not be aggregated, and fetch_data_to_file should be used for oversized or whole-geography-type queries. This clearly routes agents to the correct tool under the right conditions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fetch_data_to_fileA
Stream an unrestricted query to a CSV file instead of into the reply.
For results too large to read: whole geography types, long time series, LSOA-level census tables. Nothing is loaded into memory or context, so use this rather than paging fetch_data hundreds of times.
Without an API key the file is still capped at 25,000 records; run check_auth first if the row count looks suspiciously round.
Returns the path for onward analysis in R, DuckDB or pandas.
| Name | Required | Description | Default |
|---|---|---|---|
| select | No | Columns to include. Default: all. | |
| filters | Yes | Same as fetch_data. Codes from search_codes. | |
| filename | Yes | Output filename, e.g. 'lsoa_claimants.csv'. Written to the server's download directory. | |
| dataset_id | Yes | Dataset id, e.g. NM_1_1 |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and discloses three non-obvious behaviors that cannot be inferred from the schema: nothing is 'loaded into memory or context,' output is capped at '25,000 records' without an API key, and the tool returns a path for later analysis. These traits directly affect whether an agent should select this tool and what to expect from it.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four short passages, each earning its place: primary behavior, when-to-use examples, the API-key cap caveat, and the return value. The most decision-relevant sentence is front-loaded first, and no sentence is redundant with the schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 4-parameter tool with an output schema, the description covers the call-relevant facts: use case, cap condition, auth hint, and return value. Minor gaps remain — no statement about overwriting an existing filename, failure behavior, or whether the returned path is locally accessible to the agent versus only on the server — but nothing essential to invoking it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline of 3 applies; the schema already documents dataset_id, filters, filename, and select. The description adds little per-parameter detail, though it reinforces the 'unrestricted query' semantics of filters and that output lands in the server's download directory.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific action — 'Stream an unrestricted query to a CSV file' — and immediately contrasts it with the reply-returning behavior of the sibling fetch_data. The later phrase 'rather than paging fetch_data hundreds of times' explicitly names the closest alternative, so an agent can tell the tools apart without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives concrete when-to-use conditions: 'results too large to read' with realistic examples (whole geography types, long time series, LSOA-level census tables). It names fetch_data as the alternative and even routes to check_auth when the row count 'looks suspiciously round.' The only minor omission is an explicit when-not-to-use for small results, but that is strongly implied by 'instead of into the reply.'
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_dataset_dimensionsA
List the dimensions a dataset accepts as filters, and its geography types.
Essential before fetching. Dimensions vary per dataset: across NOMIS there are over 480 distinct dimension concepts, and only 'measures', 'freq' and 'geography' are near-universal. A filter this tool does not list will be rejected or silently ignored by fetch_data.
The returned 'geography_types' are the vintages available for this dataset, e.g. TYPE424 (local authorities as of April 2023) or TYPE151 (2021 LSOAs). Pick one, then pass it to search_codes to resolve an area name to a code.
| Name | Required | Description | Default |
|---|---|---|---|
| dataset_id | Yes | Dataset id from search_datasets, e.g. NM_1_1 |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the behavioral disclosure burden. It clearly explains that the tool returns dimension concepts and geography type vintages, and it adds practical behavioral context about filter validity and downstream usage. It does not explicitly state auth requirements or side effects, but for a read-only listing tool the described behavior is sufficiently transparent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-organized and front-loaded with the core purpose. Each sentence earns its place: the essential prerequisite warning, the variability context, and the geography_types explanation with concrete examples. It is moderately long but dense with useful guidance, not redundant filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool has an output schema, so return values are already documented. The description covers why the tool matters, how it relates to fetch_data and search_codes, and what the geographic_types values mean. For a discovery tool with one parameter, this is complete and actionable.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% for the single parameter, dataset_id, which is already described in the schema with an example. The description reinforces the importance of the dataset but does not add significant new parameter-level meaning beyond the schema. Baseline of 3 is appropriate because the schema handles parameter documentation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'List the dimensions a dataset accepts as filters, and its geography types.' It clearly distinguishes this from siblings like fetch_data and search_codes by describing the unique role of dimension discovery. It also gives concrete examples of what the output contains, leaving no ambiguity about the tool's purpose.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-to-use guidance: 'Essential before fetching.' It explains that dimensions vary per dataset and that unlisted filters will be rejected or silently ignored by fetch_data, making the prerequisite relationship clear. It also names the next step, passing the chosen geography type to search_codes, which routes the agent correctly among sibling tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_codesA
Resolve dimension values to the numeric codes fetch_data needs.
Codes are opaque integers (Bristol, City of is 1778384919 in TYPE424) and cannot be guessed or derived from ONS GSS codes. Always come here first.
Geography needs two steps. Searching geography without a type_code returns the list of geography types for the dataset, because the top of the geography codelist holds only a handful of country nodes and a search there matches nothing and returns success. Pick a type, then search within it:
search_codes('NM_1_1', 'geography') -> type list
search_codes('NM_1_1', 'geography', 'bristol', 'TYPE424')
-> value 1778384919, geog_code E06000023Non-geography dimensions are searched directly, no type_code needed.
| Name | Required | Description | Default |
|---|---|---|---|
| concept | Yes | Dimension name exactly as listed by get_dataset_dimensions, e.g. 'geography', 'sex', 'item', 'c2021_sexor_6'. | |
| pattern | No | Text to match, case-insensitive, e.g. 'bristol'. Wildcards are added automatically. Use '*' to list everything. | * |
| type_code | No | Geography type such as 'TYPE424'. Required to search geography areas. Omit to list the available geography types first. | |
| dataset_id | Yes | Dataset id, e.g. NM_1_1 |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It discloses key behaviors: codes are opaque integers that cannot be guessed, geography search without type_code returns the type list, and a geography search at the top-level codelist returns success with no matches. This is valuable operational transparency beyond the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense and every sentence earns its place. The core purpose is front-loaded, followed by essential behavioral caveats and concrete examples. It is longer than average, but the complexity of the two-step geography behavior justifies the length.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity, an output schema exists, and there is no annotation coverage, the description is complete. It covers the main use case, the tricky geography case, the fallback behavior, and example calls. An agent has enough context to invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although schema coverage is 100%, the description adds significant semantic value: it explains the relationship between concept='geography' and type_code, shows concrete call examples, and clarifies that type_code is mandatory for geography but unnecessary for other dimensions. This goes well beyond the schema's property descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description immediately states the tool's purpose: 'Resolve dimension values to the numeric codes fetch_data needs.' This is a specific verb plus resource and clearly distinguishes it from siblings like search_datasets and get_dataset_dimensions by explaining it produces the codes required by fetch_data.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit when-to-use guidance: 'Always come here first.' It explains the two-step geography workflow, when type_code is needed versus omitted, and that non-geography dimensions are searched directly. This leaves little ambiguity about how to use the tool correctly.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_datasetsA
Find NOMIS datasets by keyword. Start here, then call get_dataset_dimensions.
Returns dataset ids such as NM_1_1 along with name, description, keywords and status. Prefer datasets whose status is "Current (being actively updated)" over "Historical (not actively being updated)" unless the user asked for a historical series.
NOMIS publishes over 1,600 datasets, so a narrow term is better than a broad one. Census topic summary tables are named with a TS prefix, e.g. searching 'TS058' finds the travel-to-work table.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search term, e.g. 'claimant' or 'travel to work'. Prefix with a field to target it: 'name-*claimant*', 'description-*earnings*', 'keywords-*employment*', 'contenttype-*census*', 'units-*persons*'. A bare term searches names and descriptions. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It reveals the returned fields (dataset ids, name, description, keywords, status), the status-preference guidance, the large corpus size (over 1,600 datasets), and the TS-prefix naming convention for census tables. It does not mention pagination or limits, but this is a search tool with no side effects and an output schema already supplies the return structure.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is organized into three concise paragraphs: purpose, returned data, and search guidance. Every sentence contributes practical information, and the main purpose is front-loaded. It is slightly longer than strictly necessary but remains focused and easy to parse.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter search tool with an output schema, the description is remarkably complete. It tells the agent exactly what to search, how to phrase the search, which results to prefer, what the return will contain, and how to proceed to the next tool. No essential call-time information is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already describes the query parameter thoroughly with field-targeting prefix syntax and bare-term behavior at 100% coverage. The description goes beyond the schema by adding practical guidance: prefer narrow terms, use 'TS058' as an example, and understand that census summary tables use a TS prefix. This adds real value without being redundant.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific, unambiguous statement: 'Find NOMIS datasets by keyword.' It names the exact resource (NOMIS datasets), the action (find by keyword), and distinguishes itself from the sibling get_dataset_dimensions by saying 'Start here, then call get_dataset_dimensions.' This is a clear, non-of-a-duplicative purpose.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly tells the agent to start with this tool before calling get_dataset_dimensions, and provides a strong selection rule: prefer 'Current' status datasets over 'Historical' unless the user requested historical. It also advises using narrow search terms. It does not explicitly contrast with search_codes or fetch_data, but the resource-type distinction makes the primary use case obvious.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
TDQS
Each tool maps to a distinct stage of the NOMIS workflow: authentication, dataset discovery, dimension introspection, code lookup, inline data retrieval, and file-based retrieval. Even fetch_data and fetch_data_to_file are cleanly separated by output mode and result size, with explicit guidance on when to use each.
All six tools follow a consistent verb_noun pattern: check_auth, search_datasets, get_dataset_dimensions, search_codes, fetch_data, fetch_data_to_file. The one compound name still fits the same convention and is easy to predict.
Six tools is a well-scoped size for a read-only statistics API wrapper. Each tool earns its place and together they cover discovery, preparation, and retrieval without bloat.
The tool set covers the full read-only lifecycle: find datasets, inspect dimensions, resolve codes, fetch small results, and stream large results to file. No obvious dead ends or missing operations for the stated purpose of accessing NOMIS data.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Query UK Parliament, elections, crime stats, ONS census data, and national archives
UK Office for National Statistics dataset catalogue + Beta JSON API
UK ONS MCP — Office for National Statistics (no auth)
Live SEO workflow tools for Claude Code, Codex, and AI agents.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables access to official UK Office for National Statistics data including demographics, economics, and social statistics through the ONS Beta API. Supports browsing, searching, and querying datasets with built-in shortcuts for popular statistics like inflation, regional GDP, and wellbeing data.524MIT
- AlicenseAqualityCmaintenanceAsk Claude "What's the unemployment rate in NSW?" and get a real answer. Wraps the Australian Bureau of Statistics API with plain-English tools and curated mappings for 10 economic indicators (unemployment, inflation, wages, GDP, housing, population).7MIT
- AlicenseNot gradedqualityCmaintenanceEnables querying UK Office for National Statistics datasets and their editions through natural language, with no authentication required.14MIT
- FlicenseNot gradedqualityDmaintenanceExposes the Eurostat Statistics API, enabling LLMs to discover, explore, and retrieve official EU statistical data through search, dimension inspection, and data retrieval tools.3
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/stevecrawshaw/nomis-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server