I wanted my coding agent to answer "what's happening in AI today?" without me pasting links into the chat. So I wrote a small MCP server for the AI signals data I collect. Two things came out of it that might save you time.
1. The MCP server (single file, stdlib only, no API key)
Five tools, all read-only:
| Tool | Returns |
|---|---|
daily_signals |
today's signals — one per source (GitHub, Hugging Face, YouTube, Silicon Valley news), each linking to the original |
what_changed |
deltas since the previous snapshot: new entries, entities no longer seen, rank movers |
top_models |
Hugging Face models with rank, rank movement, momentum |
top_repos |
GitHub repos, same shape |
archive_days |
which daily editions exist, so the agent can cite a date |
Every ranked row includes a citation string, so the agent cites a number instead of inventing one. Rows whose number can't be derived come back as unranked with the reason — I'd rather return "not ranked" than a plausible-looking guess.
Install is one file plus a config block:
{
"mcpServers": {
"signals": {
"command": "python3",
"args": ["/absolute/path/to/mcp_server.py"]
}
}
}
Repo: https://github.com/jiangzhangcc-glitch/signals-api-mcp (MIT, Python 3.9+, no dependencies)
2. The trap: a 403 that your server never sees
My first version called the API with plain urllib.request.urlopen(url). Every tool failed with HTTP 403. The API was fine — the CDN in front of it rejects the bare Python-urllib/… User-Agent (and empty User-Agents) before the request ever reaches the origin.
The nasty part: because the request is blocked at the edge, the origin server has no log of it. If you only look at your own access logs, you conclude "nobody is calling my API". I measured it across clients:
| User-Agent | Result |
|---|---|
Python-urllib/3.12 (stdlib default) |
403 |
| (empty) | 403 |
python-requests/2.31.0 |
200 |
aiohttp, httpx, Go-http-client, node-fetch, axios, okhttp, Java
|
200 |
curl/8.5.0 |
200 |
So requests is fine — but the zero-dependency example that a reader will copy-paste is exactly the one that breaks. If you publish an API, test it with the dumbest client you can write, and set a real User-Agent in every sample:
curl -H 'User-Agent: my-app/1.0' https://data.jiangzhang.ca/signals4/api/today.json
The server now sends SignalsAPI-MCP/1.0 (+https://…) on every request, and the 403 case returns an error message that names the likely cause instead of a bare status code.
The data behind it
I run a small daily pipeline that collects from public sources (GitHub trending, Hugging Face, YouTube, arXiv, arena boards, tech news, hardware/chips), dedupes and archives it. The newest daily delta is public, plus a machine-readable dataset:
- today's signals: https://data.jiangzhang.ca/signals4/api/today.json
- daily dataset mirror: https://github.com/jiangzhangcc-glitch/ai-signals-daily (CC BY 4.0)
- site summary for agents: https://data.jiangzhang.ca/llms.txt
The free tier is one edition a day from four core sources. There's a paid feed if you need eight sources, four updates a day, and the full history as an API — it's linked from the repo README. Everything in the MCP server stays free.
If you build something with it, I'd genuinely like to know what breaks — the 403 above only surfaced because I used my own server as a user.
Top comments (1)
ha, a 403 that never reaches your logs is the worst kind. Did you end up allowing Python-urllib at the CDN, or just tell people to set a User-Agent? I co-founded ApyHub, and from our experience at ApyHub a listing becomes callable through our MCP with nothing extra to build, so the paid feed could sit there per call too: around 10 minutes, no exclusivity, apyhub.com/become-a-provider