seolint-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # seolint-mcp
2
+
3
+ MCP server for [SEOLint](https://seolint.dev) — scan any website for SEO issues directly from Claude.
4
+
5
+ ## Setup
6
+
7
+ Add to your Claude Desktop config (`claude_desktop_config.json`):
8
+
9
+ ```json
10
+ {
11
+ "mcpServers": {
12
+ "seolint": {
13
+ "command": "npx",
14
+ "args": ["-y", "seolint-mcp"],
15
+ "env": {
16
+ "SEOLINT_API_KEY": "sl_your_key"
17
+ }
18
+ }
19
+ }
20
+ }
21
+ ```
22
+
23
+ Get your API key at [seolint.dev/dashboard](https://seolint.dev/dashboard).
24
+
25
+ ## Tools
26
+
27
+ ### scan_website
28
+
29
+ Scan a URL for SEO, performance, accessibility, and AI search issues.
30
+
31
+ ```
32
+ "Scan https://mysite.com for SEO issues"
33
+ ```
34
+
35
+ ### get_scan
36
+
37
+ Retrieve results of a previous scan by ID.
38
+
39
+ ## License
40
+
41
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z } from "zod";
5
+ const API_BASE = process.env.SEOLINT_API_URL ?? "https://seolint.dev";
6
+ const API_KEY = process.env.SEOLINT_API_KEY ?? "";
7
+ function headers() {
8
+ const h = { "Content-Type": "application/json" };
9
+ if (API_KEY)
10
+ h["Authorization"] = `Bearer ${API_KEY}`;
11
+ return h;
12
+ }
13
+ async function pollScan(scanId, maxWaitMs = 60_000) {
14
+ const start = Date.now();
15
+ while (Date.now() - start < maxWaitMs) {
16
+ const res = await fetch(`${API_BASE}/api/v1/scan/${scanId}`, { headers: headers() });
17
+ if (!res.ok)
18
+ throw new Error(`Poll failed: ${res.status}`);
19
+ const data = await res.json();
20
+ if (data.status === "complete")
21
+ return data;
22
+ if (data.status === "error")
23
+ throw new Error(data.error_message ?? "Scan failed");
24
+ await new Promise(r => setTimeout(r, 3000));
25
+ }
26
+ throw new Error("Scan timed out after 60 seconds");
27
+ }
28
+ const server = new McpServer({
29
+ name: "seolint",
30
+ version: "0.1.0",
31
+ });
32
+ // Tool: scan a website
33
+ server.tool("scan_website", "Scan a website for SEO, performance, accessibility, and AI search issues. Returns structured issues with LLM-ready fix instructions.", { url: z.string().describe("The full URL to scan, e.g. https://example.com") }, async ({ url }) => {
34
+ // Start scan
35
+ const startRes = await fetch(`${API_BASE}/api/v1/scan`, {
36
+ method: "POST",
37
+ headers: headers(),
38
+ body: JSON.stringify({ url }),
39
+ });
40
+ if (!startRes.ok) {
41
+ const err = await startRes.json().catch(() => ({}));
42
+ return {
43
+ content: [{ type: "text", text: `Failed to start scan: ${err.error ?? startRes.statusText}` }],
44
+ isError: true,
45
+ };
46
+ }
47
+ const { scanId } = await startRes.json();
48
+ // Poll until complete
49
+ try {
50
+ const result = await pollScan(scanId);
51
+ const issues = (result.issues ?? []);
52
+ if (issues.length === 0) {
53
+ return {
54
+ content: [{ type: "text", text: `No issues found for ${url}. The site looks good!` }],
55
+ };
56
+ }
57
+ // Format as readable text with fix prompts
58
+ const lines = issues.map((issue, i) => {
59
+ return [
60
+ `## ${i + 1}. [${issue.severity.toUpperCase()}] ${issue.title}`,
61
+ `**Category:** ${issue.category}`,
62
+ issue.description,
63
+ `**Fix:** ${issue.fix}`,
64
+ ].join("\n");
65
+ });
66
+ const summary = `# SEO Audit: ${url}\n\n${issues.length} issues found.\n\n${lines.join("\n\n---\n\n")}`;
67
+ return {
68
+ content: [{ type: "text", text: summary }],
69
+ };
70
+ }
71
+ catch (err) {
72
+ return {
73
+ content: [{ type: "text", text: `Scan failed: ${err instanceof Error ? err.message : String(err)}` }],
74
+ isError: true,
75
+ };
76
+ }
77
+ });
78
+ // Tool: get an existing scan result
79
+ server.tool("get_scan", "Get the results of a previous SEOLint scan by its ID.", { scanId: z.string().describe("The scan ID (UUID)") }, async ({ scanId }) => {
80
+ const res = await fetch(`${API_BASE}/api/v1/scan/${scanId}`, { headers: headers() });
81
+ if (!res.ok) {
82
+ return {
83
+ content: [{ type: "text", text: `Scan not found: ${scanId}` }],
84
+ isError: true,
85
+ };
86
+ }
87
+ const data = await res.json();
88
+ if (data.status === "pending") {
89
+ return {
90
+ content: [{ type: "text", text: `Scan ${scanId} is still running. Try again in a few seconds.` }],
91
+ };
92
+ }
93
+ if (data.status === "error") {
94
+ return {
95
+ content: [{ type: "text", text: `Scan failed: ${data.error_message ?? "Unknown error"}` }],
96
+ isError: true,
97
+ };
98
+ }
99
+ const markdown = data.markdown ?? JSON.stringify(data.issues, null, 2);
100
+ return {
101
+ content: [{ type: "text", text: markdown }],
102
+ };
103
+ });
104
+ async function main() {
105
+ const transport = new StdioServerTransport();
106
+ await server.connect(transport);
107
+ }
108
+ main().catch((err) => {
109
+ console.error("SEOLint MCP server failed to start:", err);
110
+ process.exit(1);
111
+ });
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "seolint-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for SEOLint — scan any site for SEO issues from Claude Desktop or Claude Code",
5
+ "type": "module",
6
+ "bin": {
7
+ "seolint-mcp": "./dist/index.js"
8
+ },
9
+ "files": ["dist"],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "keywords": ["mcp", "seo", "claude", "seolint", "audit", "ai"],
15
+ "author": "Random Code",
16
+ "license": "MIT",
17
+ "dependencies": {
18
+ "@modelcontextprotocol/sdk": "^1.0.0",
19
+ "zod": "^3.23.0"
20
+ },
21
+ "devDependencies": {
22
+ "typescript": "^5.5.0",
23
+ "@types/node": "^22.0.0"
24
+ }
25
+ }