Agency Opportunity Radar
# Agency Opportunity Radar
> MCP server that finds evidence-backed opportunities for digital agencies.
Agency Opportunity Radar helps an AI research public business websites, identify
observable website or SEO problems, match them to an agency's declared services,
and explain every score with measurements, URLs, and timestamps.
It is a research tool, not an outreach tool. It does not discover personal contact
details, submit forms, send messages, or provide a standalone dashboard. The MCP
client—ChatGPT, Claude, Codex, or another compatible client—remains the interface.
## What the interaction looks like
Ask the connected AI:
> Find fintech websites in Argentina that our agency could optimize.
The AI can first discover explicit URLs or public Google Places results, request a
bounded audit for promising candidates, and then compare the saved results. A
synthetic result might be summarized like this:
```text
Río Ledger shows one directly observed technical SEO issue:
- Missing meta description (severity 3/5, confidence 0.99)
Evidence: no non-empty meta description was found on
https://rio-ledger.example/ at 2026-07-27T14:01:58Z.
Overall opportunity score: 73.5/100
- Need: 60
- Agency fit: 90
- Doability: 80
- Likely impact: 60
- Confidence: 80
Limitation: only one page was inspected. No traffic, revenue, budget, or
willingness-to-buy claim was made.
```
The complete structured example is in
[`examples/example-audit-response.json`](examples/example-audit-response.json).
## Why this is not a lead database
A lead database answers “who might I contact?” This server focuses on a different
chain of reasoning:
1. **Need** — Is there an observable problem?
2. **Fit** — Does it map to a service the agency actually offers?
3. **Doability** — Is the likely work compatible with declared capabilities and
exclusions?
4. **Evidence** — Which public observation supports each finding?
The server returns those inputs and deterministic score contributions. The
connected AI interprets them and keeps uncertainty visible.
## Requirements
- Node.js 22.19 or newer
- An MCP-compatible client
- No API key for manual discovery and static website audits
- Optional Google Places API key for query/location discovery
- Optional PageSpeed Insights API key for mobile lab measurements
## Install and build
From this repository:
```bash
npm install
npm run check
```
The compiled stdio server is `dist/server.js`. To run it directly:
```bash
node dist/server.js
```
The process speaks MCP over stdio, so it normally appears idle when started in a
terminal. Configure it in an MCP client instead of typing into it.
## MCP client configuration
Use absolute paths for the server, database, and export directory:
```json
{
"mcpServers": {
"agency-opportunity-radar": {
"command": "node",
"args": [
"/absolute/path/to/agency-opportunity-radar/dist/server.js"
],
"env": {
"RADAR_DATABASE_PATH": "/absolute/path/to/radar-data/opportunities.db",
"RADAR_EXPORT_ROOT": "/absolute/path/to/radar-data/exports"
}
}
}
}
```
A copyable version is available at
[`examples/mcp-config.json`](examples/mcp-config.json).
### Optional environment variables
| Variable | Default | Purpose |
| --- | --- | --- |
| `RADAR_DATABASE_PATH` | `./agency-opportunity-radar.db` | Local SQLite database |
| `RADAR_EXPORT_ROOT` | `./exports` | Only directory to which exports may be written |
| `GOOGLE_PLACES_API_KEY` | unset | Enables Google Places Text Search discovery |
| `PAGESPEED_API_KEY` | unset | Enables mobile PageSpeed measurements |
| `RADAR_REQUEST_TIMEOUT_MS` | `10000` | Per-request timeout |
| `RADAR_MAX_RESPONSE_BYTES` | `2000000` | Maximum response body read |
| `RADAR_MAX_REDIRECTS` | `5` | Redirect limit |
| `RADAR_REQUESTS_PER_MINUTE` | `12` | Per-domain request limit |
| `RADAR_CACHE_TTL_SECONDS` | `86400` | Provider and audit freshness window |
| `RADAR_VERBOSE` | `0` | Structured operational logs on stderr |
| `RADAR_ALLOW_PRIVATE_NETWORK` | `0` | Permit intentional local-site audits |
Keep `RADAR_ALLOW_PRIVATE_NETWORK=0` for ordinary use. Enabling it disables the
private-network SSRF boundary and is intended only for trusted local fixtures or
development sites.
## First workflow
1. Configure the agency with `agency_profile_set`. The example at
[`examples/agency-profile.json`](examples/agency-profile.json) includes
technical SEO, performance, and conversion services.
2. Ask the AI to call `opportunity_discover` with `provider: "manual"` and a list
of public websites.
3. Call `opportunity_audit` for a returned `candidateId`.
4. Compare two or more saved reports with `opportunity_compare`.
5. Record a human decision with `opportunity_record_outcome`.
6. Retrieve it in a later conversation with `opportunity_search_history`.
7. Export selected snapshots with `opportunity_export`.
Manual discovery is intentionally useful without paid APIs:
```json
{
"query": "fintech websites in Argentina",
"location": "Argentina",
"provider": "manual",
"websites": [
"https://public-business.example/"
],
"limit": 10
}
```
## MCP tools
| Tool | Bounded job |
| --- | --- |
| `agency_profile_set` | Create or replace the local agency profile |
| `agency_profile_get` | Inspect the active profile before research |
| `opportunity_discover` | Normalize manual URLs or optional Places results |
| `opportunity_audit` | Crawl and inspect one candidate with strict limits |
| `opportunity_compare` | Return a compact comparison of saved snapshots |
| `opportunity_search_history` | Filter searches, audits, and outcomes |
| `opportunity_record_outcome` | Append a human-reported business outcome |
| `opportunity_export` | Write selected results as JSON or CSV inside the configured root |
## Supported checks
The static detector pack currently covers:
- missing page titles, meta descriptions, and canonical URLs;
- accidental `noindex` directives;
- missing or multiple primary headings;
- missing mobile viewport declarations;
- multiple parser-blocking scripts;
- large declared images that load eagerly;
- missing static primary actions;
- forms without a visible privacy link;
- HTTPS pages posting forms to HTTP;
- public placeholder content;
- stale copyright years as low-confidence supporting evidence;
- crawled internal destinations returning HTTP errors;
- mobile LCP, INP, and CLS when PageSpeed is configured.
Every signal refers to one or more evidence IDs. Evidence stores the minimum
necessary observation rather than full page bodies. Audit responses also include
pages inspected, failed checks, next investigations, limitations, and the exact
scoring configuration.
## Scoring
All dimensions use a 0–100 scale. The initial, inspectable formula is:
```text
overall =
30% need
25% agency fit
20% doability
15% likely impact
10% confidence
```
This is a deterministic starting point, not an objective truth. Each audit stores
its scoring version, weights, dimension values, contributing signal IDs, and
plain-language reasons. A refresh creates a new snapshot instead of rewriting the
old result.
Confidence falls when pages or providers fail, no inspectable page is available,
or only one page is inspected. Likely impact is relative and directional; the
server does not invent revenue or conversion-loss estimates.
## Network and data safety
- Only HTTP and HTTPS targets are accepted.
- Credentials in URLs are rejected.
- Loopback, private, link-local, carrier-grade NAT, multicast, reserved, and
metadata-service addresses are blocked by default.
- DNS is checked and pinned for each request, and every redirect is checked again.
- Crawls respect `robots.txt` and enforce page, redirect, response-size, timeout,
concurrency, and per-domain request bounds.
- Login, logout, cart, checkout, and admin crawl paths are skipped.
- Query tokens and common credential parameters are redacted from stored URLs.
- API keys come from environment variables and are never written to results or
operational logs.
- Full HTML bodies are not stored.
- Forms are never submitted.
- CSV exports neutralize spreadsheet-formula prefixes and never overwrite files.
- Exports cannot escape `RADAR_EXPORT_ROOT`.
- The MVP collects no personal email addresses or phone numbers and performs no
outreach.
## Current limitations
- Google Places is optional, quota-controlled, and may omit website fields.
- Static HTML checks cannot prove that a visually rendered interaction works.
- JavaScript-rendered content may be absent from the static response.
- PageSpeed availability depends on API access and external service behavior.
- The crawler inspects only a bounded subset of public pages.
- It does not bypass authentication, CAPTCHAs, paywalls, or access controls.
- It has no backlink index, paid-ad intelligence, social analysis, contact
enrichment, CRM automation, or standalone UI.
- `deep` increases the bounded page sample; it does not perform the Phase 4
destructive or form-submission interactions excluded by the build plan.
Failed checks remain in the report and reduce confidence rather than disappearing.
## Local storage
SQLite tables keep agency-profile snapshots, services, searches, candidates,
audits, evidence, signals, scoring snapshots, outcomes, and provider cache entries.
This gives a later AI conversation canonical history even when chat history is
summarized or unavailable.
The server does not train an opaque ranking model from outcomes. Recorded outcomes
are returned as transparent context.
## Development and tests
```bash
npm run typecheck
npm test
npm run build
```
`npm run check` runs all three.
The deterministic test suite includes:
- URL normalization, secret redaction, and private-address boundaries;
- redirect, timeout, and response-size handling;
- ten local website fixtures spanning healthy, broken, ambiguous, and blocked
behavior;
- evidence-to-signal referential integrity;
- score calculation and confidence penalties;
- SQLite migrations, immutable snapshots, restart durability, and outcomes;
- Places caching and partial PageSpeed failures;
- export confinement and overwrite protection;
- MCP tool discovery and end-to-end invocation over an in-memory transport.
Network-dependent live evaluations are intentionally separate. See
[`test/evaluation/README.md`](test/evaluation/README.md) for the human-review
requirements before a public site becomes evaluation ground truth.
## Project boundaries
This MVP is for evidence-led research. It is not a spam system, contact-enrichment
service, automatic proposal generator, or substitute for human review. Use public
and authorized data, respect site policies, and verify consequential conclusions
before acting.
## License
[MIT](LICENSE)
TDQS
Scored across 8 tools
Each tool has a clear, distinct purpose without overlap. Profile tools manage the agency profile, opportunity discovery and audit are separated, and post-audit tools (compare, history, outcome, export) serve unique functions.
All tools follow a consistent 'domain_action' pattern using snake_case, with 'agency_profile_' or 'opportunity_' prefixes and clear verbs (set, get, discover, audit, compare, search_history, record_outcome, export).
8 tools align well with the server's purpose. Each tool covers a necessary step in the workflow from profile setup to discovery, audit, comparison, history, outcome recording, and export without being excessive or insufficient.
The tool set covers the main workflow comprehensively, but lacks explicit update/delete tools for audits or opportunities. There is also no direct listing of all opportunities, though search_history with filters partially addresses this.