enzostvs HF Staff commited on
Commit
03687eb
·
1 Parent(s): 6982784

Add MCP server implementation and documentation

Browse files
Files changed (2) hide show
  1. MCP-SERVER.md +428 -0
  2. app/api/mcp/route.ts +366 -0
MCP-SERVER.md ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DeepSite MCP Server
2
+
3
+ DeepSite is now available as an MCP (Model Context Protocol) server, enabling AI assistants like Claude to create websites directly using natural language.
4
+
5
+ ## Two Ways to Use DeepSite MCP
6
+
7
+ **Quick Comparison:**
8
+
9
+ | Feature | Option 1: HTTP Server | Option 2: Local Server |
10
+ |---------|----------------------|------------------------|
11
+ | **Setup Difficulty** | ✅ Easy (just config) | ⚠️ Requires installation |
12
+ | **Authentication** | HF Token in config header | HF Token or session cookie in env |
13
+ | **Best For** | Most users | Developers, custom modifications |
14
+ | **Maintenance** | ✅ Always up-to-date | Need to rebuild for updates |
15
+
16
+ **Recommendation:** Use Option 1 (HTTP Server) unless you need to modify the MCP server code.
17
+
18
+ ---
19
+
20
+ ### 🌐 Option 1: HTTP Server (Recommended)
21
+
22
+ **No installation required!** Use DeepSite's hosted MCP server.
23
+
24
+ #### Setup for Claude Desktop
25
+
26
+ Add to your Claude Desktop configuration file:
27
+
28
+ **MacOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
29
+ **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "deepsite": {
35
+ "url": "https://deepsite.hf.co/api/mcp",
36
+ "transport": {
37
+ "type": "sse"
38
+ },
39
+ "headers": {
40
+ "Authorization": "Bearer hf_your_token_here"
41
+ }
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ **Getting Your Hugging Face Token:**
48
+
49
+ 1. Go to https://huggingface.co/settings/tokens
50
+ 2. Create a new token with `write` access
51
+ 3. Copy the token
52
+ 4. Add it to the `Authorization` header in your config (recommended for security)
53
+ 5. Alternatively, you can pass it as the `hf_token` parameter when using the tool
54
+
55
+ **⚠️ Security Recommendation:** Use the `Authorization` header in your config instead of passing the token in chat. This keeps your token secure and out of conversation history.
56
+
57
+ #### Example Usage with Claude
58
+
59
+ > "Create a portfolio website using DeepSite. Include a hero section, about section, and contact form."
60
+
61
+ Claude will automatically:
62
+ 1. Use the `create_project` tool
63
+ 2. Authenticate using the token from your config
64
+ 3. Create the website on Hugging Face Spaces
65
+ 4. Return the URLs to access your new site
66
+
67
+ ---
68
+
69
+ ### 💻 Option 2: Local Server
70
+
71
+ Run the MCP server locally for more control or offline use.
72
+
73
+ > **Note:** Most users should use Option 1 (HTTP Server) instead. Option 2 is only needed if you want to run the MCP server locally or modify its behavior.
74
+
75
+ #### Installation
76
+
77
+ ```bash
78
+ cd mcp-server
79
+ npm install
80
+ npm run build
81
+ ```
82
+
83
+ #### Setup for Claude Desktop
84
+
85
+ **Method A: Using HF Token (Recommended)**
86
+
87
+ ```json
88
+ {
89
+ "mcpServers": {
90
+ "deepsite-local": {
91
+ "command": "node",
92
+ "args": ["/absolute/path/to/deepsite-v3/mcp-server/dist/index.js"],
93
+ "env": {
94
+ "HF_TOKEN": "hf_your_token_here",
95
+ "DEEPSITE_API_URL": "https://deepsite.hf.co"
96
+ }
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ **Method B: Using Session Cookie (Alternative)**
103
+
104
+ ```json
105
+ {
106
+ "mcpServers": {
107
+ "deepsite-local": {
108
+ "command": "node",
109
+ "args": ["/absolute/path/to/deepsite-v3/mcp-server/dist/index.js"],
110
+ "env": {
111
+ "DEEPSITE_AUTH_COOKIE": "your-session-cookie",
112
+ "DEEPSITE_API_URL": "https://deepsite.hf.co"
113
+ }
114
+ }
115
+ }
116
+ }
117
+ ```
118
+
119
+ **Getting Your Session Cookie (Method B only):**
120
+
121
+ 1. Log in to https://deepsite.hf.co
122
+ 2. Open Developer Tools (F12)
123
+ 3. Go to Application → Cookies
124
+ 4. Copy the session cookie value
125
+ 5. Set as `DEEPSITE_AUTH_COOKIE` in the config
126
+
127
+ ---
128
+
129
+ ## Available Tools
130
+
131
+ ### `create_project`
132
+
133
+ Creates a new DeepSite project with HTML/CSS/JS files.
134
+
135
+ **Parameters:**
136
+
137
+ | Parameter | Type | Required | Description |
138
+ |-----------|------|----------|-------------|
139
+ | `title` | string | No | Project title (defaults to "DeepSite Project") |
140
+ | `pages` | array | Yes | Array of file objects with `path` and `html` |
141
+ | `prompt` | string | No | Commit message/description |
142
+ | `hf_token` | string | No* | Hugging Face API token (*optional if provided via Authorization header in config) |
143
+
144
+ **Page Object:**
145
+ ```typescript
146
+ {
147
+ path: string; // e.g., "index.html", "styles.css", "script.js"
148
+ html: string; // File content
149
+ }
150
+ ```
151
+
152
+ **Returns:**
153
+ ```json
154
+ {
155
+ "success": true,
156
+ "message": "Project created successfully!",
157
+ "projectUrl": "https://deepsite.hf.co/username/project-name",
158
+ "spaceUrl": "https://huggingface.co/spaces/username/project-name",
159
+ "liveUrl": "https://username-project-name.hf.space",
160
+ "spaceId": "username/project-name",
161
+ "projectId": "space-id",
162
+ "files": ["index.html", "styles.css"]
163
+ }
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Example Prompts for Claude
169
+
170
+ ### Simple Landing Page
171
+ > "Create a modern landing page for my SaaS product using DeepSite. Include a hero section with CTA, features grid, and footer. Use gradient background."
172
+
173
+ ### Portfolio Website
174
+ > "Build a portfolio website with DeepSite. I need:
175
+ > - Hero section with my name and photo
176
+ > - Projects gallery with 3 sample projects
177
+ > - Skills section with tech stack
178
+ > - Contact form
179
+ > Use dark mode with accent colors."
180
+
181
+ ### Blog Homepage
182
+ > "Create a blog homepage using DeepSite. Include:
183
+ > - Header with navigation
184
+ > - Featured post section
185
+ > - Grid of recent posts (3 cards)
186
+ > - Sidebar with categories
187
+ > - Footer with social links
188
+ > Clean, minimal design."
189
+
190
+ ### Interactive Dashboard
191
+ > "Make an analytics dashboard with DeepSite:
192
+ > - Sidebar navigation
193
+ > - 4 metric cards at top
194
+ > - 2 chart placeholders
195
+ > - Data table
196
+ > - Modern, professional UI with charts.css"
197
+
198
+ ---
199
+
200
+ ## Direct API Usage
201
+
202
+ You can also call the HTTP endpoint directly:
203
+
204
+ ### Using Authorization Header (Recommended)
205
+
206
+ ```bash
207
+ curl -X POST https://deepsite.hf.co/api/mcp \
208
+ -H "Content-Type: application/json" \
209
+ -H "Authorization: Bearer hf_your_token_here" \
210
+ -d '{
211
+ "jsonrpc": "2.0",
212
+ "id": 1,
213
+ "method": "tools/call",
214
+ "params": {
215
+ "name": "create_project",
216
+ "arguments": {
217
+ "title": "My Website",
218
+ "pages": [
219
+ {
220
+ "path": "index.html",
221
+ "html": "<!DOCTYPE html><html><head><title>Hello</title></head><body><h1>Hello World!</h1></body></html>"
222
+ }
223
+ ]
224
+ }
225
+ }
226
+ }'
227
+ ```
228
+
229
+ ### Using Token Parameter (Fallback)
230
+
231
+ ```bash
232
+ curl -X POST https://deepsite.hf.co/api/mcp \
233
+ -H "Content-Type: application/json" \
234
+ -d '{
235
+ "jsonrpc": "2.0",
236
+ "id": 1,
237
+ "method": "tools/call",
238
+ "params": {
239
+ "name": "create_project",
240
+ "arguments": {
241
+ "title": "My Website",
242
+ "pages": [
243
+ {
244
+ "path": "index.html",
245
+ "html": "<!DOCTYPE html><html><head><title>Hello</title></head><body><h1>Hello World!</h1></body></html>"
246
+ }
247
+ ],
248
+ "hf_token": "hf_xxxxx"
249
+ }
250
+ }
251
+ }'
252
+ ```
253
+
254
+ ### List Available Tools
255
+
256
+ ```bash
257
+ curl -X POST https://deepsite.hf.co/api/mcp \
258
+ -H "Content-Type: application/json" \
259
+ -d '{
260
+ "jsonrpc": "2.0",
261
+ "id": 1,
262
+ "method": "tools/list",
263
+ "params": {}
264
+ }'
265
+ ```
266
+
267
+ ---
268
+
269
+ ## Testing
270
+
271
+ ### Test Local Server
272
+
273
+ ```bash
274
+ cd mcp-server
275
+ ./test.sh
276
+ ```
277
+
278
+ ### Test HTTP Server
279
+
280
+ ```bash
281
+ curl -X POST https://deepsite.hf.co/api/mcp \
282
+ -H "Content-Type: application/json" \
283
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Migration Guide: From Parameter to Header Auth
289
+
290
+ If you're currently passing the token as a parameter in your prompts, here's how to migrate to the more secure header-based authentication:
291
+
292
+ ### Step 1: Update Your Config
293
+
294
+ Edit your Claude Desktop config file and add the `headers` section:
295
+
296
+ ```json
297
+ {
298
+ "mcpServers": {
299
+ "deepsite": {
300
+ "url": "https://deepsite.hf.co/api/mcp",
301
+ "transport": {
302
+ "type": "sse"
303
+ },
304
+ "headers": {
305
+ "Authorization": "Bearer hf_your_actual_token_here"
306
+ }
307
+ }
308
+ }
309
+ }
310
+ ```
311
+
312
+ ### Step 2: Restart Claude Desktop
313
+
314
+ Completely quit and restart Claude Desktop for the changes to take effect.
315
+
316
+ ### Step 3: Use Simpler Prompts
317
+
318
+ Now you can simply say:
319
+ > "Create a portfolio website with DeepSite"
320
+
321
+ Instead of:
322
+ > "Create a portfolio website with DeepSite using token `hf_xxxxx`"
323
+
324
+ Your token is automatically included in all requests via the header!
325
+
326
+ ---
327
+
328
+ ## Security Notes
329
+
330
+ ### HTTP Server (Option 1)
331
+ - **✅ Recommended:** Store your HF token in the `Authorization` header in your Claude Desktop config
332
+ - The token is stored locally on your machine and never exposed in chat
333
+ - The token is sent with each request but only used to authenticate with Hugging Face API
334
+ - DeepSite does not store your token
335
+ - Use tokens with minimal required permissions (write access to spaces)
336
+ - You can revoke tokens anytime at https://huggingface.co/settings/tokens
337
+ - **⚠️ Fallback:** You can still pass the token as a parameter, but this is less secure as it appears in conversation history
338
+
339
+ ### Local Server (Option 2)
340
+ - Use `HF_TOKEN` environment variable (same security as Option 1)
341
+ - Or use `DEEPSITE_AUTH_COOKIE` if you prefer session-based auth
342
+ - All authentication data stays on your local machine
343
+ - Better for development and testing
344
+ - No need for both HTTP Server and Local Server - choose one!
345
+
346
+ ---
347
+
348
+ ## Troubleshooting
349
+
350
+ ### "Invalid Hugging Face token"
351
+ - Verify your token at https://huggingface.co/settings/tokens
352
+ - Ensure the token has write permissions
353
+ - Check that you copied the full token (starts with `hf_`)
354
+
355
+ ### "At least one page is required"
356
+ - Make sure you're providing the `pages` array
357
+ - Each page must have both `path` and `html` properties
358
+
359
+ ### "Failed to create project"
360
+ - Check your token permissions
361
+ - Ensure the project title doesn't conflict with existing spaces
362
+ - Verify your Hugging Face account is in good standing
363
+
364
+ ### Claude doesn't see the tool
365
+ - Restart Claude Desktop after modifying the config
366
+ - Check that the JSON config is valid (no trailing commas)
367
+ - For HTTP: verify the URL is correct
368
+ - For local: check the absolute path to index.js
369
+
370
+ ---
371
+
372
+ ## Architecture
373
+
374
+ ### HTTP Server Flow
375
+ ```
376
+ Claude Desktop
377
+
378
+ (HTTP Request)
379
+
380
+ deepsite.hf.co/api/mcp
381
+
382
+ Hugging Face API (with user's token)
383
+
384
+ New Space Created
385
+
386
+ URLs returned to Claude
387
+ ```
388
+
389
+ ### Local Server Flow
390
+ ```
391
+ Claude Desktop
392
+
393
+ (stdio transport)
394
+
395
+ Local MCP Server
396
+
397
+ (HTTP to DeepSite API)
398
+
399
+ deepsite.hf.co/api/me/projects
400
+
401
+ New Space Created
402
+ ```
403
+
404
+ ---
405
+
406
+ ## Contributing
407
+
408
+ The MCP server implementation lives in:
409
+ - HTTP Server: `/app/api/mcp/route.ts`
410
+ - Local Server: `/mcp-server/index.ts`
411
+
412
+ Both use the same core DeepSite logic for creating projects - no duplication!
413
+
414
+ ---
415
+
416
+ ## License
417
+
418
+ MIT
419
+
420
+ ---
421
+
422
+ ## Resources
423
+
424
+ - [Model Context Protocol Spec](https://modelcontextprotocol.io/)
425
+ - [DeepSite Documentation](https://deepsite.hf.co)
426
+ - [Hugging Face Spaces](https://huggingface.co/docs/hub/spaces)
427
+ - [Claude Desktop](https://claude.ai/desktop)
428
+
app/api/mcp/route.ts ADDED
@@ -0,0 +1,366 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { RepoDesignation, createRepo, uploadFiles, spaceInfo, listCommits } from "@huggingface/hub";
3
+ import { COLORS } from "@/lib/utils";
4
+ import { injectDeepSiteBadge, isIndexPage } from "@/lib/inject-badge";
5
+ import { Commit, Page } from "@/types";
6
+
7
+ interface MCPRequest {
8
+ jsonrpc: "2.0";
9
+ id: number | string;
10
+ method: string;
11
+ params?: any;
12
+ }
13
+
14
+ interface MCPResponse {
15
+ jsonrpc: "2.0";
16
+ id: number | string;
17
+ result?: any;
18
+ error?: {
19
+ code: number;
20
+ message: string;
21
+ data?: any;
22
+ };
23
+ }
24
+
25
+ interface CreateProjectParams {
26
+ title?: string;
27
+ pages: Page[];
28
+ prompt?: string;
29
+ hf_token?: string; // Optional - can come from header instead
30
+ }
31
+
32
+ // MCP Server over HTTP
33
+ export async function POST(req: NextRequest) {
34
+ try {
35
+ const body: MCPRequest = await req.json();
36
+ const { jsonrpc, id, method, params } = body;
37
+
38
+ // Validate JSON-RPC 2.0 format
39
+ if (jsonrpc !== "2.0") {
40
+ return NextResponse.json({
41
+ jsonrpc: "2.0",
42
+ id: id || null,
43
+ error: {
44
+ code: -32600,
45
+ message: "Invalid Request: jsonrpc must be '2.0'",
46
+ },
47
+ });
48
+ }
49
+
50
+ let response: MCPResponse;
51
+
52
+ switch (method) {
53
+ case "initialize":
54
+ response = {
55
+ jsonrpc: "2.0",
56
+ id,
57
+ result: {
58
+ protocolVersion: "2024-11-05",
59
+ capabilities: {
60
+ tools: {},
61
+ },
62
+ serverInfo: {
63
+ name: "deepsite-mcp-server",
64
+ version: "1.0.0",
65
+ },
66
+ },
67
+ };
68
+ break;
69
+
70
+ case "tools/list":
71
+ response = {
72
+ jsonrpc: "2.0",
73
+ id,
74
+ result: {
75
+ tools: [
76
+ {
77
+ name: "create_project",
78
+ description: `Create a new DeepSite project. This will create a new Hugging Face Space with your HTML/CSS/JS files.
79
+
80
+ Example usage:
81
+ - Create a simple website with HTML, CSS, and JavaScript files
82
+ - Each page needs a 'path' (filename like "index.html", "styles.css", "script.js") and 'html' (the actual content)
83
+ - The title will be formatted to a valid repository name
84
+ - Returns the project URL and metadata`,
85
+ inputSchema: {
86
+ type: "object",
87
+ properties: {
88
+ title: {
89
+ type: "string",
90
+ description: "Project title (optional, defaults to 'DeepSite Project'). Will be formatted to a valid repo name.",
91
+ },
92
+ pages: {
93
+ type: "array",
94
+ description: "Array of files to include in the project",
95
+ items: {
96
+ type: "object",
97
+ properties: {
98
+ path: {
99
+ type: "string",
100
+ description: "File path (e.g., 'index.html', 'styles.css', 'script.js')",
101
+ },
102
+ html: {
103
+ type: "string",
104
+ description: "File content",
105
+ },
106
+ },
107
+ required: ["path", "html"],
108
+ },
109
+ },
110
+ prompt: {
111
+ type: "string",
112
+ description: "Optional prompt/description for the commit message",
113
+ },
114
+ hf_token: {
115
+ type: "string",
116
+ description: "Hugging Face API token (optional if provided via Authorization header)",
117
+ },
118
+ },
119
+ required: ["pages"],
120
+ },
121
+ },
122
+ ],
123
+ },
124
+ };
125
+ break;
126
+
127
+ case "tools/call":
128
+ const { name, arguments: toolArgs } = params;
129
+
130
+ if (name === "create_project") {
131
+ try {
132
+ // Extract token from Authorization header if present
133
+ const authHeader = req.headers.get("authorization");
134
+ let hf_token = toolArgs.hf_token;
135
+
136
+ if (authHeader && authHeader.startsWith("Bearer ")) {
137
+ hf_token = authHeader.substring(7); // Remove "Bearer " prefix
138
+ }
139
+
140
+ const result = await handleCreateProject({
141
+ ...toolArgs,
142
+ hf_token,
143
+ } as CreateProjectParams);
144
+ response = {
145
+ jsonrpc: "2.0",
146
+ id,
147
+ result,
148
+ };
149
+ } catch (error: any) {
150
+ response = {
151
+ jsonrpc: "2.0",
152
+ id,
153
+ error: {
154
+ code: -32000,
155
+ message: error.message || "Failed to create project",
156
+ data: error.data,
157
+ },
158
+ };
159
+ }
160
+ } else {
161
+ response = {
162
+ jsonrpc: "2.0",
163
+ id,
164
+ error: {
165
+ code: -32601,
166
+ message: `Unknown tool: ${name}`,
167
+ },
168
+ };
169
+ }
170
+ break;
171
+
172
+ default:
173
+ response = {
174
+ jsonrpc: "2.0",
175
+ id,
176
+ error: {
177
+ code: -32601,
178
+ message: `Method not found: ${method}`,
179
+ },
180
+ };
181
+ }
182
+
183
+ return NextResponse.json(response);
184
+ } catch (error: any) {
185
+ return NextResponse.json({
186
+ jsonrpc: "2.0",
187
+ id: null,
188
+ error: {
189
+ code: -32700,
190
+ message: "Parse error",
191
+ data: error.message,
192
+ },
193
+ });
194
+ }
195
+ }
196
+
197
+ // Handle OPTIONS for CORS
198
+ export async function OPTIONS() {
199
+ return new NextResponse(null, {
200
+ status: 200,
201
+ headers: {
202
+ "Access-Control-Allow-Origin": "*",
203
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
204
+ "Access-Control-Allow-Headers": "Content-Type",
205
+ },
206
+ });
207
+ }
208
+
209
+ async function handleCreateProject(params: CreateProjectParams) {
210
+ const { title: titleFromRequest, pages, prompt, hf_token } = params;
211
+
212
+ // Validate required parameters
213
+ if (!hf_token || typeof hf_token !== "string") {
214
+ throw new Error("hf_token is required and must be a string");
215
+ }
216
+
217
+ if (!pages || !Array.isArray(pages) || pages.length === 0) {
218
+ throw new Error("At least one page is required");
219
+ }
220
+
221
+ // Validate that each page has required fields
222
+ for (const page of pages) {
223
+ if (!page.path || !page.html) {
224
+ throw new Error("Each page must have 'path' and 'html' properties");
225
+ }
226
+ }
227
+
228
+ // Get user info from HF token
229
+ let username: string;
230
+ try {
231
+ const userResponse = await fetch("https://huggingface.co/api/whoami-v2", {
232
+ headers: {
233
+ Authorization: `Bearer ${hf_token}`,
234
+ },
235
+ });
236
+
237
+ if (!userResponse.ok) {
238
+ throw new Error("Invalid Hugging Face token");
239
+ }
240
+
241
+ const userData = await userResponse.json();
242
+ username = userData.name;
243
+ } catch (error: any) {
244
+ throw new Error(`Authentication failed: ${error.message}`);
245
+ }
246
+
247
+ const title = titleFromRequest ?? "DeepSite Project";
248
+
249
+ const formattedTitle = title
250
+ .toLowerCase()
251
+ .replace(/[^a-z0-9]+/g, "-")
252
+ .split("-")
253
+ .filter(Boolean)
254
+ .join("-")
255
+ .slice(0, 96);
256
+
257
+ const repo: RepoDesignation = {
258
+ type: "space",
259
+ name: `${username}/${formattedTitle}`,
260
+ };
261
+
262
+ const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)];
263
+ const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)];
264
+ const README = `---
265
+ title: ${title}
266
+ colorFrom: ${colorFrom}
267
+ colorTo: ${colorTo}
268
+ emoji: 🐳
269
+ sdk: static
270
+ pinned: false
271
+ tags:
272
+ - deepsite-v3
273
+ ---
274
+
275
+ # Welcome to your new DeepSite project!
276
+ This project was created with [DeepSite](https://deepsite.hf.co).
277
+ `;
278
+
279
+ const files: File[] = [];
280
+ const readmeFile = new File([README], "README.md", { type: "text/markdown" });
281
+ files.push(readmeFile);
282
+
283
+ pages.forEach((page: Page) => {
284
+ // Determine MIME type based on file extension
285
+ let mimeType = "text/html";
286
+ if (page.path.endsWith(".css")) {
287
+ mimeType = "text/css";
288
+ } else if (page.path.endsWith(".js")) {
289
+ mimeType = "text/javascript";
290
+ } else if (page.path.endsWith(".json")) {
291
+ mimeType = "application/json";
292
+ }
293
+
294
+ // Inject the DeepSite badge script into index pages only
295
+ const content = mimeType === "text/html" && isIndexPage(page.path)
296
+ ? injectDeepSiteBadge(page.html)
297
+ : page.html;
298
+ const file = new File([content], page.path, { type: mimeType });
299
+ files.push(file);
300
+ });
301
+
302
+ try {
303
+ const { repoUrl } = await createRepo({
304
+ repo,
305
+ accessToken: hf_token,
306
+ });
307
+
308
+ const commitTitle = !prompt || prompt.trim() === "" ? "Initial project creation via MCP" : prompt;
309
+
310
+ await uploadFiles({
311
+ repo,
312
+ files,
313
+ accessToken: hf_token,
314
+ commitTitle,
315
+ });
316
+
317
+ const path = repoUrl.split("/").slice(-2).join("/");
318
+
319
+ const commits: Commit[] = [];
320
+ for await (const commit of listCommits({ repo, accessToken: hf_token })) {
321
+ if (commit.title.includes("initial commit") || commit.title.includes("image(s)") || commit.title.includes("Promote version")) {
322
+ continue;
323
+ }
324
+ commits.push({
325
+ title: commit.title,
326
+ oid: commit.oid,
327
+ date: commit.date,
328
+ });
329
+ }
330
+
331
+ const space = await spaceInfo({
332
+ name: repo.name,
333
+ accessToken: hf_token,
334
+ });
335
+
336
+ const projectUrl = `https://deepsite.hf.co/${path}`;
337
+ const spaceUrl = `https://huggingface.co/spaces/${path}`;
338
+ const liveUrl = `https://${username}-${formattedTitle}.hf.space`;
339
+
340
+ return {
341
+ content: [
342
+ {
343
+ type: "text",
344
+ text: JSON.stringify(
345
+ {
346
+ success: true,
347
+ message: "Project created successfully!",
348
+ projectUrl,
349
+ spaceUrl,
350
+ liveUrl,
351
+ spaceId: space.name,
352
+ projectId: space.id,
353
+ files: pages.map((p) => p.path),
354
+ updatedAt: space.updatedAt,
355
+ },
356
+ null,
357
+ 2
358
+ ),
359
+ },
360
+ ],
361
+ };
362
+ } catch (err: any) {
363
+ throw new Error(err.message || "Failed to create project");
364
+ }
365
+ }
366
+