@oneentry/mcp-server 1.0.0 → 1.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/README.md +0 -46
- package/dist/config.d.ts +6 -0
- package/dist/config.js +10 -0
- package/dist/content.d.ts +0 -9
- package/dist/content.js +2 -13
- package/dist/index.js +56 -98
- package/dist/registry.js +59 -26
- package/eslint.config.mjs +19 -0
- package/package.json +12 -8
- package/src/config.ts +16 -0
- package/src/content.ts +2 -16
- package/src/index.ts +71 -109
- package/src/registry.ts +59 -26
package/README.md
CHANGED
|
@@ -94,50 +94,4 @@ Invoke in Claude Code with `/skill-name`:
|
|
|
94
94
|
| `create-server-action` | Next.js Server Action |
|
|
95
95
|
| `create-subscription-events` | Price/availability subscription |
|
|
96
96
|
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## For OneEntry team — updating content
|
|
100
|
-
|
|
101
|
-
Rules and skills are fetched from the GitHub repo configured in `ONEENTRY_MCP_BASE_URL`.
|
|
102
|
-
|
|
103
|
-
**Default URL:**
|
|
104
|
-
```
|
|
105
|
-
https://raw.githubusercontent.com/ONEENTRY-PLATFORM/oneentry-sdk-rules/main
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
Update rules in the repo — all users get the changes automatically within 5 minutes (cache TTL).
|
|
109
|
-
|
|
110
|
-
**To point to a custom repo:**
|
|
111
|
-
```json
|
|
112
|
-
{
|
|
113
|
-
"mcpServers": {
|
|
114
|
-
"oneentry": {
|
|
115
|
-
"command": "npx",
|
|
116
|
-
"args": ["-y", "@oneentry/mcp-server"],
|
|
117
|
-
"env": {
|
|
118
|
-
"ONEENTRY_MCP_BASE_URL": "https://raw.githubusercontent.com/your-org/your-repo/main"
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## Local development
|
|
128
|
-
|
|
129
|
-
```bash
|
|
130
|
-
cd mcp-server
|
|
131
|
-
npm install
|
|
132
|
-
npm run dev
|
|
133
|
-
```
|
|
134
97
|
|
|
135
|
-
To point at local files during development:
|
|
136
|
-
```bash
|
|
137
|
-
ONEENTRY_MCP_BASE_URL=file:///Users/you/claude-next npm run dev
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
Build for production:
|
|
141
|
-
```bash
|
|
142
|
-
npm run build
|
|
143
|
-
```
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createRequire } from "module";
|
|
2
|
+
const require = createRequire(import.meta.url);
|
|
3
|
+
const pkg = require("../package.json");
|
|
4
|
+
export const VERSION = pkg.version;
|
|
5
|
+
export const SERVER_NAME = "oneentry-sdk";
|
|
6
|
+
export const USER_AGENT = `oneentry-mcp-server/${VERSION}`;
|
|
7
|
+
export const CLAUDE_MD_PATH = "CLAUDE.md";
|
|
8
|
+
export const BASE_URL = process.env.ONEENTRY_MCP_BASE_URL ??
|
|
9
|
+
"https://raw.githubusercontent.com/ONEENTRY-PLATFORM/oneentry-sdk-rules/main";
|
|
10
|
+
export const TTL_MS = Number(process.env.ONEENTRY_MCP_CACHE_TTL_MS) || 5 * 60 * 1000;
|
package/dist/content.d.ts
CHANGED
|
@@ -1,11 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fetches content from a remote URL (GitHub raw or any HTTP server).
|
|
3
|
-
* Caches results for TTL_MS to avoid hammering the server.
|
|
4
|
-
*
|
|
5
|
-
* Configure the base URL via env var:
|
|
6
|
-
* ONEENTRY_MCP_BASE_URL=https://raw.githubusercontent.com/your-org/your-repo/main
|
|
7
|
-
*
|
|
8
|
-
* Default points to the oneentry public repo.
|
|
9
|
-
*/
|
|
10
1
|
export declare function fetchContent(path: string): Promise<string>;
|
|
11
2
|
export declare function clearCache(): void;
|
package/dist/content.js
CHANGED
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Fetches content from a remote URL (GitHub raw or any HTTP server).
|
|
3
|
-
* Caches results for TTL_MS to avoid hammering the server.
|
|
4
|
-
*
|
|
5
|
-
* Configure the base URL via env var:
|
|
6
|
-
* ONEENTRY_MCP_BASE_URL=https://raw.githubusercontent.com/your-org/your-repo/main
|
|
7
|
-
*
|
|
8
|
-
* Default points to the oneentry public repo.
|
|
9
|
-
*/
|
|
10
|
-
const BASE_URL = process.env.ONEENTRY_MCP_BASE_URL ??
|
|
11
|
-
"https://raw.githubusercontent.com/ONEENTRY-PLATFORM/oneentry-sdk-rules/main";
|
|
12
|
-
const TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
1
|
+
import { BASE_URL, TTL_MS, USER_AGENT } from "./config.js";
|
|
13
2
|
const cache = new Map();
|
|
14
3
|
export async function fetchContent(path) {
|
|
15
4
|
const cached = cache.get(path);
|
|
@@ -19,7 +8,7 @@ export async function fetchContent(path) {
|
|
|
19
8
|
const url = `${BASE_URL}/${path}`;
|
|
20
9
|
const res = await fetch(url, {
|
|
21
10
|
headers: {
|
|
22
|
-
"User-Agent":
|
|
11
|
+
"User-Agent": USER_AGENT,
|
|
23
12
|
},
|
|
24
13
|
});
|
|
25
14
|
if (!res.ok) {
|
package/dist/index.js
CHANGED
|
@@ -1,126 +1,84 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import {
|
|
4
|
+
import { z } from "zod";
|
|
5
5
|
import { fetchContent } from "./content.js";
|
|
6
6
|
import { RULES, SKILLS } from "./registry.js";
|
|
7
|
-
|
|
7
|
+
import { CLAUDE_MD_PATH, SERVER_NAME, VERSION } from "./config.js";
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
9
|
// Server
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
11
|
-
const server = new
|
|
12
|
-
capabilities: {
|
|
13
|
-
resources: {},
|
|
14
|
-
prompts: {},
|
|
15
|
-
},
|
|
16
|
-
});
|
|
11
|
+
const server = new McpServer({ name: SERVER_NAME, version: VERSION });
|
|
17
12
|
// ---------------------------------------------------------------------------
|
|
18
13
|
// Resources — rules + CLAUDE.md
|
|
19
14
|
// ---------------------------------------------------------------------------
|
|
20
|
-
server.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
mimeType: "text/markdown",
|
|
27
|
-
},
|
|
28
|
-
...RULES.map((rule) => ({
|
|
29
|
-
uri: `oneentry://rules/${rule.name}`,
|
|
30
|
-
name: rule.displayName,
|
|
31
|
-
description: rule.description,
|
|
32
|
-
mimeType: "text/markdown",
|
|
33
|
-
})),
|
|
34
|
-
],
|
|
35
|
-
}));
|
|
36
|
-
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
37
|
-
const { uri } = request.params;
|
|
38
|
-
// CLAUDE.md
|
|
39
|
-
if (uri === "oneentry://claude-md") {
|
|
40
|
-
const text = await fetchContent(CLAUDE_MD_PATH);
|
|
41
|
-
return { contents: [{ uri, mimeType: "text/markdown", text }] };
|
|
42
|
-
}
|
|
43
|
-
// Rules
|
|
44
|
-
if (uri.startsWith("oneentry://rules/")) {
|
|
45
|
-
const name = uri.replace("oneentry://rules/", "");
|
|
46
|
-
const rule = RULES.find((r) => r.name === name);
|
|
47
|
-
if (!rule) {
|
|
48
|
-
throw new Error(`Unknown rule: ${name}`);
|
|
49
|
-
}
|
|
50
|
-
const text = await fetchContent(rule.path);
|
|
51
|
-
return { contents: [{ uri, mimeType: "text/markdown", text }] };
|
|
52
|
-
}
|
|
53
|
-
throw new Error(`Unknown resource URI: ${uri}`);
|
|
15
|
+
server.registerResource("OneEntry SDK — Main Instructions (CLAUDE.md)", "oneentry://claude-md", {
|
|
16
|
+
description: "Full system prompt with OneEntry SDK rules, patterns, anti-hallucination guidelines and module reference",
|
|
17
|
+
mimeType: "text/markdown",
|
|
18
|
+
}, async (uri) => {
|
|
19
|
+
const text = await fetchContent(CLAUDE_MD_PATH);
|
|
20
|
+
return { contents: [{ uri: uri.toString(), mimeType: "text/markdown", text }] };
|
|
54
21
|
});
|
|
22
|
+
for (const rule of RULES) {
|
|
23
|
+
server.registerResource(rule.displayName, `oneentry://rules/${rule.name}`, { description: rule.description, mimeType: "text/markdown" }, async (uri) => {
|
|
24
|
+
const text = await fetchContent(rule.path);
|
|
25
|
+
return { contents: [{ uri: uri.toString(), mimeType: "text/markdown", text }] };
|
|
26
|
+
});
|
|
27
|
+
}
|
|
55
28
|
// ---------------------------------------------------------------------------
|
|
56
29
|
// Prompts — skills
|
|
57
30
|
// ---------------------------------------------------------------------------
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
description: "Load full OneEntry SDK context — system instructions, rules and anti-hallucination guidelines. Use this at the start of a session.",
|
|
64
|
-
arguments: [],
|
|
65
|
-
},
|
|
66
|
-
// All skills
|
|
67
|
-
...SKILLS.map((skill) => ({
|
|
68
|
-
name: skill.name,
|
|
69
|
-
description: skill.description,
|
|
70
|
-
arguments: skill.hasArguments
|
|
71
|
-
? [
|
|
72
|
-
{
|
|
73
|
-
name: "arguments",
|
|
74
|
-
description: skill.argumentDescription ?? "Optional arguments",
|
|
75
|
-
required: false,
|
|
76
|
-
},
|
|
77
|
-
]
|
|
78
|
-
: [],
|
|
79
|
-
})),
|
|
80
|
-
],
|
|
81
|
-
}));
|
|
82
|
-
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
83
|
-
const { name, arguments: args } = request.params;
|
|
84
|
-
// Load full CLAUDE.md as system context
|
|
85
|
-
if (name === "oneentry-context") {
|
|
86
|
-
const text = await fetchContent(CLAUDE_MD_PATH);
|
|
87
|
-
return {
|
|
88
|
-
description: "OneEntry SDK — full system context loaded",
|
|
89
|
-
messages: [
|
|
90
|
-
{
|
|
91
|
-
role: "user",
|
|
92
|
-
content: {
|
|
93
|
-
type: "text",
|
|
94
|
-
text: `The following are the complete instructions and rules for working with OneEntry SDK. Apply them to all subsequent code generation:\n\n${text}`,
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
],
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
// Skills
|
|
101
|
-
const skill = SKILLS.find((s) => s.name === name);
|
|
102
|
-
if (!skill) {
|
|
103
|
-
throw new Error(`Unknown prompt: ${name}`);
|
|
104
|
-
}
|
|
105
|
-
let text = await fetchContent(skill.path);
|
|
106
|
-
// Replace $ARGUMENTS placeholder (used in inspect-api and create-page skills)
|
|
107
|
-
const userArguments = args?.["arguments"];
|
|
108
|
-
if (userArguments) {
|
|
109
|
-
text = text.replace(/\$ARGUMENTS/g, userArguments);
|
|
110
|
-
}
|
|
31
|
+
// Special prompt: load full context from CLAUDE.md
|
|
32
|
+
server.registerPrompt("oneentry-context", {
|
|
33
|
+
description: "Load full OneEntry SDK context — system instructions, rules and anti-hallucination guidelines. Use this at the start of a session.",
|
|
34
|
+
}, async () => {
|
|
35
|
+
const text = await fetchContent(CLAUDE_MD_PATH);
|
|
111
36
|
return {
|
|
112
|
-
description:
|
|
37
|
+
description: "OneEntry SDK — full system context loaded",
|
|
113
38
|
messages: [
|
|
114
39
|
{
|
|
115
40
|
role: "user",
|
|
116
41
|
content: {
|
|
117
42
|
type: "text",
|
|
118
|
-
text
|
|
43
|
+
text: `The following are the complete instructions and rules for working with OneEntry SDK. Apply them to all subsequent code generation:\n\n${text}`,
|
|
119
44
|
},
|
|
120
45
|
},
|
|
121
46
|
],
|
|
122
47
|
};
|
|
123
48
|
});
|
|
49
|
+
// Skills
|
|
50
|
+
for (const skill of SKILLS) {
|
|
51
|
+
if (skill.hasArguments) {
|
|
52
|
+
server.registerPrompt(skill.name, {
|
|
53
|
+
description: skill.description,
|
|
54
|
+
argsSchema: {
|
|
55
|
+
arguments: z
|
|
56
|
+
.string()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe(skill.argumentDescription ?? "Optional arguments"),
|
|
59
|
+
},
|
|
60
|
+
}, async (args) => {
|
|
61
|
+
let text = await fetchContent(skill.path);
|
|
62
|
+
// Replace $ARGUMENTS placeholder (used in inspect-api and create-page skills)
|
|
63
|
+
if (args.arguments) {
|
|
64
|
+
text = text.replace(/\$ARGUMENTS/g, args.arguments);
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
description: skill.description,
|
|
68
|
+
messages: [{ role: "user", content: { type: "text", text } }],
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
server.registerPrompt(skill.name, { description: skill.description }, async () => {
|
|
74
|
+
const text = await fetchContent(skill.path);
|
|
75
|
+
return {
|
|
76
|
+
description: skill.description,
|
|
77
|
+
messages: [{ role: "user", content: { type: "text", text } }],
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
124
82
|
// ---------------------------------------------------------------------------
|
|
125
83
|
// Start
|
|
126
84
|
// ---------------------------------------------------------------------------
|
package/dist/registry.js
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
export const RULES = [
|
|
2
2
|
{
|
|
3
|
-
name: "
|
|
4
|
-
displayName: "
|
|
5
|
-
description: "Rules for working with
|
|
6
|
-
path: ".claude/rules/
|
|
3
|
+
name: "linting",
|
|
4
|
+
displayName: "Linting",
|
|
5
|
+
description: "Rules for working with linters",
|
|
6
|
+
path: ".claude/rules/linting.md",
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
|
-
name: "
|
|
10
|
-
displayName: "
|
|
11
|
-
description: "Rules for
|
|
12
|
-
path: ".claude/rules/
|
|
9
|
+
name: "typescript",
|
|
10
|
+
displayName: "Typescript",
|
|
11
|
+
description: "Rules for working with typescript",
|
|
12
|
+
path: ".claude/rules/typescript.md",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "nextjs-pages",
|
|
16
|
+
displayName: "Next.js Pages",
|
|
17
|
+
description: "Rules for Next.js pages with OneEntry — params as Promise, pageUrl vs route, localizeInfos",
|
|
18
|
+
path: ".claude/rules/nextjs-pages.md",
|
|
13
19
|
},
|
|
14
20
|
{
|
|
15
21
|
name: "attribute-values",
|
|
@@ -17,6 +23,12 @@ export const RULES = [
|
|
|
17
23
|
description: "How to access attributeValues by type — image, text, list, date, timeInterval, etc.",
|
|
18
24
|
path: ".claude/rules/attribute-values.md",
|
|
19
25
|
},
|
|
26
|
+
{
|
|
27
|
+
name: "attribute-sets",
|
|
28
|
+
displayName: "AttributeSets",
|
|
29
|
+
description: "Rules for AttributesSets — schema vs values, listTitles, additionalFields, validators",
|
|
30
|
+
path: ".claude/rules/attribute-sets.md",
|
|
31
|
+
},
|
|
20
32
|
{
|
|
21
33
|
name: "auth-provider",
|
|
22
34
|
displayName: "AuthProvider",
|
|
@@ -24,10 +36,10 @@ export const RULES = [
|
|
|
24
36
|
path: ".claude/rules/auth-provider.md",
|
|
25
37
|
},
|
|
26
38
|
{
|
|
27
|
-
name: "
|
|
28
|
-
displayName: "
|
|
29
|
-
description: "Rules for
|
|
30
|
-
path: ".claude/rules/
|
|
39
|
+
name: "tokens",
|
|
40
|
+
displayName: "Tokens & Auth",
|
|
41
|
+
description: "Rules for auth tokens — makeUserApi, refreshToken, race condition handling, getNewToken",
|
|
42
|
+
path: ".claude/rules/tokens.md",
|
|
31
43
|
},
|
|
32
44
|
{
|
|
33
45
|
name: "server-actions",
|
|
@@ -35,38 +47,42 @@ export const RULES = [
|
|
|
35
47
|
description: "Rules for Next.js Server Actions with OneEntry — getApi() vs makeUserApi(), which methods need SA",
|
|
36
48
|
path: ".claude/rules/server-actions.md",
|
|
37
49
|
},
|
|
50
|
+
{
|
|
51
|
+
name: "forms",
|
|
52
|
+
displayName: "Forms & FormsData",
|
|
53
|
+
description: "Rules for working with OneEntry Forms API — getFormByMarker, postFormsData, formData types by field type",
|
|
54
|
+
path: ".claude/rules/forms.md",
|
|
55
|
+
},
|
|
38
56
|
{
|
|
39
57
|
name: "orders",
|
|
40
58
|
displayName: "Orders & Payments",
|
|
41
59
|
description: "Rules for Orders and Payments — getAllOrdersStorage structure, createOrder, createSession, paymentUrl",
|
|
42
60
|
path: ".claude/rules/orders.md",
|
|
43
61
|
},
|
|
44
|
-
{
|
|
45
|
-
name: "attribute-sets",
|
|
46
|
-
displayName: "AttributeSets",
|
|
47
|
-
description: "Rules for AttributesSets — schema vs values, listTitles, additionalFields, validators",
|
|
48
|
-
path: ".claude/rules/attribute-sets.md",
|
|
49
|
-
},
|
|
50
62
|
{
|
|
51
63
|
name: "localization",
|
|
52
64
|
displayName: "Localization",
|
|
53
65
|
description: "Rules for localization — locale from params, langCode, localizeInfos, useParams in client components",
|
|
54
66
|
path: ".claude/rules/localization.md",
|
|
55
67
|
},
|
|
68
|
+
{
|
|
69
|
+
name: "product-statuses",
|
|
70
|
+
displayName: "Product statuses",
|
|
71
|
+
description: "Rules product statuses",
|
|
72
|
+
path: ".claude/rules/product-statuses.md",
|
|
73
|
+
},
|
|
56
74
|
];
|
|
57
75
|
export const SKILLS = [
|
|
76
|
+
{
|
|
77
|
+
name: "setup-nextjs",
|
|
78
|
+
description: "Initialize Next.js project — configures nextjs",
|
|
79
|
+
path: ".claude/skills/setup-nextjs/SKILL.md",
|
|
80
|
+
},
|
|
58
81
|
{
|
|
59
82
|
name: "setup-oneentry",
|
|
60
83
|
description: "Initialize OneEntry SDK in a Next.js project — creates lib/oneentry.ts with singleton pattern, configures next.config.ts for images",
|
|
61
84
|
path: ".claude/skills/setup-oneentry/SKILL.md",
|
|
62
85
|
},
|
|
63
|
-
{
|
|
64
|
-
name: "create-page",
|
|
65
|
-
description: "Create a Next.js page with content from OneEntry CMS — getPageByUrl, getBlocksByPageUrl, localizeInfos",
|
|
66
|
-
path: ".claude/skills/create-page/SKILL.md",
|
|
67
|
-
hasArguments: true,
|
|
68
|
-
argumentDescription: "pageMarker — the pageUrl marker in OneEntry (e.g. 'home', 'about')",
|
|
69
|
-
},
|
|
70
86
|
{
|
|
71
87
|
name: "inspect-api",
|
|
72
88
|
description: "Read .env.local and execute curl requests to OneEntry API to get real markers, attributes and data structures before writing code",
|
|
@@ -74,6 +90,13 @@ export const SKILLS = [
|
|
|
74
90
|
hasArguments: true,
|
|
75
91
|
argumentDescription: "pages|menus|forms|products|product-statuses|auth-providers|all",
|
|
76
92
|
},
|
|
93
|
+
{
|
|
94
|
+
name: "create-page",
|
|
95
|
+
description: "Create a Next.js page with content from OneEntry CMS — getPageByUrl, getBlocksByPageUrl, localizeInfos",
|
|
96
|
+
path: ".claude/skills/create-page/SKILL.md",
|
|
97
|
+
hasArguments: true,
|
|
98
|
+
argumentDescription: "pageMarker — the pageUrl marker in OneEntry (e.g. 'home', 'about')",
|
|
99
|
+
},
|
|
77
100
|
{
|
|
78
101
|
name: "create-auth",
|
|
79
102
|
description: "Create auth/registration form with OneEntry AuthProvider — Server Actions, dynamic fields from Forms API, token sync",
|
|
@@ -86,9 +109,14 @@ export const SKILLS = [
|
|
|
86
109
|
},
|
|
87
110
|
{
|
|
88
111
|
name: "create-product-card",
|
|
89
|
-
description: "Create a single product
|
|
112
|
+
description: "Create a single product card, attribute extraction, image, price, etc",
|
|
90
113
|
path: ".claude/skills/create-product-card/SKILL.md",
|
|
91
114
|
},
|
|
115
|
+
{
|
|
116
|
+
name: "create-product-page",
|
|
117
|
+
description: "Create a single product page with getProductById, attribute extraction, image gallery, price block, related products",
|
|
118
|
+
path: ".claude/skills/create-product-page/SKILL.md",
|
|
119
|
+
},
|
|
92
120
|
{
|
|
93
121
|
name: "create-cart-manager",
|
|
94
122
|
description: "Create cart manager — Redux slice + redux-persist, add/remove/quantity, StoreProvider",
|
|
@@ -154,4 +182,9 @@ export const SKILLS = [
|
|
|
154
182
|
description: "Create product price/availability subscription — Events.subscribeByMarker / unsubscribeByMarker",
|
|
155
183
|
path: ".claude/skills/create-subscription-events/SKILL.md",
|
|
156
184
|
},
|
|
185
|
+
{
|
|
186
|
+
name: "setup-playwright",
|
|
187
|
+
description: "Setup playwright testing framework",
|
|
188
|
+
path: ".claude/skills/setup-playwright/SKILL.md",
|
|
189
|
+
},
|
|
157
190
|
];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import tseslint from "typescript-eslint";
|
|
3
|
+
|
|
4
|
+
export default tseslint.config(
|
|
5
|
+
{ ignores: ["eslint.config.mjs"] },
|
|
6
|
+
js.configs.recommended,
|
|
7
|
+
tseslint.configs.recommended,
|
|
8
|
+
{
|
|
9
|
+
languageOptions: {
|
|
10
|
+
parserOptions: {
|
|
11
|
+
project: "./tsconfig.json",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
rules: {
|
|
15
|
+
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
|
16
|
+
"@typescript-eslint/no-explicit-any": "warn",
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oneentry/mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "MCP server for OneEntry SDK — rules and skills for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,16 +10,20 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsc",
|
|
12
12
|
"dev": "tsx src/index.ts",
|
|
13
|
-
"start": "node dist/index.js"
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"lint": "eslint src"
|
|
14
15
|
},
|
|
15
16
|
"dependencies": {
|
|
16
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
17
|
-
"zod": "^3.
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
18
|
+
"zod": "^4.3.6"
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {
|
|
20
|
-
"@
|
|
21
|
-
"
|
|
22
|
-
"
|
|
21
|
+
"@eslint/js": "^10.0.1",
|
|
22
|
+
"@types/node": "^25.3.5",
|
|
23
|
+
"eslint": "^10.0.2",
|
|
24
|
+
"tsx": "^4.21.0",
|
|
25
|
+
"typescript": "^5.8.3",
|
|
26
|
+
"typescript-eslint": "^8.56.1"
|
|
23
27
|
},
|
|
24
28
|
"engines": {
|
|
25
29
|
"node": ">=18"
|
|
@@ -27,4 +31,4 @@
|
|
|
27
31
|
"publishConfig": {
|
|
28
32
|
"access": "public"
|
|
29
33
|
}
|
|
30
|
-
}
|
|
34
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createRequire } from "module";
|
|
2
|
+
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
const pkg = require("../package.json") as { version: string };
|
|
5
|
+
|
|
6
|
+
export const VERSION = pkg.version;
|
|
7
|
+
export const SERVER_NAME = "oneentry-sdk";
|
|
8
|
+
export const USER_AGENT = `oneentry-mcp-server/${VERSION}`;
|
|
9
|
+
export const CLAUDE_MD_PATH = "CLAUDE.md";
|
|
10
|
+
|
|
11
|
+
export const BASE_URL =
|
|
12
|
+
process.env.ONEENTRY_MCP_BASE_URL ??
|
|
13
|
+
"https://raw.githubusercontent.com/ONEENTRY-PLATFORM/oneentry-sdk-rules/main";
|
|
14
|
+
|
|
15
|
+
export const TTL_MS =
|
|
16
|
+
Number(process.env.ONEENTRY_MCP_CACHE_TTL_MS) || 5 * 60 * 1000;
|
package/src/content.ts
CHANGED
|
@@ -1,18 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Fetches content from a remote URL (GitHub raw or any HTTP server).
|
|
3
|
-
* Caches results for TTL_MS to avoid hammering the server.
|
|
4
|
-
*
|
|
5
|
-
* Configure the base URL via env var:
|
|
6
|
-
* ONEENTRY_MCP_BASE_URL=https://raw.githubusercontent.com/your-org/your-repo/main
|
|
7
|
-
*
|
|
8
|
-
* Default points to the oneentry public repo.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const BASE_URL =
|
|
12
|
-
process.env.ONEENTRY_MCP_BASE_URL ??
|
|
13
|
-
"https://raw.githubusercontent.com/ONEENTRY-PLATFORM/oneentry-sdk-rules/main";
|
|
14
|
-
|
|
15
|
-
const TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
1
|
+
import { BASE_URL, TTL_MS, USER_AGENT } from "./config.js";
|
|
16
2
|
|
|
17
3
|
interface CacheEntry {
|
|
18
4
|
text: string;
|
|
@@ -30,7 +16,7 @@ export async function fetchContent(path: string): Promise<string> {
|
|
|
30
16
|
const url = `${BASE_URL}/${path}`;
|
|
31
17
|
const res = await fetch(url, {
|
|
32
18
|
headers: {
|
|
33
|
-
"User-Agent":
|
|
19
|
+
"User-Agent": USER_AGENT,
|
|
34
20
|
},
|
|
35
21
|
});
|
|
36
22
|
|
package/src/index.ts
CHANGED
|
@@ -1,111 +1,59 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import {
|
|
5
|
-
ListResourcesRequestSchema,
|
|
6
|
-
ReadResourceRequestSchema,
|
|
7
|
-
ListPromptsRequestSchema,
|
|
8
|
-
GetPromptRequestSchema,
|
|
9
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { z } from "zod";
|
|
10
5
|
import { fetchContent } from "./content.js";
|
|
11
6
|
import { RULES, SKILLS } from "./registry.js";
|
|
12
|
-
|
|
13
|
-
const CLAUDE_MD_PATH = "CLAUDE.md";
|
|
7
|
+
import { CLAUDE_MD_PATH, SERVER_NAME, VERSION } from "./config.js";
|
|
14
8
|
|
|
15
9
|
// ---------------------------------------------------------------------------
|
|
16
10
|
// Server
|
|
17
11
|
// ---------------------------------------------------------------------------
|
|
18
12
|
|
|
19
|
-
const server = new
|
|
20
|
-
{ name: "oneentry-sdk", version: "1.0.0" },
|
|
21
|
-
{
|
|
22
|
-
capabilities: {
|
|
23
|
-
resources: {},
|
|
24
|
-
prompts: {},
|
|
25
|
-
},
|
|
26
|
-
}
|
|
27
|
-
);
|
|
13
|
+
const server = new McpServer({ name: SERVER_NAME, version: VERSION });
|
|
28
14
|
|
|
29
15
|
// ---------------------------------------------------------------------------
|
|
30
16
|
// Resources — rules + CLAUDE.md
|
|
31
17
|
// ---------------------------------------------------------------------------
|
|
32
18
|
|
|
33
|
-
server.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
...RULES.map((rule) => ({
|
|
43
|
-
uri: `oneentry://rules/${rule.name}`,
|
|
44
|
-
name: rule.displayName,
|
|
45
|
-
description: rule.description,
|
|
46
|
-
mimeType: "text/markdown",
|
|
47
|
-
})),
|
|
48
|
-
],
|
|
49
|
-
}));
|
|
50
|
-
|
|
51
|
-
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
52
|
-
const { uri } = request.params;
|
|
53
|
-
|
|
54
|
-
// CLAUDE.md
|
|
55
|
-
if (uri === "oneentry://claude-md") {
|
|
19
|
+
server.registerResource(
|
|
20
|
+
"OneEntry SDK — Main Instructions (CLAUDE.md)",
|
|
21
|
+
"oneentry://claude-md",
|
|
22
|
+
{
|
|
23
|
+
description:
|
|
24
|
+
"Full system prompt with OneEntry SDK rules, patterns, anti-hallucination guidelines and module reference",
|
|
25
|
+
mimeType: "text/markdown",
|
|
26
|
+
},
|
|
27
|
+
async (uri) => {
|
|
56
28
|
const text = await fetchContent(CLAUDE_MD_PATH);
|
|
57
|
-
return { contents: [{ uri, mimeType: "text/markdown", text }] };
|
|
29
|
+
return { contents: [{ uri: uri.toString(), mimeType: "text/markdown", text }] };
|
|
58
30
|
}
|
|
31
|
+
);
|
|
59
32
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
33
|
+
for (const rule of RULES) {
|
|
34
|
+
server.registerResource(
|
|
35
|
+
rule.displayName,
|
|
36
|
+
`oneentry://rules/${rule.name}`,
|
|
37
|
+
{ description: rule.description, mimeType: "text/markdown" },
|
|
38
|
+
async (uri) => {
|
|
39
|
+
const text = await fetchContent(rule.path);
|
|
40
|
+
return { contents: [{ uri: uri.toString(), mimeType: "text/markdown", text }] };
|
|
66
41
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
throw new Error(`Unknown resource URI: ${uri}`);
|
|
72
|
-
});
|
|
42
|
+
);
|
|
43
|
+
}
|
|
73
44
|
|
|
74
45
|
// ---------------------------------------------------------------------------
|
|
75
46
|
// Prompts — skills
|
|
76
47
|
// ---------------------------------------------------------------------------
|
|
77
48
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
},
|
|
87
|
-
// All skills
|
|
88
|
-
...SKILLS.map((skill) => ({
|
|
89
|
-
name: skill.name,
|
|
90
|
-
description: skill.description,
|
|
91
|
-
arguments: skill.hasArguments
|
|
92
|
-
? [
|
|
93
|
-
{
|
|
94
|
-
name: "arguments",
|
|
95
|
-
description: skill.argumentDescription ?? "Optional arguments",
|
|
96
|
-
required: false,
|
|
97
|
-
},
|
|
98
|
-
]
|
|
99
|
-
: [],
|
|
100
|
-
})),
|
|
101
|
-
],
|
|
102
|
-
}));
|
|
103
|
-
|
|
104
|
-
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
105
|
-
const { name, arguments: args } = request.params;
|
|
106
|
-
|
|
107
|
-
// Load full CLAUDE.md as system context
|
|
108
|
-
if (name === "oneentry-context") {
|
|
49
|
+
// Special prompt: load full context from CLAUDE.md
|
|
50
|
+
server.registerPrompt(
|
|
51
|
+
"oneentry-context",
|
|
52
|
+
{
|
|
53
|
+
description:
|
|
54
|
+
"Load full OneEntry SDK context — system instructions, rules and anti-hallucination guidelines. Use this at the start of a session.",
|
|
55
|
+
},
|
|
56
|
+
async () => {
|
|
109
57
|
const text = await fetchContent(CLAUDE_MD_PATH);
|
|
110
58
|
return {
|
|
111
59
|
description: "OneEntry SDK — full system context loaded",
|
|
@@ -120,34 +68,48 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
120
68
|
],
|
|
121
69
|
};
|
|
122
70
|
}
|
|
71
|
+
);
|
|
123
72
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
let text = await fetchContent(skill.path);
|
|
131
|
-
|
|
132
|
-
// Replace $ARGUMENTS placeholder (used in inspect-api and create-page skills)
|
|
133
|
-
const userArguments = args?.["arguments"];
|
|
134
|
-
if (userArguments) {
|
|
135
|
-
text = text.replace(/\$ARGUMENTS/g, userArguments);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
description: skill.description,
|
|
140
|
-
messages: [
|
|
73
|
+
// Skills
|
|
74
|
+
for (const skill of SKILLS) {
|
|
75
|
+
if (skill.hasArguments) {
|
|
76
|
+
server.registerPrompt(
|
|
77
|
+
skill.name,
|
|
141
78
|
{
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
79
|
+
description: skill.description,
|
|
80
|
+
argsSchema: {
|
|
81
|
+
arguments: z
|
|
82
|
+
.string()
|
|
83
|
+
.optional()
|
|
84
|
+
.describe(skill.argumentDescription ?? "Optional arguments"),
|
|
146
85
|
},
|
|
147
86
|
},
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
87
|
+
async (args) => {
|
|
88
|
+
let text = await fetchContent(skill.path);
|
|
89
|
+
// Replace $ARGUMENTS placeholder (used in inspect-api and create-page skills)
|
|
90
|
+
if (args.arguments) {
|
|
91
|
+
text = text.replace(/\$ARGUMENTS/g, args.arguments);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
description: skill.description,
|
|
95
|
+
messages: [{ role: "user" as const, content: { type: "text" as const, text } }],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
} else {
|
|
100
|
+
server.registerPrompt(
|
|
101
|
+
skill.name,
|
|
102
|
+
{ description: skill.description },
|
|
103
|
+
async () => {
|
|
104
|
+
const text = await fetchContent(skill.path);
|
|
105
|
+
return {
|
|
106
|
+
description: skill.description,
|
|
107
|
+
messages: [{ role: "user" as const, content: { type: "text" as const, text } }],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
151
113
|
|
|
152
114
|
// ---------------------------------------------------------------------------
|
|
153
115
|
// Start
|
package/src/registry.ts
CHANGED
|
@@ -15,16 +15,22 @@ export interface Skill {
|
|
|
15
15
|
|
|
16
16
|
export const RULES: Rule[] = [
|
|
17
17
|
{
|
|
18
|
-
name: "
|
|
19
|
-
displayName: "
|
|
20
|
-
description: "Rules for working with
|
|
21
|
-
path: ".claude/rules/
|
|
18
|
+
name: "linting",
|
|
19
|
+
displayName: "Linting",
|
|
20
|
+
description: "Rules for working with linters",
|
|
21
|
+
path: ".claude/rules/linting.md",
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
|
-
name: "
|
|
25
|
-
displayName: "
|
|
26
|
-
description: "Rules for
|
|
27
|
-
path: ".claude/rules/
|
|
24
|
+
name: "typescript",
|
|
25
|
+
displayName: "Typescript",
|
|
26
|
+
description: "Rules for working with typescript",
|
|
27
|
+
path: ".claude/rules/typescript.md",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "nextjs-pages",
|
|
31
|
+
displayName: "Next.js Pages",
|
|
32
|
+
description: "Rules for Next.js pages with OneEntry — params as Promise, pageUrl vs route, localizeInfos",
|
|
33
|
+
path: ".claude/rules/nextjs-pages.md",
|
|
28
34
|
},
|
|
29
35
|
{
|
|
30
36
|
name: "attribute-values",
|
|
@@ -32,6 +38,12 @@ export const RULES: Rule[] = [
|
|
|
32
38
|
description: "How to access attributeValues by type — image, text, list, date, timeInterval, etc.",
|
|
33
39
|
path: ".claude/rules/attribute-values.md",
|
|
34
40
|
},
|
|
41
|
+
{
|
|
42
|
+
name: "attribute-sets",
|
|
43
|
+
displayName: "AttributeSets",
|
|
44
|
+
description: "Rules for AttributesSets — schema vs values, listTitles, additionalFields, validators",
|
|
45
|
+
path: ".claude/rules/attribute-sets.md",
|
|
46
|
+
},
|
|
35
47
|
{
|
|
36
48
|
name: "auth-provider",
|
|
37
49
|
displayName: "AuthProvider",
|
|
@@ -39,10 +51,10 @@ export const RULES: Rule[] = [
|
|
|
39
51
|
path: ".claude/rules/auth-provider.md",
|
|
40
52
|
},
|
|
41
53
|
{
|
|
42
|
-
name: "
|
|
43
|
-
displayName: "
|
|
44
|
-
description: "Rules for
|
|
45
|
-
path: ".claude/rules/
|
|
54
|
+
name: "tokens",
|
|
55
|
+
displayName: "Tokens & Auth",
|
|
56
|
+
description: "Rules for auth tokens — makeUserApi, refreshToken, race condition handling, getNewToken",
|
|
57
|
+
path: ".claude/rules/tokens.md",
|
|
46
58
|
},
|
|
47
59
|
{
|
|
48
60
|
name: "server-actions",
|
|
@@ -50,39 +62,43 @@ export const RULES: Rule[] = [
|
|
|
50
62
|
description: "Rules for Next.js Server Actions with OneEntry — getApi() vs makeUserApi(), which methods need SA",
|
|
51
63
|
path: ".claude/rules/server-actions.md",
|
|
52
64
|
},
|
|
65
|
+
{
|
|
66
|
+
name: "forms",
|
|
67
|
+
displayName: "Forms & FormsData",
|
|
68
|
+
description: "Rules for working with OneEntry Forms API — getFormByMarker, postFormsData, formData types by field type",
|
|
69
|
+
path: ".claude/rules/forms.md",
|
|
70
|
+
},
|
|
53
71
|
{
|
|
54
72
|
name: "orders",
|
|
55
73
|
displayName: "Orders & Payments",
|
|
56
74
|
description: "Rules for Orders and Payments — getAllOrdersStorage structure, createOrder, createSession, paymentUrl",
|
|
57
75
|
path: ".claude/rules/orders.md",
|
|
58
76
|
},
|
|
59
|
-
{
|
|
60
|
-
name: "attribute-sets",
|
|
61
|
-
displayName: "AttributeSets",
|
|
62
|
-
description: "Rules for AttributesSets — schema vs values, listTitles, additionalFields, validators",
|
|
63
|
-
path: ".claude/rules/attribute-sets.md",
|
|
64
|
-
},
|
|
65
77
|
{
|
|
66
78
|
name: "localization",
|
|
67
79
|
displayName: "Localization",
|
|
68
80
|
description: "Rules for localization — locale from params, langCode, localizeInfos, useParams in client components",
|
|
69
81
|
path: ".claude/rules/localization.md",
|
|
70
82
|
},
|
|
83
|
+
{
|
|
84
|
+
name: "product-statuses",
|
|
85
|
+
displayName: "Product statuses",
|
|
86
|
+
description: "Rules product statuses",
|
|
87
|
+
path: ".claude/rules/product-statuses.md",
|
|
88
|
+
},
|
|
71
89
|
];
|
|
72
90
|
|
|
73
91
|
export const SKILLS: Skill[] = [
|
|
92
|
+
{
|
|
93
|
+
name: "setup-nextjs",
|
|
94
|
+
description: "Initialize Next.js project — configures nextjs",
|
|
95
|
+
path: ".claude/skills/setup-nextjs/SKILL.md",
|
|
96
|
+
},
|
|
74
97
|
{
|
|
75
98
|
name: "setup-oneentry",
|
|
76
99
|
description: "Initialize OneEntry SDK in a Next.js project — creates lib/oneentry.ts with singleton pattern, configures next.config.ts for images",
|
|
77
100
|
path: ".claude/skills/setup-oneentry/SKILL.md",
|
|
78
101
|
},
|
|
79
|
-
{
|
|
80
|
-
name: "create-page",
|
|
81
|
-
description: "Create a Next.js page with content from OneEntry CMS — getPageByUrl, getBlocksByPageUrl, localizeInfos",
|
|
82
|
-
path: ".claude/skills/create-page/SKILL.md",
|
|
83
|
-
hasArguments: true,
|
|
84
|
-
argumentDescription: "pageMarker — the pageUrl marker in OneEntry (e.g. 'home', 'about')",
|
|
85
|
-
},
|
|
86
102
|
{
|
|
87
103
|
name: "inspect-api",
|
|
88
104
|
description: "Read .env.local and execute curl requests to OneEntry API to get real markers, attributes and data structures before writing code",
|
|
@@ -90,6 +106,13 @@ export const SKILLS: Skill[] = [
|
|
|
90
106
|
hasArguments: true,
|
|
91
107
|
argumentDescription: "pages|menus|forms|products|product-statuses|auth-providers|all",
|
|
92
108
|
},
|
|
109
|
+
{
|
|
110
|
+
name: "create-page",
|
|
111
|
+
description: "Create a Next.js page with content from OneEntry CMS — getPageByUrl, getBlocksByPageUrl, localizeInfos",
|
|
112
|
+
path: ".claude/skills/create-page/SKILL.md",
|
|
113
|
+
hasArguments: true,
|
|
114
|
+
argumentDescription: "pageMarker — the pageUrl marker in OneEntry (e.g. 'home', 'about')",
|
|
115
|
+
},
|
|
93
116
|
{
|
|
94
117
|
name: "create-auth",
|
|
95
118
|
description: "Create auth/registration form with OneEntry AuthProvider — Server Actions, dynamic fields from Forms API, token sync",
|
|
@@ -102,9 +125,14 @@ export const SKILLS: Skill[] = [
|
|
|
102
125
|
},
|
|
103
126
|
{
|
|
104
127
|
name: "create-product-card",
|
|
105
|
-
description: "Create a single product
|
|
128
|
+
description: "Create a single product card, attribute extraction, image, price, etc",
|
|
106
129
|
path: ".claude/skills/create-product-card/SKILL.md",
|
|
107
130
|
},
|
|
131
|
+
{
|
|
132
|
+
name: "create-product-page",
|
|
133
|
+
description: "Create a single product page with getProductById, attribute extraction, image gallery, price block, related products",
|
|
134
|
+
path: ".claude/skills/create-product-page/SKILL.md",
|
|
135
|
+
},
|
|
108
136
|
{
|
|
109
137
|
name: "create-cart-manager",
|
|
110
138
|
description: "Create cart manager — Redux slice + redux-persist, add/remove/quantity, StoreProvider",
|
|
@@ -170,4 +198,9 @@ export const SKILLS: Skill[] = [
|
|
|
170
198
|
description: "Create product price/availability subscription — Events.subscribeByMarker / unsubscribeByMarker",
|
|
171
199
|
path: ".claude/skills/create-subscription-events/SKILL.md",
|
|
172
200
|
},
|
|
201
|
+
{
|
|
202
|
+
name: "setup-playwright",
|
|
203
|
+
description: "Setup playwright testing framework",
|
|
204
|
+
path: ".claude/skills/setup-playwright/SKILL.md",
|
|
205
|
+
},
|
|
173
206
|
];
|