Seao Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Seao Mcp (MCP Server) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A read-only Model Context Protocol server that lets an LLM analyst interrogate Quebec's SEAO public-procurement open data (security-services subset) in plain language. A small pipeline downloads the raw OCDS feed, filters it to security tenders, and normalizes it into a query-ready SQLite database (DATA/seao.db); the MCP server then turns that database into an analyst's assistant rather than a raw SQL endpoint.
Example prompt
| File | Role |
|---|---|
fetch_seao_opendata.py | Downloads the weekly OCDS JSON dumps from the open.canada.ca Atom feed into DATA/. |
build_raw_json_db.py | Filters those dumps (title regex + UNSPSC codes) into DATA/filtered_records.json. |
build_work_sqlite_db.py | Normalizes the filtered records into DATA/seao.db (relational schema + FTS5 + views + a schema_doc data dictionary). |
seao_mcp_server.py | The hybrid MCP server (FastMCP, read-only) exposing 9 tools over DATA/seao.db. |
test_mcp_tools.py | Smoke-tests every tool against the built DB. |
.mcp.json | Project-scoped MCP registration for Claude Code. |
The data is not committed. The raw weekly dumps are multiple GB and the builtDATA/seao.dbis a generated artifact — both are.gitignored. Build the database yourself with the pipeline below (a few minutes on a normal connection, dominated by the download step).
Three things in this data quietly break naive SQL, so the curated tools handle them for you:
| Trap | Reality | How the tools handle it |
|---|---|---|
| Supplier identity | The same firm has many name spellings (Garda = 9 NEQs / 6 spellings). Grouping by supplier_name badly undercounts. | Every supplier metric resolves a name → party.neq and aggregates on the NEQ. |
| Win / loss | Not stored. A bidder won a process iff its (ocid, party_id) is in award_supplier. | Win-rate, head-to-head and margins derive it, counting distinct processes (bids span multiple lots). |
| gré à gré | direct awards are sole-sourced and have no bids. | Win-rate / margin tools restrict to open + limited tenders. |
Requires Python 3.10+.
git clone <your-repo-url> SEAO_MCP
cd SEAO_MCP
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txtRun the pipeline once to populate DATA/:
# 1. Download the weekly OCDS dumps (writes DATA/week_*.json — several GB).
python fetch_seao_opendata.py
# 2. Filter to security-services tenders (writes DATA/filtered_records.json).
python build_raw_json_db.py
# 3. Normalize into the query-ready SQLite DB (writes DATA/seao.db).
python build_work_sqlite_db.pyThe filter rules (title keywords + UNSPSC codes) live at the bottom of build_raw_json_db.py — edit them to target a different sector. build_work_sqlite_db.py accepts optional input/output paths: python build_work_sqlite_db.py [input.json] [output.db].
Verify the build:
python test_mcp_tools.py # smoke-tests every tool against DATA/seao.dbThe server speaks stdio. Its DB path defaults to DATA/seao.db next to the script; override with the SEAO_DB_PATH environment variable.
A project-scoped .mcp.json is committed — open the project in Claude Code and it will prompt to enable the seao server. It launches the server via the project's .venv, so make sure you created the venv and built the DB first. Or add it from the CLI:
claude mcp add seao -- "$PWD/.venv/bin/python" "$PWD/seao_mcp_server.py"Add to claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/, Windows: %APPDATA%\Claude\), replacing /ABS/PATH with this repo's absolute path:
{
"mcpServers": {
"seao": {
"command": "/ABS/PATH/SEAO_MCP/.venv/bin/python",
"args": ["/ABS/PATH/SEAO_MCP/seao_mcp_server.py"],
"env": { "SEAO_DB_PATH": "/ABS/PATH/SEAO_MCP/DATA/seao.db" }
}
}
}Restart the client.
ChatGPT only connects to remote MCP servers (HTTP/SSE) added as custom connectors — it can't spawn a local stdio process the way Claude does. So you wrap this stdio server in an HTTP bridge and expose it with a tunnel. You'll need a ChatGPT plan that supports custom connectors / Developer Mode (Plus, Pro, or a Business/Enterprise workspace where an admin has enabled them).
mcp-proxy: source .venv/bin/activate
pip install mcp-proxy
SEAO_DB_PATH="$PWD/DATA/seao.db" \
mcp-proxy --sse-port 8000 --sse-host 127.0.0.1 -- \
"$PWD/.venv/bin/python" "$PWD/seao_mcp_server.py"This serves the server at http://127.0.0.1:8000/sse.
ngrok http 8000 # or cloudflared / any tunnel; copy the https URLDeveloper mode, then Create*. Give it a name, set the MCP server URL to https://<your-tunnel>/sse, and (since there's no auth) choose "No authentication". Save and enable it for the conversation.
⚠️ The tunnel makes your local DB reachable from the internet for as long as it runs. This server is read-only (run_sqlrejects writes/DDL and opens the DBmode=ro), but the data is still exposed — keep the tunnel up only while you're using it, and prefer an authenticated tunnel if your tooling supports it.
Mapped to four analyst objectives:
names, value_unit meanings, tags, procurement methods). Call it first.
SELECT/WITH; writes/DDL/PRAGMArejected, connection opened mode=ro, auto-LIMIT. The long-tail escape hatch.
— find the relevant tenders (FTS + filters). The usual starting point.
with awarded and contract values, most recent first.
and winning-bid distributions, plus the winner-vs-next-lowest-rival margin.
areas targeted, and most-frequent rivals (resolved by NEQ).
tender and who won (incl. both-won-different-lots).
loyalty/rotation (top-supplier share), volume by year.
top-N concentration (consolidation), method trend, geographic spread.
Anything not covered → run_sql, guided by describe_schema.
| Question | Call |
|---|---|
| "Who held security at CSS Marguerite-Bourgeoys, and for how much?" | incumbent("Marguerite-Bourgeoys", "securite") |
| "Average winning bid for guarding (90152100) in Montreal?" | price_benchmark(unspsc="90152100", delivery_area="6") |
| "Garda's win rate on open tenders?" | supplier_profile("Garda") |
| "Commissionnaires vs Neptune — who wins more?" | head_to_head("Commissionnaires", "Neptune") |
| "Does the Sûreté du Québec rotate suppliers?" | buyer_profile("Surete du Quebec") |
| "Top-3 firms' share of the market?" | market_overview(top_n=3) |
Source data: SEAO open data published via open.canada.ca in OCDS format. This repository's code is released under the MIT License; the underlying procurement data remains subject to its original publisher's terms.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.