#
tokens: 5812/50000 7/7 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── build
│   └── main.js
├── CODE_OF_CONDUCT.md
├── LICENSE
├── nodemon.json
├── package-lock.json
├── package.json
├── README.md
├── src
│   └── main.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
  1 | # Logs
  2 | logs
  3 | *.log
  4 | npm-debug.log*
  5 | yarn-debug.log*
  6 | yarn-error.log*
  7 | lerna-debug.log*
  8 | .pnpm-debug.log*
  9 | 
 10 | # Diagnostic reports (https://nodejs.org/api/report.html)
 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
 12 | 
 13 | # Runtime data
 14 | pids
 15 | *.pid
 16 | *.seed
 17 | *.pid.lock
 18 | 
 19 | # Directory for instrumented libs generated by jscoverage/JSCover
 20 | lib-cov
 21 | 
 22 | # Coverage directory used by tools like istanbul
 23 | coverage
 24 | *.lcov
 25 | 
 26 | # nyc test coverage
 27 | .nyc_output
 28 | 
 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
 30 | .grunt
 31 | 
 32 | # Bower dependency directory (https://bower.io/)
 33 | bower_components
 34 | 
 35 | # node-waf configuration
 36 | .lock-wscript
 37 | 
 38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
 39 | build/Release
 40 | 
 41 | # Dependency directories
 42 | node_modules/
 43 | jspm_packages/
 44 | 
 45 | # Snowpack dependency directory (https://snowpack.dev/)
 46 | web_modules/
 47 | 
 48 | # TypeScript cache
 49 | *.tsbuildinfo
 50 | 
 51 | # Optional npm cache directory
 52 | .npm
 53 | 
 54 | # Optional eslint cache
 55 | .eslintcache
 56 | 
 57 | # Optional stylelint cache
 58 | .stylelintcache
 59 | 
 60 | # Microbundle cache
 61 | .rpt2_cache/
 62 | .rts2_cache_cjs/
 63 | .rts2_cache_es/
 64 | .rts2_cache_umd/
 65 | 
 66 | # Optional REPL history
 67 | .node_repl_history
 68 | 
 69 | # Output of 'npm pack'
 70 | *.tgz
 71 | 
 72 | # Yarn Integrity file
 73 | .yarn-integrity
 74 | 
 75 | # dotenv environment variable files
 76 | .env
 77 | .env.development.local
 78 | .env.test.local
 79 | .env.production.local
 80 | .env.local
 81 | 
 82 | # parcel-bundler cache (https://parceljs.org/)
 83 | .cache
 84 | .parcel-cache
 85 | 
 86 | # Next.js build output
 87 | .next
 88 | out
 89 | 
 90 | # Nuxt.js build / generate output
 91 | .nuxt
 92 | dist
 93 | 
 94 | # Gatsby files
 95 | .cache/
 96 | # Comment in the public line in if your project uses Gatsby and not Next.js
 97 | # https://nextjs.org/blog/next-9-1#public-directory-support
 98 | # public
 99 | 
100 | # vuepress build output
101 | .vuepress/dist
102 | 
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 | 
107 | # vitepress build output
108 | **/.vitepress/dist
109 | 
110 | # vitepress cache directory
111 | **/.vitepress/cache
112 | 
113 | # Docusaurus cache and generated files
114 | .docusaurus
115 | 
116 | # Serverless directories
117 | .serverless/
118 | 
119 | # FuseBox cache
120 | .fusebox/
121 | 
122 | # DynamoDB Local files
123 | .dynamodb/
124 | 
125 | # TernJS port file
126 | .tern-port
127 | 
128 | # Stores VSCode versions used for testing VSCode extensions
129 | .vscode-test
130 | 
131 | # yarn v2
132 | .yarn/cache
133 | .yarn/unplugged
134 | .yarn/build-state.yml
135 | .yarn/install-state.gz
136 | .pnp.*
137 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
1 | # three-js-mcp
2 | 
3 | MCP server for controlling ThreeJs source code, only basic function
4 | 
```

--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------

```markdown
  1 | 
  2 | # Contributor Covenant Code of Conduct
  3 | 
  4 | ## Our Pledge
  5 | 
  6 | We as members, contributors, and leaders pledge to make participation in our
  7 | community a harassment-free experience for everyone, regardless of age, body
  8 | size, visible or invisible disability, ethnicity, sex characteristics, gender
  9 | identity and expression, level of experience, education, socio-economic status,
 10 | nationality, personal appearance, race, caste, color, religion, or sexual
 11 | identity and orientation.
 12 | 
 13 | We pledge to act and interact in ways that contribute to an open, welcoming,
 14 | diverse, inclusive, and healthy community.
 15 | 
 16 | ## Our Standards
 17 | 
 18 | Examples of behavior that contributes to a positive environment for our
 19 | community include:
 20 | 
 21 | * Demonstrating empathy and kindness toward other people
 22 | * Being respectful of differing opinions, viewpoints, and experiences
 23 | * Giving and gracefully accepting constructive feedback
 24 | * Accepting responsibility and apologizing to those affected by our mistakes,
 25 |   and learning from the experience
 26 | * Focusing on what is best not just for us as individuals, but for the overall
 27 |   community
 28 | 
 29 | Examples of unacceptable behavior include:
 30 | 
 31 | * The use of sexualized language or imagery, and sexual attention or advances of
 32 |   any kind
 33 | * Trolling, insulting or derogatory comments, and personal or political attacks
 34 | * Public or private harassment
 35 | * Publishing others' private information, such as a physical or email address,
 36 |   without their explicit permission
 37 | * Other conduct which could reasonably be considered inappropriate in a
 38 |   professional setting
 39 | 
 40 | ## Enforcement Responsibilities
 41 | 
 42 | Community leaders are responsible for clarifying and enforcing our standards of
 43 | acceptable behavior and will take appropriate and fair corrective action in
 44 | response to any behavior that they deem inappropriate, threatening, offensive,
 45 | or harmful.
 46 | 
 47 | Community leaders have the right and responsibility to remove, edit, or reject
 48 | comments, commits, code, wiki edits, issues, and other contributions that are
 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation
 50 | decisions when appropriate.
 51 | 
 52 | ## Scope
 53 | 
 54 | This Code of Conduct applies within all community spaces, and also applies when
 55 | an individual is officially representing the community in public spaces.
 56 | Examples of representing our community include using an official email address,
 57 | posting via an official social media account, or acting as an appointed
 58 | representative at an online or offline event.
 59 | 
 60 | ## Enforcement
 61 | 
 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
 63 | reported to the community leaders responsible for enforcement at
 64 | [email protected].
 65 | All complaints will be reviewed and investigated promptly and fairly.
 66 | 
 67 | All community leaders are obligated to respect the privacy and security of the
 68 | reporter of any incident.
 69 | 
 70 | ## Enforcement Guidelines
 71 | 
 72 | Community leaders will follow these Community Impact Guidelines in determining
 73 | the consequences for any action they deem in violation of this Code of Conduct:
 74 | 
 75 | ### 1. Correction
 76 | 
 77 | **Community Impact**: Use of inappropriate language or other behavior deemed
 78 | unprofessional or unwelcome in the community.
 79 | 
 80 | **Consequence**: A private, written warning from community leaders, providing
 81 | clarity around the nature of the violation and an explanation of why the
 82 | behavior was inappropriate. A public apology may be requested.
 83 | 
 84 | ### 2. Warning
 85 | 
 86 | **Community Impact**: A violation through a single incident or series of
 87 | actions.
 88 | 
 89 | **Consequence**: A warning with consequences for continued behavior. No
 90 | interaction with the people involved, including unsolicited interaction with
 91 | those enforcing the Code of Conduct, for a specified period of time. This
 92 | includes avoiding interactions in community spaces as well as external channels
 93 | like social media. Violating these terms may lead to a temporary or permanent
 94 | ban.
 95 | 
 96 | ### 3. Temporary Ban
 97 | 
 98 | **Community Impact**: A serious violation of community standards, including
 99 | sustained inappropriate behavior.
100 | 
101 | **Consequence**: A temporary ban from any sort of interaction or public
102 | communication with the community for a specified period of time. No public or
103 | private interaction with the people involved, including unsolicited interaction
104 | with those enforcing the Code of Conduct, is allowed during this period.
105 | Violating these terms may lead to a permanent ban.
106 | 
107 | ### 4. Permanent Ban
108 | 
109 | **Community Impact**: Demonstrating a pattern of violation of community
110 | standards, including sustained inappropriate behavior, harassment of an
111 | individual, or aggression toward or disparagement of classes of individuals.
112 | 
113 | **Consequence**: A permanent ban from any sort of public interaction within the
114 | community.
115 | 
116 | ## Attribution
117 | 
118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119 | version 2.1, available at
120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
121 | 
122 | Community Impact Guidelines were inspired by
123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
124 | 
125 | For answers to common questions about this code of conduct, see the FAQ at
126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
127 | [https://www.contributor-covenant.org/translations][translations].
128 | 
129 | [homepage]: https://www.contributor-covenant.org
130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
131 | [Mozilla CoC]: https://github.com/mozilla/diversity
132 | [FAQ]: https://www.contributor-covenant.org/faq
133 | [translations]: https://www.contributor-covenant.org/translations
134 | 
135 | 
```

--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------

```json
1 | {
2 |   "watch": ["src/**/*.ts"],
3 |   "ignore": ["src/**/*.spec.ts"],
4 |   "exec": "npx ts-node ./main.ts"
5 | }
6 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true
12 |   },
13 |   "include": ["src/**/*"],
14 |   "exclude": ["node_modules"]
15 | }
16 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "three-js-mcp",
 3 |   "version": "1.0.0",
 4 |   "main": "index.js",
 5 |   "type": "module",
 6 |   "scripts": {
 7 |     "test": "echo \"Error: no test specified\" && exit 1",
 8 |     "build": "tsc && chmod 755 build/main.js"
 9 |   },
10 |   "keywords": [],
11 |   "author": "",
12 |   "license": "ISC",
13 |   "description": "",
14 |   "devDependencies": {
15 |     "@types/express": "^4.17.1",
16 |     "@types/ws": "^8.18.0",
17 |     "typescript": "^5.8.2"
18 |   },
19 |   "dependencies": {
20 |     "@modelcontextprotocol/sdk": "^1.7.0",
21 |     "express": "^4.17.1",
22 |     "ws": "^8.18.1"
23 |   },
24 |   "files": [
25 |     "build"
26 |   ]
27 | }
28 | 
```

--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import WebSocket, { WebSocketServer } from 'ws';
  2 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  4 | import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
  5 | 
  6 | // Initialize WebSocket server
  7 | const wss = new WebSocketServer({ port: 8082 });
  8 | let clientConnection: WebSocket | null = null;
  9 | let sceneState: any = null;
 10 | 
 11 | wss.on('connection', (ws: WebSocket) => {
 12 |   console.error('Client connected');
 13 |   clientConnection = ws;
 14 | 
 15 |   ws.on('message', (message: string) => {
 16 |     try {
 17 |       sceneState = JSON.parse(message);
 18 |       console.error('Updated scene state:', sceneState);
 19 |     } catch (e) {
 20 |       console.error('Invalid scene state message:', message);
 21 |     }
 22 |   });
 23 | 
 24 |   ws.on('close', () => {
 25 |     console.error('Client disconnected');
 26 |     clientConnection = null;
 27 |     sceneState = null;
 28 |   });
 29 | });
 30 | 
 31 | // Initialize MCP server
 32 | const server = new Server(
 33 |   { name: "threejs_mcp_server", version: "1.0.0" },
 34 |   { capabilities: { prompts: {}, tools: {} } }
 35 | );
 36 | 
 37 | server.onerror = (error) => {
 38 |   console.error("[MCP Error]", error);
 39 | };
 40 | 
 41 | process.on("SIGINT", async () => {
 42 |   wss.close()
 43 |   await server.close();
 44 |   process.exit(0);
 45 | });
 46 | 
 47 | // Define MCP tools
 48 | const tools = [
 49 |   {
 50 |     name: "addObject",
 51 |     description: "Add an object to the scene",
 52 |     inputSchema: {
 53 |       type: "object",
 54 |       properties: {
 55 |         type: { type: "string" },
 56 |         position: { type: "array", items: { type: "number" }, minItems: 3, maxItems: 3 },
 57 |         color: { type: "string" }
 58 |       },
 59 |       required: ["type", "position", "color"]
 60 |     }
 61 |   },
 62 |   {
 63 |     name: "moveObject",
 64 |     description: "Move an object to a new position",
 65 |     inputSchema: {
 66 |       type: "object",
 67 |       properties: {
 68 |         id: { type: "string" },
 69 |         position: { type: "array", items: { type: "number" }, minItems: 3, maxItems: 3 }
 70 |       },
 71 |       required: ["id", "position"]
 72 |     }
 73 |   },
 74 |   {
 75 |     name: "removeObject",
 76 |     description: "Remove an object",
 77 |     inputSchema: {
 78 |       type: "object",
 79 |       properties: {
 80 |         id: { type: "string" }
 81 |       },
 82 |       required: ["id"]
 83 |     }
 84 |   },
 85 |   {
 86 |     name: "startRotation",
 87 |     description: "Start rotating an object around the y-axis",
 88 |     inputSchema: {
 89 |       type: "object",
 90 |       properties: {
 91 |         id: { type: "string" },        // The ID of the object (e.g., "cube1")
 92 |         speed: { type: "number" }      // Rotation speed in radians per frame
 93 |       },
 94 |       required: ["id", "speed"]
 95 |     }
 96 |   },
 97 |   {
 98 |     name: "stopRotation",
 99 |     description: "Stop rotating an object",
100 |     inputSchema: {
101 |       type: "object",
102 |       properties: {
103 |         id: { type: "string" }         // The ID of the object
104 |       },
105 |       required: ["id"]
106 |     }
107 |   },
108 |   {
109 |     name: "getSceneState",
110 |     description: "Get the current scene state",
111 |     inputSchema: { type: "object", properties: {} }
112 |   }
113 | ];
114 | 
115 | const prompts = [
116 |   {
117 |     name: "asset-creation-strategy",
118 |     description: "Defines the preferred strategy for creating assets in ThreeJS",
119 |     arguments: []
120 |   }
121 | ]
122 | 
123 | // Handle tool listing
124 | server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
125 | server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts }));
126 | 
127 | // server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [] }));
128 | 
129 | // Handle tool calls
130 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
131 |   const { name, arguments: input } = request.params;
132 | 
133 |   console.error("request: ===================", request)
134 |   console.error("state: ===================", sceneState)
135 | 
136 |   if (name === "addObject") {
137 |     if (!clientConnection) {
138 |       return {
139 |         content: [
140 |             {
141 |               type: "text",
142 |               text: "No client connection available"
143 |             }
144 |           ]
145 |       };
146 |     }
147 |     const command = { action: "addObject", ...(input as any) };
148 |     clientConnection.send(JSON.stringify(command));
149 |     return {
150 |       content: [
151 |         {
152 |           type: "text",
153 |           text: "sent"
154 |         }
155 |       ]
156 |     };
157 |   } else if (name === "moveObject") {
158 |     if (!clientConnection) {
159 |       return {
160 |         content: [
161 |             {
162 |               type: "text",
163 |               text: "No client connection available"
164 |             }
165 |           ]
166 |       };
167 |     }
168 |     const command = { action: "moveObject", ...(input as any) };
169 |     clientConnection.send(JSON.stringify(command));
170 |     return {
171 |       content: [
172 |         {
173 |           type: "text",
174 |           text: "sent"
175 |         }
176 |       ]
177 |     };
178 |   } else if (name === 'removeObject') {
179 |     if (!clientConnection) {
180 |       return {
181 |         content: [
182 |             {
183 |               type: "text",
184 |               text: "No client connection available"
185 |             }
186 |           ]
187 |       };
188 |     }
189 |     const command = { action: "removeObject", ...(input as any) };
190 |     clientConnection.send(JSON.stringify(command));
191 |     return {
192 |       content: [
193 |         {
194 |           type: "text",
195 |           text: "sent"
196 |         }
197 |       ]
198 |      };
199 |   } else if (name === "getSceneState") {
200 |     if (sceneState) {
201 |       return {
202 |         content: [
203 |           {
204 |             type: "text",
205 |             text: JSON.stringify(sceneState?.data, null, 2)
206 |           }
207 |         ]
208 |       };
209 |     } else {
210 |       return {
211 |         content: [
212 |           {
213 |             type: "text",
214 |             text: "No scene state available"
215 |           }
216 |         ]
217 |       };
218 |     }
219 |   } else if (name === "startRotation") {
220 |     if (!clientConnection) {
221 |       return {
222 |         content: [
223 |             {
224 |               type: "text",
225 |               text: "No client connection available"
226 |             }
227 |           ]
228 |       };
229 |     }
230 |     const command = {
231 |       action: "startRotation",
232 |       id: input?.id,
233 |       speed: input?.speed
234 |     };
235 |     clientConnection.send(JSON.stringify(command));
236 |     return {
237 |       content: [
238 |         {
239 |           type: "text",
240 |           text: "sent"
241 |         }
242 |       ]
243 |     };
244 |   } else if (name === "stopRotation") {
245 |     if (!clientConnection) {
246 |       return {
247 |         content: [
248 |             {
249 |               type: "text",
250 |               text: "No client connection available"
251 |             }
252 |           ]
253 |       };
254 |     }
255 |     const command = {
256 |       action: "stopRotation",
257 |       id: input?.id
258 |     };
259 |     clientConnection.send(JSON.stringify(command));
260 |     return {
261 |       content: [
262 |         {
263 |           type: "text",
264 |           text: "sent"
265 |         }
266 |       ]
267 |     };
268 |   }
269 |   return {
270 |     content: [
271 |       {
272 |         type: "text",
273 |         text: "Tool not found"
274 |       }
275 |     ]
276 |   };;
277 | });
278 | 
279 | server.setRequestHandler(GetPromptRequestSchema, async (request) => {
280 |   if (request.params.name !== "asset-creation-strategy") {
281 |     throw new Error("Unknown prompt");
282 |   }
283 |   return {
284 |     description: "Defines the preferred strategy for creating assets in ThreeJS",
285 |     messages: [{
286 |       role: "assistant",
287 |       content: {
288 |         type: "text",
289 |         text: `
290 |           When creating 3D content in ThreeJS, always start by checking if integrations are available:
291 |           0. Before anything, always check the scene from getSceneState() tool
292 |           1. Response of getSceneState() tool always give you with the format delimited by ### format ###
293 |              ###
294 |               {
295 |                 [
296 |                   {
297 |                     id: "cube1",
298 |                     type: "cube",
299 |                     position: [0, 0, 0],
300 |                     color: "red",
301 |                     ...
302 |                   }
303 |                 ]
304 |               }
305 |              ###
306 |           2. Always find the id of the object in response of getSceneState() tool
307 |           3. Always use the id of the object to manipulate it with other tools
308 |         `
309 |       }
310 |     }]
311 |   };
312 | });
313 | 
314 | // Start MCP server with stdio transport
315 | async function main() {
316 |   const transport = new StdioServerTransport();
317 |   await server.connect(transport);
318 | }
319 | 
320 | main().catch((error) => {
321 |   console.error("Server error:", error);
322 |   process.exit(1);
323 | });
324 | 
```