@forgemeshlabs/seo-authority-mcp 0.1.1 → 0.1.2
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 +1 -0
- package/dist/client.d.ts +9 -0
- package/dist/client.js +101 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/tools.d.ts +3 -0
- package/dist/tools.js +55 -0
- package/dist/tools.js.map +1 -0
- package/package.json +2 -2
- package/server.json +3 -3
- package/tool_manifest.json +1 -1
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[](https://m8ven.ai/mcp/forgemeshlabs-seo-authority-mcp-9niona)
|
|
4
4
|
|
|
5
5
|
[](https://glama.ai/mcp/servers/forgemeshlabs/seo-authority-mcp)
|
|
6
|
+
[](https://mcpservers.org/servers/forgemeshlabs/seo-authority-mcp)
|
|
6
7
|
|
|
7
8
|
Give AI agents typed SEO authority, keyword, competitor, content, audit, and internal-link tools backed by [seo.forgemesh.io](https://seo.forgemesh.io). Authority scores use open Common Crawl graph data. The server does not invent search volume, CPC, live SERP positions, complete backlinks, or Google metrics.
|
|
8
9
|
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type Args = Record<string, unknown>;
|
|
2
|
+
type RequestSpec = {
|
|
3
|
+
path: string;
|
|
4
|
+
method?: "GET" | "POST";
|
|
5
|
+
body?: Args;
|
|
6
|
+
};
|
|
7
|
+
export declare function requestForTool(name: string, args: Args): RequestSpec;
|
|
8
|
+
export declare function callSeoTool(name: string, args: Args): Promise<unknown>;
|
|
9
|
+
export {};
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { x402Client, x402HTTPClient } from "@x402/core/client";
|
|
2
|
+
import { ExactEvmScheme } from "@x402/evm/exact/client";
|
|
3
|
+
import { toClientEvmSigner } from "@x402/evm";
|
|
4
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
5
|
+
const configuredBase = (process.env.SEO_API_BASE || "https://seo.forgemesh.io").replace(/\/+$/, "");
|
|
6
|
+
const parsedBase = new URL(configuredBase);
|
|
7
|
+
if (parsedBase.protocol !== "https:")
|
|
8
|
+
throw new Error("SEO_API_BASE must use HTTPS");
|
|
9
|
+
if (parsedBase.hostname !== "seo.forgemesh.io" && process.env.SEO_ALLOW_CUSTOM_API_BASE !== "true") {
|
|
10
|
+
throw new Error("Custom SEO_API_BASE requires SEO_ALLOW_CUSTOM_API_BASE=true");
|
|
11
|
+
}
|
|
12
|
+
const API_BASE = parsedBase.toString().replace(/\/$/, "");
|
|
13
|
+
const timeoutMs = Math.max(1_000, Math.min(120_000, Number(process.env.SEO_REQUEST_TIMEOUT_MS || 30_000)));
|
|
14
|
+
const maxAtomic = BigInt(Math.round(Number(process.env.SEO_MAX_PAYMENT_USDC || "0.15") * 1_000_000));
|
|
15
|
+
function encodeQuery(path, params) {
|
|
16
|
+
const url = new URL(path, `${API_BASE}/`);
|
|
17
|
+
for (const [key, value] of Object.entries(params))
|
|
18
|
+
url.searchParams.set(key, value);
|
|
19
|
+
return `${url.pathname}${url.search}`;
|
|
20
|
+
}
|
|
21
|
+
export function requestForTool(name, args) {
|
|
22
|
+
switch (name) {
|
|
23
|
+
case "get_seo_capabilities": return { path: "/capabilities" };
|
|
24
|
+
case "get_domain_authority": return { path: encodeQuery("/v1/domain-authority", { domain: String(args.domain) }) };
|
|
25
|
+
case "compare_domain_authority": return { path: "/v1/authority-compare", method: "POST", body: { domains: args.domains } };
|
|
26
|
+
case "score_page_seo": return { path: "/v1/url-seo-score", method: "POST", body: { url: args.url, html: args.html } };
|
|
27
|
+
case "correct_seo_query": return { path: encodeQuery("/v1/query-correct", { q: String(args.query) }) };
|
|
28
|
+
case "expand_seo_query": return { path: encodeQuery("/v1/query-expand", { q: String(args.query) }) };
|
|
29
|
+
case "find_keyword_opportunities": return { path: "/v1/keyword-opportunity", method: "POST", body: args };
|
|
30
|
+
case "find_competitor_content_gaps": return { path: "/v1/competitor-gap", method: "POST", body: args };
|
|
31
|
+
case "create_seo_content_brief": return { path: "/v1/content-brief", method: "POST", body: args };
|
|
32
|
+
case "audit_site_seo": return { path: "/v1/site-audit", method: "POST", body: args };
|
|
33
|
+
case "find_internal_link_opportunities": return { path: "/v1/internal-link-opportunities", method: "POST", body: args };
|
|
34
|
+
default: throw new Error(`Unknown tool: ${name}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function parseChallenge(response, body) {
|
|
38
|
+
const raw = response.headers.get("payment-required");
|
|
39
|
+
if (!raw)
|
|
40
|
+
return null;
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(Buffer.from(raw, "base64").toString("utf8"));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return body;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function safeChallenge(challenge) {
|
|
49
|
+
const accept = challenge?.accepts?.[0] || {};
|
|
50
|
+
const amount = String(accept.amount || "0");
|
|
51
|
+
return { payment_required: true, amount_usdc: /^\d+$/.test(amount) ? Number(amount) / 1_000_000 : null, network: accept.network || null, pay_to: accept.payTo || null, scheme: accept.scheme || null, resource: challenge?.resource?.url || null, next_step: "Set SEO_X402_PRIVATE_KEY to let this MCP settle automatically, or pay the challenge with another x402 client." };
|
|
52
|
+
}
|
|
53
|
+
function findTx(value) {
|
|
54
|
+
if (typeof value === "string" && /^0x[a-fA-F0-9]{64}$/.test(value))
|
|
55
|
+
return value;
|
|
56
|
+
if (!value || typeof value !== "object")
|
|
57
|
+
return null;
|
|
58
|
+
for (const item of Object.values(value)) {
|
|
59
|
+
const found = findTx(item);
|
|
60
|
+
if (found)
|
|
61
|
+
return found;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
function init(spec, extraHeaders = {}) {
|
|
66
|
+
return { method: spec.method || "GET", headers: { accept: "application/json", ...extraHeaders, ...(spec.body ? { "content-type": "application/json" } : {}) }, ...(spec.body ? { body: JSON.stringify(spec.body) } : {}), signal: AbortSignal.timeout(timeoutMs) };
|
|
67
|
+
}
|
|
68
|
+
export async function callSeoTool(name, args) {
|
|
69
|
+
const spec = requestForTool(name, args);
|
|
70
|
+
const url = `${API_BASE}${spec.path}`;
|
|
71
|
+
const challengeResponse = await fetch(url, init(spec));
|
|
72
|
+
const challengeBody = await challengeResponse.clone().json().catch(() => null);
|
|
73
|
+
if (challengeResponse.status !== 402) {
|
|
74
|
+
if (!challengeResponse.ok)
|
|
75
|
+
throw new Error(`SEO API ${challengeResponse.status}: ${JSON.stringify(challengeBody).slice(0, 300)}`);
|
|
76
|
+
return challengeBody;
|
|
77
|
+
}
|
|
78
|
+
const challenge = parseChallenge(challengeResponse, challengeBody);
|
|
79
|
+
const privateKey = process.env.SEO_X402_PRIVATE_KEY;
|
|
80
|
+
if (!privateKey)
|
|
81
|
+
return safeChallenge(challenge);
|
|
82
|
+
if (!/^0x[a-fA-F0-9]{64}$/.test(privateKey))
|
|
83
|
+
throw new Error("SEO_X402_PRIVATE_KEY must be a 32-byte hex private key");
|
|
84
|
+
const amount = BigInt(challenge?.accepts?.[0]?.amount || 0);
|
|
85
|
+
if (amount <= 0n || amount > maxAtomic)
|
|
86
|
+
throw new Error(`Payment exceeds SEO_MAX_PAYMENT_USDC (${Number(maxAtomic) / 1_000_000})`);
|
|
87
|
+
const account = privateKeyToAccount(privateKey);
|
|
88
|
+
const httpClient = new x402HTTPClient(new x402Client().register("eip155:*", new ExactEvmScheme(toClientEvmSigner(account))));
|
|
89
|
+
const required = httpClient.getPaymentRequiredResponse(header => challengeResponse.headers.get(header), challengeBody);
|
|
90
|
+
const payload = await httpClient.createPaymentPayload(required);
|
|
91
|
+
const paid = await fetch(url, init(spec, httpClient.encodePaymentSignatureHeader(payload)));
|
|
92
|
+
const paidBody = await paid.json().catch(() => null);
|
|
93
|
+
if (!paid.ok)
|
|
94
|
+
throw new Error(`Paid SEO API ${paid.status}: ${JSON.stringify(paidBody).slice(0, 300)}`);
|
|
95
|
+
const receipt = httpClient.getPaymentSettleResponse(header => paid.headers.get(header));
|
|
96
|
+
const transaction = findTx(receipt);
|
|
97
|
+
if (!transaction)
|
|
98
|
+
throw new Error("Facilitator returned no settlement transaction hash");
|
|
99
|
+
return { ...paidBody, x402_settlement: { amount_usdc: Number(amount) / 1_000_000, network: challenge?.accepts?.[0]?.network, transaction } };
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { callSeoTool } from "./client.js";
|
|
6
|
+
import { tools } from "./tools.js";
|
|
7
|
+
const server = new Server({ name: "seo-authority-mcp", version: "0.1.2" }, { capabilities: { tools: {} } });
|
|
8
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
9
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
10
|
+
try {
|
|
11
|
+
const data = await callSeoTool(request.params.name, (request.params.arguments || {}));
|
|
12
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
16
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: { code: "SEO_TOOL_ERROR", message } }) }], isError: true };
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
await server.connect(new StdioServerTransport());
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AACnG,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;AAC5G,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AAC1E,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAC,OAAO,EAAC,EAAE;IAC9D,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAA4B,CAAC,CAAC;QACjH,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IACrE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;QACzE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC9H,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC"}
|
package/dist/tools.d.ts
ADDED
package/dist/tools.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const readOnly = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
|
|
2
|
+
const string = (description) => ({ type: "string", description });
|
|
3
|
+
const page = {
|
|
4
|
+
type: "object",
|
|
5
|
+
properties: { url: string("Absolute page URL."), title: string("Page title."), text: string("Visible page text."), html: string("Page HTML, when required.") },
|
|
6
|
+
required: ["url"]
|
|
7
|
+
};
|
|
8
|
+
export const tools = [
|
|
9
|
+
{ name: "get_seo_capabilities", description: "Retrieve free capability, provenance, and unavailable-metric disclosures before choosing a paid SEO tool.", annotations: readOnly, inputSchema: { type: "object", properties: {} } },
|
|
10
|
+
{ name: "get_domain_authority", description: "Measure one domain's open-web SEO authority using Common Crawl harmonic centrality and PageRank graph data; costs $0.01 USDC.", annotations: readOnly, inputSchema: { type: "object", properties: { domain: string("Domain or URL to measure, such as example.com.") }, required: ["domain"] } },
|
|
11
|
+
{ name: "compare_domain_authority", description: "Compare open-web SEO authority for 2 to 20 domains and rank the results; costs $0.02 USDC.", annotations: readOnly, inputSchema: { type: "object", properties: { domains: { type: "array", description: "Two to twenty domains or URLs.", minItems: 2, maxItems: 20, items: { type: "string" } } }, required: ["domains"] } },
|
|
12
|
+
{ name: "score_page_seo", description: "Score supplied page HTML for deterministic on-page SEO checks without claiming page authority; costs $0.01 USDC.", annotations: readOnly, inputSchema: { type: "object", properties: { url: string("Canonical URL for the supplied page."), html: string("Complete page HTML, up to 512 KB.") }, required: ["url", "html"] } },
|
|
13
|
+
{ name: "correct_seo_query", description: "Correct likely spelling errors in an SEO search query; costs $0.005 USDC.", annotations: readOnly, inputSchema: { type: "object", properties: { query: string("SEO query to correct.") }, required: ["query"] } },
|
|
14
|
+
{ name: "expand_seo_query", description: "Generate deterministic long-tail and intent-oriented variants of an SEO query; costs $0.01 USDC.", annotations: readOnly, inputSchema: { type: "object", properties: { query: string("Seed SEO query to expand.") }, required: ["query"] } },
|
|
15
|
+
{ name: "find_keyword_opportunities", description: "Generate and prioritize long-tail keyword opportunities without fabricating volume, CPC, SERP, or difficulty metrics; costs $0.05 USDC.", annotations: readOnly, inputSchema: { type: "object", properties: { seed: string("Seed topic or keyword."), audience: string("Optional target audience."), domain_authority_score: { type: "number", minimum: 0, maximum: 100, description: "Optional known authority score used for prioritization." } }, required: ["seed"] } },
|
|
16
|
+
{
|
|
17
|
+
name: "find_competitor_content_gaps",
|
|
18
|
+
description: "Find content gaps from customer-supplied site and competitor pages without claiming live SERP verification; costs $0.10 USDC.",
|
|
19
|
+
annotations: readOnly,
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: {
|
|
23
|
+
site: {
|
|
24
|
+
type: "object",
|
|
25
|
+
description: "Your domain and supplied pages.",
|
|
26
|
+
properties: {
|
|
27
|
+
domain: string("Your domain."),
|
|
28
|
+
pages: { type: "array", minItems: 1, maxItems: 50, items: page }
|
|
29
|
+
},
|
|
30
|
+
required: ["domain", "pages"]
|
|
31
|
+
},
|
|
32
|
+
competitors: {
|
|
33
|
+
type: "array",
|
|
34
|
+
description: "One to ten competitors with supplied pages.",
|
|
35
|
+
minItems: 1,
|
|
36
|
+
maxItems: 10,
|
|
37
|
+
items: {
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: {
|
|
40
|
+
domain: string("Competitor domain."),
|
|
41
|
+
pages: { type: "array", minItems: 1, maxItems: 50, items: page }
|
|
42
|
+
},
|
|
43
|
+
required: ["domain", "pages"]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
required: ["site", "competitors"]
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
{ name: "create_seo_content_brief", description: "Create an evidence-tagged SEO content brief from a keyword and supplied competitor or site pages; costs $0.10 USDC.", annotations: readOnly, inputSchema: { type: "object", properties: { keyword: string("Primary keyword or topic."), competitor_pages: { type: "array", maxItems: 50, items: page, description: "Optional competitor pages providing evidence." }, site_pages: { type: "array", maxItems: 50, items: page, description: "Optional existing site pages for differentiation and linking." } }, required: ["keyword"] } },
|
|
51
|
+
{ name: "audit_site_seo", description: "Audit supplied page HTML for on-page, duplication, and internal-link problems; costs $0.15 USDC.", annotations: readOnly, inputSchema: { type: "object", properties: { pages: { type: "array", minItems: 1, maxItems: 50, description: "Pages to audit; each requires url and html.", items: { ...page, required: ["url", "html"] } } }, required: ["pages"] } },
|
|
52
|
+
{ name: "find_internal_link_opportunities", description: "Find missing contextually relevant internal links across supplied pages; costs $0.05 USDC.", annotations: readOnly, inputSchema: { type: "object", properties: { domain: string("Site domain."), pages: { type: "array", minItems: 2, maxItems: 50, items: page, description: "Two or more site pages with title and text for best results." } }, required: ["domain", "pages"] } }
|
|
53
|
+
];
|
|
54
|
+
export const paidToolNames = new Set(tools.slice(1).map(tool => tool.name));
|
|
55
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgemeshlabs/seo-authority-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"mcpName": "io.github.forgemeshlabs/seo-authority-mcp",
|
|
5
5
|
"description": "SEO authority, keyword opportunities, competitor gaps, content briefs, site audits, and internal-link intelligence via x402.",
|
|
6
6
|
"type": "module",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^22.10.0",
|
|
38
38
|
"typescript": "^5.7.3",
|
|
39
|
-
"vitest": "^4.1.
|
|
39
|
+
"vitest": "^4.1.11"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"mcp",
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/forgemeshlabs/seo-authority-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@forgemeshlabs/seo-authority-mcp",
|
|
14
|
-
"version": "0.1.
|
|
14
|
+
"version": "0.1.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
|
@@ -49,4 +49,4 @@
|
|
|
49
49
|
]
|
|
50
50
|
}
|
|
51
51
|
]
|
|
52
|
-
}
|
|
52
|
+
}
|