stitch-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/.github/ISSUE_TEMPLATE/bug_report.md +31 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
- package/.github/workflows/ci.yml +34 -0
- package/.github/workflows/release.yml +44 -0
- package/CONTRIBUTING.md +34 -0
- package/LICENSE +21 -0
- package/README.md +92 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +9 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
- package/src/cli.ts +7 -0
- package/src/handlers/prompts.ts +32 -0
- package/src/handlers/resources.ts +54 -0
- package/src/handlers/tools.ts +248 -0
- package/src/index.ts +40 -0
- package/src/setup.ts +129 -0
- package/src/utils/stitch.ts +15 -0
- package/tests/tools.test.ts +149 -0
- package/tsconfig.json +16 -0
- package/tsup.config.ts +10 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Create a report to help us improve the MCP server
|
|
4
|
+
title: '[BUG] '
|
|
5
|
+
labels: bug
|
|
6
|
+
assignees: ''
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
**Describe the bug**
|
|
10
|
+
A clear and concise description of what the bug is.
|
|
11
|
+
|
|
12
|
+
**To Reproduce**
|
|
13
|
+
Steps to reproduce the behavior:
|
|
14
|
+
1. Call tool '...'
|
|
15
|
+
2. With arguments '...'
|
|
16
|
+
3. See error
|
|
17
|
+
|
|
18
|
+
**Expected behavior**
|
|
19
|
+
A clear and concise description of what you expected to happen.
|
|
20
|
+
|
|
21
|
+
**Screenshots**
|
|
22
|
+
If applicable, add screenshots to help explain your problem.
|
|
23
|
+
|
|
24
|
+
**Environment (please complete the following information):**
|
|
25
|
+
- OS: [e.g. iOS, Windows]
|
|
26
|
+
- Node Version: [e.g. 20.x]
|
|
27
|
+
- MCP Client: [e.g. Claude Code, VS Code, Cursor]
|
|
28
|
+
- Version [e.g. 1.0.0]
|
|
29
|
+
|
|
30
|
+
**Additional context**
|
|
31
|
+
Add any other context about the problem here (e.g., error logs).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest an idea for this project
|
|
4
|
+
title: '[FEAT] '
|
|
5
|
+
labels: enhancement
|
|
6
|
+
assignees: ''
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
**Is your feature request related to a problem? Please describe.**
|
|
10
|
+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
11
|
+
|
|
12
|
+
**Describe the solution you'd like**
|
|
13
|
+
A clear and concise description of what you want to happen (e.g., a new MCP tool, a new resource).
|
|
14
|
+
|
|
15
|
+
**Describe alternatives you've considered**
|
|
16
|
+
A clear and concise description of any alternative solutions or features you've considered.
|
|
17
|
+
|
|
18
|
+
**Additional context**
|
|
19
|
+
Add any other context or screenshots about the feature request here.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ "main" ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ "main" ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build-and-test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
node-version: [20.x, 22.x]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
20
|
+
uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: ${{ matrix.node-version }}
|
|
23
|
+
cache: 'npm'
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: npm ci
|
|
27
|
+
|
|
28
|
+
- name: Build project
|
|
29
|
+
run: npm run build
|
|
30
|
+
|
|
31
|
+
- name: Run Tests
|
|
32
|
+
env:
|
|
33
|
+
STITCH_API_KEY: ${{ secrets.STITCH_API_KEY }}
|
|
34
|
+
run: npm run test
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Release and Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*.*.*'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build-and-publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- name: Checkout Code
|
|
13
|
+
uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Setup Node.js
|
|
16
|
+
uses: actions/setup-node@v4
|
|
17
|
+
with:
|
|
18
|
+
node-version: '20'
|
|
19
|
+
registry-url: 'https://registry.npmjs.org/'
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: npm ci
|
|
23
|
+
|
|
24
|
+
- name: Build
|
|
25
|
+
run: npm run build
|
|
26
|
+
|
|
27
|
+
# You can uncomment this step once you have an NPM_TOKEN set in your GitHub secrets
|
|
28
|
+
# - name: Publish to NPM
|
|
29
|
+
# run: npm publish --access public
|
|
30
|
+
# env:
|
|
31
|
+
# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
32
|
+
|
|
33
|
+
create-github-release:
|
|
34
|
+
needs: build-and-publish
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
permissions:
|
|
37
|
+
contents: write
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v4
|
|
40
|
+
|
|
41
|
+
- name: Create Release
|
|
42
|
+
uses: softprops/action-gh-release@v2
|
|
43
|
+
with:
|
|
44
|
+
generate_release_notes: true
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Contributing to Stitch MCP Server
|
|
2
|
+
|
|
3
|
+
First off, thank you for considering contributing to the Stitch MCP Server! It's people like you that make the open source community such a great place to learn, inspire, and create.
|
|
4
|
+
|
|
5
|
+
## How to Contribute
|
|
6
|
+
|
|
7
|
+
1. **Fork the Repository**: Start by forking the repository to your GitHub account.
|
|
8
|
+
2. **Clone the File**: Clone your fork locally (`git clone https://github.com/0x-Professor/Stitch-mcp-server.git`).
|
|
9
|
+
3. **Install Dependencies**: Run `npm install` to install necessary Node modules.
|
|
10
|
+
4. **Branch Out**: Create a new branch for your feature or bug fix (`git checkout -b feature/your-feature-name`).
|
|
11
|
+
5. **Develop**: Write your code. Make sure your changes follow the existing style and pass tests (if applicable).
|
|
12
|
+
6. **Build**: Ensure the project still builds perfectly with `npm run build`.
|
|
13
|
+
7. **Commit**: Keep your commit messages clear and concise.
|
|
14
|
+
8. **Push & Pull Request**: Push your branch to GitHub and submit a Pull Request against the `main` branch.
|
|
15
|
+
|
|
16
|
+
## Setting Up Your Development Environment
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Install dependencies
|
|
20
|
+
npm install
|
|
21
|
+
|
|
22
|
+
# Build the Node server locally
|
|
23
|
+
npm run build
|
|
24
|
+
|
|
25
|
+
# Start the server (requires an API Key)
|
|
26
|
+
STITCH_API_KEY="your-api-key" npm start
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Code of Conduct
|
|
30
|
+
|
|
31
|
+
By participating in this project, you agree to abide by friendly and professional conduct. We expect contributors to respect each other and communicate courteously.
|
|
32
|
+
|
|
33
|
+
## Bug Reports and Feature Requests
|
|
34
|
+
Please use the GitHub Issues tab to report any bugs you find or feature requests you might have. Ensure that your reports are reproducible and your feature requests outline clear user benefits.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stitch MCP Server Contributors
|
|
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,92 @@
|
|
|
1
|
+
# Stitch MCP Server
|
|
2
|
+
|
|
3
|
+
An enterprise-ready Model Context Protocol (MCP) server for the [Google Stitch SDK](https://github.com/google-labs-code/stitch-sdk).
|
|
4
|
+
|
|
5
|
+
This server acts as a bridge between your coding agent (Claude, Gemini, Copilot, Cursor, etc.) and Google's Stitch UI Generation API. It exposes powerful native tools, intelligent macro features, resources, and templates so AI agents can fluidly design, generate, and scaffold UI components straight into your workspace.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Standard Tools**: Direct support for Native Stitch functionalities (`create_project`, `generate_screen`, `edit_screen`, `list_projects`, `generate_variants`).
|
|
10
|
+
- **Advanced Agent Macro Tools**:
|
|
11
|
+
- `get_screen_code`: Fetches raw HTML for generated screens.
|
|
12
|
+
- `get_screen_image`: Fetches screenshot images associated with your screens.
|
|
13
|
+
- `generate_and_fetch_code`: Create a UI and retrieve its HTML code in a single round-trip, saving extreme token latency and minimizing prompt complexity.
|
|
14
|
+
- `scaffold_project_files`: Seamlessly save and map an abstract Stitch UI directly to local files in the active workspace.
|
|
15
|
+
- **Context Resources**: Map abstract IDs natively with `stitch://projects` and `stitch://projects/{projectId}/screens` context providers.
|
|
16
|
+
- **Bootstrapping Prompts**: Leverage the native `create_web_app` prompt for zero-shot orchestration to guide agents from project creation to scaffolding.
|
|
17
|
+
|
|
18
|
+
## Installation & Setup
|
|
19
|
+
|
|
20
|
+
You need an active `STITCH_API_KEY` to run this server. You can sign up with your Google account to get one.
|
|
21
|
+
|
|
22
|
+
This package comes with a built-in automated installer for **Claude Desktop**, **Cline**, and **Cursor**.
|
|
23
|
+
|
|
24
|
+
### The Easy Way (Interactive Setup)
|
|
25
|
+
|
|
26
|
+
Run the following command anywhere on your machine. It will ask which AI tools you want to configure, and it will safely insert the server and prompt you for your `STITCH_API_KEY`:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx -y stitch-mcp-server@latest setup
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
That's it! Restart your target application and the tools will appear.
|
|
33
|
+
|
|
34
|
+
### The Manual Way (JSON Config)
|
|
35
|
+
|
|
36
|
+
If you prefer to configure your client manually, use the following snippet. Ensure you replace `your-api-key` with your actual Stitch API Key.
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"mcpServers": {
|
|
41
|
+
"stitch": {
|
|
42
|
+
"command": "npx",
|
|
43
|
+
"args": ["-y", "stitch-mcp-server@latest"],
|
|
44
|
+
"env": {
|
|
45
|
+
"STITCH_API_KEY": "your-api-key"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Capabilities
|
|
55
|
+
|
|
56
|
+
The `stitch-mcp-server` gives your AI assistant several specialized tools:
|
|
57
|
+
|
|
58
|
+
### Local Development
|
|
59
|
+
1. Install dependencies:
|
|
60
|
+
```bash
|
|
61
|
+
npm install
|
|
62
|
+
```
|
|
63
|
+
2. Build the project:
|
|
64
|
+
```bash
|
|
65
|
+
npm run build
|
|
66
|
+
```
|
|
67
|
+
3. Run the server (or configure your client to run it locally):
|
|
68
|
+
```bash
|
|
69
|
+
STITCH_API_KEY=your-api-key npm start
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Development
|
|
73
|
+
|
|
74
|
+
Built natively with:
|
|
75
|
+
- Node.js & TypeScript
|
|
76
|
+
- `@modelcontextprotocol/sdk` (Official SDK)
|
|
77
|
+
- `@google/stitch-sdk`
|
|
78
|
+
- Zod for rigorous run-time schema validation
|
|
79
|
+
- `tsup` for rapid transparent builds
|
|
80
|
+
|
|
81
|
+
## Publishing to NPM
|
|
82
|
+
|
|
83
|
+
This project is fully weaponized for NPM publishing.
|
|
84
|
+
|
|
85
|
+
1. Ensure you are logged into NPM: `npm login`
|
|
86
|
+
2. Test the build: `npm run build`
|
|
87
|
+
3. Publish publicly: `npm publish --access public`
|
|
88
|
+
|
|
89
|
+
The `package.json` uses the `files` directive to only pack the optimized `dist` folder, keeping the package extremely lightweight.
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
MIT
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import r from"fs";import o from"path";import S from"os";import{checkbox as h,input as w}from"@inquirer/prompts";var n=S.homedir(),t=S.platform(),u=()=>t==="win32"?process.env.APPDATA||o.join(n,"AppData","Roaming"):t==="darwin"?o.join(n,"Library","Application Support"):o.join(n,".config"),g=()=>t==="darwin"?o.join(n,"Library","Application Support","Claude","claude_desktop_config.json"):o.join(u(),"Claude","claude_desktop_config.json"),m=()=>t==="darwin"?o.join(n,"Library","Application Support","Code","User","globalStorage","saoudrizwan.claude-dev","settings","cline_mcp_settings.json"):t==="win32"?o.join(u(),"Code","User","globalStorage","saoudrizwan.claude-dev","settings","cline_mcp_settings.json"):o.join(u(),"Code","User","globalStorage","saoudrizwan.claude-dev","settings","cline_mcp_settings.json"),f=()=>o.join(process.cwd(),".cursor","mcp.json"),C=async()=>{console.log(`
|
|
3
|
+
\u{1F680} Welcome to the Stitch MCP Server Setup!`),console.log(`This utility will automatically configure your favorite tools to use the Stitch MCP Server.
|
|
4
|
+
`);let c=[{name:"Claude Desktop",value:"claude",checked:r.existsSync(g())},{name:"Cline (VS Code Extension)",value:"cline",checked:r.existsSync(m())},{name:"Cursor (Workspace Config)",value:"cursor",checked:r.existsSync(f())}],p=await h({message:"Which tools would you like to configure this MCP Server for?",choices:c});if(p.length===0){console.log("\u274C No tools selected. Exiting.");return}let d=await w({message:"Please enter your Stitch API Key (STITCH_API_KEY):"});if(!d){console.log("\u274C API Key is required. Exiting.");return}console.log(`
|
|
5
|
+
Configuring selected tools...
|
|
6
|
+
`);let y={command:t==="win32"?"npx.cmd":"npx",args:["-y","stitch-mcp-server"],env:{STITCH_API_KEY:d}},l=(e,i="stitch",v=y)=>{let s={mcpServers:{}};try{if(r.existsSync(e)){let a=r.readFileSync(e,"utf-8");s=JSON.parse(a),s.mcpServers||(s.mcpServers={})}else r.mkdirSync(o.dirname(e),{recursive:!0});s.mcpServers[i]=v,r.writeFileSync(e,JSON.stringify(s,null,2)),console.log(`\u2705 Successfully updated: ${e}`)}catch(a){console.error(`\u274C Failed to update ${e}:`,a)}};for(let e of p)if(e==="claude")l(g());else if(e==="cline")l(m());else if(e==="cursor"){let i=f();console.log(`
|
|
7
|
+
\u{1F4A1} Note: Cursor handles MCP per-workspace or via its UI Settings.`),console.log(`Generating workspace config for Cursor at ${i}`),l(i),console.log("\u{1F449} In Cursor: Go to Cursor Settings Menu > Settings > Features > MCP to manage servers visually.")}console.log(`
|
|
8
|
+
\u{1F389} Setup complete! You can now use Stitch-mcp-server in your selected tools.`),console.log("Make sure to restart Claude Desktop or reload your Editor for changes to take effect.")};C().catch(c=>{console.error("An error occurred during setup:",c),process.exitCode=1});
|
|
9
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/setup.ts","../src/cli.ts"],"sourcesContent":["import fs from \"fs\";\r\nimport path from \"path\";\r\nimport os from \"os\";\r\nimport { checkbox, input, confirm } from \"@inquirer/prompts\";\r\n\r\nconst homedir = os.homedir();\r\nconst platform = os.platform();\r\n\r\nconst getAppDataPath = () => {\r\n if (platform === \"win32\") {\r\n return process.env.APPDATA || path.join(homedir, \"AppData\", \"Roaming\");\r\n } else if (platform === \"darwin\") {\r\n return path.join(homedir, \"Library\", \"Application Support\");\r\n } else {\r\n return path.join(homedir, \".config\");\r\n }\r\n};\r\n\r\nconst getClaudeDesktopConfigPath = () => {\r\n if (platform === \"darwin\") {\r\n return path.join(homedir, \"Library\", \"Application Support\", \"Claude\", \"claude_desktop_config.json\");\r\n }\r\n return path.join(getAppDataPath(), \"Claude\", \"claude_desktop_config.json\");\r\n};\r\n\r\nconst getClineConfigPath = () => {\r\n if (platform === \"darwin\") {\r\n return path.join(homedir, \"Library\", \"Application Support\", \"Code\", \"User\", \"globalStorage\", \"saoudrizwan.claude-dev\", \"settings\", \"cline_mcp_settings.json\");\r\n } else if (platform === \"win32\") {\r\n return path.join(getAppDataPath(), \"Code\", \"User\", \"globalStorage\", \"saoudrizwan.claude-dev\", \"settings\", \"cline_mcp_settings.json\");\r\n }\r\n return path.join(getAppDataPath(), \"Code\", \"User\", \"globalStorage\", \"saoudrizwan.claude-dev\", \"settings\", \"cline_mcp_settings.json\");\r\n};\r\n\r\nconst getCursorConfigPath = () => {\r\n return path.join(process.cwd(), \".cursor\", \"mcp.json\");\r\n};\r\n\r\nexport const runSetup = async () => {\r\n console.log(\"\\n🚀 Welcome to the Stitch MCP Server Setup!\");\r\n console.log(\"This utility will automatically configure your favorite tools to use the Stitch MCP Server.\\n\");\r\n\r\n const tools = [\r\n {\r\n name: \"Claude Desktop\",\r\n value: \"claude\",\r\n checked: fs.existsSync(getClaudeDesktopConfigPath()),\r\n },\r\n {\r\n name: \"Cline (VS Code Extension)\",\r\n value: \"cline\",\r\n checked: fs.existsSync(getClineConfigPath()),\r\n },\r\n {\r\n name: \"Cursor (Workspace Config)\",\r\n value: \"cursor\",\r\n checked: fs.existsSync(getCursorConfigPath()),\r\n }\r\n ];\r\n\r\n const selectedTools = await checkbox({\r\n message: \"Which tools would you like to configure this MCP Server for?\",\r\n choices: tools,\r\n });\r\n\r\n if (selectedTools.length === 0) {\r\n console.log(\"❌ No tools selected. Exiting.\");\r\n return;\r\n }\r\n\r\n const apiKey = await input({\r\n message: \"Please enter your Stitch API Key (STITCH_API_KEY):\",\r\n });\r\n\r\n if (!apiKey) {\r\n console.log(\"❌ API Key is required. Exiting.\");\r\n return;\r\n }\r\n\r\n console.log(\"\\nConfiguring selected tools...\\n\");\r\n\r\n const serverCommand = platform === \"win32\" ? \"npx.cmd\" : \"npx\";\r\n const serverArgs = [\"-y\", \"stitch-mcp-server\"];\r\n\r\n const mcpConfigEntry = {\r\n command: serverCommand,\r\n args: serverArgs,\r\n env: {\r\n STITCH_API_KEY: apiKey\r\n }\r\n };\r\n\r\n const updateJsonConfig = (configPath: string, keyName: string = \"stitch\", serverDef = mcpConfigEntry) => {\r\n let config: any = { mcpServers: {} };\r\n try {\r\n if (fs.existsSync(configPath)) {\r\n const fileContent = fs.readFileSync(configPath, \"utf-8\");\r\n config = JSON.parse(fileContent);\r\n if (!config.mcpServers) config.mcpServers = {};\r\n } else {\r\n fs.mkdirSync(path.dirname(configPath), { recursive: true });\r\n }\r\n \r\n config.mcpServers[keyName] = serverDef;\r\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2));\r\n console.log(`✅ Successfully updated: ${configPath}`);\r\n } catch (err) {\r\n console.error(`❌ Failed to update ${configPath}:`, err);\r\n }\r\n };\r\n\r\n for (const tool of selectedTools) {\r\n if (tool === \"claude\") {\r\n updateJsonConfig(getClaudeDesktopConfigPath());\r\n } else if (tool === \"cline\") {\r\n updateJsonConfig(getClineConfigPath());\r\n } else if (tool === \"cursor\") {\r\n const cursorPath = getCursorConfigPath();\r\n console.log(`\\n💡 Note: Cursor handles MCP per-workspace or via its UI Settings.`);\r\n console.log(`Generating workspace config for Cursor at ${cursorPath}`);\r\n updateJsonConfig(cursorPath);\r\n console.log(`👉 In Cursor: Go to Cursor Settings Menu > Settings > Features > MCP to manage servers visually.`);\r\n }\r\n }\r\n\r\n console.log(\"\\n🎉 Setup complete! You can now use Stitch-mcp-server in your selected tools.\");\r\n console.log(\"Make sure to restart Claude Desktop or reload your Editor for changes to take effect.\");\r\n};\r\n\r\n","#!/usr/bin/env node\r\nimport { runSetup } from \"./setup.js\";\r\n\r\nrunSetup().catch((err) => {\r\n console.error(\"An error occurred during setup:\", err);\r\n process.exitCode = 1;\r\n});\r\n"],"mappings":"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","names":["fs","path","os","checkbox","input","homedir","platform","getAppDataPath","getClaudeDesktopConfigPath","getClineConfigPath","getCursorConfigPath","runSetup","tools","selectedTools","apiKey","mcpConfigEntry","updateJsonConfig","configPath","keyName","serverDef","config","fileContent","err","tool","cursorPath","runSetup","err"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{McpServer as E}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as b}from"@modelcontextprotocol/sdk/server/stdio.js";import _ from"dotenv";import{z as r}from"zod";import{Stitch as x,StitchToolClient as w}from"@google/stitch-sdk";function m(){let n=process.env.STITCH_API_KEY;if(!n)throw new Error("STITCH_API_KEY environment variable is missing.");let s=new w({apiKey:n});return{stitch:new x(s),toolClient:s}}import h from"fs/promises";import f from"path";function u(n){let{stitch:s,toolClient:d}=m();n.tool("create_project","Create a new Stitch project",{title:r.string().describe("Title of the new project")},async({title:e})=>{try{let t=await d.callTool("create_project",{title:e});return{content:[{type:"text",text:`Project created successfully!
|
|
3
|
+
|
|
4
|
+
${JSON.stringify(t,null,2)}`}]}}catch(t){return{content:[{type:"text",text:`Error creating project: ${t?.message||String(t)}`}],isError:!0}}}),n.tool("list_projects","List all available Stitch projects",{},async()=>{try{return{content:[{type:"text",text:`Projects:
|
|
5
|
+
${(await s.projects()).map(o=>`- ${o.id}`).join("\\n")}`}]}}catch(e){return{content:[{type:"text",text:`Error listing projects: ${e?.message||String(e)}`}],isError:!0}}}),n.tool("generate_screen","Generate a UI screen from a text prompt within a project",{projectId:r.string().describe("The ID of the project to generate the screen in"),prompt:r.string().describe("Text prompt describing the desired screen (e.g., 'A login page with email and password fields')"),deviceType:r.enum(["MOBILE","DESKTOP","TABLET","AGNOSTIC"]).optional().describe("Target device type")},async({projectId:e,prompt:t,deviceType:o})=>{try{let i=await s.project(e).generate(t,o);return{content:[{type:"text",text:`Screen generated successfully!
|
|
6
|
+
Screen ID: ${i.id}
|
|
7
|
+
Project ID: ${i.projectId}
|
|
8
|
+
Use get_screen_code tool to retrieve the HTML.`}]}}catch(c){return{content:[{type:"text",text:`Error generating screen: ${c?.message||String(c)}`}],isError:!0}}}),n.tool("edit_screen","Edit an existing screen using a text prompt",{projectId:r.string().describe("The ID of the project containing the screen"),screenId:r.string().describe("The ID of the screen to edit"),prompt:r.string().describe("Text prompt describing the changes (e.g., 'Make the background dark and add a sidebar')"),deviceType:r.enum(["MOBILE","DESKTOP","TABLET","AGNOSTIC"]).optional().describe("Target device type"),modelId:r.enum(["GEMINI_3_PRO","GEMINI_3_FLASH"]).optional().describe("Generative model to use")},async({projectId:e,screenId:t,prompt:o,deviceType:c,modelId:i})=>{try{let p=await s.project(e).getScreen(t);return{content:[{type:"text",text:`Screen edited successfully!
|
|
9
|
+
New Screen ID: ${(await p.edit(o,c,i)).id}
|
|
10
|
+
Original Screen ID: ${p.id}
|
|
11
|
+
Use get_screen_code to retrieve the updated HTML.`}]}}catch(a){return{content:[{type:"text",text:`Error editing screen: ${a?.message||String(a)}`}],isError:!0}}}),n.tool("generate_variants","Generate design variants of an existing screen",{projectId:r.string().describe("The ID of the project containing the screen"),screenId:r.string().describe("The ID of the screen to generate variants from"),prompt:r.string().describe("Prompt guiding the variants (e.g., 'Try different color schemes')"),variantCount:r.number().min(1).max(5).default(3).describe("Number of variants to generate (1-5)"),creativeRange:r.enum(["REFINE","EXPLORE","REIMAGINE"]).default("EXPLORE").describe("How different the variants should be"),aspects:r.array(r.enum(["COLOR_SCHEME","LAYOUT","IMAGES","TEXT_FONT","TEXT_CONTENT"])).optional().describe("Aspects of the design to vary")},async({projectId:e,screenId:t,prompt:o,variantCount:c,creativeRange:i,aspects:a})=>{try{return{content:[{type:"text",text:`Variants generated successfully:
|
|
12
|
+
${(await(await s.project(e).getScreen(t)).variants(o,{variantCount:c,creativeRange:i,aspects:a})).map((I,T)=>`Variant ${T+1} ID: ${I.id}`).join("\\n")}`}]}}catch(p){return{content:[{type:"text",text:`Error generating variants: ${p?.message||String(p)}`}],isError:!0}}}),n.tool("get_screen_code","Retrieve the HTML code download URL (and extract full HTML if possible) for a generated screen",{projectId:r.string().describe("The ID of the project"),screenId:r.string().describe("The ID of the screen")},async({projectId:e,screenId:t})=>{try{let i=await(await s.project(e).getScreen(t)).getHtml(),a=await fetch(i);if(!a.ok)return{content:[{type:"text",text:`Failed to fetch HTML content from URL: ${i}`}],isError:!0};let p=await a.text();return{content:[{type:"text",text:`Code fetched successfully from URL: ${i}`},{type:"text",text:`
|
|
13
|
+
|
|
14
|
+
\`\`\`html
|
|
15
|
+
${p}
|
|
16
|
+
\`\`\`
|
|
17
|
+
`}]}}catch(o){return{content:[{type:"text",text:`Error retrieving screen code: ${o?.message||String(o)}`}],isError:!0}}}),n.tool("get_screen_image","Retrieve the base64 screenshot image download URL for a generated screen.",{projectId:r.string().describe("The ID of the project"),screenId:r.string().describe("The ID of the screen")},async({projectId:e,screenId:t})=>{try{return{content:[{type:"text",text:`Screenshot fetched successfully! Image available at URL: ${await(await s.project(e).getScreen(t)).getImage()}`}]}}catch(o){return{content:[{type:"text",text:`Error retrieving screen image: ${o?.message||String(o)}`}],isError:!0}}}),n.tool("generate_and_fetch_code","One-step tool to generate a UI screen and immediately retrieve its HTML code",{projectId:r.string().describe("The ID of the project"),prompt:r.string().describe("Text prompt describing the desired screen"),deviceType:r.enum(["MOBILE","DESKTOP","TABLET","AGNOSTIC"]).optional().describe("Target device type")},async({projectId:e,prompt:t,deviceType:o})=>{try{let i=await s.project(e).generate(t,o),a=await i.getHtml(),l=await(await fetch(a)).text();return{content:[{type:"text",text:`Screen generated! Screen ID: ${i.id}`},{type:"text",text:`
|
|
18
|
+
|
|
19
|
+
\`\`\`html
|
|
20
|
+
${l}
|
|
21
|
+
\`\`\`
|
|
22
|
+
`}]}}catch(c){return{content:[{type:"text",text:`Error in generate_and_fetch_code: ${c?.message||String(c)}`}],isError:!0}}}),n.tool("scaffold_project_files","Maps a specific screen from a project to a local file path. Use this to save UI designs directly into the user's workspace.",{projectId:r.string().describe("The ID of the project"),screenId:r.string().describe("The ID of the screen"),filePath:r.string().describe("Absolute or relative file path to save the HTML (e.g., 'src/components/MyComponent.html')")},async({projectId:e,screenId:t,filePath:o})=>{try{let a=await(await s.project(e).getScreen(t)).getHtml(),l=await(await fetch(a)).text(),g=f.resolve(process.cwd(),o);return await h.mkdir(f.dirname(g),{recursive:!0}),await h.writeFile(g,l,"utf-8"),{content:[{type:"text",text:`Successfully wrote screen ${t} to file: ${g}`}]}}catch(c){return{content:[{type:"text",text:`Error scaffolding file: ${c?.message||String(c)}`}],isError:!0}}})}import{ResourceTemplate as S}from"@modelcontextprotocol/sdk/server/mcp.js";function y(n){let{stitch:s}=m();n.resource("projects","stitch://projects",{mimeType:"application/json"},async d=>{try{let e=await s.projects();return{contents:[{uri:d.href,text:JSON.stringify(e.map(t=>({id:t.id,projectId:t.projectId})),null,2)}]}}catch(e){throw new Error(`Failed to list projects: ${e.message}`)}}),n.resource("project_screens",new S("stitch://projects/{projectId}/screens",{list:void 0}),async(d,{projectId:e})=>{try{if(typeof e!="string")throw new Error("Invalid projectId");let o=await s.project(e).screens();return{contents:[{uri:d.href,text:JSON.stringify(o.map(c=>({id:c.id,screenId:c.screenId,projectId:c.projectId})),null,2)}]}}catch(t){throw new Error(`Failed to load screens for project ${e}: ${t.message}`)}})}import{z as v}from"zod";function j(n){n.prompt("create_web_app",{description:"Generates instructions for the agent to orchestrate the generation and scaffolding of a web app using Google Stitch.",appIdea:v.string().describe("What kind of web app you'd like to build")},({appIdea:s})=>({messages:[{role:"user",content:{type:"text",text:`I want to build a web application using the Google Stitch SDK. Here is my idea: "${s}"
|
|
23
|
+
|
|
24
|
+
Please act as an expert frontend engineer and perform the following steps:
|
|
25
|
+
1. Call \`create_project\` to create a new workspace for this idea.
|
|
26
|
+
2. Formulate a detailed visual prompt for a high-quality UI layout based on my idea.
|
|
27
|
+
3. Call \`generate_and_fetch_code\` to produce the initial screen UI and fetch its HTML code instantly.
|
|
28
|
+
4. Review the generated code visually if possible, or propose edits. Use \`scaffold_project_files\` to save the HTML correctly inside this workspace (e.g. into an \`index.html\` or wrapped into a component file).
|
|
29
|
+
5. Let me know when you are done, or if you encounter any errors!`}}]}))}_.config();async function L(){process.env.STITCH_API_KEY||(console.error("Error: STITCH_API_KEY environment variable is required."),console.error("Please provide it via .env file or environment variable."),process.exit(1));let n=new E({name:"Stitch-MCP-Server",version:"1.0.0"});u(n),y(n),j(n);let s=new b;await n.connect(s),console.error("Stitch MCP Server is running and listening on stdio.")}L().catch(n=>{console.error("Fatal error starting server:",n),process.exit(1)});
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/handlers/tools.ts","../src/utils/stitch.ts","../src/handlers/resources.ts","../src/handlers/prompts.ts"],"sourcesContent":["#!/usr/bin/env node\r\n\r\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\r\nimport dotenv from \"dotenv\";\r\nimport { registerTools } from \"./handlers/tools.js\";\r\nimport { registerResources } from \"./handlers/resources.js\";\r\nimport { registerPrompts } from \"./handlers/prompts.js\";\r\n\r\n// Load environment variables\r\ndotenv.config();\r\n\r\nasync function main() {\r\n if (!process.env.STITCH_API_KEY) {\r\n console.error(\"Error: STITCH_API_KEY environment variable is required.\");\r\n console.error(\"Please provide it via .env file or environment variable.\");\r\n process.exit(1);\r\n }\r\n\r\n const server = new McpServer({\r\n name: \"Stitch-MCP-Server\",\r\n version: \"1.0.0\",\r\n });\r\n\r\n // Register Handlers\r\n registerTools(server);\r\n registerResources(server);\r\n registerPrompts(server);\r\n\r\n const transport = new StdioServerTransport();\r\n await server.connect(transport);\r\n \r\n // Informative log (to stderr, so it doesn't pollute stdout where MCP protocol communicates)\r\n console.error(\"Stitch MCP Server is running and listening on stdio.\");\r\n}\r\n\r\nmain().catch((err) => {\r\n console.error(\"Fatal error starting server:\", err);\r\n process.exit(1);\r\n});","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { z } from \"zod\";\r\nimport { getStitchClient } from \"../utils/stitch.js\";\r\n\r\nimport fs from 'fs/promises';\r\nimport path from 'path';\r\n\r\nexport function registerTools(server: McpServer) {\r\n const { stitch, toolClient } = getStitchClient();\r\n\r\n // ----- Core Tools -----\r\n\r\n server.tool(\r\n \"create_project\",\r\n \"Create a new Stitch project\",\r\n {\r\n title: z.string().describe(\"Title of the new project\"),\r\n },\r\n async ({ title }) => {\r\n try {\r\n const result = await toolClient.callTool(\"create_project\", { title });\r\n return {\r\n content: [{ type: \"text\", text: `Project created successfully!\\n\\n${JSON.stringify(result, null, 2)}` }],\r\n };\r\n } catch (error: any) {\r\n return { content: [{ type: \"text\", text: `Error creating project: ${error?.message || String(error)}` }], isError: true };\r\n }\r\n }\r\n );\r\n\r\n server.tool(\r\n \"list_projects\",\r\n \"List all available Stitch projects\",\r\n {},\r\n async () => {\r\n try {\r\n const projects = await stitch.projects();\r\n const projectList = projects.map(p => `- ${p.id}`).join('\\\\n');\r\n return {\r\n content: [{ type: \"text\", text: `Projects:\\n${projectList}` }],\r\n };\r\n } catch (error: any) {\r\n return { content: [{ type: \"text\", text: `Error listing projects: ${error?.message || String(error)}` }], isError: true };\r\n }\r\n }\r\n );\r\n\r\n server.tool(\r\n \"generate_screen\",\r\n \"Generate a UI screen from a text prompt within a project\",\r\n {\r\n projectId: z.string().describe(\"The ID of the project to generate the screen in\"),\r\n prompt: z.string().describe(\"Text prompt describing the desired screen (e.g., 'A login page with email and password fields')\"),\r\n deviceType: z.enum([\"MOBILE\", \"DESKTOP\", \"TABLET\", \"AGNOSTIC\"]).optional().describe(\"Target device type\"),\r\n },\r\n async ({ projectId, prompt, deviceType }) => {\r\n try {\r\n const project = stitch.project(projectId);\r\n const screen = await project.generate(prompt, deviceType);\r\n \r\n return {\r\n content: [\r\n { \r\n type: \"text\", \r\n text: `Screen generated successfully!\\nScreen ID: ${screen.id}\\nProject ID: ${screen.projectId}\\nUse get_screen_code tool to retrieve the HTML.` \r\n }\r\n ],\r\n };\r\n } catch (error: any) {\r\n return { content: [{ type: \"text\", text: `Error generating screen: ${error?.message || String(error)}` }], isError: true };\r\n }\r\n }\r\n );\r\n\r\n server.tool(\r\n \"edit_screen\",\r\n \"Edit an existing screen using a text prompt\",\r\n {\r\n projectId: z.string().describe(\"The ID of the project containing the screen\"),\r\n screenId: z.string().describe(\"The ID of the screen to edit\"),\r\n prompt: z.string().describe(\"Text prompt describing the changes (e.g., 'Make the background dark and add a sidebar')\"),\r\n deviceType: z.enum([\"MOBILE\", \"DESKTOP\", \"TABLET\", \"AGNOSTIC\"]).optional().describe(\"Target device type\"),\r\n modelId: z.enum([\"GEMINI_3_PRO\", \"GEMINI_3_FLASH\"]).optional().describe(\"Generative model to use\"),\r\n },\r\n async ({ projectId, screenId, prompt, deviceType, modelId }) => {\r\n try {\r\n const project = stitch.project(projectId);\r\n const screen = await project.getScreen(screenId);\r\n const editedScreen = await screen.edit(prompt, deviceType, modelId);\r\n \r\n return {\r\n content: [{ type: \"text\", text: `Screen edited successfully!\\nNew Screen ID: ${editedScreen.id}\\nOriginal Screen ID: ${screen.id}\\nUse get_screen_code to retrieve the updated HTML.` }],\r\n };\r\n } catch (error: any) {\r\n return { content: [{ type: \"text\", text: `Error editing screen: ${error?.message || String(error)}` }], isError: true };\r\n }\r\n }\r\n );\r\n\r\n server.tool(\r\n \"generate_variants\",\r\n \"Generate design variants of an existing screen\",\r\n {\r\n projectId: z.string().describe(\"The ID of the project containing the screen\"),\r\n screenId: z.string().describe(\"The ID of the screen to generate variants from\"),\r\n prompt: z.string().describe(\"Prompt guiding the variants (e.g., 'Try different color schemes')\"),\r\n variantCount: z.number().min(1).max(5).default(3).describe(\"Number of variants to generate (1-5)\"),\r\n creativeRange: z.enum([\"REFINE\", \"EXPLORE\", \"REIMAGINE\"]).default(\"EXPLORE\").describe(\"How different the variants should be\"),\r\n aspects: z.array(z.enum([\"COLOR_SCHEME\", \"LAYOUT\", \"IMAGES\", \"TEXT_FONT\", \"TEXT_CONTENT\"])).optional().describe(\"Aspects of the design to vary\"),\r\n },\r\n async ({ projectId, screenId, prompt, variantCount, creativeRange, aspects }) => {\r\n try {\r\n const project = stitch.project(projectId);\r\n const screen = await project.getScreen(screenId);\r\n \r\n const variants = await screen.variants(prompt, { variantCount, creativeRange, aspects });\r\n const variantList = variants.map((v, i) => `Variant ${i + 1} ID: ${v.id}`).join('\\\\n');\r\n\r\n return {\r\n content: [{ type: \"text\", text: `Variants generated successfully:\\n${variantList}` }],\r\n };\r\n } catch (error: any) {\r\n return { content: [{ type: \"text\", text: `Error generating variants: ${error?.message || String(error)}` }], isError: true };\r\n }\r\n }\r\n );\r\n\r\n // ----- Advanced Macro Tools -----\r\n\r\n server.tool(\r\n \"get_screen_code\",\r\n \"Retrieve the HTML code download URL (and extract full HTML if possible) for a generated screen\",\r\n {\r\n projectId: z.string().describe(\"The ID of the project\"),\r\n screenId: z.string().describe(\"The ID of the screen\"),\r\n },\r\n async ({ projectId, screenId }) => {\r\n try {\r\n const project = stitch.project(projectId);\r\n const screen = await project.getScreen(screenId);\r\n const htmlUrl = await screen.getHtml();\r\n \r\n // Fetch the actual HTML content\r\n const response = await fetch(htmlUrl);\r\n if (!response.ok) {\r\n return { content: [{ type: \"text\", text: `Failed to fetch HTML content from URL: ${htmlUrl}` }], isError: true };\r\n }\r\n const htmlContent = await response.text();\r\n\r\n return {\r\n content: [\r\n { type: \"text\", text: `Code fetched successfully from URL: ${htmlUrl}` },\r\n { type: \"text\", text: `\\n\\n\\`\\`\\`html\\n${htmlContent}\\n\\`\\`\\`\\n` }\r\n ],\r\n };\r\n } catch (error: any) {\r\n return { content: [{ type: \"text\", text: `Error retrieving screen code: ${error?.message || String(error)}` }], isError: true };\r\n }\r\n }\r\n );\r\n\r\n server.tool(\r\n \"get_screen_image\",\r\n \"Retrieve the base64 screenshot image download URL for a generated screen.\",\r\n {\r\n projectId: z.string().describe(\"The ID of the project\"),\r\n screenId: z.string().describe(\"The ID of the screen\"),\r\n },\r\n async ({ projectId, screenId }) => {\r\n try {\r\n const project = stitch.project(projectId);\r\n const screen = await project.getScreen(screenId);\r\n const imageUrl = await screen.getImage();\r\n\r\n return {\r\n content: [\r\n { type: \"text\", text: `Screenshot fetched successfully! Image available at URL: ${imageUrl}` },\r\n ],\r\n };\r\n } catch (error: any) {\r\n return { content: [{ type: \"text\", text: `Error retrieving screen image: ${error?.message || String(error)}` }], isError: true };\r\n }\r\n }\r\n );\r\n\r\n server.tool(\r\n \"generate_and_fetch_code\",\r\n \"One-step tool to generate a UI screen and immediately retrieve its HTML code\",\r\n {\r\n projectId: z.string().describe(\"The ID of the project\"),\r\n prompt: z.string().describe(\"Text prompt describing the desired screen\"),\r\n deviceType: z.enum([\"MOBILE\", \"DESKTOP\", \"TABLET\", \"AGNOSTIC\"]).optional().describe(\"Target device type\"),\r\n },\r\n async ({ projectId, prompt, deviceType }) => {\r\n try {\r\n const project = stitch.project(projectId);\r\n const screen = await project.generate(prompt, deviceType);\r\n const htmlUrl = await screen.getHtml();\r\n \r\n const response = await fetch(htmlUrl);\r\n const htmlContent = await response.text();\r\n\r\n return {\r\n content: [\r\n { type: \"text\", text: `Screen generated! Screen ID: ${screen.id}` },\r\n { type: \"text\", text: `\\n\\n\\`\\`\\`html\\n${htmlContent}\\n\\`\\`\\`\\n` }\r\n ],\r\n };\r\n } catch (error: any) {\r\n return { content: [{ type: \"text\", text: `Error in generate_and_fetch_code: ${error?.message || String(error)}` }], isError: true };\r\n }\r\n }\r\n );\r\n\r\n server.tool(\r\n \"scaffold_project_files\",\r\n \"Maps a specific screen from a project to a local file path. Use this to save UI designs directly into the user's workspace.\",\r\n {\r\n projectId: z.string().describe(\"The ID of the project\"),\r\n screenId: z.string().describe(\"The ID of the screen\"),\r\n filePath: z.string().describe(\"Absolute or relative file path to save the HTML (e.g., 'src/components/MyComponent.html')\"),\r\n },\r\n async ({ projectId, screenId, filePath }) => {\r\n try {\r\n const project = stitch.project(projectId);\r\n const screen = await project.getScreen(screenId);\r\n const htmlUrl = await screen.getHtml();\r\n \r\n const response = await fetch(htmlUrl);\r\n const htmlContent = await response.text();\r\n\r\n // Save to file system\r\n const resolvedPath = path.resolve(process.cwd(), filePath);\r\n await fs.mkdir(path.dirname(resolvedPath), { recursive: true });\r\n await fs.writeFile(resolvedPath, htmlContent, 'utf-8');\r\n\r\n return {\r\n content: [\r\n { type: \"text\", text: `Successfully wrote screen ${screenId} to file: ${resolvedPath}` }\r\n ],\r\n };\r\n } catch (error: any) {\r\n return { content: [{ type: \"text\", text: `Error scaffolding file: ${error?.message || String(error)}` }], isError: true };\r\n }\r\n }\r\n );\r\n\r\n}","import { Stitch, StitchToolClient } from '@google/stitch-sdk';\r\nimport { z } from 'zod';\r\n\r\nexport function getStitchClient() {\r\n const apiKey = process.env.STITCH_API_KEY;\r\n if (!apiKey) {\r\n throw new Error('STITCH_API_KEY environment variable is missing.');\r\n }\r\n\r\n // Under the hood, Stitch instance manages authorization and project scoping\r\n const toolClient = new StitchToolClient({ apiKey });\r\n const stitch = new Stitch(toolClient);\r\n \r\n return { stitch, toolClient };\r\n}","import { McpServer, ResourceTemplate } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { getStitchClient } from \"../utils/stitch.js\";\r\n\r\nexport function registerResources(server: McpServer) {\r\n const { stitch } = getStitchClient();\r\n\r\n // Resource to list all projects\r\n server.resource(\r\n \"projects\",\r\n \"stitch://projects\",\r\n { mimeType: \"application/json\" },\r\n async (uri) => {\r\n try {\r\n const projects = await stitch.projects();\r\n return {\r\n contents: [\r\n {\r\n uri: uri.href,\r\n text: JSON.stringify(projects.map(p => ({ id: p.id, projectId: p.projectId })), null, 2),\r\n },\r\n ],\r\n };\r\n } catch (error: any) {\r\n throw new Error(`Failed to list projects: ${error.message}`);\r\n }\r\n }\r\n );\r\n\r\n // Resource template to fetch screens for a specific project\r\n server.resource(\r\n \"project_screens\",\r\n new ResourceTemplate(\"stitch://projects/{projectId}/screens\", { list: undefined }),\r\n async (uri, { projectId }) => {\r\n try {\r\n if (typeof projectId !== \"string\") {\r\n throw new Error(\"Invalid projectId\");\r\n }\r\n \r\n const project = stitch.project(projectId);\r\n const screens = await project.screens();\r\n return {\r\n contents: [\r\n {\r\n uri: uri.href,\r\n text: JSON.stringify(screens.map(s => ({ id: s.id, screenId: s.screenId, projectId: s.projectId })), null, 2),\r\n },\r\n ],\r\n };\r\n } catch (error: any) {\r\n throw new Error(`Failed to load screens for project ${projectId}: ${error.message}`);\r\n }\r\n }\r\n );\r\n}","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\r\nimport { z } from \"zod\";\r\n\r\nexport function registerPrompts(server: McpServer) {\r\n server.prompt(\r\n \"create_web_app\",\r\n {\r\n description: \"Generates instructions for the agent to orchestrate the generation and scaffolding of a web app using Google Stitch.\",\r\n appIdea: z.string().describe(\"What kind of web app you'd like to build\"),\r\n },\r\n ({ appIdea }) => {\r\n return {\r\n messages: [\r\n {\r\n role: \"user\",\r\n content: {\r\n type: \"text\",\r\n text: `I want to build a web application using the Google Stitch SDK. Here is my idea: \"${appIdea}\"\r\n\r\nPlease act as an expert frontend engineer and perform the following steps:\r\n1. Call \\`create_project\\` to create a new workspace for this idea.\r\n2. Formulate a detailed visual prompt for a high-quality UI layout based on my idea.\r\n3. Call \\`generate_and_fetch_code\\` to produce the initial screen UI and fetch its HTML code instantly.\r\n4. Review the generated code visually if possible, or propose edits. Use \\`scaffold_project_files\\` to save the HTML correctly inside this workspace (e.g. into an \\`index.html\\` or wrapped into a component file).\r\n5. Let me know when you are done, or if you encounter any errors!`,\r\n },\r\n },\r\n ],\r\n };\r\n }\r\n );\r\n}"],"mappings":"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","names":["McpServer","StdioServerTransport","dotenv","z","Stitch","StitchToolClient","getStitchClient","apiKey","toolClient","fs","path","registerTools","server","stitch","toolClient","getStitchClient","z","title","result","error","p","projectId","prompt","deviceType","screen","screenId","modelId","variantCount","creativeRange","aspects","v","i","htmlUrl","response","htmlContent","filePath","resolvedPath","ResourceTemplate","registerResources","server","stitch","getStitchClient","uri","projects","p","error","ResourceTemplate","projectId","screens","s","z","registerPrompts","server","appIdea","dotenv","main","server","McpServer","registerTools","registerResources","registerPrompts","transport","StdioServerTransport","err"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "stitch-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Enterprise-ready MCP Server for Google Stitch UI generation SDK",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node --experimental-vm-modules node_modules/vitest/vitest.mjs run",
|
|
8
|
+
"build": "tsup",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"prepare": "npm run build"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"mcp",
|
|
14
|
+
"model-context-protocol",
|
|
15
|
+
"stitch",
|
|
16
|
+
"ai",
|
|
17
|
+
"google-stitch"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/0x-Professor/Stitch-mcp-server.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/0x-Professor/Stitch-mcp-server/issues"
|
|
25
|
+
},
|
|
26
|
+
"author": "",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"type": "module",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@google/stitch-sdk": "^0.0.3",
|
|
31
|
+
"@inquirer/prompts": "^8.3.2",
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
33
|
+
"dotenv": "^17.3.1",
|
|
34
|
+
"zod": "^4.3.6"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^25.5.0",
|
|
38
|
+
"tsup": "^8.5.1",
|
|
39
|
+
"typescript": "^5.9.3",
|
|
40
|
+
"vitest": "^4.1.0"
|
|
41
|
+
},
|
|
42
|
+
"bin": {
|
|
43
|
+
"stitch-mcp": "dist/index.js",
|
|
44
|
+
"stitch-mcp-setup": "dist/cli.js"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export function registerPrompts(server: McpServer) {
|
|
5
|
+
server.prompt(
|
|
6
|
+
"create_web_app",
|
|
7
|
+
{
|
|
8
|
+
description: "Generates instructions for the agent to orchestrate the generation and scaffolding of a web app using Google Stitch.",
|
|
9
|
+
appIdea: z.string().describe("What kind of web app you'd like to build"),
|
|
10
|
+
},
|
|
11
|
+
({ appIdea }) => {
|
|
12
|
+
return {
|
|
13
|
+
messages: [
|
|
14
|
+
{
|
|
15
|
+
role: "user",
|
|
16
|
+
content: {
|
|
17
|
+
type: "text",
|
|
18
|
+
text: `I want to build a web application using the Google Stitch SDK. Here is my idea: "${appIdea}"
|
|
19
|
+
|
|
20
|
+
Please act as an expert frontend engineer and perform the following steps:
|
|
21
|
+
1. Call \`create_project\` to create a new workspace for this idea.
|
|
22
|
+
2. Formulate a detailed visual prompt for a high-quality UI layout based on my idea.
|
|
23
|
+
3. Call \`generate_and_fetch_code\` to produce the initial screen UI and fetch its HTML code instantly.
|
|
24
|
+
4. Review the generated code visually if possible, or propose edits. Use \`scaffold_project_files\` to save the HTML correctly inside this workspace (e.g. into an \`index.html\` or wrapped into a component file).
|
|
25
|
+
5. Let me know when you are done, or if you encounter any errors!`,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { getStitchClient } from "../utils/stitch.js";
|
|
3
|
+
|
|
4
|
+
export function registerResources(server: McpServer) {
|
|
5
|
+
const { stitch } = getStitchClient();
|
|
6
|
+
|
|
7
|
+
// Resource to list all projects
|
|
8
|
+
server.resource(
|
|
9
|
+
"projects",
|
|
10
|
+
"stitch://projects",
|
|
11
|
+
{ mimeType: "application/json" },
|
|
12
|
+
async (uri) => {
|
|
13
|
+
try {
|
|
14
|
+
const projects = await stitch.projects();
|
|
15
|
+
return {
|
|
16
|
+
contents: [
|
|
17
|
+
{
|
|
18
|
+
uri: uri.href,
|
|
19
|
+
text: JSON.stringify(projects.map(p => ({ id: p.id, projectId: p.projectId })), null, 2),
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
} catch (error: any) {
|
|
24
|
+
throw new Error(`Failed to list projects: ${error.message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// Resource template to fetch screens for a specific project
|
|
30
|
+
server.resource(
|
|
31
|
+
"project_screens",
|
|
32
|
+
new ResourceTemplate("stitch://projects/{projectId}/screens", { list: undefined }),
|
|
33
|
+
async (uri, { projectId }) => {
|
|
34
|
+
try {
|
|
35
|
+
if (typeof projectId !== "string") {
|
|
36
|
+
throw new Error("Invalid projectId");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const project = stitch.project(projectId);
|
|
40
|
+
const screens = await project.screens();
|
|
41
|
+
return {
|
|
42
|
+
contents: [
|
|
43
|
+
{
|
|
44
|
+
uri: uri.href,
|
|
45
|
+
text: JSON.stringify(screens.map(s => ({ id: s.id, screenId: s.screenId, projectId: s.projectId })), null, 2),
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
} catch (error: any) {
|
|
50
|
+
throw new Error(`Failed to load screens for project ${projectId}: ${error.message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getStitchClient } from "../utils/stitch.js";
|
|
4
|
+
|
|
5
|
+
import fs from 'fs/promises';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
8
|
+
export function registerTools(server: McpServer) {
|
|
9
|
+
const { stitch, toolClient } = getStitchClient();
|
|
10
|
+
|
|
11
|
+
// ----- Core Tools -----
|
|
12
|
+
|
|
13
|
+
server.tool(
|
|
14
|
+
"create_project",
|
|
15
|
+
"Create a new Stitch project",
|
|
16
|
+
{
|
|
17
|
+
title: z.string().describe("Title of the new project"),
|
|
18
|
+
},
|
|
19
|
+
async ({ title }) => {
|
|
20
|
+
try {
|
|
21
|
+
const result = await toolClient.callTool("create_project", { title });
|
|
22
|
+
return {
|
|
23
|
+
content: [{ type: "text", text: `Project created successfully!\n\n${JSON.stringify(result, null, 2)}` }],
|
|
24
|
+
};
|
|
25
|
+
} catch (error: any) {
|
|
26
|
+
return { content: [{ type: "text", text: `Error creating project: ${error?.message || String(error)}` }], isError: true };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
server.tool(
|
|
32
|
+
"list_projects",
|
|
33
|
+
"List all available Stitch projects",
|
|
34
|
+
{},
|
|
35
|
+
async () => {
|
|
36
|
+
try {
|
|
37
|
+
const projects = await stitch.projects();
|
|
38
|
+
const projectList = projects.map(p => `- ${p.id}`).join('\\n');
|
|
39
|
+
return {
|
|
40
|
+
content: [{ type: "text", text: `Projects:\n${projectList}` }],
|
|
41
|
+
};
|
|
42
|
+
} catch (error: any) {
|
|
43
|
+
return { content: [{ type: "text", text: `Error listing projects: ${error?.message || String(error)}` }], isError: true };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
server.tool(
|
|
49
|
+
"generate_screen",
|
|
50
|
+
"Generate a UI screen from a text prompt within a project",
|
|
51
|
+
{
|
|
52
|
+
projectId: z.string().describe("The ID of the project to generate the screen in"),
|
|
53
|
+
prompt: z.string().describe("Text prompt describing the desired screen (e.g., 'A login page with email and password fields')"),
|
|
54
|
+
deviceType: z.enum(["MOBILE", "DESKTOP", "TABLET", "AGNOSTIC"]).optional().describe("Target device type"),
|
|
55
|
+
},
|
|
56
|
+
async ({ projectId, prompt, deviceType }) => {
|
|
57
|
+
try {
|
|
58
|
+
const project = stitch.project(projectId);
|
|
59
|
+
const screen = await project.generate(prompt, deviceType);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
content: [
|
|
63
|
+
{
|
|
64
|
+
type: "text",
|
|
65
|
+
text: `Screen generated successfully!\nScreen ID: ${screen.id}\nProject ID: ${screen.projectId}\nUse get_screen_code tool to retrieve the HTML.`
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
} catch (error: any) {
|
|
70
|
+
return { content: [{ type: "text", text: `Error generating screen: ${error?.message || String(error)}` }], isError: true };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
server.tool(
|
|
76
|
+
"edit_screen",
|
|
77
|
+
"Edit an existing screen using a text prompt",
|
|
78
|
+
{
|
|
79
|
+
projectId: z.string().describe("The ID of the project containing the screen"),
|
|
80
|
+
screenId: z.string().describe("The ID of the screen to edit"),
|
|
81
|
+
prompt: z.string().describe("Text prompt describing the changes (e.g., 'Make the background dark and add a sidebar')"),
|
|
82
|
+
deviceType: z.enum(["MOBILE", "DESKTOP", "TABLET", "AGNOSTIC"]).optional().describe("Target device type"),
|
|
83
|
+
modelId: z.enum(["GEMINI_3_PRO", "GEMINI_3_FLASH"]).optional().describe("Generative model to use"),
|
|
84
|
+
},
|
|
85
|
+
async ({ projectId, screenId, prompt, deviceType, modelId }) => {
|
|
86
|
+
try {
|
|
87
|
+
const project = stitch.project(projectId);
|
|
88
|
+
const screen = await project.getScreen(screenId);
|
|
89
|
+
const editedScreen = await screen.edit(prompt, deviceType, modelId);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
content: [{ type: "text", text: `Screen edited successfully!\nNew Screen ID: ${editedScreen.id}\nOriginal Screen ID: ${screen.id}\nUse get_screen_code to retrieve the updated HTML.` }],
|
|
93
|
+
};
|
|
94
|
+
} catch (error: any) {
|
|
95
|
+
return { content: [{ type: "text", text: `Error editing screen: ${error?.message || String(error)}` }], isError: true };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
server.tool(
|
|
101
|
+
"generate_variants",
|
|
102
|
+
"Generate design variants of an existing screen",
|
|
103
|
+
{
|
|
104
|
+
projectId: z.string().describe("The ID of the project containing the screen"),
|
|
105
|
+
screenId: z.string().describe("The ID of the screen to generate variants from"),
|
|
106
|
+
prompt: z.string().describe("Prompt guiding the variants (e.g., 'Try different color schemes')"),
|
|
107
|
+
variantCount: z.number().min(1).max(5).default(3).describe("Number of variants to generate (1-5)"),
|
|
108
|
+
creativeRange: z.enum(["REFINE", "EXPLORE", "REIMAGINE"]).default("EXPLORE").describe("How different the variants should be"),
|
|
109
|
+
aspects: z.array(z.enum(["COLOR_SCHEME", "LAYOUT", "IMAGES", "TEXT_FONT", "TEXT_CONTENT"])).optional().describe("Aspects of the design to vary"),
|
|
110
|
+
},
|
|
111
|
+
async ({ projectId, screenId, prompt, variantCount, creativeRange, aspects }) => {
|
|
112
|
+
try {
|
|
113
|
+
const project = stitch.project(projectId);
|
|
114
|
+
const screen = await project.getScreen(screenId);
|
|
115
|
+
|
|
116
|
+
const variants = await screen.variants(prompt, { variantCount, creativeRange, aspects });
|
|
117
|
+
const variantList = variants.map((v, i) => `Variant ${i + 1} ID: ${v.id}`).join('\\n');
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
content: [{ type: "text", text: `Variants generated successfully:\n${variantList}` }],
|
|
121
|
+
};
|
|
122
|
+
} catch (error: any) {
|
|
123
|
+
return { content: [{ type: "text", text: `Error generating variants: ${error?.message || String(error)}` }], isError: true };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// ----- Advanced Macro Tools -----
|
|
129
|
+
|
|
130
|
+
server.tool(
|
|
131
|
+
"get_screen_code",
|
|
132
|
+
"Retrieve the HTML code download URL (and extract full HTML if possible) for a generated screen",
|
|
133
|
+
{
|
|
134
|
+
projectId: z.string().describe("The ID of the project"),
|
|
135
|
+
screenId: z.string().describe("The ID of the screen"),
|
|
136
|
+
},
|
|
137
|
+
async ({ projectId, screenId }) => {
|
|
138
|
+
try {
|
|
139
|
+
const project = stitch.project(projectId);
|
|
140
|
+
const screen = await project.getScreen(screenId);
|
|
141
|
+
const htmlUrl = await screen.getHtml();
|
|
142
|
+
|
|
143
|
+
// Fetch the actual HTML content
|
|
144
|
+
const response = await fetch(htmlUrl);
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
return { content: [{ type: "text", text: `Failed to fetch HTML content from URL: ${htmlUrl}` }], isError: true };
|
|
147
|
+
}
|
|
148
|
+
const htmlContent = await response.text();
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
content: [
|
|
152
|
+
{ type: "text", text: `Code fetched successfully from URL: ${htmlUrl}` },
|
|
153
|
+
{ type: "text", text: `\n\n\`\`\`html\n${htmlContent}\n\`\`\`\n` }
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
} catch (error: any) {
|
|
157
|
+
return { content: [{ type: "text", text: `Error retrieving screen code: ${error?.message || String(error)}` }], isError: true };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
server.tool(
|
|
163
|
+
"get_screen_image",
|
|
164
|
+
"Retrieve the base64 screenshot image download URL for a generated screen.",
|
|
165
|
+
{
|
|
166
|
+
projectId: z.string().describe("The ID of the project"),
|
|
167
|
+
screenId: z.string().describe("The ID of the screen"),
|
|
168
|
+
},
|
|
169
|
+
async ({ projectId, screenId }) => {
|
|
170
|
+
try {
|
|
171
|
+
const project = stitch.project(projectId);
|
|
172
|
+
const screen = await project.getScreen(screenId);
|
|
173
|
+
const imageUrl = await screen.getImage();
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
content: [
|
|
177
|
+
{ type: "text", text: `Screenshot fetched successfully! Image available at URL: ${imageUrl}` },
|
|
178
|
+
],
|
|
179
|
+
};
|
|
180
|
+
} catch (error: any) {
|
|
181
|
+
return { content: [{ type: "text", text: `Error retrieving screen image: ${error?.message || String(error)}` }], isError: true };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
server.tool(
|
|
187
|
+
"generate_and_fetch_code",
|
|
188
|
+
"One-step tool to generate a UI screen and immediately retrieve its HTML code",
|
|
189
|
+
{
|
|
190
|
+
projectId: z.string().describe("The ID of the project"),
|
|
191
|
+
prompt: z.string().describe("Text prompt describing the desired screen"),
|
|
192
|
+
deviceType: z.enum(["MOBILE", "DESKTOP", "TABLET", "AGNOSTIC"]).optional().describe("Target device type"),
|
|
193
|
+
},
|
|
194
|
+
async ({ projectId, prompt, deviceType }) => {
|
|
195
|
+
try {
|
|
196
|
+
const project = stitch.project(projectId);
|
|
197
|
+
const screen = await project.generate(prompt, deviceType);
|
|
198
|
+
const htmlUrl = await screen.getHtml();
|
|
199
|
+
|
|
200
|
+
const response = await fetch(htmlUrl);
|
|
201
|
+
const htmlContent = await response.text();
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
content: [
|
|
205
|
+
{ type: "text", text: `Screen generated! Screen ID: ${screen.id}` },
|
|
206
|
+
{ type: "text", text: `\n\n\`\`\`html\n${htmlContent}\n\`\`\`\n` }
|
|
207
|
+
],
|
|
208
|
+
};
|
|
209
|
+
} catch (error: any) {
|
|
210
|
+
return { content: [{ type: "text", text: `Error in generate_and_fetch_code: ${error?.message || String(error)}` }], isError: true };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
server.tool(
|
|
216
|
+
"scaffold_project_files",
|
|
217
|
+
"Maps a specific screen from a project to a local file path. Use this to save UI designs directly into the user's workspace.",
|
|
218
|
+
{
|
|
219
|
+
projectId: z.string().describe("The ID of the project"),
|
|
220
|
+
screenId: z.string().describe("The ID of the screen"),
|
|
221
|
+
filePath: z.string().describe("Absolute or relative file path to save the HTML (e.g., 'src/components/MyComponent.html')"),
|
|
222
|
+
},
|
|
223
|
+
async ({ projectId, screenId, filePath }) => {
|
|
224
|
+
try {
|
|
225
|
+
const project = stitch.project(projectId);
|
|
226
|
+
const screen = await project.getScreen(screenId);
|
|
227
|
+
const htmlUrl = await screen.getHtml();
|
|
228
|
+
|
|
229
|
+
const response = await fetch(htmlUrl);
|
|
230
|
+
const htmlContent = await response.text();
|
|
231
|
+
|
|
232
|
+
// Save to file system
|
|
233
|
+
const resolvedPath = path.resolve(process.cwd(), filePath);
|
|
234
|
+
await fs.mkdir(path.dirname(resolvedPath), { recursive: true });
|
|
235
|
+
await fs.writeFile(resolvedPath, htmlContent, 'utf-8');
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
content: [
|
|
239
|
+
{ type: "text", text: `Successfully wrote screen ${screenId} to file: ${resolvedPath}` }
|
|
240
|
+
],
|
|
241
|
+
};
|
|
242
|
+
} catch (error: any) {
|
|
243
|
+
return { content: [{ type: "text", text: `Error scaffolding file: ${error?.message || String(error)}` }], isError: true };
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import dotenv from "dotenv";
|
|
6
|
+
import { registerTools } from "./handlers/tools.js";
|
|
7
|
+
import { registerResources } from "./handlers/resources.js";
|
|
8
|
+
import { registerPrompts } from "./handlers/prompts.js";
|
|
9
|
+
|
|
10
|
+
// Load environment variables
|
|
11
|
+
dotenv.config();
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
if (!process.env.STITCH_API_KEY) {
|
|
15
|
+
console.error("Error: STITCH_API_KEY environment variable is required.");
|
|
16
|
+
console.error("Please provide it via .env file or environment variable.");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const server = new McpServer({
|
|
21
|
+
name: "Stitch-MCP-Server",
|
|
22
|
+
version: "1.0.0",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Register Handlers
|
|
26
|
+
registerTools(server);
|
|
27
|
+
registerResources(server);
|
|
28
|
+
registerPrompts(server);
|
|
29
|
+
|
|
30
|
+
const transport = new StdioServerTransport();
|
|
31
|
+
await server.connect(transport);
|
|
32
|
+
|
|
33
|
+
// Informative log (to stderr, so it doesn't pollute stdout where MCP protocol communicates)
|
|
34
|
+
console.error("Stitch MCP Server is running and listening on stdio.");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
main().catch((err) => {
|
|
38
|
+
console.error("Fatal error starting server:", err);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
});
|
package/src/setup.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import { checkbox, input, confirm } from "@inquirer/prompts";
|
|
5
|
+
|
|
6
|
+
const homedir = os.homedir();
|
|
7
|
+
const platform = os.platform();
|
|
8
|
+
|
|
9
|
+
const getAppDataPath = () => {
|
|
10
|
+
if (platform === "win32") {
|
|
11
|
+
return process.env.APPDATA || path.join(homedir, "AppData", "Roaming");
|
|
12
|
+
} else if (platform === "darwin") {
|
|
13
|
+
return path.join(homedir, "Library", "Application Support");
|
|
14
|
+
} else {
|
|
15
|
+
return path.join(homedir, ".config");
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const getClaudeDesktopConfigPath = () => {
|
|
20
|
+
if (platform === "darwin") {
|
|
21
|
+
return path.join(homedir, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
22
|
+
}
|
|
23
|
+
return path.join(getAppDataPath(), "Claude", "claude_desktop_config.json");
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const getClineConfigPath = () => {
|
|
27
|
+
if (platform === "darwin") {
|
|
28
|
+
return path.join(homedir, "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json");
|
|
29
|
+
} else if (platform === "win32") {
|
|
30
|
+
return path.join(getAppDataPath(), "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json");
|
|
31
|
+
}
|
|
32
|
+
return path.join(getAppDataPath(), "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json");
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const getCursorConfigPath = () => {
|
|
36
|
+
return path.join(process.cwd(), ".cursor", "mcp.json");
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const runSetup = async () => {
|
|
40
|
+
console.log("\n🚀 Welcome to the Stitch MCP Server Setup!");
|
|
41
|
+
console.log("This utility will automatically configure your favorite tools to use the Stitch MCP Server.\n");
|
|
42
|
+
|
|
43
|
+
const tools = [
|
|
44
|
+
{
|
|
45
|
+
name: "Claude Desktop",
|
|
46
|
+
value: "claude",
|
|
47
|
+
checked: fs.existsSync(getClaudeDesktopConfigPath()),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "Cline (VS Code Extension)",
|
|
51
|
+
value: "cline",
|
|
52
|
+
checked: fs.existsSync(getClineConfigPath()),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "Cursor (Workspace Config)",
|
|
56
|
+
value: "cursor",
|
|
57
|
+
checked: fs.existsSync(getCursorConfigPath()),
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const selectedTools = await checkbox({
|
|
62
|
+
message: "Which tools would you like to configure this MCP Server for?",
|
|
63
|
+
choices: tools,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (selectedTools.length === 0) {
|
|
67
|
+
console.log("❌ No tools selected. Exiting.");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const apiKey = await input({
|
|
72
|
+
message: "Please enter your Stitch API Key (STITCH_API_KEY):",
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!apiKey) {
|
|
76
|
+
console.log("❌ API Key is required. Exiting.");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log("\nConfiguring selected tools...\n");
|
|
81
|
+
|
|
82
|
+
const serverCommand = platform === "win32" ? "npx.cmd" : "npx";
|
|
83
|
+
const serverArgs = ["-y", "stitch-mcp-server"];
|
|
84
|
+
|
|
85
|
+
const mcpConfigEntry = {
|
|
86
|
+
command: serverCommand,
|
|
87
|
+
args: serverArgs,
|
|
88
|
+
env: {
|
|
89
|
+
STITCH_API_KEY: apiKey
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const updateJsonConfig = (configPath: string, keyName: string = "stitch", serverDef = mcpConfigEntry) => {
|
|
94
|
+
let config: any = { mcpServers: {} };
|
|
95
|
+
try {
|
|
96
|
+
if (fs.existsSync(configPath)) {
|
|
97
|
+
const fileContent = fs.readFileSync(configPath, "utf-8");
|
|
98
|
+
config = JSON.parse(fileContent);
|
|
99
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
100
|
+
} else {
|
|
101
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
config.mcpServers[keyName] = serverDef;
|
|
105
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
106
|
+
console.log(`✅ Successfully updated: ${configPath}`);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
console.error(`❌ Failed to update ${configPath}:`, err);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
for (const tool of selectedTools) {
|
|
113
|
+
if (tool === "claude") {
|
|
114
|
+
updateJsonConfig(getClaudeDesktopConfigPath());
|
|
115
|
+
} else if (tool === "cline") {
|
|
116
|
+
updateJsonConfig(getClineConfigPath());
|
|
117
|
+
} else if (tool === "cursor") {
|
|
118
|
+
const cursorPath = getCursorConfigPath();
|
|
119
|
+
console.log(`\n💡 Note: Cursor handles MCP per-workspace or via its UI Settings.`);
|
|
120
|
+
console.log(`Generating workspace config for Cursor at ${cursorPath}`);
|
|
121
|
+
updateJsonConfig(cursorPath);
|
|
122
|
+
console.log(`👉 In Cursor: Go to Cursor Settings Menu > Settings > Features > MCP to manage servers visually.`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log("\n🎉 Setup complete! You can now use Stitch-mcp-server in your selected tools.");
|
|
127
|
+
console.log("Make sure to restart Claude Desktop or reload your Editor for changes to take effect.");
|
|
128
|
+
};
|
|
129
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Stitch, StitchToolClient } from '@google/stitch-sdk';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
export function getStitchClient() {
|
|
5
|
+
const apiKey = process.env.STITCH_API_KEY;
|
|
6
|
+
if (!apiKey) {
|
|
7
|
+
throw new Error('STITCH_API_KEY environment variable is missing.');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Under the hood, Stitch instance manages authorization and project scoping
|
|
11
|
+
const toolClient = new StitchToolClient({ apiKey });
|
|
12
|
+
const stitch = new Stitch(toolClient);
|
|
13
|
+
|
|
14
|
+
return { stitch, toolClient };
|
|
15
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
4
|
+
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
|
5
|
+
import { registerTools } from '../src/handlers/tools.js';
|
|
6
|
+
import { registerResources } from '../src/handlers/resources.js';
|
|
7
|
+
import dotenv from 'dotenv';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
11
|
+
|
|
12
|
+
dotenv.config();
|
|
13
|
+
|
|
14
|
+
describe('Stitch MCP Server Integration Tests', () => {
|
|
15
|
+
let mcpServer: McpServer;
|
|
16
|
+
let client: Client;
|
|
17
|
+
let clientTransport: InMemoryTransport;
|
|
18
|
+
let serverTransport: InMemoryTransport;
|
|
19
|
+
|
|
20
|
+
beforeAll(async () => {
|
|
21
|
+
if (!process.env.STITCH_API_KEY) {
|
|
22
|
+
throw new Error("STITCH_API_KEY is not defined in environment.");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
mcpServer = new McpServer({
|
|
26
|
+
name: "Test-Stitch-MCP",
|
|
27
|
+
version: "1.0.0"
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
registerTools(mcpServer);
|
|
31
|
+
registerResources(mcpServer);
|
|
32
|
+
|
|
33
|
+
// Create linked transports
|
|
34
|
+
const [t1, t2] = InMemoryTransport.createLinkedPair();
|
|
35
|
+
clientTransport = t1;
|
|
36
|
+
serverTransport = t2;
|
|
37
|
+
|
|
38
|
+
await mcpServer.connect(serverTransport);
|
|
39
|
+
|
|
40
|
+
client = new Client({ name: "test-client", version: "1.0.0" }, { capabilities: {} });
|
|
41
|
+
await client.connect(clientTransport);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterAll(async () => {
|
|
45
|
+
await clientTransport.close();
|
|
46
|
+
await serverTransport.close();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should list projects without error', async () => {
|
|
50
|
+
const listResult = await client.listTools();
|
|
51
|
+
expect(listResult.tools.some(t => t.name === 'list_projects')).toBe(true);
|
|
52
|
+
|
|
53
|
+
const result = await client.callTool({
|
|
54
|
+
name: 'list_projects',
|
|
55
|
+
arguments: {}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(result.isError).toBeFalsy();
|
|
59
|
+
// @ts-ignore
|
|
60
|
+
expect(result.content[0].type).toBe('text');
|
|
61
|
+
// @ts-ignore
|
|
62
|
+
expect(result.content[0].text).toContain('Projects:');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should create a project', async () => {
|
|
66
|
+
const result = await client.callTool({
|
|
67
|
+
name: 'create_project',
|
|
68
|
+
arguments: { title: 'Test Project via Vitest' }
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(result.isError).toBeFalsy();
|
|
72
|
+
// @ts-ignore
|
|
73
|
+
expect(result.content[0].text).toContain('Project created successfully!');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should generate a screen and fetch code (generate_and_fetch_code tool)', async () => {
|
|
77
|
+
const listResult = await client.callTool({
|
|
78
|
+
name: 'list_projects',
|
|
79
|
+
arguments: {}
|
|
80
|
+
});
|
|
81
|
+
// @ts-ignore
|
|
82
|
+
const projectsText = listResult.content[0].text as string;
|
|
83
|
+
|
|
84
|
+
const match = projectsText.match(/- (\d+)/);
|
|
85
|
+
if (match && match[1]) {
|
|
86
|
+
const projectId = match[1];
|
|
87
|
+
|
|
88
|
+
const result = await client.request(
|
|
89
|
+
{ method: "tools/call", params: { name: 'generate_and_fetch_code', arguments: { projectId: projectId, prompt: "A simple red button centered on the screen", deviceType: "DESKTOP" } } },
|
|
90
|
+
CallToolResultSchema,
|
|
91
|
+
{ timeout: 120000 }
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(result.isError).toBeFalsy();
|
|
95
|
+
// @ts-ignore
|
|
96
|
+
expect(result.content.length).toBeGreaterThan(1);
|
|
97
|
+
// @ts-ignore
|
|
98
|
+
const text0 = result.content[0].text || "";
|
|
99
|
+
// @ts-ignore
|
|
100
|
+
const text1 = result.content[1].text || "";
|
|
101
|
+
|
|
102
|
+
expect(text0).toContain('Screen generated!');
|
|
103
|
+
expect(text1).toContain('```html');
|
|
104
|
+
expect(text1).toContain('</html>');
|
|
105
|
+
}
|
|
106
|
+
}, 120000);
|
|
107
|
+
|
|
108
|
+
it('should scaffold project files successfully', async () => {
|
|
109
|
+
const listResult = await client.callTool({
|
|
110
|
+
name: 'list_projects',
|
|
111
|
+
arguments: {}
|
|
112
|
+
});
|
|
113
|
+
// @ts-ignore
|
|
114
|
+
const projectIdMatch = (listResult.content[0].text as string).match(/- (\d+)/);
|
|
115
|
+
|
|
116
|
+
if (projectIdMatch && projectIdMatch[1]) {
|
|
117
|
+
const projectId = projectIdMatch[1];
|
|
118
|
+
|
|
119
|
+
const genResult = await client.request(
|
|
120
|
+
{ method: "tools/call", params: { name: 'generate_screen', arguments: { projectId: projectId, prompt: "A test heading for scaffolding", deviceType: "MOBILE" } } },
|
|
121
|
+
CallToolResultSchema,
|
|
122
|
+
{ timeout: 120000 }
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// @ts-ignore
|
|
126
|
+
const screenIdMatch = (genResult.content[0].text as string).match(/Screen ID: ([a-zA-Z0-9]+)/);
|
|
127
|
+
if (screenIdMatch && screenIdMatch[1]) {
|
|
128
|
+
const screenId = screenIdMatch[1];
|
|
129
|
+
|
|
130
|
+
const filePath = 'tests/fixtures/test-scaffold.html';
|
|
131
|
+
|
|
132
|
+
const scaffoldResult = await client.request(
|
|
133
|
+
{ method: "tools/call", params: { name: 'scaffold_project_files', arguments: { projectId: projectId, screenId: screenId, filePath: filePath } } },
|
|
134
|
+
CallToolResultSchema,
|
|
135
|
+
{ timeout: 120000 }
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
expect(scaffoldResult.isError).toBeFalsy();
|
|
139
|
+
|
|
140
|
+
const resolvedPath = path.resolve(process.cwd(), filePath);
|
|
141
|
+
const fileStat = await fs.stat(resolvedPath);
|
|
142
|
+
expect(fileStat.isFile()).toBe(true);
|
|
143
|
+
|
|
144
|
+
const content = await fs.readFile(resolvedPath, 'utf8');
|
|
145
|
+
expect(content).toContain('<!DOCTYPE html');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}, 120000);
|
|
149
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"declaration": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"]
|
|
16
|
+
}
|