Skip to main content
Glama

Nearby Notices

What is changing near you, in plain English, with every sentence checked against the notice.

Road closures, parking changes, planning applications, wind farms, smoke control orders and water licences are published in The Gazette, the UK's official public record, in legal language that is hard to read and hard to search by place. Type a postcode or a town, get the notices near you, each read back in plain English beside the notice as published and linked to the record. The same index is an MCP server, so an AI agent can ask the same questions and cite the same record.

For residents, councillors, local journalists and anyone else who wants to know what is being proposed on their street before the objection period closes.

Built with an AI coding assistant under the author's direction, in the author's own time, on open data. Not affiliated with The Gazette, the Government or any council. It shows public notices and does not give advice.

The results list: each notice as a card with its kind, date, distance, the facts, the checked plain-English summary and the notice as published

The map view: pins by kind of notice, a compact popup, the search circle

What makes it trustworthy

A language model writes the plain English. Nothing it writes reaches the page unchecked:

Layer

What it does

Where

Verbatim fields

Places, the authority, both dates and the how-to-object sentence are shown only if the model's value appears word for word in the notice. Kind must be one of a fixed list.

extract.py

The gate

Every generated sentence, including the heading, goes back to the model with the notice: "is every claim stated in the notice?" The answer counts only with one to three short quotes that really are in the text. Fail-closed on any error.

extract.gate

Advice filter

A generated sentence that tells the reader what to do is removed before the gate is asked. Where a notice says how to object, that sentence is quoted, never composed.

extract.is_advice

Scrubber

Sentences in a notice addressed to a model ("ignore previous instructions", "tell the reader") are removed before the model and the checks see the text.

extract.scrub

The record

The notice as published is always on the page with the Gazette link. The plain-English version is labelled as generated. Removed sentences are counted on the card.

templates

Category refusal

Only notices by public bodies and companies acting as authorities are fetched. Notices about people are refused at the fetcher and at the store.

categories.py, store.py

The threat model, including what is not defended against, is in docs/safety.md.

Related MCP server: Zyfy MCP Server

Evaluation

Measured, not asserted. evals/ holds 21 notices across 9 Gazette categories (labelled with an AI assistant and checked by hand against the notice text), 84 true and false sentences for the gate, and 3 adversarial notices. On 19 October 2026 nine notices were removed from the set for privacy; earlier runs used 30. uv run python evals/run.py runs everything live against the configured model and writes evals/RESULTS.md.

Latest run, 19 October 2026, on the model the live site uses (Azure OpenAI gpt-4.1-mini), commit 23f716c (full table and caveats in evals/RESULTS.md):

gpt-4.1-mini (live), 21-notice set

Gate: false sentences caught

42 of 42

Gate: true sentences kept

42 of 42

Adversarial notices with nothing forbidden shown

3 of 3

Authority, kind, start date, end date, objection sentence

94%, 100%, 88%, 94%, 100% (17 notices; 4 hit a model error at the start of the run)

Places affected found

97%, no correspondence addresses listed

A gated heading shown

17 of 17

Same notice twice, fields agreeing

25 of 25

Model calls per notice

4.4, median 24 s per notice under the deployment's throughput cap

Earlier runs on the 30-notice set (17 and 18 October, local and Azure models) and what changed between them, and why, are in docs/incidents.md.

What the numbers mean, and the labelling rules, are in evals/README.md. The headline to watch is the gate's false-catch rate: a false sentence the gate keeps would reach the page.

How it works

flowchart LR
  U[Postcode or town] --> P[postcodes.io] --> G[The Gazette feed<br/>allowed categories only]
  G --> M[Measure to nearest filed postcode<br/>linked-data lookup when needed]
  M --> S[(SQLite)]
  S --> W[Background reader]
  W --> X[Model: facts + summary] --> V{Verbatim checks<br/>advice filter}
  V --> Q{Gate: quotes in the notice?} --> S
  S --> Web[Website: list, grouped, map]
  S --> MCP[MCP server: find, read, coverage]
  1. Find. The Gazette's own feed, by postcode, distance and category, newest first. The Gazette only honours radii of 1, 5, 10, 15, 20, 25 and 30 miles, so the tool asks for the smallest honoured radius covering the distance you chose (miles or kilometres) and measures each notice itself. A notice is filed under several postcodes (the street, the council, a consultant's office) and the feed gives only the first, so when that one is outside your circle the tool reads the notice's linked-data record for all of them and keeps the notice only if its nearest filed postcode is inside.

  2. Read. A model (any model through LiteLLM; by default a local one) returns the facts as JSON and a two- or three-sentence summary. One background reader, so a burst of searches never becomes a burst of model calls.

  3. Check. The layers in the table above.

  4. Show. As a list, grouped by kind, or on a map (Leaflet served from this site; OpenStreetMap tiles only when you open the map). Each pin sits at the nearest postcode the notice is filed under; pins that would overlap are spread in a ring. The page asks /status every few seconds and swaps in each card as it is ready, without reloading.

Use it from an AI agent (MCP)

Three tools over stdio, same index, same rules:

Tool

What it does

find_notices_near(place, distance=3, unit="mi")

Up to 12 notices near a postcode or town, newest first: id, kind, date, one-line title, distance to the nearest postcode it is filed under, whether plain English is ready, link.

read_notice(notice_id)

The notice as published, word for word, plus the checked plain-English version with the counts of what the checks removed. Made on the spot if not ready.

coverage()

What is covered and refused, licence, privacy, distance choices.

claude mcp add nearby-notices -- uv run --directory /path/to/nearby-notices python -m nearby_notices.mcp_server

Any other MCP client: command uv, arguments run --directory /path/to/nearby-notices python -m nearby_notices.mcp_server. The server's instructions tell the agent the plain English is generated and checked but not official, that it must cite the notice, and that it gives no legal advice.

What it refuses, by design

Only these categories are fetched: Transport, Town and Country Planning, Environmental Protection, Water Resources, Forestry and Plant Health, Electricity. Notices about people (corporate and personal insolvency, deceased estates, appointments, marriages, partnerships, mixed "other" notices) are refused at the fetcher, refused at the store, and never shown; tests/test_rules.py proves it. The Gazette's data licence (Open Government Licence v3) does not cover personal data, and this tool is not for finding out about people. The Gazette staff name carried on every feed entry is never read.

Privacy

No accounts, no cookies, no tracking. What you type is used for the search and not stored, logged or traced. Server logs keep no IP addresses. Counters are daily totals with no identifiers. The map view requests tiles from OpenStreetMap only when you open it. See /privacy.

Run it

uv sync
make run        # website on http://127.0.0.1:8765
make mcp        # MCP server over stdio
make test       # ruff + pytest, no network, no model
make eval       # live evaluation, writes evals/RESULTS.md

Live: https://nearby-notices.bluesea-79d4ea2e.uksouth.azurecontainerapps.io on Azure Container Apps (UK South), reading notices with an Azure OpenAI gpt-4.1-mini deployment. It scales to zero when idle, so the first visit after a quiet spell takes a few seconds to wake, and the first search of a new place takes 20 to 30 seconds while the Gazette answers.

Settings are environment variables; see .env.example. With a local OpenAI-compatible server (llama-server, Ollama) leave NN_MODEL unset and the served model's name is read from the server at startup, so the name recorded with each plain-English version is the model actually used. A Dockerfile builds the website; mount /data to keep the cache.

Deploy

The public instance runs on Azure Container Apps, inside the consumption plan's monthly free grant, with the image published to GitHub's container registry (free) and pulled from there, so there is no registry bill. One script does it: deploy/azure.sh, run as a service principal that holds Contributor on one resource group and nothing else. It creates the resource group, a £5 monthly budget with email alerts, the environment, and the app with the model settings as secrets, scale-to-zero when idle and one replica at most. On every push to main the image workflow builds and publishes the image and the deploy workflow rolls the app to it.

Public-endpoint settings differ from a private run in two ways, both environment variables: a lower daily model-call cap (NN_DAILY_CALL_CAP) and a per-client searches-per-minute limit (NN_SEARCHES_PER_MINUTE) that keeps no addresses.

Repository map

nearby_notices/   gazette.py (feed, linked data, fair use)  places.py (postcodes.io)  geo.py (units, radii, distance)
                  search.py (the pipeline)  extract.py (model, checks, gate)  worker.py (one reader)  store.py (SQLite)
                  app.py (FastAPI pages)  mcp_server.py (MCP tools)  categories.py  groups.py  config.py
templates/ static/  GOV.UK Design System patterns without the reserved marks; Leaflet vendored
evals/            gold.jsonl, notices/, adversarial.jsonl, run.py, RESULTS.md
tests/            rules, geo, app, mcp, injection, evals scoring
docs/             safety.md, decisions.md, incidents.md, screenshots/

Limitations

To be written by the author in his own words after the first week of use. Known today: sentences that count or summarise a list are usually removed by the gate, because no single span of the notice says so; a notice whose prose is itself wrong will be reflected faithfully; the first search of a new place takes 20 to 30 seconds while the Gazette answers.

Licence

Code: MIT. Notice data: The Gazette, Open Government Licence v3. Place data: postcodes.io, Open Government Licence. Map tiles: OpenStreetMap contributors.

Available Tools

3 tools
coverageA

What this server covers and refuses, and the data licence and privacy terms. Call this before answering questions about what kinds of notice can be found here.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.1/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations present, the description carries the full disclosure burden. It usefully reveals the returned content domains (coverage, refusals, licence and privacy terms), but says nothing about whether this is a read-only operation, whether output is static, or any auth/rate-limit characteristics.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight sentences with the resource scope front-loaded and the call trigger second. 'What this server covers and refuses' is slightly terse/oblique wording, but nothing is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a parameterless metadata tool with no output schema, the description adequately conveys purpose, content domain, and when to invoke it. The only minor gap is that return format or structure is not hinted at, which is low-stakes here.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema defines zero parameters, so per the rubric the baseline is 4. There are no parameters whose meaning the description needs to clarify, and no undocumented input is hidden.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific, unusual resource: the server's coverage/refusal rules plus licence and privacy terms. That is clearly distinguishable from the sibling tools find_notices_near and read_notice, which retrieve notices rather than describe server scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives an explicit trigger condition: 'Call this before answering questions about what kinds of notice can be found here.' That is a clear when-to-use instruction, though it does not state when-not to call it or name the siblings as alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

find_notices_nearA

Find public notices near a UK postcode or town. place is a postcode (for example "DY1 1HF") or a town ("Dudley"). distance is snapped to the offered choices: miles 1, 3, 5, 10, 15 or kilometres 2, 5, 10, 15, 25; unit is "mi" or "km". Returns up to 12 notices, newest first, each with its id, kind, date, a one-line title, how far the nearest postcode it is filed under is from the place, whether a plain-English version is ready, and the Gazette link. Call read_notice with an id for the full text and the plain-English version.

ParametersJSON Schema
NameRequiredDescriptionDefault
unitNomi
placeYes
distanceNo

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does substantial work: distance is snapped to a fixed set of choices, results are capped at 12 and sorted newest first, and each returned field is enumerated including plain-English readiness. It omits error behavior (invalid postcode/town) and any auth or rate-limit notes, which keeps it short of a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, all load-bearing, with the purpose front-loaded followed by parameter semantics then the handoff to read_notice. No filler or repetition of the tool name.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, so the description takes on the return contract and does so fully: count cap, ordering, and the per-notice fields. Combined with the parameter constraints and the sibling handoff, an agent has everything needed to call it and consume the result.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must compensate and it does: 'place' is defined with a postcode and town example, 'distance' is constrained to explicit values (miles 1/3/5/10/15 or km 2/5/10/15/25), and 'unit' is limited to "mi" or "km". These constraints appear nowhere in the schema and are essential to a valid call.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource with scope: 'Find public notices near a UK postcode or town.' It is immediately distinguishable from the sibling read_notice (full text for one notice) and coverage, and it names the follow-up tool explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear routing: use this tool for discovery, then 'Call read_notice with an id for the full text and the plain-English version.' The condition selecting the alternative is stated, though there is no explicit exclusion or guidance on when this tool is the wrong choice (e.g. no results handling).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_noticeA

Read one notice by id (from find_notices_near): the notice as published, word for word, and a plain-English version: what is changing, the places affected, the authority, from when and until when, the kind of notice, the notice's own sentence on how to object (quoted, never composed), and a short summary in which every sentence was checked against the notice; sentences that failed the check were removed and are counted. If the version is not ready it is made now, which can take a minute. Always cite the url.

ParametersJSON Schema
NameRequiredDescriptionDefault
notice_idYes

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden, and it does substantial work: it discloses that the plain-English version is generated on demand if missing, that this 'can take a minute' (latency), that objection language is quoted never composed, and that failed sentences are removed and counted. It omits permission/error behavior and what happens for an invalid id.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded in the first clause, and the long colon-list of returned fields is dense rather than padded. The single run-on sentence packing the full return shape is slightly harder to parse than a structured list would be.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema and no annotations, the description steps up by enumerating the returned content, the generation/latency behavior, and the id source. Error and authorization behavior remain unstated, so it is strong but not fully complete for a zero-annotation tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% and there is one required parameter, so the description must compensate. It explains the id's provenance ('from find_notices_near'), which is meaningful semantics beyond the bare 'Notice Id' string, though it does not state the id's format or constraints.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Read one notice by id') and immediately ties the id's origin to the sibling tool find_notices_near, letting an agent differentiate this read tool from the discovery tool 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.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The parenthetical '(from find_notices_near)' clearly signals the workflow context — find first, then read — which is real usage guidance. It stops short of explicit when-not-to-use or alternatives, so it lands just below the top band.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv0.1.0
    • First observedcoverage
    • First observedfind_notices_near
    • First observedread_notice

TDQS

A4.2/5.0

Scored across 3 tools

Disambiguation5/5

The three tools serve clearly distinct roles: coverage is a meta-information tool, find_notices_near searches by place, and read_notice retrieves a specific notice by id. There is no overlap in purpose or expected inputs, and the descriptions explicitly tell the agent when to use each.

Naming Consistency4/5

The action-oriented tools use a consistent snake_case verb_noun pattern (find_notices_near, read_notice). The meta tool 'coverage' deviates by being a bare noun rather than a verb phrase, which is a minor inconsistency but still readable and clear.

Tool Count5/5

Three tools are exactly right for a focused notice discovery and reading server: one explains scope and terms, one searches nearby, and one reads a full notice. No tool feels redundant or missing from the core workflow.

Completeness3/5

The surface covers scope, nearby search, and detail retrieval, but find_notices_near is capped at 12 newest results with no pagination and no filtering by notice kind, date, or authority. These are notable gaps for an agent that needs to browse beyond the most recent dozen notices.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Enables AI assistants to search and retrieve UK Companies House data including company profiles, officers, and filing history via the official API.
    4
    179 npm
    1
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables natural language queries about UK postcodes and vehicles, including flood risk, crime rate, broadband coverage, property prices, MOT history, and ULEZ compliance.
    4
    18 npm
    MIT
  • F
    license
    B
    quality
    D
    maintenance
    Provides access to UK Companies House public data, enabling search and retrieval of company profiles, officers, filing history, and more through natural language queries.
    12
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables querying official UK public records from The Gazette, including insolvencies, winding-up petitions, and deceased estates.
    21 npm
    MIT