@svelte-vitals/mcp 0.1.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/LICENSE.md +21 -0
- package/README.md +25 -0
- package/dist/bin.js +16 -0
- package/dist/chunk-Y3ZXSYVB.js +104 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +14 -0
- package/package.json +51 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kazuma Oe
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @svelte-vitals/mcp
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) server for [svelte-vitals](https://github.com/oekazuma/svelte-vitals). Lets an AI agent run SvelteKit SEO analysis inside its tool loop and receive structured, fixable findings.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
- **`analyze`** — run static-mode analysis on a project path; returns per-route and site-wide scores plus findings with `fix`/`recommendation`/`docsUrl`. Inputs: `path?`, `metaComponents?`, `route?`, `treatDynamicAs?`, `rules?`, `ignore?`, `failOn?`.
|
|
8
|
+
- **`explain_rule`** — given a rule id (e.g. `SEO001`), returns its title, category, severity, rationale, docs URL, and fix template.
|
|
9
|
+
|
|
10
|
+
## Usage (stdio)
|
|
11
|
+
|
|
12
|
+
Add to your MCP client config:
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"mcpServers": {
|
|
17
|
+
"svelte-vitals": {
|
|
18
|
+
"command": "npx",
|
|
19
|
+
"args": ["-y", "@svelte-vitals/mcp"]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
ESM-only. Requires Node 18+.
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createServer
|
|
4
|
+
} from "./chunk-Y3ZXSYVB.js";
|
|
5
|
+
|
|
6
|
+
// src/bin.ts
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
async function main() {
|
|
9
|
+
const server = createServer();
|
|
10
|
+
const transport = new StdioServerTransport();
|
|
11
|
+
await server.connect(transport);
|
|
12
|
+
}
|
|
13
|
+
void main().catch((err) => {
|
|
14
|
+
console.error(`svelte-vitals-mcp: ${err instanceof Error ? err.message : String(err)}`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// src/tools/analyze.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { analyzeProject, buildRulesConfig, findUnknownRuleIds, knownRuleIds, ProjectError } from "svelte-vitals";
|
|
4
|
+
import { buildJsonReport } from "@svelte-vitals/core";
|
|
5
|
+
function textError(message) {
|
|
6
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
7
|
+
}
|
|
8
|
+
var analyzeInputSchema = z.object({
|
|
9
|
+
path: z.string().optional().describe("Project root to analyze (defaults to the server cwd)."),
|
|
10
|
+
metaComponents: z.array(z.string()).optional().describe(
|
|
11
|
+
'Component names that resolve SEO tags into <head> (e.g. ["Seo"]); their presence suppresses missing-tag findings for the head they own. Mirrors the CLI --meta-components flag.'
|
|
12
|
+
),
|
|
13
|
+
route: z.string().optional().describe('Glob to restrict which routes are analyzed, e.g. "blog/**".'),
|
|
14
|
+
treatDynamicAs: z.enum(["pass", "warn", "fail"]).optional().describe("How dynamic ({data.title}) values are scored. Default: pass."),
|
|
15
|
+
rules: z.array(z.string()).optional().describe("Enable only these rule ids (all others disabled)."),
|
|
16
|
+
ignore: z.array(z.string()).optional().describe("Disable these rule ids."),
|
|
17
|
+
failOn: z.enum(["critical", "warning", "info"]).optional().describe("Minimum severity that counts as a failure in the summary. Default: critical.")
|
|
18
|
+
});
|
|
19
|
+
var analyzeInputShape = analyzeInputSchema.shape;
|
|
20
|
+
async function handleAnalyze(args) {
|
|
21
|
+
const allow = (args.rules ?? []).map((id) => id.toUpperCase());
|
|
22
|
+
const ignore = (args.ignore ?? []).map((id) => id.toUpperCase());
|
|
23
|
+
const unknown = findUnknownRuleIds([...allow, ...ignore]);
|
|
24
|
+
if (unknown.length > 0) {
|
|
25
|
+
return textError(`Unknown rule id(s): ${unknown.join(", ")}. Known rule ids: ${knownRuleIds().join(", ")}.`);
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const { results, config, version } = await analyzeProject({
|
|
29
|
+
cwd: args.path,
|
|
30
|
+
metaComponents: args.metaComponents,
|
|
31
|
+
treatDynamicAs: args.treatDynamicAs,
|
|
32
|
+
route: args.route,
|
|
33
|
+
failOn: args.failOn,
|
|
34
|
+
rules: buildRulesConfig(allow, ignore)
|
|
35
|
+
});
|
|
36
|
+
const report = buildJsonReport(results, config, { version });
|
|
37
|
+
return {
|
|
38
|
+
content: [{ type: "text", text: JSON.stringify(report, null, 2) }],
|
|
39
|
+
structuredContent: report
|
|
40
|
+
};
|
|
41
|
+
} catch (err) {
|
|
42
|
+
if (err instanceof ProjectError) return textError(err.message);
|
|
43
|
+
return textError(`svelte-vitals: ${err instanceof Error ? err.message : String(err)}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/tools/explain-rule.ts
|
|
48
|
+
import { z as z2 } from "zod";
|
|
49
|
+
import { explainRule } from "@svelte-vitals/core";
|
|
50
|
+
import { knownRuleIds as knownRuleIds2 } from "svelte-vitals";
|
|
51
|
+
var explainRuleInputSchema = z2.object({
|
|
52
|
+
id: z2.string().describe('Rule id to explain, e.g. "SEO001".')
|
|
53
|
+
});
|
|
54
|
+
var explainRuleInputShape = explainRuleInputSchema.shape;
|
|
55
|
+
async function handleExplainRule(args) {
|
|
56
|
+
const info = explainRule(args.id);
|
|
57
|
+
if (!info) {
|
|
58
|
+
return {
|
|
59
|
+
content: [{ type: "text", text: `Unknown rule id: ${args.id}. Known rule ids: ${knownRuleIds2().join(", ")}.` }],
|
|
60
|
+
isError: true
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const text = `${info.id} \u2014 ${info.title} (${info.severity}, ${info.category})
|
|
64
|
+
|
|
65
|
+
${info.rationale}
|
|
66
|
+
|
|
67
|
+
Docs: ${info.docsUrl}` + (info.fix ? `
|
|
68
|
+
|
|
69
|
+
Fix: ${info.fix.description}` : "");
|
|
70
|
+
return { content: [{ type: "text", text }], structuredContent: info };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/server.ts
|
|
74
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
75
|
+
function createServer() {
|
|
76
|
+
const server = new McpServer({ name: "svelte-vitals", version: "0.0.0" });
|
|
77
|
+
server.registerTool(
|
|
78
|
+
"analyze",
|
|
79
|
+
{
|
|
80
|
+
title: "Analyze SvelteKit SEO",
|
|
81
|
+
description: "Run svelte-vitals static-mode SEO analysis on a SvelteKit project and return a structured report (per-route and site-wide scores, findings with fix/recommendation/docs).",
|
|
82
|
+
inputSchema: analyzeInputShape
|
|
83
|
+
},
|
|
84
|
+
async (args) => await handleAnalyze(args)
|
|
85
|
+
);
|
|
86
|
+
server.registerTool(
|
|
87
|
+
"explain_rule",
|
|
88
|
+
{
|
|
89
|
+
title: "Explain an SEO rule",
|
|
90
|
+
description: "Return a rule's title, category, default severity, rationale, docs URL, and fix template.",
|
|
91
|
+
inputSchema: explainRuleInputShape
|
|
92
|
+
},
|
|
93
|
+
async (args) => await handleExplainRule(args)
|
|
94
|
+
);
|
|
95
|
+
return server;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export {
|
|
99
|
+
analyzeInputShape,
|
|
100
|
+
handleAnalyze,
|
|
101
|
+
explainRuleInputShape,
|
|
102
|
+
handleExplainRule,
|
|
103
|
+
createServer
|
|
104
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
/** Build the svelte-vitals MCP server with all tools registered. */
|
|
5
|
+
declare function createServer(): McpServer;
|
|
6
|
+
|
|
7
|
+
interface McpToolResult {
|
|
8
|
+
content: Array<{
|
|
9
|
+
type: 'text';
|
|
10
|
+
text: string;
|
|
11
|
+
}>;
|
|
12
|
+
structuredContent?: unknown;
|
|
13
|
+
isError?: boolean;
|
|
14
|
+
}
|
|
15
|
+
declare const analyzeInputSchema: z.ZodObject<{
|
|
16
|
+
path: z.ZodOptional<z.ZodString>;
|
|
17
|
+
metaComponents: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
18
|
+
route: z.ZodOptional<z.ZodString>;
|
|
19
|
+
treatDynamicAs: z.ZodOptional<z.ZodEnum<{
|
|
20
|
+
pass: "pass";
|
|
21
|
+
warn: "warn";
|
|
22
|
+
fail: "fail";
|
|
23
|
+
}>>;
|
|
24
|
+
rules: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
25
|
+
ignore: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
26
|
+
failOn: z.ZodOptional<z.ZodEnum<{
|
|
27
|
+
critical: "critical";
|
|
28
|
+
warning: "warning";
|
|
29
|
+
info: "info";
|
|
30
|
+
}>>;
|
|
31
|
+
}, z.core.$strip>;
|
|
32
|
+
/** zod raw shape for the analyze tool's input (registered with the MCP server). */
|
|
33
|
+
declare const analyzeInputShape: {
|
|
34
|
+
path: z.ZodOptional<z.ZodString>;
|
|
35
|
+
metaComponents: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
36
|
+
route: z.ZodOptional<z.ZodString>;
|
|
37
|
+
treatDynamicAs: z.ZodOptional<z.ZodEnum<{
|
|
38
|
+
pass: "pass";
|
|
39
|
+
warn: "warn";
|
|
40
|
+
fail: "fail";
|
|
41
|
+
}>>;
|
|
42
|
+
rules: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
43
|
+
ignore: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
44
|
+
failOn: z.ZodOptional<z.ZodEnum<{
|
|
45
|
+
critical: "critical";
|
|
46
|
+
warning: "warning";
|
|
47
|
+
info: "info";
|
|
48
|
+
}>>;
|
|
49
|
+
};
|
|
50
|
+
type AnalyzeArgs = z.infer<typeof analyzeInputSchema>;
|
|
51
|
+
/**
|
|
52
|
+
* Handle the `analyze` tool call: run static-mode analysis and return the
|
|
53
|
+
* structured JSON report as both text and `structuredContent`. Unknown rule ids
|
|
54
|
+
* and non-SvelteKit paths are returned as `isError` results, not thrown.
|
|
55
|
+
*/
|
|
56
|
+
declare function handleAnalyze(args: AnalyzeArgs): Promise<McpToolResult>;
|
|
57
|
+
|
|
58
|
+
declare const explainRuleInputSchema: z.ZodObject<{
|
|
59
|
+
id: z.ZodString;
|
|
60
|
+
}, z.core.$strip>;
|
|
61
|
+
declare const explainRuleInputShape: {
|
|
62
|
+
id: z.ZodString;
|
|
63
|
+
};
|
|
64
|
+
type ExplainRuleArgs = z.infer<typeof explainRuleInputSchema>;
|
|
65
|
+
/**
|
|
66
|
+
* Handle the `explain_rule` tool call: look up a rule's static metadata and
|
|
67
|
+
* return it as both a text rendering and `structuredContent`. An unknown id is
|
|
68
|
+
* returned as an `isError` result listing the known rule ids.
|
|
69
|
+
*/
|
|
70
|
+
declare function handleExplainRule(args: ExplainRuleArgs): Promise<McpToolResult>;
|
|
71
|
+
|
|
72
|
+
export { type AnalyzeArgs, type ExplainRuleArgs, type McpToolResult, analyzeInputShape, createServer, explainRuleInputShape, handleAnalyze, handleExplainRule };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
analyzeInputShape,
|
|
3
|
+
createServer,
|
|
4
|
+
explainRuleInputShape,
|
|
5
|
+
handleAnalyze,
|
|
6
|
+
handleExplainRule
|
|
7
|
+
} from "./chunk-Y3ZXSYVB.js";
|
|
8
|
+
export {
|
|
9
|
+
analyzeInputShape,
|
|
10
|
+
createServer,
|
|
11
|
+
explainRuleInputShape,
|
|
12
|
+
handleAnalyze,
|
|
13
|
+
handleExplainRule
|
|
14
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@svelte-vitals/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Model Context Protocol server for svelte-vitals — run SEO analysis inside an agent's tool loop.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Kazuma Oe (https://github.com/oekazuma)",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"svelte",
|
|
10
|
+
"sveltekit",
|
|
11
|
+
"seo",
|
|
12
|
+
"mcp",
|
|
13
|
+
"model-context-protocol",
|
|
14
|
+
"svelte-vitals"
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/oekazuma/svelte-vitals.git",
|
|
19
|
+
"directory": "packages/mcp"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/oekazuma/svelte-vitals/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/oekazuma/svelte-vitals#readme",
|
|
25
|
+
"bin": {
|
|
26
|
+
"svelte-vitals-mcp": "./dist/bin.js"
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
39
|
+
"zod": "^4.4.3",
|
|
40
|
+
"@svelte-vitals/core": "0.6.0",
|
|
41
|
+
"svelte-vitals": "0.5.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^24.7.0"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsup",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"test": "vitest run"
|
|
50
|
+
}
|
|
51
|
+
}
|