AgenticSEO MCP Server
README.md
# AgenticSEO MCP Server
AgenticSEO is a local TypeScript MCP server that gives coding agents structured SEO evidence. It audits public or localhost pages, identifies the affected page and an evidence-backed CSS selector when the source provides one, and saves a complete, redacted report for every call.
The server never intentionally edits an audited website or repository. Its only writes are numeric run directories under its own report root.
## Requirements
- Node.js 22.19 or newer. The project plan originally named 22.18, but the pinned Lighthouse 13.4.0 package requires Node 22.19+.
- Google Chrome or Chromium for Lighthouse, Unlighthouse, and rendered-URL structured-data validation.
- Google credentials only when using Search Console tools.
- A CrUX API key only when using `query_crux`.
## Install and verify
### Use the published package
After the first public release, configure a stdio-capable MCP client to launch AgenticSEO directly from npm:
```json
{
"mcpServers": {
"agenticseo": {
"command": "npx",
"args": ["-y", "agenticseo-mcp@0.1.2"]
}
}
}
```
Pinning the version makes reviewer runs reproducible. AgenticSEO and Chrome run on the reviewer's computer, so the server can audit that computer's `localhost` projects. No Google credentials are required for Lighthouse, Unlighthouse, or local structured-data validation.
### Develop from source
```powershell
git clone https://github.com/mahmoud-the-dev/agenticseo-mcp.git
cd agenticseo-mcp
npm ci
npm run check
npm run smoke:stdio
```
`npm run check` runs strict TypeScript validation, unit/contract tests, and the production build. `npm run smoke:stdio` then runs explicit legacy and MCP `2026-07-28` lanes against the same compiled `dist/cli.js` artifact. The server automatically negotiates the applicable era: current legacy hosts continue through their initialize path, while clients that opt into MCP `2026-07-28` use discovery without an initialize sequence. Both expose the same tools, resources, and schemas. Protocol and startup diagnostics are JSON lines on stderr only; stdout remains reserved for the MCP stdio stream.
After the build, run the browser-backed acceptance smoke against the local multi-page fixture. It runs Lighthouse, rendered structured-data validation, and Unlighthouse, so it requires Chrome and can take several minutes:
```powershell
npm run smoke:browser
```
To install the built checkout as a local global npm package:
```powershell
npm install --global .
```
This installs the `agenticseo-mcp` executable on `PATH`. It waits for an MCP client on stdio rather than providing an interactive prompt.
## Tools
| Tool | Purpose | Compact-result and targeting behavior |
| ---------------------------- | ------- | ------------------------------------- |
| `run_lighthouse` | One-page SEO and performance lab audit | Returns a compact initial result with report-wide summary, diversity-first backlog findings, bounded scores/metrics, `resourceUri` for `summary.json`, `fullReportUri` for `report.json`, and one `next.primary` action. Uses only Lighthouse affected-node selectors; missing elements use `expectedSelector`. Before choosing `mode`, check whether the audited server is a development or production build; create a production build if you have code access and need performance. |
| `run_unlighthouse` | Multi-page discovery and audits, up to 200 pages | Unlighthouse already runs Lighthouse per crawled route, so results include Lighthouse SEO/performance detail for those pages. Returns one global diversity-first finding result and crawl coverage provenance. It accepts same-origin `urls` as prioritized crawl seeds, preserves successful pages after partial failure, and omits the page array from compact data; use `get_audited_pages` for exact page rows. Before choosing `mode`, check development vs production build; create a production build if you have code access and need performance. |
| `get_report` | Read a backlog-complete compact page from an existing report | Returns a diversity-first page of compact rows, defaulting to `error`/`warning` work. Supports status (including `unassessed` execution failures), `sourceId`, stable `pageRef`, exact page URL, `area`, sibling exclusions, offset, and a requested maximum `limit` (default 10, maximum 25). |
| `get_finding` | Read one complete normalized finding | Returns one finding's exact URL, complete evidence and source interpretation, normalization explanation, nullable remediation, bounded provider detail, audit error information, and explicit human-input guidance when business intent is required. Its primary action retrieves same-source siblings while excluding the selected occurrence. |
| `get_audited_pages` | Read exact crawl page records | Independently filters and paginates stable page references, exact URLs, crawl status, scores, and finding counts. Non-crawl reports return `not_applicable`. |
| `get_artifacts` | Discover registered report artifacts | Independently filters and paginates manifest-validated artifact name, MIME type, and exact `resourceUri`; read content through the secured artifact resource. |
| `query_search_analytics` | Page-grouped clicks, impressions, CTR, and position | Descriptive evidence only; metrics alone never justify code changes. Retains bounded primary evidence rows with report resources and one `next.primary` action. |
| `inspect_search_console_url` | Google's last-known index, crawl, canonical, and rich-result state | Document-level and not a live URL test. Returns bounded indexing evidence with report resources and one `next.primary` action. |
| `query_crux` | URL- or origin-level LCP, INP, and CLS field data | Never silently falls back from URL to origin and never claims a DOM cause. Returns bounded field-data evidence without a DOM selector unless the provider supplies one. |
| `validate_structured_data` | Rendered URL, raw HTML, or proposed JSON-LD validation | Returns compact prioritized findings and deliberate outcome/entity-count evidence, including an explicit `no_structured_data_detected` empty state when applicable. Rendered entities can have browser-verified selectors; raw markup uses JSON paths and offsets. |
### MCP workflow and response levels
AgenticSEO exposes three response levels so clients can build an SEO backlog without loading a full report:
1. **Initial audit result** — audit tools return visible compact JSON plus matching `structuredContent`. Use the report-wide `summary`, compact `findings`, bounded tool-specific `data`, `coverage` when applicable, `resourceUri`, and `next.primary` to start prioritization.
2. **Backlog-complete compact report** — `agentseo://reports/{runId}/summary.json` and `get_report` return diversity-first compact rows. After filters, the highest-ranked representative for each `sourceId` appears before repeated occurrences. `summary.json` is exactly the first page of the default `get_report` view; custom filters change pagination totals, not report-wide summary counts.
3. **Selected finding detail** — `get_finding` returns one finding's exact URL, complete evidence and source interpretation, normalization explanation, nullable remediation, bounded provider detail, audit error information, and explicit human-input guidance when business intent is required. Its primary action retrieves same-source siblings and excludes the selected occurrence (and, when useful, its page).
Every compact success response is serialized as valid compact JSON in visible MCP text and mirrored in schema-validated `structuredContent`. The public DTO is measured against a hard 7,600-byte UTF-8 budget. `limit` is a requested maximum, defaulting to 10 and capped at 25; if the budget admits fewer complete rows, the result includes `truncation: { "reason": "byte_budget", "requested": ..., "returned": ..., "budgetBytes": 7600 }`. Never assume the requested limit was fully returned; advance using `next.primary.arguments` or `pagination.nextOffset`, based on the actual returned count.
Compact finding rows are triage records, not full remediation records. They include `id`, `sourceId`, bounded `title`, normalized `status`, `area`, occurrence/page counts, optional display value and quantified impact, an optional short source-backed `suggestedAction`, a bounded target with a stable `page.ref`/`displayUrl`/`truncated` marker, and `drillDown` containing only `runId` and `findingId`. Full evidence belongs in `get_finding`. Current `area` values are `technical_seo`, `content_metadata`, `structured_data`, `performance`, `indexing`, and `search_analytics`. Status `unassessed` identifies provider execution failures and is excluded from error/warning issue triage.
The public DTO remains schema `2.0.0`: this removal of inferred classification fields is a breaking pre-release contract correction rather than a version increment. Persisted canonical reports remain schema `1.0.0`.
Every successful initial result and first report page includes `resourceUri` for the compact summary and `fullReportUri` for the complete normalized report, plus a matching resource link. Use `next.primary` for the main workflow. Use `get_audited_pages` and `get_artifacts` for independently filtered and paginated page/artifact retrieval; use `fullReportUri` only when compact pages and finding detail are insufficient.
For crawls, trust the reported coverage provenance. `run_unlighthouse` accepts `urls` as relative routes or same-origin HTTP(S) crawl seeds, rejects credential-bearing, secret-bearing, unsupported-protocol, duplicate, and cross-origin entries before browser work starts, and enforces `maxPages` across seeded and link-discovered routes. The first seed is the crawl entry page, and all seeds are queued before page-discovered links. Seeded mode disables sitemap discovery so sitemap entries cannot consume the limit ahead of requested seeds; internal-link crawling remains enabled. It omits page arrays from compact data; use `get_audited_pages` for exact URLs and page scores.
## Configuration
Set environment variables on the MCP server process:
| Variable | Required | Default / meaning |
| -------------------------------- | ------------------- | --------------------------------------------------------------------------------- |
| `SEO_MCP_OUTPUT_DIR` | No | Explicit report root (recommended for MCP `2026-07-28` clients). If unset: first usable local root on a negotiated legacy connection, otherwise `<server cwd>/.seo-mcp/reports`. |
| `SEO_MCP_MAX_INLINE_FINDINGS` | No | `10`; bounded to 1–20. This is the requested initial compact finding maximum; the 7,600-byte UTF-8 budget may return fewer complete findings with explicit `byte_budget` truncation. Passed, informational, and extra findings remain available through `get_report`, `get_finding`, or `report.json`. |
| `SEO_MCP_TOOL_TIMEOUT_MS` | No | `180000`; bounded to 1 second–1 hour. Site-wide scans allow at least thirty minutes. |
| `SEO_MCP_BROWSER_GATE_MAX_QUEUE` | No | `3` total pending browser-gate callers (1 running + up to 2 waiters); bounded to 1–16. Overflow rejects with `BROWSER_GATE_QUEUE_FULL`. |
| `CHROME_PATH` | No | Explicit Chrome/Chromium executable when automatic discovery is insufficient. |
| `GOOGLE_APPLICATION_CREDENTIALS` | Search Console only | Path to Application Default Credentials with Search Console property access. |
| `CRUX_API_KEY` | CrUX only | Google API key with the Chrome UX Report API enabled. |
The Search Console principal needs read access to the requested URL-prefix or domain property. The server requests only `https://www.googleapis.com/auth/webmasters.readonly`.
Report-root precedence is: (1) explicit `SEO_MCP_OUTPUT_DIR`; (2) the first usable local workspace root supplied by a negotiated legacy client; (3) `.seo-mcp/reports` under the server working directory. MCP `2026-07-28` requests never invoke the deprecated roots exchange. Relative explicit paths resolve from the server working directory. Public tool results expose `agentseo://` resource URIs, never absolute local paths.
## MCP Inspector
Build first, then start the official Inspector with the local server command:
```powershell
npx @modelcontextprotocol/inspector node C:\absolute\path\to\agenticseo-mcp\dist\cli.js
```
The Inspector should list all ten tools, including read-only `get_report`, `get_finding`, `get_audited_pages`, and `get_artifacts`, plus the `agentseo-summary` and `agentseo-report` resource templates. The server supplies the compact workflow instructions through whichever negotiated protocol era the Inspector uses: initial result, diversity-first report pages, independent page/artifact retrieval, then selected finding detail. This follows the [official MCP Inspector local-server pattern](https://modelcontextprotocol.io/docs/tools/inspector).
## OpenCode
Add a local server to `opencode.json` and replace the paths. OpenCode resolves the credential values from the environment of the process that launches it:
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"agenticseo": {
"type": "local",
"command": [
"node",
"C:\\absolute\\path\\to\\agenticseo-mcp\\dist\\cli.js"
],
"enabled": true,
"environment": {
"SEO_MCP_OUTPUT_DIR": "C:\\absolute\\path\\to\\reports",
"GOOGLE_APPLICATION_CREDENTIALS": "{env:GOOGLE_APPLICATION_CREDENTIALS}",
"CRUX_API_KEY": "{env:CRUX_API_KEY}"
}
}
}
}
```
Set those environment variables before launching OpenCode. Omit credential entries for tools you do not use; never put a real key in `opencode.json`. The structure matches [OpenCode's current local MCP configuration](https://opencode.ai/docs/mcp-servers) and [environment-variable substitution](https://opencode.ai/docs/config#env-vars).
### Experimental Codex MCP `2026-07-28` canary
Codex's modern MCP path is experimental and should not replace the default configuration yet. Maintainers can opt in with the host's `mcp_2026_07_28` feature and set `CODEX_MCP_PROTOCOL_VERSION=2026-07-28`; also set an explicit `SEO_MCP_OUTPUT_DIR`. This canary is separate from normal client setup and is not a claim of production host validation. If it regresses, disable the feature/marker; the same AgentSEO binary then remains available through legacy negotiation. Roll back the package only if a failure also affects the default legacy path.
## Example calls
The examples below show tool argument objects. MCP clients generate the surrounding JSON-RPC request.
```jsonc
// One-page production lab audit (the default mode)
{ "url": "http://localhost:3000/pricing", "device": "mobile", "mode": "production" }
// Site-wide audit. URLs are prioritized same-origin crawl seeds; links discovered
// from those pages fill the remaining maxPages capacity.
{
"url": "https://example.com/",
"device": "mobile",
"mode": "production",
"maxPages": 50,
"urls": ["/pricing", "https://example.com/docs/getting-started"],
"includePaths": ["/docs/**"],
"excludePaths": ["/docs/archive/**"]
}
// Development keeps SEO findings but suppresses performance findings, scores, and metrics.
// Prefer a production build + mode "production" when you need performance (dev servers add noise).
{ "url": "http://localhost:3000/pricing", "mode": "development" }
// Backlog-complete, diversity-first report page after receiving a run ID.
// limit is a requested maximum; follow next.primary or pagination.nextOffset.
{ "runId": 12, "statuses": ["error", "warning"], "area": "performance", "offset": 0, "limit": 10 }
// Retrieve Lighthouse audits that could not execute; these do not enter error/warning issue triage.
{ "runId": 12, "statuses": ["unassessed"], "offset": 0, "limit": 10 }
// Prefer a stable page reference from a compact row over an exact page URL filter.
{ "runId": 12, "pageRef": "page-0123456789abcdef", "offset": 0, "limit": 10 }
// Retrieve exact crawl pages and registered artifacts independently.
{ "runId": 12, "status": "failed", "offset": 0, "limit": 10 }
{ "runId": 12, "mimeType": "application/json", "offset": 0, "limit": 10 }
// Selected-finding detail, then same-source sibling navigation via next.primary.
{ "runId": 12, "findingId": "finding-example-0001" }
// Search Analytics; dates default to the latest 28 complete Pacific-time days
{
"property": "sc-domain:example.com",
"secondaryDimensions": ["query", "device"],
"filters": {
"country": { "value": "usa", "operator": "equals" }
},
"rowLimit": 1000
}
// URL Inspection (last-known Google state, not live)
{
"property": "https://example.com/",
"inspectionUrl": "https://example.com/products/widget"
}
// Exact URL-level field data; no origin fallback
{
"url": "https://example.com/products/widget",
"formFactor": "phone"
}
// Validate proposed JSON-LD before deployment
{
"markup": "{\"@context\":\"https://schema.org\",\"@type\":\"Article\",\"headline\":\"Example\"}",
"format": "json_ld"
}
```
## Representative outputs
Each successful call returns compact JSON in visible MCP text, the same public DTO in `structuredContent`, and a compact-summary resource link when the result carries `resourceUri`. Public results contain report identity, execution status, report-wide summary where applicable, bounded tool data, highlights/coverage where applicable, compact findings, pagination, errors/limitations, and `next.primary`. Saved `summary.json` contains the first default `get_report` page; saved `report.json` contains complete canonical findings, normalization rules, and provider artifacts. Measured values and provider verdicts vary by call.
A compact initial audit result looks like this (fields not relevant to the example are omitted). The first rows show diversity-first ordering: different `sourceId` representatives precede repeated occurrences.
```json
{
"schemaVersion": "2.0.0",
"runId": 12,
"executionStatus": "success",
"summary": {
"total": 7,
"error": 3,
"warning": 4,
"info": 0,
"passed": 0,
"notApplicable": 0,
"unassessed": 0,
"areaCounts": { "technical_seo": 1, "content_metadata": 2, "structured_data": 0, "performance": 4, "indexing": 0, "search_analytics": 0 }
},
"findings": [
{
"id": "finding-example-0001",
"sourceId": "largest-contentful-paint",
"title": "Largest Contentful Paint element rendered late",
"status": "error",
"area": "performance",
"occurrenceCount": 1,
"affectedPageCount": 1,
"displayValue": "4.2 s",
"quantifiedImpact": { "value": 1200, "unit": "ms", "label": "Estimated time savings" },
"target": { "kind": "document", "page": { "ref": "page-0123456789abcdef", "displayUrl": "https://example.com/pricing", "truncated": false } },
"drillDown": { "runId": 12, "findingId": "finding-example-0001" }
},
{
"id": "finding-example-0002",
"sourceId": "missing-image-alt",
"title": "Images are missing alternative text",
"status": "warning",
"area": "content_metadata",
"occurrenceCount": 3,
"affectedPageCount": 2,
"suggestedAction": "Add concise alternative text to each informative image.",
"target": { "kind": "element", "page": { "ref": "page-fedcba9876543210", "displayUrl": "https://example.com/docs", "truncated": false } },
"drillDown": { "runId": 12, "findingId": "finding-example-0002" }
}
],
"pagination": { "offset": 0, "limit": 10, "returned": 2, "total": 7, "hasMore": true, "nextOffset": 2 },
"resourceUri": "agentseo://reports/12/summary.json",
"fullReportUri": "agentseo://reports/12/report.json",
"next": { "primary": { "tool": "get_report", "description": "Retrieve the next compact page using the same deterministic filters.", "arguments": { "runId": 12, "offset": 2, "limit": 10 } }, "optional": [] }
}
```
If the requested maximum cannot fit in 7,600 UTF-8 bytes, the result remains valid JSON and reports the fallback explicitly:
```json
"truncation": { "reason": "byte_budget", "requested": 10, "returned": 8, "budgetBytes": 7600 }
```
A later `get_report` page keeps only the page identity, findings, pagination, truncation when applicable, and `next.primary`; the report-wide summary, highlights, coverage, and resource URIs appear only at offset zero. `get_report` uses the same compact row shape and stable page references. `get_finding` then resolves the exact URL and full evidence for one row, with `next.primary` configured for same-source sibling navigation.
`get_audited_pages` and `get_artifacts` are independent read-only streams:
```json
{ "schemaVersion": "2.0.0", "runId": 12, "applicability": "applicable", "pages": [{ "page": { "ref": "page-0123456789abcdef", "displayUrl": "https://example.com/pricing", "truncated": false }, "url": "https://example.com/pricing", "status": "success", "seoScore": 0.92, "performanceScore": 0.81, "findingCount": 2 }], "pagination": { "offset": 0, "limit": 10, "returned": 1, "total": 1, "hasMore": false, "nextOffset": null }, "resourceUri": "agentseo://reports/12/summary.json", "fullReportUri": "agentseo://reports/12/report.json", "next": { "primary": null, "optional": [] } }
```
```json
{ "schemaVersion": "2.0.0", "runId": 12, "artifacts": [{ "name": "lighthouse.json", "mimeType": "application/json", "resourceUri": "agentseo://reports/12/artifacts/lighthouse.json" }], "pagination": { "offset": 0, "limit": 10, "returned": 1, "total": 1, "hasMore": false, "nextOffset": null }, "resourceUri": "agentseo://reports/12/summary.json", "fullReportUri": "agentseo://reports/12/report.json", "next": { "primary": null, "optional": [] } }
```
The illustrative excerpts below show the tool-specific `structuredContent.data` shape.
`run_lighthouse`:
```json
{
"kind": "lighthouse",
"requestedUrl": "https://example.com/",
"finalUrl": "https://example.com/",
"device": "mobile",
"labData": true,
"mode": "production",
"categoryScores": { "seo": 0.92, "performance": 0.81 },
"metrics": [
{
"id": "largest-contentful-paint",
"title": "Largest Contentful Paint",
"numericValue": 2100,
"displayValue": "2.1 s",
"unit": "millisecond",
"score": 0.91
}
]
}
```
`run_unlighthouse` (page records are retrieved with `get_audited_pages`):
```json
{
"kind": "unlighthouse",
"siteUrl": "https://example.com/",
"device": "mobile",
"mode": "production",
"maxPages": 50,
"discoveryMode": "sitemap_and_links",
"explicitUrlCount": 2,
"explicitUrls": ["https://example.com/docs/", "https://example.com/pricing"],
"explicitUrlsTruncated": false
}
```
`query_search_analytics`:
```json
{
"kind": "search_analytics",
"property": "sc-domain:example.com",
"dateRange": { "startDate": "2026-06-01", "endDate": "2026-06-28" },
"dimensions": ["page", "query", "device"],
"totalRows": 1,
"rowsTruncated": false,
"rows": [
{
"page": "https://example.com/products/widget",
"dimensions": {
"page": "https://example.com/products/widget",
"query": "example widget",
"device": "MOBILE"
},
"clicks": 42,
"impressions": 1000,
"ctr": 0.042,
"position": 8.4
}
]
}
```
`inspect_search_console_url`:
```json
{
"kind": "url_inspection",
"property": "https://example.com/",
"inspectionUrl": "https://example.com/products/widget",
"indexStatus": {
"verdict": "PASS",
"coverageState": "Submitted and indexed",
"robotsTxtState": "ALLOWED"
},
"richResults": [
{ "richResultType": "Product snippets", "itemCount": 1 }
],
"richResultsVerdict": "PASS"
}
```
`query_crux`:
```json
{
"kind": "crux",
"granularity": "url",
"subject": "https://example.com/products/widget",
"formFactor": "phone",
"collectionPeriod": { "firstDate": "2026-06-01", "lastDate": "2026-06-28" },
"metrics": [
{
"id": "LCP",
"p75": 2400,
"unit": "milliseconds",
"rating": "good"
}
],
"missingMetrics": ["INP", "CLS"]
}
```
`validate_structured_data`:
```json
{
"kind": "structured_data",
"sourceKind": "raw_json_ld",
"subject": "raw-markup:sha256:0123456789abcdef0123",
"entityTypes": ["Article"],
"entityCount": 1,
"outcome": "structured_data_validated",
"expectedEntityType": null,
"detectedPageIntent": null
}
```
## Reports
Each invocation atomically allocates the next positive numeric directory:
```text
.seo-mcp/reports/
1/
manifest.json
summary.json
report.json
artifacts/
2/
manifest.json
summary.json
report.json
artifacts/
```
Runs are never overwritten and gaps are not reused. `summary.json` is the immutable compact summary resource and is equivalent to the first default `get_report` page, including diversity-first backlog findings, report-wide summary/context, coverage, pagination, resource references, and `next.primary`. `report.json` contains all canonical findings, including passed and not-applicable checks, report-level normalization rules, and raw/provider artifacts. Inline MCP results contain no more than the requested finding maximum and may contain fewer when the 7,600-byte UTF-8 budget requires explicit `byte_budget` truncation; the compact row shape stays fixed and pagination advances by the actual returned count. Use `get_report` for paginated backlog findings, `get_audited_pages` for exact crawl page records, and `get_finding` for one finding's full evidence before reading the full resource. Lighthouse source reports, structured-data issue summaries, Search Console responses, Unlighthouse per-page captures, and the static Unlighthouse dashboard are stored under `artifacts/` when applicable and are exposed only through manifest-validated resource references rather than embedded in compact payloads.
This repository already ignores `.seo-mcp/`; add the same rule to each client project that uses the server.
## Evidence and safety rules
- An `element` target always has an actual selector obtained from Lighthouse or verified against the rendered DOM.
- An `expectedSelector` describes a missing element; it is never presented as an observed element.
- Compact finding rows are triage records: bounded display values and quantified impact support prioritization, while exact URLs, full evidence, interpretation, remediation, and provider payloads stay in `get_finding`, `report.json`, or artifacts.
- `suggestedAction` can be `null`; AgenticSEO reports available remediation without manufacturing advice when evidence is insufficient.
- Search Console and CrUX evidence stays page-, origin-, or external-level unless the provider explicitly supplies finer evidence.
- `humanInput` is included only for an explicit business- or user-intent decision and carries the question to ask, a safe recommendation, the reason, and a warning not to invent facts or approved wording.
- API keys, credential-like fields, bearer tokens, private keys, and configured secrets are redacted before logs and report JSON are written.
- Raw markup is represented by a hash in reports and is not copied wholesale into artifacts.
Navigation can still trigger ordinary server-side GET behavior, analytics, or third-party requests on the audited site. “Read-only” means AgenticSEO makes no intentional edits or mutation requests; it cannot guarantee that a website treats every page load as side-effect-free.
Saved provider artifacts can contain public page content and URLs. Protect the report directory accordingly. The local validator is not an official substitute for Google's live Rich Results Test, and Lighthouse/Unlighthouse results are synthetic lab measurements rather than real-user field data.
The bundled Schema.org vocabulary is third-party material distributed under CC BY-SA 3.0; see [Third-Party Notices](THIRD_PARTY_NOTICES.md). AgenticSEO's own code is MIT-licensed.
## Development
```powershell
npm run dev
npm run typecheck
npm test
npm run test:coverage
npm run build
npm run smoke:stdio
npm run smoke:browser
```
Before release, run the static checks, unit/integration tests, production build, legacy and modern stdio smoke lanes (or their aggregate script), and browser-backed smoke. `prepublishOnly` runs `check` plus the aggregate stdio smoke. Browser smoke exercises Lighthouse, rendered structured-data validation, Unlighthouse, report retrieval, and resource workflow coverage; it requires Chrome/Chromium and may take several minutes. Inspect the package with `npm pack --dry-run` before publishing; the package allowlist should exclude reports, test fixtures, scripts, and credentials.
The test fixture under `tests/fixtures/site/` contains multiple routes, missing image alt text, weak link text, problematic canonical markup, and incomplete structured data. Google and CrUX tests use injected clients so automated runs do not require credentials or consume quotas.
## Documentation
See the [overview](docs/overview.md), [getting started guide](docs/getting-started.md), and [architecture](docs/architecture.md).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues