@thenextgennexus/seo-web-analysis-mcp-server 1.0.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,74 @@
1
+ # SEO & Web Analysis MCP Server
2
+
3
+ Run Lighthouse audits, check SSL certificates, detect tech stacks, query WHOIS and DNS records — powered by [nexgendata](https://apify.com/nexgendata) on Apify.
4
+
5
+ ## Quick Start
6
+
7
+ ### Using npx (recommended)
8
+
9
+ ```bash
10
+ npx @nexgendata/seo-web-analysis-mcp-server
11
+ ```
12
+
13
+ ### Install globally
14
+
15
+ ```bash
16
+ npm install -g @nexgendata/seo-web-analysis-mcp-server
17
+ ```
18
+
19
+ ## Configure with Claude Desktop
20
+
21
+ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
22
+
23
+ ```json
24
+ {
25
+ "mcpServers": {
26
+ "seo-web-analysis-mcp-server": {
27
+ "command": "npx",
28
+ "args": [
29
+ "-y",
30
+ "@nexgendata/seo-web-analysis-mcp-server"
31
+ ],
32
+ "env": {
33
+ "APIFY_TOKEN": "your-apify-token-optional"
34
+ }
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ ## Configure with Cline
41
+
42
+ Add the same configuration to your Cline MCP settings.
43
+
44
+ ## Available Tools
45
+
46
+ | Tool | Description |
47
+ |------|-------------|
48
+ | `run_lighthouse_audit` | Run a Google Lighthouse performance audit on a URL |
49
+ | `get_pagespeed_insights` | Get Google PageSpeed Insights for a URL |
50
+ | `check_ssl_certificate` | Check SSL certificate details for a domain |
51
+ | `identify_tech_stack` | Identify the technology stack of a website |
52
+ | `query_whois` | Query WHOIS records for a domain |
53
+ | `query_dns_records` | Query DNS records for a domain |
54
+
55
+
56
+ ## Environment Variables
57
+
58
+ | Variable | Required | Description |
59
+ |----------|----------|-------------|
60
+ | `APIFY_TOKEN` | No | Your Apify API token for authenticated access. Without it, the server uses the public endpoint (rate-limited). |
61
+
62
+ ## How It Works
63
+
64
+ This MCP server acts as a local stdio bridge to the [nexgendata Apify MCP endpoint](https://nexgendata--seo-web-analysis-mcp-server.apify.actor/mcp). When you call a tool, it forwards the request to Apify and returns the results.
65
+
66
+ ## Links
67
+
68
+ - [Apify Store](https://apify.com/nexgendata/seo-web-analysis-mcp-server)
69
+ - [GitHub Repository](https://github.com/TheNextGenNexus/web-intelligence-mcp-servers)
70
+ - [nexgendata Blog](https://thenextgennexus.com)
71
+
72
+ ## License
73
+
74
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,210 @@
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 APIFY_ENDPOINT = "https://nexgendata--seo-web-analysis-mcp-server.apify.actor/mcp";
6
+ const APIFY_TOKEN = process.env.APIFY_TOKEN || "";
7
+ async function callApifyTool(toolName, args) {
8
+ const url = new URL(APIFY_ENDPOINT);
9
+ if (APIFY_TOKEN) {
10
+ url.searchParams.set("token", APIFY_TOKEN);
11
+ }
12
+ const body = {
13
+ jsonrpc: "2.0",
14
+ id: 1,
15
+ method: "tools/call",
16
+ params: {
17
+ name: toolName,
18
+ arguments: args
19
+ }
20
+ };
21
+ const response = await fetch(url.toString(), {
22
+ method: "POST",
23
+ headers: { "Content-Type": "application/json" },
24
+ body: JSON.stringify(body)
25
+ });
26
+ if (!response.ok) {
27
+ const errorText = await response.text();
28
+ throw new Error(`Apify API error (${response.status}): ${errorText}`);
29
+ }
30
+ const data = await response.json();
31
+ if (data.error) {
32
+ throw new Error(`Tool error: ${JSON.stringify(data.error)}`);
33
+ }
34
+ // Extract text content from MCP response
35
+ const result = data.result;
36
+ if (result?.content && Array.isArray(result.content)) {
37
+ return result.content
38
+ .filter((c) => c.type === "text")
39
+ .map((c) => c.text)
40
+ .join("\n");
41
+ }
42
+ return JSON.stringify(data.result, null, 2);
43
+ }
44
+ const server = new McpServer({
45
+ name: "seo-web-analysis-mcp-server",
46
+ version: "1.0.0"
47
+ });
48
+ server.registerTool("run_lighthouse_audit", {
49
+ title: "Run Lighthouse Audit",
50
+ description: "Run Lighthouse performance audit",
51
+ inputSchema: {
52
+ url: z.string().describe('URL to audit')
53
+ },
54
+ annotations: {
55
+ readOnlyHint: true,
56
+ destructiveHint: false,
57
+ openWorldHint: true
58
+ }
59
+ }, async (args) => {
60
+ try {
61
+ const result = await callApifyTool("run_lighthouse_audit", args);
62
+ return {
63
+ content: [{ type: "text", text: result }]
64
+ };
65
+ }
66
+ catch (error) {
67
+ const message = error instanceof Error ? error.message : String(error);
68
+ return {
69
+ content: [{ type: "text", text: `Error: ${message}` }],
70
+ isError: true
71
+ };
72
+ }
73
+ });
74
+ server.registerTool("get_pagespeed_insights", {
75
+ title: "Get Pagespeed Insights",
76
+ description: "Get PageSpeed Insights",
77
+ inputSchema: {
78
+ url: z.string().describe('URL to analyze')
79
+ },
80
+ annotations: {
81
+ readOnlyHint: true,
82
+ destructiveHint: false,
83
+ openWorldHint: true
84
+ }
85
+ }, async (args) => {
86
+ try {
87
+ const result = await callApifyTool("get_pagespeed_insights", args);
88
+ return {
89
+ content: [{ type: "text", text: result }]
90
+ };
91
+ }
92
+ catch (error) {
93
+ const message = error instanceof Error ? error.message : String(error);
94
+ return {
95
+ content: [{ type: "text", text: `Error: ${message}` }],
96
+ isError: true
97
+ };
98
+ }
99
+ });
100
+ server.registerTool("check_ssl_certificate", {
101
+ title: "Check Ssl Certificate",
102
+ description: "Check SSL certificate details",
103
+ inputSchema: {
104
+ domain: z.string().describe('Domain to check')
105
+ },
106
+ annotations: {
107
+ readOnlyHint: true,
108
+ destructiveHint: false,
109
+ openWorldHint: true
110
+ }
111
+ }, async (args) => {
112
+ try {
113
+ const result = await callApifyTool("check_ssl_certificate", args);
114
+ return {
115
+ content: [{ type: "text", text: result }]
116
+ };
117
+ }
118
+ catch (error) {
119
+ const message = error instanceof Error ? error.message : String(error);
120
+ return {
121
+ content: [{ type: "text", text: `Error: ${message}` }],
122
+ isError: true
123
+ };
124
+ }
125
+ });
126
+ server.registerTool("identify_tech_stack", {
127
+ title: "Identify Tech Stack",
128
+ description: "Identify website technology stack",
129
+ inputSchema: {
130
+ url: z.string().describe('Website URL')
131
+ },
132
+ annotations: {
133
+ readOnlyHint: true,
134
+ destructiveHint: false,
135
+ openWorldHint: true
136
+ }
137
+ }, async (args) => {
138
+ try {
139
+ const result = await callApifyTool("identify_tech_stack", args);
140
+ return {
141
+ content: [{ type: "text", text: result }]
142
+ };
143
+ }
144
+ catch (error) {
145
+ const message = error instanceof Error ? error.message : String(error);
146
+ return {
147
+ content: [{ type: "text", text: `Error: ${message}` }],
148
+ isError: true
149
+ };
150
+ }
151
+ });
152
+ server.registerTool("query_whois", {
153
+ title: "Query Whois",
154
+ description: "Query WHOIS records",
155
+ inputSchema: {
156
+ domain: z.string().describe('Domain to look up')
157
+ },
158
+ annotations: {
159
+ readOnlyHint: true,
160
+ destructiveHint: false,
161
+ openWorldHint: true
162
+ }
163
+ }, async (args) => {
164
+ try {
165
+ const result = await callApifyTool("query_whois", args);
166
+ return {
167
+ content: [{ type: "text", text: result }]
168
+ };
169
+ }
170
+ catch (error) {
171
+ const message = error instanceof Error ? error.message : String(error);
172
+ return {
173
+ content: [{ type: "text", text: `Error: ${message}` }],
174
+ isError: true
175
+ };
176
+ }
177
+ });
178
+ server.registerTool("query_dns_records", {
179
+ title: "Query Dns Records",
180
+ description: "Query DNS records",
181
+ inputSchema: {
182
+ domain: z.string().describe('Domain to query'),
183
+ recordType: z.string().optional().describe('DNS record type: A, AAAA, MX, TXT')
184
+ },
185
+ annotations: {
186
+ readOnlyHint: true,
187
+ destructiveHint: false,
188
+ openWorldHint: true
189
+ }
190
+ }, async (args) => {
191
+ try {
192
+ const result = await callApifyTool("query_dns_records", args);
193
+ return {
194
+ content: [{ type: "text", text: result }]
195
+ };
196
+ }
197
+ catch (error) {
198
+ const message = error instanceof Error ? error.message : String(error);
199
+ return {
200
+ content: [{ type: "text", text: `Error: ${message}` }],
201
+ isError: true
202
+ };
203
+ }
204
+ });
205
+ async function main() {
206
+ const transport = new StdioServerTransport();
207
+ await server.connect(transport);
208
+ console.error("SEO & Web Analysis MCP Server running on stdio");
209
+ }
210
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@thenextgennexus/seo-web-analysis-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "Run Lighthouse audits, check SSL certificates, detect tech stacks, query WHOIS and DNS records",
5
+ "type": "module",
6
+ "bin": {
7
+ "seo-web-analysis-mcp-server": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "node dist/index.js",
12
+ "dev": "tsx src/index.ts"
13
+ },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.0.0",
16
+ "zod": "^3.22.0"
17
+ },
18
+ "devDependencies": {
19
+ "typescript": "^5.3.0",
20
+ "tsx": "^4.7.0",
21
+ "@types/node": "^20.11.0"
22
+ },
23
+ "engines": {
24
+ "node": ">=18.0.0"
25
+ },
26
+ "keywords": [
27
+ "mcp",
28
+ "model-context-protocol",
29
+ "ai",
30
+ "apify",
31
+ "nexgendata"
32
+ ],
33
+ "author": "nexgendata",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/TheNextGenNexus/web-intelligence-mcp-servers"
38
+ },
39
+ "mcpName": "io.github.thenextgennexus/seo-web-analysis-mcp-server"
40
+ }
package/smithery.yaml ADDED
@@ -0,0 +1,13 @@
1
+ startCommand:
2
+ type: stdio
3
+ configSchema:
4
+ type: object
5
+ required:
6
+ - apifyToken
7
+ properties:
8
+ apifyToken:
9
+ type: string
10
+ description: Your Apify API token. Get one free at https://apify.com
11
+ commandFunction:
12
+ |-
13
+ (config) => ({{ command: 'npx', args: ['tsx', 'src/index.ts'], env: {{ APIFY_TOKEN: config.apifyToken }} }})
package/src/index.ts ADDED
@@ -0,0 +1,247 @@
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
+
6
+ const APIFY_ENDPOINT = "https://nexgendata--seo-web-analysis-mcp-server.apify.actor/mcp";
7
+ const APIFY_TOKEN = process.env.APIFY_TOKEN || "";
8
+
9
+ async function callApifyTool(toolName: string, args: Record<string, unknown>): Promise<string> {
10
+ const url = new URL(APIFY_ENDPOINT);
11
+ if (APIFY_TOKEN) {
12
+ url.searchParams.set("token", APIFY_TOKEN);
13
+ }
14
+
15
+ const body = {
16
+ jsonrpc: "2.0",
17
+ id: 1,
18
+ method: "tools/call",
19
+ params: {
20
+ name: toolName,
21
+ arguments: args
22
+ }
23
+ };
24
+
25
+ const response = await fetch(url.toString(), {
26
+ method: "POST",
27
+ headers: { "Content-Type": "application/json" },
28
+ body: JSON.stringify(body)
29
+ });
30
+
31
+ if (!response.ok) {
32
+ const errorText = await response.text();
33
+ throw new Error(`Apify API error (${response.status}): ${errorText}`);
34
+ }
35
+
36
+ const data = await response.json();
37
+
38
+ if (data.error) {
39
+ throw new Error(`Tool error: ${JSON.stringify(data.error)}`);
40
+ }
41
+
42
+ // Extract text content from MCP response
43
+ const result = data.result;
44
+ if (result?.content && Array.isArray(result.content)) {
45
+ return result.content
46
+ .filter((c: { type: string }) => c.type === "text")
47
+ .map((c: { text: string }) => c.text)
48
+ .join("\n");
49
+ }
50
+
51
+ return JSON.stringify(data.result, null, 2);
52
+ }
53
+
54
+ const server = new McpServer({
55
+ name: "seo-web-analysis-mcp-server",
56
+ version: "1.0.0"
57
+ });
58
+
59
+ server.registerTool(
60
+ "run_lighthouse_audit",
61
+ {
62
+ title: "Run Lighthouse Audit",
63
+ description: "Run Lighthouse performance audit",
64
+ inputSchema: {
65
+ url: z.string().describe('URL to audit')
66
+ },
67
+ annotations: {
68
+ readOnlyHint: true,
69
+ destructiveHint: false,
70
+ openWorldHint: true
71
+ }
72
+ },
73
+ async (args) => {
74
+ try {
75
+ const result = await callApifyTool("run_lighthouse_audit", args);
76
+ return {
77
+ content: [{ type: "text", text: result }]
78
+ };
79
+ } catch (error) {
80
+ const message = error instanceof Error ? error.message : String(error);
81
+ return {
82
+ content: [{ type: "text", text: `Error: ${message}` }],
83
+ isError: true
84
+ };
85
+ }
86
+ }
87
+ );
88
+
89
+ server.registerTool(
90
+ "get_pagespeed_insights",
91
+ {
92
+ title: "Get Pagespeed Insights",
93
+ description: "Get PageSpeed Insights",
94
+ inputSchema: {
95
+ url: z.string().describe('URL to analyze')
96
+ },
97
+ annotations: {
98
+ readOnlyHint: true,
99
+ destructiveHint: false,
100
+ openWorldHint: true
101
+ }
102
+ },
103
+ async (args) => {
104
+ try {
105
+ const result = await callApifyTool("get_pagespeed_insights", args);
106
+ return {
107
+ content: [{ type: "text", text: result }]
108
+ };
109
+ } catch (error) {
110
+ const message = error instanceof Error ? error.message : String(error);
111
+ return {
112
+ content: [{ type: "text", text: `Error: ${message}` }],
113
+ isError: true
114
+ };
115
+ }
116
+ }
117
+ );
118
+
119
+ server.registerTool(
120
+ "check_ssl_certificate",
121
+ {
122
+ title: "Check Ssl Certificate",
123
+ description: "Check SSL certificate details",
124
+ inputSchema: {
125
+ domain: z.string().describe('Domain to check')
126
+ },
127
+ annotations: {
128
+ readOnlyHint: true,
129
+ destructiveHint: false,
130
+ openWorldHint: true
131
+ }
132
+ },
133
+ async (args) => {
134
+ try {
135
+ const result = await callApifyTool("check_ssl_certificate", args);
136
+ return {
137
+ content: [{ type: "text", text: result }]
138
+ };
139
+ } catch (error) {
140
+ const message = error instanceof Error ? error.message : String(error);
141
+ return {
142
+ content: [{ type: "text", text: `Error: ${message}` }],
143
+ isError: true
144
+ };
145
+ }
146
+ }
147
+ );
148
+
149
+ server.registerTool(
150
+ "identify_tech_stack",
151
+ {
152
+ title: "Identify Tech Stack",
153
+ description: "Identify website technology stack",
154
+ inputSchema: {
155
+ url: z.string().describe('Website URL')
156
+ },
157
+ annotations: {
158
+ readOnlyHint: true,
159
+ destructiveHint: false,
160
+ openWorldHint: true
161
+ }
162
+ },
163
+ async (args) => {
164
+ try {
165
+ const result = await callApifyTool("identify_tech_stack", args);
166
+ return {
167
+ content: [{ type: "text", text: result }]
168
+ };
169
+ } catch (error) {
170
+ const message = error instanceof Error ? error.message : String(error);
171
+ return {
172
+ content: [{ type: "text", text: `Error: ${message}` }],
173
+ isError: true
174
+ };
175
+ }
176
+ }
177
+ );
178
+
179
+ server.registerTool(
180
+ "query_whois",
181
+ {
182
+ title: "Query Whois",
183
+ description: "Query WHOIS records",
184
+ inputSchema: {
185
+ domain: z.string().describe('Domain to look up')
186
+ },
187
+ annotations: {
188
+ readOnlyHint: true,
189
+ destructiveHint: false,
190
+ openWorldHint: true
191
+ }
192
+ },
193
+ async (args) => {
194
+ try {
195
+ const result = await callApifyTool("query_whois", args);
196
+ return {
197
+ content: [{ type: "text", text: result }]
198
+ };
199
+ } catch (error) {
200
+ const message = error instanceof Error ? error.message : String(error);
201
+ return {
202
+ content: [{ type: "text", text: `Error: ${message}` }],
203
+ isError: true
204
+ };
205
+ }
206
+ }
207
+ );
208
+
209
+ server.registerTool(
210
+ "query_dns_records",
211
+ {
212
+ title: "Query Dns Records",
213
+ description: "Query DNS records",
214
+ inputSchema: {
215
+ domain: z.string().describe('Domain to query'),
216
+ recordType: z.string().optional().describe('DNS record type: A, AAAA, MX, TXT')
217
+ },
218
+ annotations: {
219
+ readOnlyHint: true,
220
+ destructiveHint: false,
221
+ openWorldHint: true
222
+ }
223
+ },
224
+ async (args) => {
225
+ try {
226
+ const result = await callApifyTool("query_dns_records", args);
227
+ return {
228
+ content: [{ type: "text", text: result }]
229
+ };
230
+ } catch (error) {
231
+ const message = error instanceof Error ? error.message : String(error);
232
+ return {
233
+ content: [{ type: "text", text: `Error: ${message}` }],
234
+ isError: true
235
+ };
236
+ }
237
+ }
238
+ );
239
+
240
+
241
+ async function main() {
242
+ const transport = new StdioServerTransport();
243
+ await server.connect(transport);
244
+ console.error("SEO & Web Analysis MCP Server running on stdio");
245
+ }
246
+
247
+ main().catch(console.error);
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true
12
+ },
13
+ "include": [
14
+ "src/**/*"
15
+ ]
16
+ }