gearvn-pages-mcp-server 1.0.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/.env.example +3 -0
- package/README.md +286 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +153 -0
- package/dist/index.js.map +1 -0
- package/package.json +30 -0
- package/src/index.ts +219 -0
- package/tsconfig.json +20 -0
package/.env.example
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# GearVN Pages MCP Server
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) Server cho việc quản lý và deploy các trang HTML tĩnh lên GearVN Pages.
|
|
4
|
+
|
|
5
|
+
## Tính năng
|
|
6
|
+
|
|
7
|
+
MCP server này cung cấp 2 tools:
|
|
8
|
+
|
|
9
|
+
### 1. deploy-page
|
|
10
|
+
Deploy hoặc update một trang HTML tĩnh.
|
|
11
|
+
|
|
12
|
+
**Parameters:**
|
|
13
|
+
- `title` (string, required): Tiêu đề của trang
|
|
14
|
+
- `content` (string, required): Nội dung HTML đầy đủ của trang. Phải bao gồm complete HTML document với TailwindCSS CDN. JavaScript libraries được phép nhưng phải import qua CDN (ví dụ: Alpine.js, Chart.js, GSAP). Không dùng React, Vue, Angular hoặc build tools
|
|
15
|
+
- `slug` (string, required): URL slug cho trang (ví dụ: "my-page-123")
|
|
16
|
+
|
|
17
|
+
**Response:**
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"success": true,
|
|
21
|
+
"message": "Page deployed successfully",
|
|
22
|
+
"url": "https://pages.react.uat.gearvn.xyz/pages/my-page-123"
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. list-pages
|
|
27
|
+
Liệt kê tất cả các pages của API key hiện tại.
|
|
28
|
+
|
|
29
|
+
**Parameters:** Không có
|
|
30
|
+
|
|
31
|
+
**Response:**
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"success": true,
|
|
35
|
+
"count": 2,
|
|
36
|
+
"pages": [
|
|
37
|
+
{
|
|
38
|
+
"id": 1,
|
|
39
|
+
"slug": "my-page-123",
|
|
40
|
+
"title": "My Page",
|
|
41
|
+
"created_at": "2025-01-20T10:00:00Z",
|
|
42
|
+
"updated_at": "2025-01-20T10:00:00Z"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Cài đặt
|
|
49
|
+
|
|
50
|
+
### Prerequisites
|
|
51
|
+
- Node.js 18 hoặc cao hơn
|
|
52
|
+
- npm hoặc pnpm
|
|
53
|
+
- API key từ GearVN Pages
|
|
54
|
+
|
|
55
|
+
### Bước 1: Cài đặt dependencies
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
cd mcp-server
|
|
59
|
+
npm install
|
|
60
|
+
# hoặc
|
|
61
|
+
pnpm install
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Bước 2: Build TypeScript
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm run build
|
|
68
|
+
# hoặc
|
|
69
|
+
pnpm build
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Cấu hình
|
|
73
|
+
|
|
74
|
+
MCP server cần 1 environment variable:
|
|
75
|
+
|
|
76
|
+
- `API_KEY` (required): API key của bạn để authenticate với GearVN Pages API
|
|
77
|
+
|
|
78
|
+
### Cách lấy API Key
|
|
79
|
+
|
|
80
|
+
Sử dụng API để tạo API key:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
curl -X POST https://pages.react.uat.gearvn.xyz/v1/api-keys \
|
|
84
|
+
-H "Content-Type: application/json" \
|
|
85
|
+
-d '{"name": "My MCP Key"}'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Response sẽ trả về API key, lưu lại để sử dụng.
|
|
89
|
+
|
|
90
|
+
## Sử dụng với Claude Desktop
|
|
91
|
+
|
|
92
|
+
### Bước 1: Thêm vào Claude Desktop config
|
|
93
|
+
|
|
94
|
+
Mở file config của Claude Desktop:
|
|
95
|
+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
96
|
+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
97
|
+
|
|
98
|
+
### Bước 2: Thêm MCP server config
|
|
99
|
+
|
|
100
|
+
**Cách 1: Sử dụng npx (Khuyến nghị)**
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"mcpServers": {
|
|
105
|
+
"gearvn-pages": {
|
|
106
|
+
"command": "npx",
|
|
107
|
+
"args": [
|
|
108
|
+
"-y",
|
|
109
|
+
"/absolute/path/to/backend-webpage-go/mcp-server"
|
|
110
|
+
],
|
|
111
|
+
"env": {
|
|
112
|
+
"API_KEY": "sk-your-api-key-here"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Cách 2: Chạy trực tiếp với node**
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"mcpServers": {
|
|
124
|
+
"gearvn-pages": {
|
|
125
|
+
"command": "node",
|
|
126
|
+
"args": [
|
|
127
|
+
"/absolute/path/to/backend-webpage-go/mcp-server/dist/index.js"
|
|
128
|
+
],
|
|
129
|
+
"env": {
|
|
130
|
+
"API_KEY": "sk-your-api-key-here"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Lưu ý:** Thay `/absolute/path/to/` bằng đường dẫn tuyệt đối đến folder mcp-server của bạn.
|
|
138
|
+
|
|
139
|
+
### Bước 3: Restart Claude Desktop
|
|
140
|
+
|
|
141
|
+
Sau khi thêm config, restart Claude Desktop để load MCP server.
|
|
142
|
+
|
|
143
|
+
## Sử dụng với Cline (VS Code Extension)
|
|
144
|
+
|
|
145
|
+
### Bước 1: Mở VS Code Settings
|
|
146
|
+
|
|
147
|
+
1. Mở Command Palette (Cmd+Shift+P / Ctrl+Shift+P)
|
|
148
|
+
2. Gõ "Preferences: Open User Settings (JSON)"
|
|
149
|
+
|
|
150
|
+
### Bước 2: Thêm MCP server config
|
|
151
|
+
|
|
152
|
+
Tìm section `cline.mcpServers` và thêm:
|
|
153
|
+
|
|
154
|
+
**Cách 1: Sử dụng npx (Khuyến nghị)**
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"cline.mcpServers": {
|
|
159
|
+
"gearvn-pages": {
|
|
160
|
+
"command": "npx",
|
|
161
|
+
"args": [
|
|
162
|
+
"-y",
|
|
163
|
+
"/absolute/path/to/backend-webpage-go/mcp-server"
|
|
164
|
+
],
|
|
165
|
+
"env": {
|
|
166
|
+
"API_KEY": "sk-your-api-key-here"
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Cách 2: Chạy trực tiếp với node**
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"cline.mcpServers": {
|
|
178
|
+
"gearvn-pages": {
|
|
179
|
+
"command": "node",
|
|
180
|
+
"args": [
|
|
181
|
+
"/absolute/path/to/backend-webpage-go/mcp-server/dist/index.js"
|
|
182
|
+
],
|
|
183
|
+
"env": {
|
|
184
|
+
"API_KEY": "sk-your-api-key-here"
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Bước 3: Reload VS Code
|
|
192
|
+
|
|
193
|
+
Reload window để Cline load MCP server.
|
|
194
|
+
|
|
195
|
+
## Test MCP Server
|
|
196
|
+
|
|
197
|
+
Bạn có thể test MCP server bằng cách chạy trực tiếp:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
export API_KEY="sk-your-api-key-here"
|
|
201
|
+
npm start
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Server sẽ chạy trên stdio và chờ nhận input theo MCP protocol.
|
|
205
|
+
|
|
206
|
+
## Ví dụ sử dụng trong Claude
|
|
207
|
+
|
|
208
|
+
Sau khi cấu hình xong, bạn có thể sử dụng trong Claude:
|
|
209
|
+
|
|
210
|
+
### Deploy một trang mới
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
Hãy deploy một trang HTML với:
|
|
214
|
+
- Title: "Test Page"
|
|
215
|
+
- Content: Một trang HTML hoàn chỉnh với TailwindCSS và một nút button
|
|
216
|
+
- Slug: "test-page-1"
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Claude sẽ tự động tạo HTML document hoàn chỉnh với TailwindCSS CDN, sử dụng tool `deploy-page` và trả về URL của trang.
|
|
220
|
+
|
|
221
|
+
**Lưu ý:** AI Agent sẽ tự động convert nội dung thành HTML chuẩn với:
|
|
222
|
+
- TailwindCSS CDN link trong `<head>`
|
|
223
|
+
- TailwindCSS utility classes cho styling
|
|
224
|
+
- JavaScript libraries qua CDN nếu cần (Alpine.js, Chart.js, etc.)
|
|
225
|
+
- Vanilla JavaScript cũng được (không dùng SPA frameworks như React/Vue/Angular)
|
|
226
|
+
|
|
227
|
+
### List các pages
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
Hãy liệt kê tất cả các pages của tôi
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Claude sẽ sử dụng tool `list-pages` và hiển thị danh sách pages.
|
|
234
|
+
|
|
235
|
+
## Development
|
|
236
|
+
|
|
237
|
+
### Watch mode
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
npm run watch
|
|
241
|
+
# hoặc
|
|
242
|
+
pnpm watch
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Sẽ tự động rebuild khi có thay đổi trong source code.
|
|
246
|
+
|
|
247
|
+
## Troubleshooting
|
|
248
|
+
|
|
249
|
+
### MCP server không xuất hiện trong Claude
|
|
250
|
+
|
|
251
|
+
1. Kiểm tra file config đã đúng format JSON
|
|
252
|
+
2. Kiểm tra đường dẫn đến `dist/index.js` là absolute path
|
|
253
|
+
3. Kiểm tra đã build project: `npm run build`
|
|
254
|
+
4. Restart Claude Desktop
|
|
255
|
+
|
|
256
|
+
### API Key không hoạt động
|
|
257
|
+
|
|
258
|
+
1. Kiểm tra API key đã được set đúng trong env với key là `API_KEY`
|
|
259
|
+
2. Kiểm tra API key còn valid bằng cách call API trực tiếp:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
curl -X GET https://pages.react.uat.gearvn.xyz/v1/pages \
|
|
263
|
+
-H "X-API-Key: your-api-key"
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Permission denied khi chạy
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
chmod +x dist/index.js
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Cấu trúc project
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
mcp-server/
|
|
276
|
+
├── src/
|
|
277
|
+
│ └── index.ts # MCP server implementation
|
|
278
|
+
├── dist/ # Compiled JavaScript (generated)
|
|
279
|
+
├── package.json
|
|
280
|
+
├── tsconfig.json
|
|
281
|
+
└── README.md
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## License
|
|
285
|
+
|
|
286
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
// Configuration
|
|
6
|
+
const API_BASE_URL = "https://pages.react.uat.gearvn.xyz";
|
|
7
|
+
const API_KEY = process.env.API_KEY;
|
|
8
|
+
if (!API_KEY) {
|
|
9
|
+
console.error("Error: API_KEY environment variable is required");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
// API helper function
|
|
13
|
+
async function callAPI(endpoint, method = "GET", body) {
|
|
14
|
+
const url = `${API_BASE_URL}${endpoint}`;
|
|
15
|
+
const headers = {
|
|
16
|
+
"X-API-Key": API_KEY,
|
|
17
|
+
};
|
|
18
|
+
if (body) {
|
|
19
|
+
headers["Content-Type"] = "application/json";
|
|
20
|
+
}
|
|
21
|
+
const response = await fetch(url, {
|
|
22
|
+
method,
|
|
23
|
+
headers,
|
|
24
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
25
|
+
});
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
const errorText = await response.text();
|
|
28
|
+
throw new Error(`API request failed: ${response.status} ${errorText}`);
|
|
29
|
+
}
|
|
30
|
+
return response.json();
|
|
31
|
+
}
|
|
32
|
+
// Tool definitions
|
|
33
|
+
const DEPLOY_PAGE_TOOL = {
|
|
34
|
+
name: "deploy-page",
|
|
35
|
+
description: "Deploy or update a static HTML page. If the slug already exists, the page will be updated with the new content and title. IMPORTANT: The content must be a complete, valid HTML document with TailwindCSS for styling. JavaScript libraries are allowed but MUST be loaded via CDN (e.g., Alpine.js, Chart.js, GSAP, etc.). No build tools or npm packages. No React, Vue, or Angular frameworks allowed.",
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {
|
|
39
|
+
title: {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "The title of the page",
|
|
42
|
+
},
|
|
43
|
+
content: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "The complete HTML content of the page. Must include <!DOCTYPE html>, <html>, <head> with TailwindCSS CDN link (https://cdn.tailwindcss.com), and <body> tags. Use TailwindCSS utility classes for all styling. JavaScript libraries are allowed but must be imported via CDN links in <script> tags (e.g., Alpine.js from CDN, Chart.js from CDN). Vanilla JavaScript is also acceptable. NO build tools, npm packages, or SPA frameworks (React/Vue/Angular).",
|
|
46
|
+
},
|
|
47
|
+
slug: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "The URL slug for the page (e.g., 'my-page-123'). This will be used in the URL as /pages/{slug}",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
required: ["title", "content", "slug"],
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const LIST_PAGES_TOOL = {
|
|
56
|
+
name: "list-pages",
|
|
57
|
+
description: "List all pages associated with your API key. Returns a summary of each page including id, slug, title, and timestamps. Does not include page content.",
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
// Create MCP server
|
|
64
|
+
const server = new Server({
|
|
65
|
+
name: "gearvn-pages-mcp-server",
|
|
66
|
+
version: "1.0.0",
|
|
67
|
+
}, {
|
|
68
|
+
capabilities: {
|
|
69
|
+
tools: {},
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
// Handler for listing available tools
|
|
73
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
74
|
+
return {
|
|
75
|
+
tools: [DEPLOY_PAGE_TOOL, LIST_PAGES_TOOL],
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
// Handler for tool calls
|
|
79
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
80
|
+
const { name, arguments: args } = request.params;
|
|
81
|
+
try {
|
|
82
|
+
if (name === "deploy-page") {
|
|
83
|
+
const { title, content, slug } = args;
|
|
84
|
+
// Validate inputs
|
|
85
|
+
if (!title || !content || !slug) {
|
|
86
|
+
throw new Error("Missing required parameters: title, content, and slug are required");
|
|
87
|
+
}
|
|
88
|
+
// Call the deploy API
|
|
89
|
+
const response = await callAPI("/v1/deploy", "POST", {
|
|
90
|
+
title,
|
|
91
|
+
content,
|
|
92
|
+
slug,
|
|
93
|
+
});
|
|
94
|
+
return {
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: JSON.stringify({
|
|
99
|
+
success: true,
|
|
100
|
+
message: "Page deployed successfully",
|
|
101
|
+
url: response.url,
|
|
102
|
+
}, null, 2),
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
else if (name === "list-pages") {
|
|
108
|
+
// Call the list pages API
|
|
109
|
+
const response = await callAPI("/v1/pages", "GET");
|
|
110
|
+
return {
|
|
111
|
+
content: [
|
|
112
|
+
{
|
|
113
|
+
type: "text",
|
|
114
|
+
text: JSON.stringify({
|
|
115
|
+
success: true,
|
|
116
|
+
count: response.count,
|
|
117
|
+
pages: response.pages,
|
|
118
|
+
}, null, 2),
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
129
|
+
return {
|
|
130
|
+
content: [
|
|
131
|
+
{
|
|
132
|
+
type: "text",
|
|
133
|
+
text: JSON.stringify({
|
|
134
|
+
success: false,
|
|
135
|
+
error: errorMessage,
|
|
136
|
+
}, null, 2),
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
isError: true,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
// Start the server
|
|
144
|
+
async function main() {
|
|
145
|
+
const transport = new StdioServerTransport();
|
|
146
|
+
await server.connect(transport);
|
|
147
|
+
console.error("GearVN Pages MCP Server running on stdio");
|
|
148
|
+
}
|
|
149
|
+
main().catch((error) => {
|
|
150
|
+
console.error("Fatal error:", error);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
});
|
|
153
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gearvn-pages-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP Server for GearVN Pages deployment and management",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"gearvn-pages-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"watch": "tsc --watch",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"gearvn"
|
|
20
|
+
],
|
|
21
|
+
"author": "GearVN",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@modelcontextprotocol/sdk": "^1.0.4"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^22.10.2",
|
|
28
|
+
"typescript": "^5.7.2"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
Tool,
|
|
9
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
|
|
11
|
+
// Configuration
|
|
12
|
+
const API_BASE_URL = "https://pages.react.uat.gearvn.xyz";
|
|
13
|
+
const API_KEY = process.env.API_KEY;
|
|
14
|
+
|
|
15
|
+
if (!API_KEY) {
|
|
16
|
+
console.error("Error: API_KEY environment variable is required");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Types
|
|
21
|
+
interface DeployPageArgs {
|
|
22
|
+
title: string;
|
|
23
|
+
content: string;
|
|
24
|
+
slug: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface DeployPageResponse {
|
|
28
|
+
url: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface PageSummary {
|
|
32
|
+
id: number;
|
|
33
|
+
slug: string;
|
|
34
|
+
title: string;
|
|
35
|
+
created_at: string;
|
|
36
|
+
updated_at: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface ListPagesResponse {
|
|
40
|
+
count: number;
|
|
41
|
+
pages: PageSummary[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// API helper function
|
|
45
|
+
async function callAPI<T>(
|
|
46
|
+
endpoint: string,
|
|
47
|
+
method: string = "GET",
|
|
48
|
+
body?: unknown
|
|
49
|
+
): Promise<T> {
|
|
50
|
+
const url = `${API_BASE_URL}${endpoint}`;
|
|
51
|
+
const headers: Record<string, string> = {
|
|
52
|
+
"X-API-Key": API_KEY!,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (body) {
|
|
56
|
+
headers["Content-Type"] = "application/json";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const response = await fetch(url, {
|
|
60
|
+
method,
|
|
61
|
+
headers,
|
|
62
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
const errorText = await response.text();
|
|
67
|
+
throw new Error(`API request failed: ${response.status} ${errorText}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return response.json() as Promise<T>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Tool definitions
|
|
74
|
+
const DEPLOY_PAGE_TOOL: Tool = {
|
|
75
|
+
name: "deploy-page",
|
|
76
|
+
description:
|
|
77
|
+
"Deploy or update a static HTML page. If the slug already exists, the page will be updated with the new content and title. IMPORTANT: The content must be a complete, valid HTML document with TailwindCSS for styling. JavaScript libraries are allowed but MUST be loaded via CDN (e.g., Alpine.js, Chart.js, GSAP, etc.). No build tools or npm packages. No React, Vue, or Angular frameworks allowed.",
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: "object",
|
|
80
|
+
properties: {
|
|
81
|
+
title: {
|
|
82
|
+
type: "string",
|
|
83
|
+
description: "The title of the page",
|
|
84
|
+
},
|
|
85
|
+
content: {
|
|
86
|
+
type: "string",
|
|
87
|
+
description: "The complete HTML content of the page. Must include <!DOCTYPE html>, <html>, <head> with TailwindCSS CDN link (https://cdn.tailwindcss.com), and <body> tags. Use TailwindCSS utility classes for all styling. JavaScript libraries are allowed but must be imported via CDN links in <script> tags (e.g., Alpine.js from CDN, Chart.js from CDN). Vanilla JavaScript is also acceptable. NO build tools, npm packages, or SPA frameworks (React/Vue/Angular).",
|
|
88
|
+
},
|
|
89
|
+
slug: {
|
|
90
|
+
type: "string",
|
|
91
|
+
description:
|
|
92
|
+
"The URL slug for the page (e.g., 'my-page-123'). This will be used in the URL as /pages/{slug}",
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
required: ["title", "content", "slug"],
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const LIST_PAGES_TOOL: Tool = {
|
|
100
|
+
name: "list-pages",
|
|
101
|
+
description:
|
|
102
|
+
"List all pages associated with your API key. Returns a summary of each page including id, slug, title, and timestamps. Does not include page content.",
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: "object",
|
|
105
|
+
properties: {},
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Create MCP server
|
|
110
|
+
const server = new Server(
|
|
111
|
+
{
|
|
112
|
+
name: "gearvn-pages-mcp-server",
|
|
113
|
+
version: "1.0.0",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
capabilities: {
|
|
117
|
+
tools: {},
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Handler for listing available tools
|
|
123
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
124
|
+
return {
|
|
125
|
+
tools: [DEPLOY_PAGE_TOOL, LIST_PAGES_TOOL],
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Handler for tool calls
|
|
130
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
131
|
+
const { name, arguments: args } = request.params;
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
if (name === "deploy-page") {
|
|
135
|
+
const { title, content, slug } = args as unknown as DeployPageArgs;
|
|
136
|
+
|
|
137
|
+
// Validate inputs
|
|
138
|
+
if (!title || !content || !slug) {
|
|
139
|
+
throw new Error("Missing required parameters: title, content, and slug are required");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Call the deploy API
|
|
143
|
+
const response = await callAPI<DeployPageResponse>("/v1/deploy", "POST", {
|
|
144
|
+
title,
|
|
145
|
+
content,
|
|
146
|
+
slug,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
content: [
|
|
151
|
+
{
|
|
152
|
+
type: "text",
|
|
153
|
+
text: JSON.stringify(
|
|
154
|
+
{
|
|
155
|
+
success: true,
|
|
156
|
+
message: "Page deployed successfully",
|
|
157
|
+
url: response.url,
|
|
158
|
+
},
|
|
159
|
+
null,
|
|
160
|
+
2
|
|
161
|
+
),
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
};
|
|
165
|
+
} else if (name === "list-pages") {
|
|
166
|
+
// Call the list pages API
|
|
167
|
+
const response = await callAPI<ListPagesResponse>("/v1/pages", "GET");
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
content: [
|
|
171
|
+
{
|
|
172
|
+
type: "text",
|
|
173
|
+
text: JSON.stringify(
|
|
174
|
+
{
|
|
175
|
+
success: true,
|
|
176
|
+
count: response.count,
|
|
177
|
+
pages: response.pages,
|
|
178
|
+
},
|
|
179
|
+
null,
|
|
180
|
+
2
|
|
181
|
+
),
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
};
|
|
185
|
+
} else {
|
|
186
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
190
|
+
return {
|
|
191
|
+
content: [
|
|
192
|
+
{
|
|
193
|
+
type: "text",
|
|
194
|
+
text: JSON.stringify(
|
|
195
|
+
{
|
|
196
|
+
success: false,
|
|
197
|
+
error: errorMessage,
|
|
198
|
+
},
|
|
199
|
+
null,
|
|
200
|
+
2
|
|
201
|
+
),
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
isError: true,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Start the server
|
|
210
|
+
async function main() {
|
|
211
|
+
const transport = new StdioServerTransport();
|
|
212
|
+
await server.connect(transport);
|
|
213
|
+
console.error("GearVN Pages MCP Server running on stdio");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
main().catch((error) => {
|
|
217
|
+
console.error("Fatal error:", error);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationMap": true,
|
|
16
|
+
"sourceMap": true
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "dist"]
|
|
20
|
+
}
|