@operstack/mcp 0.1.0 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@operstack/mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server that lets Claude, Cursor and any MCP client measure a site the way a search engine and an AI read it: AI visibility score, llms.txt health, the sixteen content gates, and a full public-signal audit. Free data only, no account, nothing stored.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@modelcontextprotocol/sdk": "^1.0.0",
43
- "@operstack/audit": "^0.13.1",
43
+ "@operstack/audit": "^0.23.0",
44
44
  "zod": "^4.6.2"
45
45
  }
46
46
  }
package/src/server.mjs CHANGED
@@ -22,7 +22,7 @@ import { existsSync } from 'node:fs';
22
22
  import { collect, localiseChecks, AREAS_RU } from '@operstack/audit';
23
23
  import { checkLlms, normaliseSite } from './llms.mjs';
24
24
 
25
- export const VERSION = '0.1.0';
25
+ export const VERSION = '0.2.0';
26
26
 
27
27
  const text = (value) => ({ content: [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }] });
28
28
  const fail = (message) => ({ content: [{ type: 'text', text: message }], isError: true });
@@ -33,10 +33,23 @@ function summarise(audit, lang) {
33
33
  const checks = ru ? localiseChecks(audit.checks || [], 'ru') : (audit.checks || []);
34
34
  const areaName = (k) => (ru ? (AREAS_RU[k] || k) : k);
35
35
  const open = checks.filter((c) => c.status === 'bad' || c.status === 'warn');
36
+ /*
37
+ * Главный балл в ответе обязателен.
38
+ *
39
+ * До 16.09.2026 здесь были только шесть областей из десяти, а число 0-100 отсутствовало. Это
40
+ * то самое число, которое человек видит на нашей странице и в удалённом MCP, и без него он не
41
+ * мог сопоставить ответ инструмента с тем, что показывает сайт. Число то же самое: аудит берёт
42
+ * его из той же проверки, проверено на шести живых сайтах, расхождений ноль.
43
+ */
44
+ const overall = typeof audit.overall === 'object' ? audit.overall?.score : audit.overall;
36
45
  return {
37
46
  site: audit.meta?.site,
38
47
  reachable: audit.meta?.reachable !== false,
39
48
  collectedAt: audit.meta?.collectedAt,
49
+ score: overall ?? (ru ? 'не измерялось' : 'not measured'),
50
+ scoreMeans: ru
51
+ ? 'Это тот же балл от 0 до 100, что показывает бесплатная проверка на oper-stack.ru. Шесть областей ниже это разбор того же сайта по другим вопросам, каждая из десяти.'
52
+ : 'This is the same 0 to 100 score the free check on oper-stack.com shows. The six areas below look at the same site from other angles, each out of ten.',
40
53
  pagesSampled: (audit.sample || []).filter((p) => p.title !== undefined).length,
41
54
  scores: Object.fromEntries(Object.entries(audit.scores || {}).map(([k, v]) => [areaName(k), v ?? (ru ? 'не измерялось' : 'not measured')])),
42
55
  failing: checks.filter((c) => c.status === 'bad').map((c) => ({ check: c.label, found: c.value })),
@@ -89,7 +102,12 @@ export function buildServer() {
89
102
  },
90
103
  },
91
104
  async ({ url, maxLinks = 20 }) => {
92
- try { return text(await checkLlms(url, { maxLinks })); }
105
+ // Адрес приводим к корню, как и в остальных инструментах. Без этого «oper-stack.com»,
106
+ // адрес с путём и даже прямая ссылка на сам файл давали «llms.txt не найден» на сайте,
107
+ // где файл есть. Человек пишет адрес как придётся, и это наша работа, а не его.
108
+ let root;
109
+ try { root = normaliseSite(url); } catch (e) { return fail(e.message); }
110
+ try { return text(await checkLlms(root, { maxLinks })); }
93
111
  catch (e) { return fail(e.message); }
94
112
  },
95
113
  );
@@ -7,8 +7,10 @@
7
7
  * npm test
8
8
  * npm test -- --live ещё и сходить на живой сайт (медленнее, нужна сеть)
9
9
  */
10
+ import { normaliseSite } from './llms.mjs';
10
11
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
11
12
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
13
+ import { readFileSync } from 'node:fs';
12
14
  import { resolve, dirname } from 'node:path';
13
15
  import { fileURLToPath } from 'node:url';
14
16
 
@@ -67,6 +69,26 @@ async function main() {
67
69
  }
68
70
 
69
71
  await client.close();
72
+ // ---- главный балл в ответе
73
+ // До 16.09.2026 устанавливаемый пакет отдавал шесть областей из десяти и ни разу не называл
74
+ // число 0-100, то самое, что человек видит на нашей странице. Сопоставить одно с другим было
75
+ // нельзя. Число берётся из той же проверки, расхождений нет: сверено на шести живых сайтах.
76
+ const serverSrc = readFileSync(resolve(ROOT, 'src/server.mjs'), 'utf8');
77
+ ok('ответ audit_site называет главный балл', /score: overall \?\?/.test(serverSrc));
78
+ ok('и объясняет, что это тот же балл, что на сайте', /scoreMeans:/.test(serverSrc)
79
+ && /бесплатная проверка на oper-stack\.ru/.test(serverSrc)
80
+ && /free check on oper-stack\.com/.test(serverSrc));
81
+
82
+ // ---- адрес, написанный по-человечески
83
+ // 16.09.2026 живой пользователь сообщил, что check_llms_txt отвечает «файла нет» на сайте, где
84
+ // файл есть: для этого инструмента адрес не приводился к корню, хотя для остальных приводился.
85
+ // Люди пишут адрес как придётся, и разобрать написание это наша работа, а не их.
86
+ for (const raw of ['oper-stack.com', 'www.oper-stack.com', 'https://oper-stack.com/products/gates/',
87
+ 'https://oper-stack.com/llms.txt', 'HTTPS://Oper-Stack.com']) {
88
+ const root = normaliseSite(raw);
89
+ ok(`адрес приводится к корню: ${raw}`, root === 'https://oper-stack.com' || root === 'https://www.oper-stack.com', root);
90
+ }
91
+
70
92
  if (failed) { console.error(`\n${failed} проверок упало`); process.exit(1); }
71
93
  console.log('\nсервер отвечает как положено');
72
94
  }