@frase/mcp-server 0.3.7 → 0.3.8
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/dist/lib/clean-render.d.ts +42 -0
- package/dist/lib/clean-render.d.ts.map +1 -0
- package/dist/lib/clean-render.js +126 -0
- package/dist/lib/clean-render.js.map +1 -0
- package/dist/tools/index.d.ts +7 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +16 -2
- package/dist/tools/index.js.map +1 -1
- package/package.json +4 -3
- package/server.json +2 -2
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ToolExecutor } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Detects "the tool ran successfully but shipped garbage" — the class of bug
|
|
4
|
+
* where an object is coerced into markdown as `[object Object]`, or a leaked
|
|
5
|
+
* `undefined`/`NaN` lands in rendered prose. (Real example: People Also Ask
|
|
6
|
+
* entries rendered as `[object Object]` to every user, found only via a
|
|
7
|
+
* customer report.)
|
|
8
|
+
*
|
|
9
|
+
* In tests/CI a dirty render throws (pre-merge gate). In real traffic it never
|
|
10
|
+
* throws — it emits a structured warning and returns the result unchanged.
|
|
11
|
+
*/
|
|
12
|
+
export interface RenderIssue {
|
|
13
|
+
/** The offending token, e.g. "[object Object]", "undefined", "empty-table". */
|
|
14
|
+
pattern: string;
|
|
15
|
+
/** A short slice of surrounding text, for the warning/error message. */
|
|
16
|
+
snippet: string;
|
|
17
|
+
}
|
|
18
|
+
export interface AssertCleanRenderOpts {
|
|
19
|
+
/**
|
|
20
|
+
* Set for `success: false` results — only the `[object …]` check runs, since
|
|
21
|
+
* error messages may legitimately mention "undefined"/"NaN".
|
|
22
|
+
*/
|
|
23
|
+
errorResult?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare class CleanRenderError extends Error {
|
|
26
|
+
readonly toolName: string;
|
|
27
|
+
readonly issues: RenderIssue[];
|
|
28
|
+
constructor(toolName: string, issues: RenderIssue[]);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Returns the render defects found in `markdown` (empty array = clean). Pure;
|
|
32
|
+
* conservative by design — tuned to catch the real bug class with near-zero
|
|
33
|
+
* false positives on legitimate empty-state output.
|
|
34
|
+
*/
|
|
35
|
+
export declare function assertCleanRender(markdown: string, opts?: AssertCleanRenderOpts): RenderIssue[];
|
|
36
|
+
/**
|
|
37
|
+
* Wraps a tool executor so its rendered markdown is validated on every call.
|
|
38
|
+
* Applied at the `TOOL_EXECUTORS` chokepoint, so it covers every tool on both
|
|
39
|
+
* the stdio and in-app HTTP surfaces. Never alters the result.
|
|
40
|
+
*/
|
|
41
|
+
export declare function withCleanRender(toolName: string, executor: ToolExecutor): ToolExecutor;
|
|
42
|
+
//# sourceMappingURL=clean-render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clean-render.d.ts","sourceRoot":"","sources":["../../src/lib/clean-render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;;GASG;AAEH,MAAM,WAAW,WAAW;IAC1B,+EAA+E;IAC/E,OAAO,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAgBD,qBAAa,gBAAiB,SAAQ,KAAK;aAEvB,QAAQ,EAAE,MAAM;aAChB,MAAM,EAAE,WAAW,EAAE;gBADrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,WAAW,EAAE;CAQxC;AAyBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,qBAA0B,GAC/B,WAAW,EAAE,CA0Df;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,YAAY,GACrB,YAAY,CAmBd"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throw on a dirty render instead of warning. Tests, CI, and the scheduled
|
|
3
|
+
* synthetic smoke opt in; real stdio (customer machine) and in-app HTTP traffic
|
|
4
|
+
* never throw — a bad render must not break a live tool call. Evaluated per
|
|
5
|
+
* call so the environment can be toggled (e.g. in tests).
|
|
6
|
+
*/
|
|
7
|
+
function isRenderStrict() {
|
|
8
|
+
return (!!process.env.VITEST ||
|
|
9
|
+
process.env.NODE_ENV === "test" ||
|
|
10
|
+
process.env.FRASE_RENDER_STRICT === "1");
|
|
11
|
+
}
|
|
12
|
+
export class CleanRenderError extends Error {
|
|
13
|
+
toolName;
|
|
14
|
+
issues;
|
|
15
|
+
constructor(toolName, issues) {
|
|
16
|
+
super(`Tool "${toolName}" produced a dirty render: ` +
|
|
17
|
+
issues.map((i) => `${i.pattern} (…${i.snippet}…)`).join("; "));
|
|
18
|
+
this.toolName = toolName;
|
|
19
|
+
this.issues = issues;
|
|
20
|
+
this.name = "CleanRenderError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Blank out spans where these tokens are legitimate so they don't trip the
|
|
25
|
+
* scanner: fenced code blocks, inline code, and italic placeholders (the
|
|
26
|
+
* `_No results found._` empty-state convention, which `hidden: true` branches
|
|
27
|
+
* also use). Replaced with a space to preserve word boundaries around them.
|
|
28
|
+
*/
|
|
29
|
+
function stripSafeSpans(md) {
|
|
30
|
+
return md
|
|
31
|
+
.replace(/```[\s\S]*?```/g, " ")
|
|
32
|
+
.replace(/`[^`]*`/g, " ")
|
|
33
|
+
// NOTE: greedy `_…_` blanking can also blank a snake_case span in prose
|
|
34
|
+
// (e.g. `field_undefined_value`), which could mask a token sitting between
|
|
35
|
+
// two snake_case words. That's a false-negative only (we'd miss a defect),
|
|
36
|
+
// never a false-positive — acceptable for this conservative validator.
|
|
37
|
+
.replace(/_[^_]+_/g, " ");
|
|
38
|
+
}
|
|
39
|
+
function snippetAround(text, index, len) {
|
|
40
|
+
const start = Math.max(0, index - 30);
|
|
41
|
+
const end = Math.min(text.length, index + len + 30);
|
|
42
|
+
return text.slice(start, end).replace(/\s+/g, " ").trim();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Returns the render defects found in `markdown` (empty array = clean). Pure;
|
|
46
|
+
* conservative by design — tuned to catch the real bug class with near-zero
|
|
47
|
+
* false positives on legitimate empty-state output.
|
|
48
|
+
*/
|
|
49
|
+
export function assertCleanRender(markdown, opts = {}) {
|
|
50
|
+
const issues = [];
|
|
51
|
+
if (!markdown)
|
|
52
|
+
return issues;
|
|
53
|
+
// A coerced object is always a bug — even inside code or error messages.
|
|
54
|
+
const objMatch = markdown.match(/\[object \w+\]/);
|
|
55
|
+
if (objMatch) {
|
|
56
|
+
issues.push({
|
|
57
|
+
pattern: objMatch[0],
|
|
58
|
+
snippet: snippetAround(markdown, objMatch.index ?? 0, objMatch[0].length),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
// Error results legitimately mention "undefined"/"NaN"; stop after the
|
|
62
|
+
// object check above.
|
|
63
|
+
if (opts.errorResult)
|
|
64
|
+
return issues;
|
|
65
|
+
const stripped = stripSafeSpans(markdown);
|
|
66
|
+
// Leaked `undefined`/`NaN` — but ONLY in a formatted-value position, never in
|
|
67
|
+
// free prose. Many tools pass verbatim user/LLM text through (article bodies,
|
|
68
|
+
// brief sections, descriptions) that legitimately contains the words
|
|
69
|
+
// "undefined"/"NaN"; scanning prose would false-positive and, in strict mode,
|
|
70
|
+
// throw on a valid tool call. So we anchor to where the formatter emits a
|
|
71
|
+
// value: a table cell, a bold "key:" value, a colon-terminated heading/label,
|
|
72
|
+
// or a bare list item. Case-sensitive — JS coercion yields exactly these.
|
|
73
|
+
// `null` is deliberately not checked (pervasive in legit JSON/prose).
|
|
74
|
+
const SCAFFOLD_TOKEN_RES = [
|
|
75
|
+
/\|\s*(undefined|NaN)\s*(?=\|)/g, // | undefined | (table cell)
|
|
76
|
+
/:\*\*\s*(undefined|NaN)\b/g, // **Key:** undefined
|
|
77
|
+
/:[^\S\n]*(undefined|NaN)[^\S\n]*$/gm, // ## Heading: undefined / Label: undefined
|
|
78
|
+
/^[^\S\n]*[-*][^\S\n]+(undefined|NaN)[^\S\n]*$/gm, // - undefined (bare list value)
|
|
79
|
+
];
|
|
80
|
+
for (const re of SCAFFOLD_TOKEN_RES) {
|
|
81
|
+
let m;
|
|
82
|
+
while ((m = re.exec(stripped)) !== null) {
|
|
83
|
+
issues.push({
|
|
84
|
+
pattern: m[1],
|
|
85
|
+
snippet: snippetAround(stripped, m.index, m[0].length),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// A markdown table separator row with no data row after it. `formatTable()`
|
|
90
|
+
// guards this, but a hand-rolled table can still emit an empty grid.
|
|
91
|
+
const lines = stripped.split("\n");
|
|
92
|
+
const separatorRe = /^\s*\|(?:\s*:?-+:?\s*\|)+\s*$/;
|
|
93
|
+
for (let i = 0; i < lines.length; i++) {
|
|
94
|
+
if (separatorRe.test(lines[i])) {
|
|
95
|
+
const next = lines[i + 1];
|
|
96
|
+
const nextIsRow = next !== undefined && /^\s*\|/.test(next);
|
|
97
|
+
if (!nextIsRow) {
|
|
98
|
+
issues.push({ pattern: "empty-table", snippet: lines[i].trim() });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return issues;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Wraps a tool executor so its rendered markdown is validated on every call.
|
|
106
|
+
* Applied at the `TOOL_EXECUTORS` chokepoint, so it covers every tool on both
|
|
107
|
+
* the stdio and in-app HTTP surfaces. Never alters the result.
|
|
108
|
+
*/
|
|
109
|
+
export function withCleanRender(toolName, executor) {
|
|
110
|
+
return async (input, context) => {
|
|
111
|
+
const result = await executor(input, context);
|
|
112
|
+
const issues = assertCleanRender(result.markdown ?? "", {
|
|
113
|
+
errorResult: !result.success,
|
|
114
|
+
});
|
|
115
|
+
if (issues.length > 0) {
|
|
116
|
+
if (isRenderStrict()) {
|
|
117
|
+
throw new CleanRenderError(toolName, issues);
|
|
118
|
+
}
|
|
119
|
+
// Structured warning → Sentry Logs via consoleLoggingIntegration on the
|
|
120
|
+
// in-app surface; visible in stderr on the stdio surface.
|
|
121
|
+
console.warn("mcp_dirty_render", JSON.stringify({ tool: toolName, issues }));
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=clean-render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clean-render.js","sourceRoot":"","sources":["../../src/lib/clean-render.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -11,7 +11,13 @@ import type { McpToolDefinition, ToolContext, ToolResult } from "../types.js";
|
|
|
11
11
|
*/
|
|
12
12
|
export declare const ALL_TOOLS: McpToolDefinition[];
|
|
13
13
|
/**
|
|
14
|
-
* Tool executor mapping
|
|
14
|
+
* Tool executor mapping.
|
|
15
|
+
*
|
|
16
|
+
* Every executor is wrapped with `withCleanRender` so a malformed render (an
|
|
17
|
+
* object coerced to `[object Object]`, a leaked `undefined`/`NaN`) is caught in
|
|
18
|
+
* tests and recorded in prod instead of silently shipping to users. This is the
|
|
19
|
+
* single chokepoint both MCP surfaces dispatch through — the stdio server and
|
|
20
|
+
* the in-app HTTP endpoint — so wrapping here instruments every tool on both.
|
|
15
21
|
*/
|
|
16
22
|
export declare const TOOL_EXECUTORS: Record<string, (input: unknown, context: ToolContext) => Promise<ToolResult>>;
|
|
17
23
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAwC9E;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,iBAAiB,EAwExC,CAAC;AAkFF;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CACjC,MAAM,EACN,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,UAAU,CAAC,CAM9D,CAAC;AAEF;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAEzE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,GACX,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,SAAS,CAE7E;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC"}
|
package/dist/tools/index.js
CHANGED
|
@@ -33,6 +33,7 @@ import { cmsPostTools, cmsPostExecutors } from "./cms-posts.js";
|
|
|
33
33
|
import { commentTools, commentExecutors } from "./comments.js";
|
|
34
34
|
// Dynamic tool discovery (MCP gateway pattern)
|
|
35
35
|
import { discoverToolsTool, executeDiscoverTools } from "./discover.js";
|
|
36
|
+
import { withCleanRender } from "../lib/clean-render.js";
|
|
36
37
|
// ============================================
|
|
37
38
|
// Tool Registry
|
|
38
39
|
// ============================================
|
|
@@ -90,9 +91,9 @@ export const ALL_TOOLS = [
|
|
|
90
91
|
discoverToolsTool,
|
|
91
92
|
];
|
|
92
93
|
/**
|
|
93
|
-
*
|
|
94
|
+
* Raw executor mapping, before instrumentation.
|
|
94
95
|
*/
|
|
95
|
-
|
|
96
|
+
const RAW_TOOL_EXECUTORS = {
|
|
96
97
|
// Sites (Phase 1)
|
|
97
98
|
...siteExecutors,
|
|
98
99
|
// Briefs (Phase 2)
|
|
@@ -142,6 +143,19 @@ export const TOOL_EXECUTORS = {
|
|
|
142
143
|
// Dynamic Tool Discovery (Gateway Pattern)
|
|
143
144
|
discover_tools: executeDiscoverTools,
|
|
144
145
|
};
|
|
146
|
+
/**
|
|
147
|
+
* Tool executor mapping.
|
|
148
|
+
*
|
|
149
|
+
* Every executor is wrapped with `withCleanRender` so a malformed render (an
|
|
150
|
+
* object coerced to `[object Object]`, a leaked `undefined`/`NaN`) is caught in
|
|
151
|
+
* tests and recorded in prod instead of silently shipping to users. This is the
|
|
152
|
+
* single chokepoint both MCP surfaces dispatch through — the stdio server and
|
|
153
|
+
* the in-app HTTP endpoint — so wrapping here instruments every tool on both.
|
|
154
|
+
*/
|
|
155
|
+
export const TOOL_EXECUTORS = Object.fromEntries(Object.entries(RAW_TOOL_EXECUTORS).map(([name, exec]) => [
|
|
156
|
+
name,
|
|
157
|
+
withCleanRender(name, exec),
|
|
158
|
+
]));
|
|
145
159
|
/**
|
|
146
160
|
* Get tool definition by name
|
|
147
161
|
*/
|
package/dist/tools/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frase/mcp-server",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"description": "Frase MCP Server — The complete content platform for AI agents. Research, write, optimize, and host SEO & GEO-optimized content with built-in AI search visibility monitoring. Includes Frase CMS for auto-optimized web hosting, 95 tools, 9 agent skills, and 7 workflows. The only MCP server that creates, optimizes, publishes, AND monitors content across Google and AI search engines (ChatGPT, Perplexity, Claude, Gemini, AI Overviews). Works with Claude Desktop, Claude Code, Cursor, Windsurf, VS Code Copilot, Lovable, Replit, and any MCP-compatible tool.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@anthropic-ai/mcpb": "^2.1.2",
|
|
56
56
|
"@types/node": "^22.10.2",
|
|
57
|
-
"esbuild": "^0.
|
|
57
|
+
"esbuild": "^0.28.1",
|
|
58
58
|
"tsx": "^4.19.2",
|
|
59
59
|
"typescript": "^5.7.2",
|
|
60
60
|
"vitest": "^4.1.2"
|
|
@@ -63,7 +63,8 @@
|
|
|
63
63
|
"hono": "^4.12.18",
|
|
64
64
|
"@hono/node-server": "^1.19.13",
|
|
65
65
|
"fast-uri": "^3.1.2",
|
|
66
|
-
"tmp": "^0.2.7"
|
|
66
|
+
"tmp": "^0.2.7",
|
|
67
|
+
"esbuild": "^0.28.1"
|
|
67
68
|
},
|
|
68
69
|
"engines": {
|
|
69
70
|
"node": ">=20.0.0"
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.frase/mcp-server",
|
|
4
4
|
"title": "Frase SEO",
|
|
5
5
|
"description": "SEO, GEO & AI Visibility — research, write, optimize, publish & monitor content. 95 tools.",
|
|
6
|
-
"version": "0.3.
|
|
6
|
+
"version": "0.3.8",
|
|
7
7
|
"websiteUrl": "https://frase.io/agents",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/frase-io/mcp-server.git",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
{
|
|
14
14
|
"registryType": "npm",
|
|
15
15
|
"identifier": "@frase/mcp-server",
|
|
16
|
-
"version": "0.3.
|
|
16
|
+
"version": "0.3.8",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
|
19
19
|
},
|