seo-gravity-mcp 1.0.0 → 1.0.1

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.
Files changed (2) hide show
  1. package/dist/utils/nlp.js +42 -7
  2. package/package.json +1 -4
package/dist/utils/nlp.js CHANGED
@@ -1,5 +1,40 @@
1
- import natural from 'natural';
2
- const tokenizer = new natural.WordTokenizer();
1
+ // Native lightweight word tokenizer
2
+ function tokenize(text) {
3
+ return text.match(/[a-zA-Z0-9'-]+/g) || [];
4
+ }
5
+ // Built-in TF-IDF implementation without heavy external dependencies
6
+ class SimpleTfIdf {
7
+ docs = [];
8
+ addDocument(doc) {
9
+ const rawTokens = tokenize(doc.toLowerCase());
10
+ const tokens = rawTokens.filter(t => t.length >= 3 && !stopwords.has(t) && !/^\d+$/.test(t));
11
+ this.docs.push(tokens);
12
+ }
13
+ listTerms(docIndex) {
14
+ if (docIndex < 0 || docIndex >= this.docs.length)
15
+ return [];
16
+ const docTokens = this.docs[docIndex];
17
+ if (docTokens.length === 0)
18
+ return [];
19
+ const totalDocs = this.docs.length;
20
+ const termCounts = new Map();
21
+ for (const t of docTokens) {
22
+ termCounts.set(t, (termCounts.get(t) || 0) + 1);
23
+ }
24
+ const results = [];
25
+ for (const [term, count] of termCounts.entries()) {
26
+ const tf = count / docTokens.length;
27
+ let docFreq = 0;
28
+ for (const d of this.docs) {
29
+ if (d.includes(term))
30
+ docFreq++;
31
+ }
32
+ const idf = Math.log((1 + totalDocs) / (1 + docFreq)) + 1;
33
+ results.push({ term, tfidf: tf * idf * 10 });
34
+ }
35
+ return results.sort((a, b) => b.tfidf - a.tfidf);
36
+ }
37
+ }
3
38
  const stopwords = new Set([
4
39
  'a', 'about', 'above', 'after', 'again', 'against', 'all', 'am', 'an', 'and', 'any', 'are', 'aren\'t', 'as', 'at',
5
40
  'be', 'because', 'been', 'before', 'being', 'below', 'between', 'both', 'but', 'by', 'can', 'can\'t', 'cannot',
@@ -21,7 +56,7 @@ const stopwords = new Set([
21
56
  */
22
57
  export function extractKeyphrases(text, maxItems = 30) {
23
58
  const clean = text.toLowerCase().replace(/[^a-z0-9\s-]/g, ' ');
24
- const rawTokens = tokenizer.tokenize(clean) || [];
59
+ const rawTokens = tokenize(clean);
25
60
  const tokens = rawTokens.filter(t => t.length > 2 && !stopwords.has(t) && !/^\d+$/.test(t));
26
61
  const totalTokens = tokens.length || 1;
27
62
  const phraseCounts = new Map();
@@ -54,7 +89,7 @@ export function extractKeyphrases(text, maxItems = 30) {
54
89
  * Computes TF-IDF between target text and a set of competitor texts to detect content gaps.
55
90
  */
56
91
  export function computeContentGapTfIdf(targetText, competitorTexts) {
57
- const tfidf = new natural.TfIdf();
92
+ const tfidf = new SimpleTfIdf();
58
93
  // Doc 0 is target text
59
94
  tfidf.addDocument(targetText);
60
95
  // Docs 1..N are competitors
@@ -75,7 +110,7 @@ export function computeContentGapTfIdf(targetText, competitorTexts) {
75
110
  });
76
111
  }
77
112
  // Find target frequencies
78
- const targetTokens = (tokenizer.tokenize(targetText.toLowerCase()) || []).filter(t => !stopwords.has(t));
113
+ const targetTokens = tokenize(targetText.toLowerCase()).filter(t => !stopwords.has(t));
79
114
  const targetTokenCounts = new Map();
80
115
  for (const t of targetTokens) {
81
116
  targetTokenCounts.set(t, (targetTokenCounts.get(t) || 0) + 1);
@@ -104,7 +139,7 @@ export function computeContentGapTfIdf(targetText, competitorTexts) {
104
139
  export function calculateReadability(text) {
105
140
  const clean = text.replace(/\s+/g, ' ').trim();
106
141
  const sentences = clean.split(/[.!?]+/).filter(s => s.trim().length > 0);
107
- const words = (tokenizer.tokenize(clean) || []).filter(w => /[a-zA-Z]/.test(w));
142
+ const words = tokenize(clean).filter(w => /[a-zA-Z]/.test(w));
108
143
  const numSentences = Math.max(sentences.length, 1);
109
144
  const numWords = Math.max(words.length, 1);
110
145
  let numSyllables = 0;
@@ -129,7 +164,7 @@ export function calculateReadability(text) {
129
164
  // Long sentences (> 25 words)
130
165
  const longSentences = sentences
131
166
  .map(s => s.trim())
132
- .filter(s => (tokenizer.tokenize(s) || []).length > 25);
167
+ .filter(s => tokenize(s).length > 25);
133
168
  return {
134
169
  fleschReadingEase: Math.max(0, Math.min(100, fleschReadingEase)),
135
170
  gradeLevel,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seo-gravity-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Comprehensive Next-Gen SEO, GEO & Competitor Intelligence MCP Server for Antigravity",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -32,15 +32,12 @@
32
32
  "@modelcontextprotocol/sdk": "^1.6.1",
33
33
  "axios": "^1.7.9",
34
34
  "cheerio": "^1.0.0",
35
- "dotenv": "^16.4.7",
36
35
  "fast-xml-parser": "^5.0.8",
37
36
  "jsdom": "^26.0.0",
38
- "natural": "^8.0.1",
39
37
  "zod": "^3.24.2"
40
38
  },
41
39
  "devDependencies": {
42
40
  "@types/jsdom": "^21.1.7",
43
- "@types/natural": "^5.1.5",
44
41
  "@types/node": "^22.13.5",
45
42
  "ts-node": "^10.9.2",
46
43
  "typescript": "^5.7.3"