Opportunity Radar
by kevin9327
README.md
# Opportunity Radar π‘
**Ask out loud what public funding you can actually apply for β and get an answer with real numbers, or none at all.**
Every year billions in public grant funding goes unclaimed, not because people
are unqualified but because nobody has time to read `grants.gov` on a Tuesday
night. Opportunity Radar is a self-hosted **MCP server** that turns that
database into something you can ask a question to: *"any grants open for a
small AI company?"*, *"what closes in the next two weeks?"*, *"am I even
allowed to apply for that one?"*
Because Alexa+ speaks MCP, the same server answers in the kitchen.
> Built for the **Build, Ship, Shape: Amazon Developer Hackathon** β Alexa+ track,
> with the **AWS Builder** and **Open Source** mini challenges.
## What makes it different: it refuses to guess
A funding answer that is confidently wrong costs someone a weekend. So the
honesty rules are structural, not prompt-deep:
| Failure mode | What this server does |
|---|---|
| Source down | Returns `error: source_unavailable` + "I have no numbers for you" β never an estimate |
| Notice publishes no applicant list | Reports `status: see_notice`, **not** "you're ineligible" |
| Opportunity has no deadline | Counted and reported separately, never silently dropped |
| Model ranks fit | Model may set **score and reason only**; deadlines and dollar figures are attached from grants.gov afterwards |
The test suite asserts each of those (`tests/test_radar.py::HonestyUnderFailure`).
## Tools
| Tool | Question it answers |
|---|---|
| `find_opportunities` | "What's open for rural health right now?" |
| `opportunity_detail` | "How much is it, and who runs it?" |
| `check_eligibility_fit` | "Can an individual apply, or only universities?" |
| `deadline_watch` | "What do I have to act on this week?" |
| `weekly_briefing` | The scheduled Monday sweep across all your interests |
| `rank_for_me` | "Of these, which are actually worth my Saturday?" (Bedrock) |
## Run it
```bash
git clone https://github.com/kevin9327/opportunity-radar && cd opportunity-radar
python -m radar.server # http://0.0.0.0:8080/mcp
```
No API key is needed: `grants.gov` search is public. Responses cache for 6h.
```bash
# point any MCP client at it
curl -s localhost:8080/mcp -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"deadline_watch","arguments":{"interest":"artificial intelligence","within_days":30}}}'
```
### Fit ranking runs on your own machine
`rank_for_me` asks a model whether each opening is worth your Saturday. It
prefers a **local model over Ollama** β no key, no account, and the sentence
describing your situation never leaves the house. That matters when the query
is *"grants for a family caring for a disabled child"*.
```bash
ollama pull llama3.1:8b # engine: local
```
Fallbacks are automatic and always labelled in the response: Amazon Bedrock if
AWS credentials happen to exist, then plain keyword overlap so the radar still
answers offline. Set `RADAR_RANK_ENGINE=local|bedrock|overlap` to pin one.
## How it fits together
```mermaid
flowchart LR
A["Alexa+ / any MCP client"] -->|Streamable HTTP Β· spec 2025-11-25| B["radar.server<br/>JSON-RPC + SSE"]
B --> C["6 tools<br/>honesty gates"]
C --> D["grants.gov<br/>search2 + fetchOpportunity"]
C -->|fit score + reason only| E["Amazon Bedrock<br/>Claude Haiku"]
C --> F[(6h disk cache)]
```
`radar/server.py` implements the transport by hand against the MCP spec β one
endpoint, `initialize` handing out a session id, `tools/list` schemas generated
from Python type hints, `tools/call` returning both spoken text and
`structuredContent`. No framework in the way, which made the spec easy to read
off the wire while debugging.
## Tests
```bash
python -m unittest discover -s tests -v # 19 offline, 2 live skipped
RADAR_LIVE=1 python -m unittest discover -s tests # all 21, incl. grants.gov
```
CI runs the offline suite on every push and the live-source suite weekly, so a
silent API change shows up as a red build rather than a wrong answer.
The three problems that cost me the most time are written up in
[FRICTION_LOG.md](FRICTION_LOG.md) β two of them shipped as silent wrong
answers before I caught them, and both are now pinned by tests.
MIT Β© 2026 kevin9327
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues