stackblitz-mcp 0.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/LICENSE +21 -0
- package/README.md +119 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +316 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright © 2026-PRESENT Kevin Deng (https://github.com/sxzz)
|
|
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,119 @@
|
|
|
1
|
+
# stackblitz-mcp
|
|
2
|
+
|
|
3
|
+
[![npm version][npm-version-src]][npm-version-href]
|
|
4
|
+
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
5
|
+
[![Unit Test][unit-test-src]][unit-test-href]
|
|
6
|
+
|
|
7
|
+
[MCP](https://modelcontextprotocol.io/) server for reading files from [StackBlitz](https://stackblitz.com/) projects. Enables AI to access file contents and project structure from StackBlitz reproduction repositories.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i -g stackblitz-mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Claude Desktop
|
|
18
|
+
|
|
19
|
+
Add to your `claude_desktop_config.json`:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"stackblitz": {
|
|
25
|
+
"command": "npx",
|
|
26
|
+
"args": ["-y", "stackblitz-mcp"]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Claude Code
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
claude mcp add stackblitz -- npx -y stackblitz-mcp
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Cursor
|
|
39
|
+
|
|
40
|
+
Add to your `.cursor/mcp.json`:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"stackblitz": {
|
|
46
|
+
"command": "npx",
|
|
47
|
+
"args": ["-y", "stackblitz-mcp"]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Tools
|
|
54
|
+
|
|
55
|
+
### `resolve_project`
|
|
56
|
+
|
|
57
|
+
Resolve a StackBlitz project URL or ID and return project metadata (title, description, preset, visibility, file count).
|
|
58
|
+
|
|
59
|
+
**Input:**
|
|
60
|
+
|
|
61
|
+
- `projectRef` (string) — Project ID or StackBlitz URL (e.g. `stackblitz-starters-rf7brvcm` or `https://stackblitz.com/edit/stackblitz-starters-rf7brvcm`)
|
|
62
|
+
|
|
63
|
+
### `list_files`
|
|
64
|
+
|
|
65
|
+
List files in a StackBlitz project as an ASCII tree, optionally filtered by path prefix.
|
|
66
|
+
|
|
67
|
+
**Input:**
|
|
68
|
+
|
|
69
|
+
- `projectRef` (string) — Project ID or URL
|
|
70
|
+
- `path` (string, optional) — Filter files by path prefix
|
|
71
|
+
|
|
72
|
+
### `read_file`
|
|
73
|
+
|
|
74
|
+
Read the contents of a file from a StackBlitz project.
|
|
75
|
+
|
|
76
|
+
**Input:**
|
|
77
|
+
|
|
78
|
+
- `projectRef` (string) — Project ID or URL
|
|
79
|
+
- `path` (string) — File path within the project
|
|
80
|
+
|
|
81
|
+
### `search_files`
|
|
82
|
+
|
|
83
|
+
Search for content within files of a StackBlitz project.
|
|
84
|
+
|
|
85
|
+
**Input:**
|
|
86
|
+
|
|
87
|
+
- `projectRef` (string) — Project ID or URL
|
|
88
|
+
- `query` (string) — Search query
|
|
89
|
+
- `regex` (boolean, default: `false`) — Treat query as regex
|
|
90
|
+
- `caseSensitive` (boolean, default: `false`) — Case-sensitive search
|
|
91
|
+
- `maxResults` (number, default: `50`) — Maximum number of results
|
|
92
|
+
|
|
93
|
+
## Resources
|
|
94
|
+
|
|
95
|
+
| URI Pattern | Description |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `stackblitz://{projectId}/tree` | File tree of a project |
|
|
98
|
+
| `stackblitz://{projectId}/files/{path}` | Contents of a specific file |
|
|
99
|
+
|
|
100
|
+
## Sponsors
|
|
101
|
+
|
|
102
|
+
<p align="center">
|
|
103
|
+
<a href="https://cdn.jsdelivr.net/gh/sxzz/sponsors/sponsors.svg">
|
|
104
|
+
<img src='https://cdn.jsdelivr.net/gh/sxzz/sponsors/sponsors.svg'/>
|
|
105
|
+
</a>
|
|
106
|
+
</p>
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
[MIT](./LICENSE) License © 2026-PRESENT [Kevin Deng](https://github.com/sxzz)
|
|
111
|
+
|
|
112
|
+
<!-- Badges -->
|
|
113
|
+
|
|
114
|
+
[npm-version-src]: https://img.shields.io/npm/v/stackblitz-mcp.svg
|
|
115
|
+
[npm-version-href]: https://npmjs.com/package/stackblitz-mcp
|
|
116
|
+
[npm-downloads-src]: https://img.shields.io/npm/dm/stackblitz-mcp
|
|
117
|
+
[npm-downloads-href]: https://www.npmcharts.com/compare/stackblitz-mcp?interval=30
|
|
118
|
+
[unit-test-src]: https://github.com/sxzz/stackblitz-mcp/actions/workflows/unit-test.yml/badge.svg
|
|
119
|
+
[unit-test-href]: https://github.com/sxzz/stackblitz-mcp/actions/workflows/unit-test.yml
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
//#region src/stackblitz.ts
|
|
8
|
+
const cache = /* @__PURE__ */ new Map();
|
|
9
|
+
const CACHE_TTL = 300 * 1e3;
|
|
10
|
+
const CACHE_MAX_SIZE = 50;
|
|
11
|
+
const KNOWN_PATH_SEGMENTS = new Set([
|
|
12
|
+
"edit",
|
|
13
|
+
"fork",
|
|
14
|
+
"github",
|
|
15
|
+
"~"
|
|
16
|
+
]);
|
|
17
|
+
function resolveProjectId(ref) {
|
|
18
|
+
let url;
|
|
19
|
+
try {
|
|
20
|
+
url = new URL(ref);
|
|
21
|
+
} catch {
|
|
22
|
+
return ref;
|
|
23
|
+
}
|
|
24
|
+
if (url.hostname !== "stackblitz.com") throw new Error(`Unsupported URL: only stackblitz.com URLs are supported, got ${url.hostname}`);
|
|
25
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
26
|
+
if (parts[0] === "edit" && parts[1]) return parts[1];
|
|
27
|
+
if (parts.length === 1 && !KNOWN_PATH_SEGMENTS.has(parts[0])) return parts[0];
|
|
28
|
+
throw new Error(`Could not extract project ID from URL: ${ref}`);
|
|
29
|
+
}
|
|
30
|
+
async function fetchProject(projectId) {
|
|
31
|
+
const cached = cache.get(projectId);
|
|
32
|
+
if (cached && cached.expiry > Date.now()) return cached.data;
|
|
33
|
+
const response = await fetch(`https://stackblitz.com/api/projects/${encodeURIComponent(projectId)}?include_files=true`);
|
|
34
|
+
if (!response.ok) throw new Error(`Failed to fetch project ${projectId}: ${response.status} ${response.statusText}`);
|
|
35
|
+
const data = await response.json();
|
|
36
|
+
if (cache.size >= CACHE_MAX_SIZE) {
|
|
37
|
+
const oldest = cache.keys().next().value;
|
|
38
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
39
|
+
}
|
|
40
|
+
cache.set(projectId, {
|
|
41
|
+
data,
|
|
42
|
+
expiry: Date.now() + CACHE_TTL
|
|
43
|
+
});
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
function getFileList(project) {
|
|
47
|
+
return Object.values(project.appFiles).map((f) => f.fullPath).toSorted();
|
|
48
|
+
}
|
|
49
|
+
function getFileContents(project, path) {
|
|
50
|
+
return Object.values(project.appFiles).find((f) => f.fullPath === path);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/tree.ts
|
|
55
|
+
function buildTree(paths) {
|
|
56
|
+
const root = {
|
|
57
|
+
path: "",
|
|
58
|
+
name: "",
|
|
59
|
+
type: "directory",
|
|
60
|
+
children: []
|
|
61
|
+
};
|
|
62
|
+
for (const filePath of paths.toSorted()) {
|
|
63
|
+
const parts = filePath.split("/");
|
|
64
|
+
let current = root;
|
|
65
|
+
for (let i = 0; i < parts.length; i++) {
|
|
66
|
+
const part = parts[i];
|
|
67
|
+
const isFile = i === parts.length - 1;
|
|
68
|
+
const currentPath = parts.slice(0, i + 1).join("/");
|
|
69
|
+
if (!current.children) current.children = [];
|
|
70
|
+
if (isFile) current.children.push({
|
|
71
|
+
path: currentPath,
|
|
72
|
+
name: part,
|
|
73
|
+
type: "file"
|
|
74
|
+
});
|
|
75
|
+
else {
|
|
76
|
+
let dir = current.children.find((c) => c.name === part && c.type === "directory");
|
|
77
|
+
if (!dir) {
|
|
78
|
+
dir = {
|
|
79
|
+
path: currentPath,
|
|
80
|
+
name: part,
|
|
81
|
+
type: "directory",
|
|
82
|
+
children: []
|
|
83
|
+
};
|
|
84
|
+
current.children.push(dir);
|
|
85
|
+
}
|
|
86
|
+
current = dir;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return root;
|
|
91
|
+
}
|
|
92
|
+
function formatTree(node) {
|
|
93
|
+
if (!node.children || node.children.length === 0) return "";
|
|
94
|
+
const sorted = sortChildren(node.children);
|
|
95
|
+
const lines = [];
|
|
96
|
+
for (let i = 0; i < sorted.length; i++) formatNode(sorted[i], "", i === sorted.length - 1, lines);
|
|
97
|
+
return lines.join("\n");
|
|
98
|
+
}
|
|
99
|
+
function sortChildren(children) {
|
|
100
|
+
return children.toSorted((a, b) => {
|
|
101
|
+
if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
|
|
102
|
+
return a.name.localeCompare(b.name);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function formatNode(node, prefix, isLast, lines) {
|
|
106
|
+
const connector = isLast ? "└── " : "├── ";
|
|
107
|
+
const suffix = node.type === "directory" ? "/" : "";
|
|
108
|
+
lines.push(`${prefix}${connector}${node.name}${suffix}`);
|
|
109
|
+
if (node.children && node.children.length > 0) {
|
|
110
|
+
const childPrefix = prefix + (isLast ? " " : "│ ");
|
|
111
|
+
const sorted = sortChildren(node.children);
|
|
112
|
+
for (let i = 0; i < sorted.length; i++) formatNode(sorted[i], childPrefix, i === sorted.length - 1, lines);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/index.ts
|
|
118
|
+
const server = new McpServer({
|
|
119
|
+
name: "stackblitz-mcp",
|
|
120
|
+
version: "0.1.0"
|
|
121
|
+
});
|
|
122
|
+
server.registerResource("project_tree", new ResourceTemplate("stackblitz://{projectId}/tree", { list: void 0 }), {
|
|
123
|
+
description: "File tree of a StackBlitz project",
|
|
124
|
+
mimeType: "text/plain"
|
|
125
|
+
}, async (uri, variables) => {
|
|
126
|
+
const { project } = await fetchProject(resolveProjectId(String(variables.projectId)));
|
|
127
|
+
const tree = buildTree(getFileList(project));
|
|
128
|
+
return { contents: [{
|
|
129
|
+
uri: uri.href,
|
|
130
|
+
mimeType: "text/plain",
|
|
131
|
+
text: formatTree(tree)
|
|
132
|
+
}] };
|
|
133
|
+
});
|
|
134
|
+
server.registerResource("project_file", new ResourceTemplate("stackblitz://{projectId}/files/{+path}", { list: void 0 }), { description: "Contents of a file in a StackBlitz project" }, async (uri, variables) => {
|
|
135
|
+
const projectId = resolveProjectId(String(variables.projectId));
|
|
136
|
+
const path = String(variables.path);
|
|
137
|
+
const { project } = await fetchProject(projectId);
|
|
138
|
+
const file = getFileContents(project, path);
|
|
139
|
+
if (!file) throw new Error(`File not found: ${path}`);
|
|
140
|
+
return { contents: [{
|
|
141
|
+
uri: uri.href,
|
|
142
|
+
mimeType: guessMimeType(path),
|
|
143
|
+
text: file.contents
|
|
144
|
+
}] };
|
|
145
|
+
});
|
|
146
|
+
server.registerTool("resolve_project", {
|
|
147
|
+
description: "Resolve a StackBlitz project URL or ID and return project metadata",
|
|
148
|
+
inputSchema: { projectRef: z.string().min(1).describe("StackBlitz project ID or URL") }
|
|
149
|
+
}, async ({ projectRef }) => {
|
|
150
|
+
const projectId = resolveProjectId(projectRef);
|
|
151
|
+
const { project } = await fetchProject(projectId);
|
|
152
|
+
const files = getFileList(project);
|
|
153
|
+
return { content: [{
|
|
154
|
+
type: "text",
|
|
155
|
+
text: JSON.stringify({
|
|
156
|
+
projectId,
|
|
157
|
+
title: project.title,
|
|
158
|
+
description: project.description,
|
|
159
|
+
preset: project.preset,
|
|
160
|
+
visibility: project.visibility,
|
|
161
|
+
fileCount: files.length
|
|
162
|
+
}, null, 2)
|
|
163
|
+
}, {
|
|
164
|
+
type: "resource_link",
|
|
165
|
+
uri: `stackblitz://${projectId}/tree`,
|
|
166
|
+
name: `${projectId} file tree`,
|
|
167
|
+
mimeType: "text/plain"
|
|
168
|
+
}] };
|
|
169
|
+
});
|
|
170
|
+
server.registerTool("list_files", {
|
|
171
|
+
description: "List files in a StackBlitz project, optionally filtered by path prefix",
|
|
172
|
+
inputSchema: {
|
|
173
|
+
projectRef: z.string().min(1).describe("StackBlitz project ID or URL"),
|
|
174
|
+
path: z.string().optional().describe("Filter files by path prefix")
|
|
175
|
+
}
|
|
176
|
+
}, async ({ projectRef, path }) => {
|
|
177
|
+
const { project } = await fetchProject(resolveProjectId(projectRef));
|
|
178
|
+
let files = getFileList(project);
|
|
179
|
+
if (path) {
|
|
180
|
+
const prefix = path.endsWith("/") ? path : `${path}/`;
|
|
181
|
+
files = files.filter((f) => f.startsWith(prefix) || f === path);
|
|
182
|
+
}
|
|
183
|
+
return { content: [{
|
|
184
|
+
type: "text",
|
|
185
|
+
text: formatTree(buildTree(files))
|
|
186
|
+
}] };
|
|
187
|
+
});
|
|
188
|
+
server.registerTool("read_file", {
|
|
189
|
+
description: "Read the contents of a file from a StackBlitz project",
|
|
190
|
+
inputSchema: {
|
|
191
|
+
projectRef: z.string().min(1).describe("StackBlitz project ID or URL"),
|
|
192
|
+
path: z.string().min(1).describe("File path within the project")
|
|
193
|
+
}
|
|
194
|
+
}, async ({ projectRef, path }) => {
|
|
195
|
+
const projectId = resolveProjectId(projectRef);
|
|
196
|
+
const { project } = await fetchProject(projectId);
|
|
197
|
+
const file = getFileContents(project, path);
|
|
198
|
+
if (!file) {
|
|
199
|
+
const files = getFileList(project);
|
|
200
|
+
const basename = path.split("/").pop() || "";
|
|
201
|
+
const suggestions = files.filter((f) => f.includes(basename)).slice(0, 5);
|
|
202
|
+
return {
|
|
203
|
+
content: [{
|
|
204
|
+
type: "text",
|
|
205
|
+
text: `File not found: ${path}${suggestions.length ? `\n\nDid you mean:\n${suggestions.map((s) => ` - ${s}`).join("\n")}` : ""}`
|
|
206
|
+
}],
|
|
207
|
+
isError: true
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
return { content: [{
|
|
211
|
+
type: "text",
|
|
212
|
+
text: file.contents
|
|
213
|
+
}, {
|
|
214
|
+
type: "resource_link",
|
|
215
|
+
uri: `stackblitz://${projectId}/files/${path}`,
|
|
216
|
+
name: path,
|
|
217
|
+
mimeType: guessMimeType(path)
|
|
218
|
+
}] };
|
|
219
|
+
});
|
|
220
|
+
server.registerTool("search_files", {
|
|
221
|
+
description: "Search for content within files of a StackBlitz project",
|
|
222
|
+
inputSchema: {
|
|
223
|
+
projectRef: z.string().min(1).describe("StackBlitz project ID or URL"),
|
|
224
|
+
query: z.string().min(1).describe("Search query string"),
|
|
225
|
+
regex: z.boolean().default(false).describe("Treat query as regex"),
|
|
226
|
+
caseSensitive: z.boolean().default(false).describe("Case-sensitive search"),
|
|
227
|
+
maxResults: z.number().int().positive().default(50).describe("Maximum number of results")
|
|
228
|
+
}
|
|
229
|
+
}, async ({ projectRef, query, regex, caseSensitive, maxResults }) => {
|
|
230
|
+
const { project } = await fetchProject(resolveProjectId(projectRef));
|
|
231
|
+
let pattern;
|
|
232
|
+
if (regex) try {
|
|
233
|
+
pattern = new RegExp(query, caseSensitive ? "g" : "gi");
|
|
234
|
+
} catch (error) {
|
|
235
|
+
return {
|
|
236
|
+
content: [{
|
|
237
|
+
type: "text",
|
|
238
|
+
text: `Invalid regex: ${error instanceof Error ? error.message : String(error)}`
|
|
239
|
+
}],
|
|
240
|
+
isError: true
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const searchQuery = caseSensitive ? query : query.toLowerCase();
|
|
244
|
+
const matches = [];
|
|
245
|
+
for (const file of Object.values(project.appFiles)) {
|
|
246
|
+
if (matches.length >= maxResults) break;
|
|
247
|
+
const lines = file.contents.split("\n");
|
|
248
|
+
for (const [i, line] of lines.entries()) {
|
|
249
|
+
if (matches.length >= maxResults) break;
|
|
250
|
+
let found;
|
|
251
|
+
if (pattern) {
|
|
252
|
+
found = pattern.test(line);
|
|
253
|
+
pattern.lastIndex = 0;
|
|
254
|
+
} else found = (caseSensitive ? line : line.toLowerCase()).includes(searchQuery);
|
|
255
|
+
if (found) matches.push({
|
|
256
|
+
file: file.fullPath,
|
|
257
|
+
line: i + 1,
|
|
258
|
+
text: line.trimEnd()
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (matches.length === 0) return { content: [{
|
|
263
|
+
type: "text",
|
|
264
|
+
text: `No matches found for "${query}"`
|
|
265
|
+
}] };
|
|
266
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
267
|
+
for (const match of matches) {
|
|
268
|
+
const group = grouped.get(match.file) || [];
|
|
269
|
+
group.push(match);
|
|
270
|
+
grouped.set(match.file, group);
|
|
271
|
+
}
|
|
272
|
+
let output = `Found ${matches.length} match${matches.length === 1 ? "" : "es"} in ${grouped.size} file${grouped.size === 1 ? "" : "s"}:\n\n`;
|
|
273
|
+
for (const [file, fileMatches] of grouped) {
|
|
274
|
+
output += `## ${file}\n`;
|
|
275
|
+
for (const m of fileMatches) output += ` L${m.line}: ${m.text}\n`;
|
|
276
|
+
output += "\n";
|
|
277
|
+
}
|
|
278
|
+
return { content: [{
|
|
279
|
+
type: "text",
|
|
280
|
+
text: output.trimEnd()
|
|
281
|
+
}] };
|
|
282
|
+
});
|
|
283
|
+
const MIME_TYPES = {
|
|
284
|
+
ts: "text/typescript",
|
|
285
|
+
tsx: "text/typescript",
|
|
286
|
+
js: "text/javascript",
|
|
287
|
+
jsx: "text/javascript",
|
|
288
|
+
json: "application/json",
|
|
289
|
+
md: "text/markdown",
|
|
290
|
+
html: "text/html",
|
|
291
|
+
css: "text/css",
|
|
292
|
+
scss: "text/scss",
|
|
293
|
+
less: "text/less",
|
|
294
|
+
svg: "image/svg+xml",
|
|
295
|
+
yaml: "text/yaml",
|
|
296
|
+
yml: "text/yaml",
|
|
297
|
+
xml: "text/xml",
|
|
298
|
+
txt: "text/plain",
|
|
299
|
+
vue: "text/x-vue",
|
|
300
|
+
svelte: "text/x-svelte"
|
|
301
|
+
};
|
|
302
|
+
function guessMimeType(path) {
|
|
303
|
+
return MIME_TYPES[path.split(".").pop()?.toLowerCase() || ""] || "text/plain";
|
|
304
|
+
}
|
|
305
|
+
async function main() {
|
|
306
|
+
const transport = new StdioServerTransport();
|
|
307
|
+
await server.connect(transport);
|
|
308
|
+
console.error("StackBlitz MCP server running on stdio");
|
|
309
|
+
}
|
|
310
|
+
main().catch((error) => {
|
|
311
|
+
console.error("Fatal error:", error);
|
|
312
|
+
process.exit(1);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
//#endregion
|
|
316
|
+
export { };
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "stackblitz-mcp",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"description": "MCP server for reading files from StackBlitz projects",
|
|
6
|
+
"author": "Kevin Deng <sxzz@sxzz.moe>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"funding": "https://github.com/sponsors/sxzz",
|
|
9
|
+
"homepage": "https://github.com/sxzz/stackblitz-mcp#readme",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/sxzz/stackblitz-mcp.git"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/sxzz/stackblitz-mcp/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"model-context-protocol",
|
|
20
|
+
"stackblitz",
|
|
21
|
+
"ai",
|
|
22
|
+
"llm"
|
|
23
|
+
],
|
|
24
|
+
"exports": {
|
|
25
|
+
".": "./dist/index.mjs",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"stackblitz-mcp": "./dist/index.mjs"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=20.19.0"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
42
|
+
"zod": "^3.25.23"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@sxzz/eslint-config": "^7.5.1",
|
|
46
|
+
"@sxzz/prettier-config": "^2.2.6",
|
|
47
|
+
"@types/node": "^25.0.10",
|
|
48
|
+
"@typescript/native-preview": "7.0.0-dev.20260124.1",
|
|
49
|
+
"bumpp": "^10.4.0",
|
|
50
|
+
"eslint": "^9.39.2",
|
|
51
|
+
"prettier": "^3.8.1",
|
|
52
|
+
"tsdown": "^0.20.1",
|
|
53
|
+
"tsdown-preset-sxzz": "^0.3.1",
|
|
54
|
+
"typescript": "^5.9.3",
|
|
55
|
+
"vitest": "^4.0.18"
|
|
56
|
+
},
|
|
57
|
+
"prettier": "@sxzz/prettier-config",
|
|
58
|
+
"scripts": {
|
|
59
|
+
"lint": "eslint --cache .",
|
|
60
|
+
"lint:fix": "pnpm run lint --fix",
|
|
61
|
+
"build": "tsdown",
|
|
62
|
+
"dev": "tsdown --watch",
|
|
63
|
+
"test": "vitest",
|
|
64
|
+
"typecheck": "tsgo --noEmit",
|
|
65
|
+
"format": "prettier --cache --write .",
|
|
66
|
+
"release": "bumpp"
|
|
67
|
+
}
|
|
68
|
+
}
|