sars-dates-mcp
Click on "Deploy 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., "@sars-dates-mcpwhat tax deadlines are coming up in the next 30 days?"
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.
sars-dates-mcp
An MCP (Model Context Protocol) server exposing a curated dataset of South African tax deadlines — provisional tax, individual filing season, VAT, PAYE, and employer reconciliations. Built to demonstrate tool-contract design, not just to answer a date question: typed schemas, error messages that teach the calling agent what to do next, and a response-size budget applied before it was ever needed.
Status: Complete for its current scope. Two tools, 41 tests, strict mypy, verified against MCP Inspector CLI.
~745 lines across server.py (172 — the two MCP tools and their formatting),
deadlines.py (191 — the data layer, with no MCP dependency at all), and
__init__.py, plus 41 tests covering both layers, including an explicit
clock seam so date-dependent tests don't depend on when they're run.
Why this exists
MCP servers are harness components — tool interfaces with contracts, the same way a REST endpoint or a function signature is a contract. This one is deliberately small in scope (13 curated deadlines) so the engineering discipline is the point: schema design, error handling, response budgeting, and a documented security posture, not dataset size.
Related MCP server: OpenAccountants
The two tools
get_upcoming_deadlines(within_days, category=None) — deadlines falling
within the next N days from today. Use when the question is time-bounded:
"what's due in the next 30 days?"
list_deadlines(category=None) — every known deadline, optionally
filtered by category, including ones already past. Use when the question
isn't time-bounded: "what are all the VAT dates you know about?"
Both accept an optional category, typed as a Literal rather than a bare
string:
Category: TypeAlias = Literal[
"provisional_tax", "filing_season", "vat", "paye", "emp501"
]This isn't cosmetic. FastMCP turns a Literal into an enum in the tool's
JSON schema, so a calling agent sees the valid values up front instead of
guessing at a string and finding out it was wrong. A test asserts this
Literal and the runtime validation list (VALID_CATEGORIES) can't drift
apart.
Errors teach, they don't just fail. An invalid category doesn't return a
bare exception — it returns a message listing the valid categories and how to
omit the filter entirely. That's the same principle used in bare-agent's
permission-denial messages: a tool response is part of the prompt, so it
should steer the next action, not just report failure.
Data model
Each deadline is a Deadline: id, category, title, date,
recurrence, notes, source_url, source_detail, last_verified. The
dataset lives in data/deadlines.json, loaded and strictly validated at
startup — a missing or wrong-typed field fails loudly (ValueError) rather
than silently producing a malformed Deadline.
Every deadline carries its own provenance: citation() renders it as
(sars.gov.za, verified YYYY-MM-DD), appended to every formatted result. The
data is a curated, hand-maintained dataset, not a live SARS feed — it
reflects filing-season notices and legislation as read and recorded on the
last_verified date, not a real-time source. Both tool docstrings say this
explicitly, because "hallucinated dates presented as authoritative" is the
single worst failure mode a tool like this could have.
Response-size discipline
Added ahead of MCP server #2, so the pair demonstrates a standard rather than
two one-off designs. _format_deadlines — the formatter shared by both
tools — caps output at 50 entries. If a result set is ever larger, the
response is truncated with a line stating how many were omitted and how to
narrow the query using the category filter, rather than either silently
truncating or growing unbounded.
At the current dataset size (13 deadlines total) this cap never fires — the honest state of things is "no query has ever hit this limit." It exists on principle: Anthropic's tool-writing guidance treats response-size budgeting as a first-class design concern, not something to retrofit once a dataset happens to grow large enough to hurt.
Security posture
Read-only, no state mutation, no external network calls. Both tools query an in-memory list built once from a local JSON file at startup. There is no code path in this server that writes anything, calls out to a network service, or executes a subprocess. This is the smallest possible attack surface for an MCP server and it's worth stating plainly rather than leaving it implicit.
No user-supplied string reaches a file path, a query, or a shell. The only argument that varies per call (
category) is validated against a closed five-value enum before it's used for anything. There's no way for a calling agent's input to be interpreted as anything other than one of those five literal strings.Tool descriptions contain no dynamic content. Both docstrings are static, hand-written text, not assembled from the dataset or any external source. Since tool descriptions are loaded into the calling agent's context (OWASP MCP Top 10 — tool poisoning), this closes off the most direct version of that risk for this server: there's nothing in the dataset that could get concatenated into a description an agent trusts.
A dependency was found unpinned during this session —
dependencies = ["mcp"], no version constraint. A routinepip install -e .pulledmcp2.1.1, which renamed theFastMCPAPI this server is built on (MCPServerin 2.x) and broke the import outright. Fixed by pinningmcp<2inpyproject.toml. Included here rather than left in a commit message: an unpinned dependency in a published MCP server is exactly the agentic-supply-chain risk OWASP ASI04 names — anyone installing this server inherits whatever version resolution pip does that day, and this incident is a concrete example of that going wrong on the very machine that built it.What this doesn't cover: no rate limiting beyond what an MCP client enforces; no automated check that
data/deadlines.jsonhasn't been tampered with between commits (it's a plain tracked file, verified by normal git history rather than a checksum).
Known limitations
Stated honestly, not hidden — a passing test suite proves the code does what it says, nothing about whether the underlying claim is true.
Citations are source-supported, not independently verified at subsection level. During this dataset's construction, some legal citations were initially written at a more specific level (exact subsection references) than the source material actually supported, and were caught and softened to the general provision the source could substantiate. Every citation in the dataset reflects that correction, but it means "verified" here means "the date and provision are supported by the cited source," not "independently checked against the exact subsection of the Act." Worth stating precisely rather than letting "verified" imply more rigor than was actually applied.
The dataset requires manual updates. There's no mechanism that detects when a SARS deadline changes or a new filing season is announced.
last_verifiedtells you when a human last checked, not that the date is still current today.Small dataset, so the response-size cap is currently unexercised. The 50-result truncation is tested via the code path, not via a real query that actually triggers it — worth flagging rather than implying it's been proven under load. Live verification via the MCP Inspector CLI confirmed the cap doesn't fire early against the current 13-entry dataset — it does not confirm the cap fires correctly when a result set actually exceeds 50, since nothing in the current dataset does.
The custom "teaching" error for an invalid
categoryis unreachable through the live MCP protocol. Becausecategoryis typed as aLiteral, FastMCP's own schema validation rejects an invalid value with a Pydantic error before the tool function body — including its custom error message — ever runs. Confirmed live via the MCP Inspector CLI: calling either tool with an invalidcategoryreturns FastMCP's ownliteral_errormessage (which does list the valid enum values), not the app's own "Errors teach, they don't just fail" text described above. That custom error-handling code still exists and is still covered by unit tests that call the function directly, bypassing protocol-level schema validation — but a real MCP client can never actually reach it for this field. Worth stating plainly rather than letting the tools section imply the app's own error path is what a live caller sees.
Development
pip install -e ".[dev]"
pytest
mypy srcRequires Python 3.11+. mcp<2 is a hard pin — see Security posture above
for why.
Verification
Protocol wiring verified against the MCP Inspector CLI. Full test suite: 41
tests across the data layer (test_deadlines.py) and the MCP tool layer
(test_server.py), the latter using an explicit clock seam so
get_upcoming_deadlines can be tested against fixed dates rather than
datetime.now().
This server cannot be deployed
Maintenance
Related MCP Connectors
Reliable Australian business-day, public-holiday and deadline tools for AI agents.
Receita Federal: Agenda Tributária, official-source lookup. Platform-hosted, pay per query with prep
Every deadline in a lease, mortgage or policy: date, consequence, source quote. $0.50, 3 free.
Mobile money fees, African tax rates, currency conversion and Uganda PAYE for AI agents.
Related MCP Servers
- AlicenseAqualityCmaintenanceProvides access to official Spanish fiscal data and tools based on AEAT and BOE sources, covering income tax, VAT, and regional deductions. It enables AI assistants to answer tax-related queries and verify filing deadlines using verified information.1011 npm13MIT

OpenAccountantsofficial
AlicenseAqualityAmaintenanceOpen-source, accountant-verified tax computation skills for AI agents. 261+ skills across 172+ jurisdictions covering income tax, VAT/GST, payroll, corporate tax, crypto, and cross-border planning. Every skill is verified section-by-section by licensed CPAs and chartered accountants. 3 tools (list_skills, get_skill, get_skill_sections) and 1 prompt (skill-review).3391AGPL 3.0- AlicenseNot gradedqualityBmaintenanceEnables natural-language queries about US federal and New York privacy law obligations, returning citation-anchored answers with computed deadlines, usable from Claude Desktop or the command line.1Apache 2.0
- FlicenseNot gradedqualityBmaintenanceProvides AI agents with verified Vietnamese tax and legal retrieval capabilities, including searching legal documents, fetching document details, evaluating effective tax rules at a specified date, and tracking recent legal updates.-