maple-procure
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., "@maple-procureFind open IT tenders closing this 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.
MapleProcure
An MCP server that gives Claude access to Canada's public procurement data.
Ask "which IT tenders close in the next 30 days?" or "who has won snow-removal contracts in Ontario, and for how much?" and get an answer built from official CanadaBuys records, dated and traceable to the source file, and linked to the government notice wherever the source publishes one.
Status: deployed and serving. All five tools work over stdio and over streamable HTTP, behind a scoped bearer token, with a structured log line per call. Driven end to end from Claude Desktop on 2026-08-16 and deployed the same day — the transcript under Try it live is real output from the running service, not an illustration. There is no demo recording yet. This README will not describe a capability until it exists in the code. Current state is in
PROGRESS.md; accepted limitations are inKNOWN_LIMITATIONS.md.
Try it live
The server is deployed at https://maple-procure.onrender.com (Render free tier — the
first request after idle triggers a fresh ingest from CanadaBuys, so give a cold start up to a
minute — measured at 41s from fully idle).
A read-only demo token is published here on purpose. It can search tenders and awards; it cannot export reports or read buyer contact details.
# no auth needed
curl https://maple-procure.onrender.com/version
# list the tools (public read-only token)
curl -X POST https://maple-procure.onrender.com/mcp \
-H "Authorization: Bearer 1e33c26cdaaecddb2fe6b4aa73d2a758bb2f6c56" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Verified in production
The session below was run against https://maple-procure.onrender.com on 2026-08-16.
Commands and responses are copied verbatim; the only edit is trimming tools/list to the
tool names, which is what the jq filter does.
The five tools are live. Responses come back as SSE, so data: is stripped before jq:
$ curl -s -X POST https://maple-procure.onrender.com/mcp \
-H "Authorization: Bearer 1e33c26cdaaecddb2fe6b4aa73d2a758bb2f6c56" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
| sed -n 's/^data: //p' | jq -r '.result.tools[].name'
search_tenders
get_tender
search_awards
summarize_spend
export_reportThe auth boundary is real, not decorative. Both failures are RFC 6750 challenges, and they are distinguishable — a missing credential is not the same error as a bad one:
$ curl -si -X POST .../mcp -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | grep -i 'HTTP/\|www-authenticate'
HTTP/2 401
www-authenticate: Bearer error="invalid_request", error_description="an Authorization header is required"
$ curl -si -X POST .../mcp -H "Authorization: Bearer not-a-real-token" ... | grep -i 'HTTP/\|www-authenticate'
HTTP/2 401
www-authenticate: Bearer error="invalid_token", error_description="the bearer token is not recognised"The published token really is read-only. It holds read and nothing else, so the two
tools that reach past reading are refused at the scope check — the export never previews, and
the contact fields are never assembled:
$ ... -d '{... "name":"export_report","arguments":{"tender_refs":["MX-2026-00001"]}}}'
{"code":-32002,"message":"this request needs the 'export' scope, which this caller does not
hold. Held scopes: read.","data":{"remedy":"this needs a token carrying the missing scope;
do not retry as-is"}}
$ ... -d '{... "name":"get_tender","arguments":{"reference_number":"...","include_contact":true}}}'
{"code":-32002,"message":"this request needs the 'pii' scope, which this caller does not
hold. Held scopes: read.","data":{"remedy":"this needs a token carrying the missing scope;
do not retry as-is"}}Every result is dated and traceable. A live search_tenders for software, limited to
two rows — note that the provenance block and notes are part of the payload, not added here:
$ ... -d '{... "name":"search_tenders","arguments":{"keywords":"software","limit":2}}}'
{
"rows": [
{
"reference_number": "SSC-26-00034447:T",
"title": "VIRTANA HARDWARE & SOFTWARE MAINTENANCE AND SUPPORT",
"buyer_name": "Shared Services Canada",
"closing_date": "2026-08-17T14:00:00",
"category": "Services related to goods",
"regions_of_delivery": "Ontario (except NCR)",
"notice_url": null
},
{
"reference_number": "WS3971158100-Doc3971158168",
"title": "Software Licensing Supply Arrangement (SLSA) RFSA",
"buyer_name": "Department of Public Works and Government Services (PSPC)",
"closing_date": "2028-09-29T10:00:00",
"category": "Goods",
"regions_of_delivery": "Canada",
"notice_url": "https://canadabuys.canada.ca/en/tender-opportunities/tender-notice/WS3971158100-Doc3971158168"
}
],
"row_count": 2,
"total_matches": 53,
"withheld": 51,
"truncated": true,
"as_of": "2026-08-16T21:13:41+00:00",
"source_file": "https://canadabuys.canada.ca/opendata/pub/openTenderNotice-ouvertAvisAppelOffres.csv",
"source_last_modified": "Sun, 16 Aug 2026 10:20:15 GMT",
"source_row_count": 937,
"notes": [
"Data as of 2026-08-16T21:13:41+00:00, from the last ingest of ... — a snapshot, not a
live feed. Newer notices may exist.",
"Showing 2 of 53 matches; 51 withheld. Narrow the search or raise `limit` (max 100) —
do not describe these rows as the complete set."
]
}The first row is the honest case for the caveat above: notice_url is null because its
legacy SSC- reference does not resolve on the modern CanadaBuys site, so the row carries no
URL rather than a broken one. That is 5% of open tenders — and 64% of awards, which is why
neither tool promises a link on every row. See
KNOWN_LIMITATIONS.md.
Related MCP server: govtenders-mcp
Tools
Tool | Question it answers | |
| What's open right now, by keyword, category, region, or closing date? | ✅ |
| Everything on one notice. | ✅ |
| Who won what, when, and for how much? | ✅ |
| Totals by buyer, supplier, category or period. | ✅ |
| Write a brief — only after a human confirms. | ✅ |
Every result carries the source file it came from and when that file was last ingested, so an answer can always be dated and traced back to the official notice.
Quick start
make install # dependencies into .venv
make ingest # download CanadaBuys data, build data/maple.sqlite (~6s)
make test
make serve-stdio # or: make serve-httpRequires Python 3.12+ and uv. No data is committed to this
repository — a clean clone reproduces the database from the official source with make ingest.
Connect a client
Point a client at the stdio server. For Claude Desktop or Claude Code:
{
"mcpServers": {
"maple-procure": {
"command": "uv",
"args": ["run", "--directory", "/absolute/path/to/maple-procure",
"python", "-m", "maple_procure.server"]
}
}
}Over stdio there is no transport that could carry a credential, so scopes come from the
environment and default to read alone. Buyer contact details and exports each need theirs
turned on explicitly:
MAPLE_PROCURE_SCOPES=read,pii,export make serve-stdioAn unknown scope name aborts at startup rather than surfacing later as a permission error. This variable is the stdio fallback only — over HTTP, scopes come from the token.
The write needs two calls
export_report is the only tool that writes, and it cannot be made to write on a single call —
on any client, under any argument combination. The first call renders the exact document,
returns it as a preview, and writes nothing. Writing needs a second call carrying the
confirm_handle the preview minted.
A handle is sealed with the SDK's AES-256-GCM request_state codec, so a client cannot read or
forge one. It expires in five minutes, works exactly once, and is bound to a hash of the
rendered content — change the notices or the format and the old handle stops matching, because
what a human approved was a document, not an argument list.
Clients that declare form elicitation get a real prompt instead (MRTR: the server returns
input_required and the client retries with the answer). Both paths redeem the same handle, so
the optional one is not a weaker gate. Today's Claude Desktop and Claude Code negotiate
2025-11-25, so in practice you will see the handle.
What this does not prove is that a human read the preview. On the handle path, a model that passes the handle straight back has satisfied the mechanism — and in live testing one did exactly that when told to "export it again". So state the guarantee precisely:
What gets written is exactly what was previewed — content-bound, single-use, tamper-evident. Not: a human read the preview.
Only the elicitation path can make the confirmation terminate at a person. The full live-session
account is in KNOWN_LIMITATIONS.md.
Over HTTP
export MAPLE_PROCURE_TOKENS="analyst:$(openssl rand -hex 16):read,pii"
make serve-http # HOST and PORT are overridablePOST /mcp is the MCP endpoint. GET /healthz, GET /readyz and GET /version need no
credential — a platform health check arrives without one, and a health endpoint that 401s marks
a deploy unhealthy forever.
/healthz and /readyz answer different questions, and the difference matters here.
/healthz is liveness and never opens the database, because this deploy rebuilds it at boot and
a check that failed during the rebuild would keep the service from ever coming up. The cost of
that is a blind spot: it still returns ok when an ingest has failed and every tool call is
about to return -32003. /readyz closes it — it reads each source's ingest provenance and
returns 503 with a reason when a source is missing or loaded no rows:
$ curl -s https://maple-procure.onrender.com/readyz | jq -c '{status, tenders: .sources.tenders.source_row_count}'
{"status":"ready","tenders":937}Leave /healthz as the platform health check and point external monitoring at /readyz.
Tokens are configured as space-separated subject:secret:scopes entries. The server refuses to
start if none are set, rather than serving 401 to everyone including its owner. subject is a
label for logs, not a credential.
MAPLE_PROCURE_CONFIRM_KEYS (space-separated, each ≥32 bytes) keeps export confirmations valid
across restarts. Leaving it unset is the safer default and what the deploy below does: a key is
then generated at startup, so a restart invalidates every outstanding handle. Persisting the key
without also sharing the spent-handle set does the opposite — a restart clears that set in
memory while already-issued handles stay cryptographically valid, opening a five-minute window
for a replay. Set a key only when spent handles are shared too.
The auth model, stated plainly
These are static bearer tokens, not OAuth. The spec's authorization model is OAuth 2.1 with this server as a resource server; there is no authorization server here, no rotation and no per-user identity. What is real is the boundary — a token carries scopes and they are enforced. This is a stand-in and should not be described as OAuth-compliant.
Refusals come at two layers, because they are different answers:
Meaning | Response | |
No | We do not know who you are |
|
Unrecognised token | That credential is wrong |
|
Token without | Known, but not for this endpoint |
|
Token without | Known, allowed here, not for that |
|
The last row is deliberate. Once a caller is authenticated, a per-tool refusal is returned inside the protocol so the model reads an error with a remedy and can explain it, rather than a transport failure it cannot interpret.
What gets logged
One JSON line per tool call, on stderr — MCP's own Logging feature is deprecated as of
2026-07-28 and the named migration is stderr and OpenTelemetry.
{"event": "tool_call", "tool": "get_tender", "subject": "analyst", "transport": "http",
"arguments": {"include_contact": true, "reference_number": "str(14)"},
"outcome": "ok", "rows": 1, "duration_ms": 0.41, "request_id": "3", "ts": "..."}Argument values are redacted by name, not by type. reference_number becomes str(14);
only names on an allowlist of closed-domain arguments (limit, group_by, currency,
include_contact, the dates) are recorded as themselves. The default is redaction, so a tool
added later is safe before anyone thinks about it — and users type contact details into search
boxes, because this dataset puts them in free-text descriptions.
include_contact is logged deliberately: it is a bool, so it discloses nothing, and it is the
one field a reviewer asking who pulled contact details needs. Rejected credentials are never
logged — mistyping a hostname sends a valid token somewhere it shouldn't go, and that is exactly
how logs come to hold secrets.
Spans come free: the SDK installs OpenTelemetryMiddleware on every server and it already reads
traceparent from _meta. It is inert until you configure an exporter, and once you do, each
log line carries the trace_id that joins it to its span.
Deploying
render.yaml is a Render blueprint: connect this repo as a Blueprint in the
Render dashboard and it builds the service described there. The one thing it cannot carry is the
token list, which is set in the dashboard as MAPLE_PROCURE_TOKENS and never committed.
Two properties of the deploy are deliberate, and both are argued in the blueprint's own comments:
The database is rebuilt at boot, because Render's disk is ephemeral. A cold start downloads ~16 MB from CanadaBuys and loads it in about six seconds, which serves fresher data than a persisted volume would. The costs are a slow first request after a spin-down and a dependency on CanadaBuys being up at boot — its WAF returns transient 403s to repeated bulk downloads, so a service that cold-starts constantly may occasionally fail to start.
No confirmation key is configured, so a restart invalidates outstanding export handles rather than leaving replayable ones behind. See the note above.
PORT comes from the platform and the Makefile defers to it; HOST is 0.0.0.0; and
MAPLE_PROCURE_HOST is filled from Render's own RENDER_EXTERNAL_HOSTNAME, because the SDK
only auto-enables DNS-rebinding protection for localhost.
Why the tool descriptions read the way they do
A tool description is the only thing a model sees when it decides which tool to call, so in
this repo they are treated as code and snapshot-tested
(tests/snapshots/tools.json) — an accidental edit is a
reviewable diff, not a silent behaviour change.
Three problems shaped the current wording, and each is a limit of the data rather than of the code:
A model will summarise the rows in front of it and never mention the ones that are missing.
About 30% of award notices publish no dollar figure, so a "total spend" from this data is
always a floor. Reporting unvalued_rows: 810 next to the rows was not enough — a number in a
payload gets used for arithmetic, not repeated in prose. So every result also carries a notes
list of plain sentences ("These totals are a floor, not a complete figure… Say this when
reporting a total"), and both the tool description and the server instructions tell the model
to pass them on. Sentences get repeated; fields do not.
Two tools a human can't tell apart, a model can't either. search_tenders and
search_awards both take keywords and both return procurement notices. Each description
therefore names the other and says what it is not for — open bids versus completed
contracts — and summarize_spend and search_awards point at each other across the same
line, totals versus individual contracts.
The search is dumber than it looks. It is SQLite FTS5: all words must match, and there are
no synonyms. Left unsaid, a model responds to an empty result by adding more words, which can
only narrow it further. So the keywords description states that adding words narrows the
search and to try fewer, and that OR and NEAR are ordinary words here, not operators.
A half-finished flow reads like a bug worth working around. export_report returning a
preview instead of a file looks, to a model, like a step it should complete. So its description
opens by saying the first call never writes by design, and both the description and the
preview's own notes say to ask the user before sending the handle back. The mechanism blocks
a single-call write on its own; this wording is what stops the second call being automatic.
What a live session actually showed
First run against Claude Desktop, 2026-08-16. Two of the bets above were finally testable, and they did not both come out the same way.
The notes bet held. Unprompted and in its own prose, the model relayed that spend totals
were a floor rather than a total, that keyword coverage was limited, that the data was a
snapshot rather than a live feed, and that individual notices might be stale. Sentences in the
payload get repeated where numbers in a field do not. The wording stands as written.
The confirmation wording held only under ambiguity. Asked for "the three tenders closing this week", the model got a preview listing four, stopped, and asked which set was wanted before confirming — the instruction working exactly as intended. Told "export it again", it ran a fresh preview and passed the new handle back to itself in the same turn, writing without showing anyone the new preview.
That second behaviour is defensible given an explicit instruction, and it is the honest limit of
this design: on the handle path the checkpoint is a norm the model observes, not a wall it
cannot cross. Recorded rather than papered over in
KNOWN_LIMITATIONS.md, with the log evidence.
Built on
mcp2.0.0, targeting MCP spec revision2026-07-28SQLite with FTS5
FastAPI, hosting the streamable-HTTP transport and its auth boundary
Both stdio and streamable HTTP work. Notes on what the 2026-07-28 revision changed, and how
those changes shaped this design, are in docs/SPEC-NOTES.md.
Data
Tender and award notices published by Public Services and Procurement Canada via CanadaBuys, used under the Open Government Licence – Canada.
Contains information licensed under the Open Government Licence – Canada.
This project is not affiliated with or endorsed by the Government of Canada. Source files,
refresh cadence and the column map are documented in docs/DATA.md.
Licence
MIT — see LICENSE. The licence covers this software, not the government data it indexes; the terms for that data are in NOTICE.
Available Tools
5 toolsexport_reportA
Write a report of specific tender notices to a file on the server, as markdown or CSV.
This tool never writes on the first call, by design. Call it with tender_refs and it
returns a preview of the exact document plus a confirm_handle, having written nothing. To
actually write, call it again with identical arguments plus that handle — or answer the
confirmation prompt, on clients that show one. Tell the user what the preview contains and let
them decide; do not confirm on their behalf because the flow looks unfinished.
A handle lasts five minutes, works once, and is tied to the exact content previewed. If the notices or the format change, the old handle stops matching and a fresh preview is needed.
Requires the export scope. Reports contain notice summaries and links only — never buyer
contact details, whatever scopes the caller holds.
| Name | Required | Description | Default |
|---|---|---|---|
| format | No | 'markdown' for a readable brief, or 'csv' for a spreadsheet. | markdown |
| tender_refs | Yes | Exact reference numbers to include, as returned by search_tenders — for example ['MX-2026-12345']. Up to 50. References with no matching open notice are listed in the report as not found rather than silently dropped. | |
| confirm_handle | No | The handle from this tool's own preview response. Omit it to preview. Only send one the user has approved: supplying it is what causes the write. Never invent or guess a value. |
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 excellently: it discloses the never-writes-on-first-call design, the 5-minute single-use handle tied to exact content, the export scope requirement, and the content limitation (no buyer contact details). This fully informs the agent about side effects and constraints.
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 front-loaded with a concise summary sentence, then expands with necessary behavioral details. Every sentence contributes unique value; the multi-paragraph structure is appropriate for the two-phase workflow and is not padded.
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 two-phase mutation complexity, no annotations, and an existing output schema, the description covers all needed context: purpose, side effects, permissions, prerequisite workflow, and failure behavior. It is complete enough for an agent to select and safely invoke the tool.
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 description coverage is 100% (baseline 3), the description adds significant workflow semantics: confirm_handle duration and single-use behavior, the effect of changing format/notices on handle validity, and the handling of not-found tender refs. These go beyond the schema and materially improve invocation correctness.
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 writes a report of tender notices to a server file in markdown or CSV format. This verb+resource+format specification clearly distinguishes export_report from the read-only sibling tools (search, get, summarize).
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 provides an explicit two-phase workflow: call with tender_refs to preview, then call again with confirm_handle to write. It also instructs the agent to let the user decide and never confirm on their behalf, and mentions the required export scope. This is actionable guidance beyond simply stating existence.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_tenderA
Fetch one open tender notice in full by its exact reference number: complete description, dates, category, trade agreements and buyer.
Use after search_tenders has found a notice worth reading. This does not search — the
reference number must match exactly, and an unknown one returns no rows rather than an error.
Buyer contact details are personal data and are withheld by default. Set include_contact to
true only when the user has actually asked how to reach the buyer; the call is refused without
the pii scope.
| Name | Required | Description | Default |
|---|---|---|---|
| include_contact | No | Include the buyer's name, email and phone. This is personal data: set it true only when the user has asked how to contact the buyer, never speculatively. Requires the `pii` scope; without it the call fails. | |
| reference_number | Yes | The notice's exact reference number, as returned by `search_tenders` — for example 'MX-2026-12345'. Not a title or a keyword. |
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 the exact-match requirement, non-error return for unknown references, default withholding of buyer contact details, the need for the 'pii' scope, and the policy that `include_contact` should only be set when the user explicitly asks. This is thorough and actionable.
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?
Three short paragraphs: purpose, usage context, and privacy/param guidance. Every sentence carries necessary information, and the description is front-loaded with the core function. There is no fluff or repetition beyond what the schema already states.
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 output schema exists, the description does not need to explain return structure. It covers purpose, usage, failure behavior, privacy, and permissions. It is fully complete for a fetch-by-reference tool, and the distinction from siblings makes it contextually robust.
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 description adds meaningful context beyond the schema by explaining that an unknown reference_number returns no rows rather than an error, and by clarifying the privacy rationale for include_contact ('Buyer contact details are personal data and are withheld by default'). This reinforces and enriches the schema, though some of it is 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 states a specific action ('Fetch one open tender notice in full') with a precise resource and scope ('by its exact reference number'), and enumerates the returned contents ('complete description, dates, category, trade agreements and buyer'). It clearly distinguishes itself from sibling tools like search_tenders (search vs. exact fetch) and search_awards (different resource type).
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?
Explicitly tells when to use the tool: 'Use after `search_tenders` has found a notice worth reading.' It also defines exclusions: 'This does not search — the reference number must match exactly, and an unknown one returns no rows rather than an error.' This directly guides the agent away from misuse.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_awardsA
Search awarded Canadian federal contracts — who won, for how much, when, and for which buyer. Use this for a supplier's track record, for what similar work has gone for, or for "who won X".
Not for opportunities still open: search_tenders covers notices accepting bids.
For ranked totals rather than individual contracts, use summarize_spend.
About 30% of award notices disclose no dollar figure. Those rows come back with award_value
null and are counted in coverage.unvalued_rows — that is a withheld amount, not a free
contract. Read notes and pass on what it says.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Rows to return, 1–100. Defaults to 20. | |
| since | No | Only awards dated on or after this date, as YYYY-MM-DD. | |
| until | No | Only awards dated on or before this date, as YYYY-MM-DD. The whole day counts. | |
| keywords | No | Words that must ALL appear in the award title, description, buyer or supplier name. Keyword matching only, no synonyms. More words narrow the search; if nothing comes back, use fewer. To filter by who won, prefer `supplier`. | |
| supplier | No | Match suppliers whose legal name contains this text, e.g. 'Deloitte'. Partial names work; legal names in the source often carry suffixes like 'Inc.' or 'LLP', so a shorter fragment matches more. |
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 burden. It discloses that roughly 30% of award notices have no dollar figure, resulting in `award_value` null and counted in `coverage.unvalued_rows`, and interprets this as a withheld amount rather than a free contract. It also instructs to read `notes` and pass on its content. This adds meaningful behavioral context about data quality and null handling.
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 three short paragraphs: purpose, use cases and alternatives, and a data caveat. Every sentence adds value, and the most important info is front-loaded. No wasted words.
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 search tool with fully described parameters and an output schema, the description covers purpose, when to use, exclusions, and a critical data nuance (missing award values). It is complete enough for an agent to decide when to invoke this tool and know what to expect in results, without needing to explain the full output schema.
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?
Input schema has 100% coverage, with each of the 5 parameters described in detail. The description adds minimal parameter-specific insight beyond emphasizing that `supplier` is preferred for filtering by winner, which is already noted in the schema's `keywords` description. Baseline 3 applies because schema does the heavy lifting.
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 clearly states 'Search awarded Canadian federal contracts' and specifies what info it returns: 'who won, for how much, when, and for which buyer.' It distinguishes from siblings by explicitly pointing to search_tenders and summarize_spend for different use cases.
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?
Explicitly says 'Use this for a supplier's track record, for what similar work has gone for, or for "who won X".' and then gives exclusions: 'Not for opportunities still open: search_tenders covers notices accepting bids.' and 'For ranked totals rather than individual contracts, use summarize_spend.' This is clear when-to-use and alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_tendersA
Search open Canadian federal tender notices — solicitations still accepting bids. Use this for "what can we bid on", "which contracts are coming up", or to find work in a field or region.
Not for contracts already awarded: search_awards covers who won what, for how much.
Results are ordered soonest-closing first, or by relevance when keywords is given. Cite the
notice_url when a row has one — about 5% are null, because legacy reference numbers do not
resolve on the current site and a null is preferred to a link that 404s. Quote the reference
number instead for those; do not construct a URL. Read the notes field and pass on what it
says; it states what this result does not cover.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Rows to return, 1–100. Defaults to 20. | |
| region | No | Province, territory or region of delivery, e.g. 'Ontario'. Matches notices that name that region. 126 open notices state no region at all and are excluded — the count is reported back, so mention it rather than implying the result is exhaustive. | |
| category | No | Procurement category. One of: Goods, Services, Construction, Services related to goods. | |
| keywords | No | Words that must ALL appear in the notice title, description or buyer name. Keyword matching only — it has no idea about synonyms, so 'snow removal' will not find a notice titled 'winter maintenance'. Extra words only narrow the search; if nothing comes back, try fewer and broader words. Operators like OR, NEAR and quoted phrases are treated as ordinary words, not syntax. | |
| closing_before | No | Return only notices closing on or before this date, as YYYY-MM-DD. The whole day is included. Closing times in the source carry no timezone, so do not convert them. |
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 delivers richly: it discloses ordering behavior (soonest-closing first, or relevance with keywords), the ~5% null notice_url rate with instruction to quote reference number instead, the requirement to pass on notes field content, keyword matching limitations (no synonyms, OR/NEAR treated as words), and region exclusion reporting. This is exceptional transparency.
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, usage scenarios, sibling differentiation, ordering, URL null handling, notes field instructions, and keyword limitations. It is front-loaded with the most important use-case information and each subsequent sentence addresses a distinct aspect, making it dense but efficient.
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 (5 optional params, output schema present, multiple siblings), the description is fully complete. It explains ordering, null URLs, notes propagation, keyword behavior, region caveats, and timezone handling. The presence of an output schema means return-value details are not needed, and the description covers all operational edge cases an agent would need.
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% with solid per-parameter descriptions, but the description goes well beyond: it explains keyword semantics ('has no idea about synonyms', operators are ordinary words), warns that region filtering excludes 126 notices and to mention the count, and clarifies closing_before has no timezone conversion. This adds meaningful semantic context that the schema alone does not provide.
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+resource+scope: 'Search open Canadian federal tender notices — solicitations still accepting bids.' It clearly distinguishes itself from sibling search_awards by stating it covers open tenders, not awarded contracts, and even names the alternative.
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?
Explicitly says when to use: for 'what can we bid on', 'which contracts are coming up', or finding work in a field or region. It also gives a clear exclusion: 'Not for contracts already awarded: search_awards covers who won what, for how much.' This is model guidance with named alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
summarize_spendA
Rank total disclosed contract value by buyer, supplier or category. Use for "who spends most on X", "which suppliers earn the most", or comparing buyers over a period.
For the individual contracts behind a total, use search_awards.
Two things make every total a floor rather than a true total, and both are reported on the
result: awards that disclosed no amount are excluded rather than counted as zero, and only one
currency is summed per call because the source mixes CAD, USD and EUR with no conversion. The
notes field states both in words — repeat them when you report a figure. A total presented
as complete is wrong.
| Name | Required | Description | Default |
|---|---|---|---|
| since | No | Only awards dated on or after this date, as YYYY-MM-DD. | |
| top_n | No | How many groups to rank. | |
| until | No | Only awards dated on or before this date, as YYYY-MM-DD. The whole day counts. | |
| currency | No | The single currency to total, e.g. 'CAD' (the default and 92% of rows), 'USD', 'EUR', or 'unspecified' for awards naming no currency. Amounts are never converted or combined. Every result reports the period's full currency split so you can see what was left out. | CAD |
| group_by | No | What to rank. 'buyer' for the government department awarding the contracts, 'supplier' for the company winning them, 'category' for the kind of procurement. | buyer |
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 fully carries the behavioral disclosure burden. It transparently explains the two major caveats: undisclosed awards are excluded (not counted as zero) and only one currency is summed per call, plus instructs the agent to repeat the `notes` field when reporting figures.
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 front-loaded with purpose and examples, then delivers critical caveats in a compact second paragraph. Every sentence adds value; there is no filler or redundant restating of schema information.
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?
Despite having an output schema, the description covers all needed decision-making context: use cases, alternatives, data caveats, and reporting instructions. It fully prepares the agent to use the tool correctly without needing external clues.
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 has full descriptions for all 5 parameters, so the baseline is 3. The description adds meaningful extra semantics, particularly around the `currency` parameter (source mixes CAD/USD/EUR with no conversion, default is CAD, amount never combined) and group_by behavior, elevating it above baseline.
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 clearly specifies a concrete action: 'Rank total disclosed contract value by buyer, supplier or category,' immediately identifying the resource and capabilities. It also distinguishes itself from siblings by explicitly pointing to `search_awards` for individual contract detail.
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 provides direct usage guidance with concrete example queries ('who spends most on X', 'which suppliers earn the most') and explicitly contrasts the tool with `search_awards` for 'individual contracts behind a total'. This gives clear when-to-use and when-not-to-use context.
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. Dates show when Glama detected each change.
5 tool updates
v0.1.0- First observed
export_report - First observed
get_tender - First observed
search_awards - First observed
search_tenders - First observed
summarize_spend
TDQS
Each tool has a distinct role: searching open tenders, fetching tender details, searching awarded contracts, aggregating spend, and exporting reports. No overlap between any pair.
All tools follow a consistent verb_noun pattern with snake_case: search_tenders, get_tender, search_awards, summarize_spend, export_report. The naming is uniform and predictable.
Five tools is well-scoped for a procurement data access server, covering search, detail retrieval, aggregation, and export without unnecessary redundancy.
The tool set provides complete coverage for the domain: search open tenders, get full tender details, search awards, summarize spend, and export reports. The workflow from discovery to export is fully supported.
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
Canada Government Procurement MCP — CanadaBuys open data (keyless).
MCP server for French (BOAMP) + EU (TED) public procurement data via TenderAPI.
Government tenders, awards and pre-tender pipelines from 21 official sources as MCP tools.
MCP access to the U.S. federal procurement graph: contracts, opportunities, entities, and more.
Related MCP Servers
- AlicenseAqualityAmaintenanceThe most comprehensive keyless federal-data MCP server. 36 tools for SAM.gov + USAspending + Federal Register + eCFR + Grants.gov. No API key, no registration, no signup. Works in Claude Desktop, Claude Code, Codex CLI, Cursor, Continue, Gemini CLI, and any MCP-aware host.361001076MIT
- FlicenseAqualityDmaintenanceMCP server for searching government tenders from CanadaBuys and SAM.gov with free stats and paid search, latest, and AI matching tools using x402 micropayments.41-
- AlicenseNot gradedqualityBmaintenanceMCP server for analyzing Canadian federal spending data, offering tools for contract search, NLP, semantic search, anomaly detection, and money-flow tracing.MIT
- FlicenseNot gradedqualityBmaintenanceMCP server for Canadian procurement intelligence, enabling unified search of federal and Alberta tender opportunities, deadline tracking, profile-based matching, daily briefs, and AI-assisted bid analysis.-
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/samarpassey/maple-procure'
If you have feedback or need assistance with the MCP directory API, please join our Discord server