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

```
├── .DS_Store
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
├── src
│   ├── index.ts
│   └── types
│       └── modelcontextprotocol.d.ts
└── tsconfig.json
```

# Files

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

```
1 | node_modules/
2 | build/
3 | *.log
4 | .env*
```

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

```markdown
  1 | # Flutter Tools MCP Server
  2 | 
  3 | ## Overview
  4 | 
  5 | The `flutter-tools` MCP server provides tools for interacting with the Flutter SDK. It offers two main tools: `get_diagnostics` and `apply_fixes`. These tools help in analyzing and fixing Dart/Flutter files.
  6 | 
  7 | ## Tools
  8 | 
  9 | ### get_diagnostics
 10 | 
 11 | **Description:** Get Flutter/Dart diagnostics for a file.
 12 | 
 13 | **Input Schema:**
 14 | ```json
 15 | {
 16 |   "type": "object",
 17 |   "properties": {
 18 |     "file": {
 19 |       "type": "string",
 20 |       "description": "Path to the Dart/Flutter file"
 21 |     }
 22 |   },
 23 |   "required": ["file"]
 24 | }
 25 | ```
 26 | 
 27 | **Example Usage:**
 28 | ```json
 29 | {
 30 |   "name": "get_diagnostics",
 31 |   "arguments": {
 32 |     "file": "/path/to/your/file.dart"
 33 |   }
 34 | }
 35 | ```
 36 | 
 37 | ### apply_fixes
 38 | 
 39 | **Description:** Apply Dart fix suggestions to a file.
 40 | 
 41 | **Input Schema:**
 42 | ```json
 43 | {
 44 |   "type": "object",
 45 |   "properties": {
 46 |     "file": {
 47 |       "type": "string",
 48 |       "description": "Path to the Dart/Flutter file"
 49 |     }
 50 |   },
 51 |   "required": ["file"]
 52 | }
 53 | ```
 54 | 
 55 | **Example Usage:**
 56 | ```json
 57 | {
 58 |   "name": "apply_fixes",
 59 |   "arguments": {
 60 |     "file": "/path/to/your/file.dart"
 61 |   }
 62 | }
 63 | ```
 64 | 
 65 | ## Dependencies
 66 | 
 67 | - `@modelcontextprotocol/sdk`: ^1.0.0
 68 | - `node-pty`: ^1.0.0
 69 | - `which`: ^4.0.0
 70 | 
 71 | ## Dev Dependencies
 72 | 
 73 | - `@types/node`: ^18.19.0
 74 | - `@types/which`: ^3.0.3
 75 | - `typescript`: ^5.3.3
 76 | 
 77 | ## Scripts
 78 | 
 79 | - `build`: Compiles the TypeScript code and sets the executable permissions on the compiled JavaScript file.
 80 | - `prepare`: Runs the `build` script.
 81 | - `watch`: Compiles the TypeScript code and watches for changes, recompiling automatically.
 82 | 
 83 | ## Installation
 84 | 
 85 | To install the MCP server, add the following configuration to your MCP settings file:
 86 | 
 87 | ```json
 88 | {
 89 |   "mcpServers": {
 90 |     "flutter-tools": {
 91 |       "command": "node",
 92 |       "args": ["/path/to/flutter-tools/build/index.js"],
 93 |       "env": {}
 94 |     }
 95 |   }
 96 | }
 97 | ```
 98 | 
 99 | Replace `/path/to/flutter-tools/build/index.js` with the actual path to the compiled JavaScript file.
100 | 
101 | ## Usage
102 | 
103 | 1. Ensure the Flutter SDK is installed and available in your PATH.
104 | 2. Start the MCP server using the configured command.
105 | 3. Use the `get_diagnostics` and `apply_fixes` tools as needed.
106 | 
107 | ## Example
108 | 
109 | ```bash
110 | node /path/to/flutter-tools/build/index.js
111 | 
```

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

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "NodeNext",
 5 |     "moduleResolution": "NodeNext",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true,
12 |     "allowJs": true,
13 |     "resolveJsonModule": true,
14 |     "baseUrl": ".",
15 |     "typeRoots": [
16 |       "./src/types",
17 |       "./node_modules/@types"
18 |     ]
19 |   },
20 |   "include": ["src/**/*"],
21 |   "exclude": ["node_modules"]
22 | }
23 | 
```

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

```json
 1 | {
 2 |   "name": "flutter-tools",
 3 |   "version": "0.1.0",
 4 |   "description": "A Model Context Protocol server for Flutter/Dart tools",
 5 |   "private": true,
 6 |   "type": "module",
 7 |   "bin": {
 8 |     "flutter-tools": "./build/index.js"
 9 |   },
10 |   "files": [
11 |     "build"
12 |   ],
13 |   "engines": {
14 |     "node": ">=16.0.0"
15 |   },
16 |   "scripts": {
17 |     "build": "tsc && node --experimental-modules -e \"import('fs').then(fs => fs.chmodSync('build/index.js', '755'))\"",
18 |     "prepare": "npm run build",
19 |     "watch": "tsc --watch"
20 |   },
21 |   "dependencies": {
22 |     "@modelcontextprotocol/sdk": "^1.0.0",
23 |     "node-pty": "^1.0.0",
24 |     "which": "^4.0.0"
25 |   },
26 |   "devDependencies": {
27 |     "@types/node": "^18.19.0",
28 |     "@types/which": "^3.0.3",
29 |     "typescript": "^5.3.3"
30 |   },
31 |   "exports": {
32 |     ".": {
33 |       "import": "./build/index.js"
34 |     }
35 |   }
36 | }
37 | 
```

--------------------------------------------------------------------------------
/src/types/modelcontextprotocol.d.ts:
--------------------------------------------------------------------------------

```typescript
 1 | declare module '@modelcontextprotocol/sdk' {
 2 |   export class Server {
 3 |     constructor(info: { name: string; version: string }, config: { capabilities: { tools: {} } });
 4 |     onerror: (error: Error) => void;
 5 |     setRequestHandler<T>(schema: RequestSchema, handler: (request: T) => Promise<HandlerResponse>): void;
 6 |     connect(transport: StdioServerTransport): Promise<void>;
 7 |     close(): Promise<void>;
 8 |   }
 9 | 
10 |   export class StdioServerTransport {
11 |     constructor();
12 |   }
13 | 
14 |   export interface RequestSchema {
15 |     type: string;
16 |     properties: Record<string, any>;
17 |     required?: string[];
18 |   }
19 | 
20 |   export interface HandlerResponse {
21 |     content: Array<{
22 |       type: string;
23 |       text: string;
24 |     }>;
25 |   }
26 | 
27 |   export const CallToolRequestSchema: RequestSchema;
28 |   export const ListToolsRequestSchema: RequestSchema;
29 | 
30 |   export class McpError extends Error {
31 |     constructor(code: ErrorCode, message: string);
32 |   }
33 | 
34 |   export enum ErrorCode {
35 |     InternalError = 'InternalError',
36 |     InvalidParams = 'InvalidParams',
37 |     MethodNotFound = 'MethodNotFound'
38 |   }
39 | 
40 |   export interface CallToolRequest {
41 |     params: {
42 |       name: string;
43 |       arguments?: {
44 |         file?: string;
45 |         [key: string]: any;
46 |       };
47 |     };
48 |   }
49 | 
50 |   export interface Tool {
51 |     name: string;
52 |     description: string;
53 |     inputSchema: {
54 |       type: string;
55 |       properties: Record<string, any>;
56 |       required?: string[];
57 |     };
58 |   }
59 | 
60 |   export interface ListToolsResponse extends HandlerResponse {
61 |     tools: Tool[];
62 |   }
63 | }
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  4 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  5 | import {
  6 |   CallToolRequestSchema,
  7 |   ErrorCode,
  8 |   ListToolsRequestSchema,
  9 |   McpError,
 10 | } from '@modelcontextprotocol/sdk/types.js';
 11 | import { spawn } from 'node-pty';
 12 | import which from 'which';
 13 | 
 14 | interface FlutterToolsServer {
 15 |   flutterProcess?: any;
 16 | }
 17 | 
 18 | interface McpRequest {
 19 |   jsonrpc: '2.0';
 20 |   id: number;
 21 |   method: string;
 22 |   params: any;
 23 | }
 24 | 
 25 | interface McpResponse {
 26 |   jsonrpc: '2.0';
 27 |   id: number;
 28 |   result?: any;
 29 |   error?: {
 30 |     code: number;
 31 |     message: string;
 32 |   };
 33 | }
 34 | 
 35 | class FlutterTools {
 36 |   private nextId = 1;
 37 |   private state: FlutterToolsServer = {};
 38 |   private server: Server;
 39 | 
 40 |   constructor() {
 41 |     this.server = new Server(
 42 |       {
 43 |         name: 'flutter-tools',
 44 |         version: '0.1.0',
 45 |       },
 46 |       {
 47 |         capabilities: {
 48 |           resources: {},
 49 |           tools: {},
 50 |         },
 51 |       }
 52 |     );
 53 | 
 54 |     this.setupToolHandlers();
 55 |     this.server.onerror = (error) => console.error('[MCP Error]', error);
 56 |     process.on('SIGINT', async () => {
 57 |       await this.server.close();
 58 |       process.exit(0);
 59 |     });
 60 |   }
 61 | 
 62 |   private async findFlutterSdk(): Promise<string> {
 63 |     try {
 64 |       return await which('flutter');
 65 |     } catch (error) {
 66 |       throw new Error('Flutter SDK not found in PATH. Please ensure Flutter is installed and in your PATH.');
 67 |     }
 68 |   }
 69 | 
 70 |   private async startFlutterDaemon() {
 71 |     if (!this.state.flutterProcess) {
 72 |       const flutterPath = await this.findFlutterSdk();
 73 |       this.state.flutterProcess = spawn(flutterPath, ['daemon'], {
 74 |         name: 'xterm-color',
 75 |         cols: 80,
 76 |         rows: 30,
 77 |       });
 78 |     }
 79 |   }
 80 | 
 81 |   private async cleanup() {
 82 |     if (this.state.flutterProcess) {
 83 |       this.state.flutterProcess.kill();
 84 |     }
 85 |     process.exit(0);
 86 |   }
 87 | 
 88 |   private setupToolHandlers() {
 89 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
 90 |       tools: [
 91 |         {
 92 |           name: 'get_diagnostics',
 93 |           description: 'Get Flutter/Dart diagnostics for a file',
 94 |           inputSchema: {
 95 |             type: 'object',
 96 |             properties: {
 97 |               file: {
 98 |                 type: 'string',
 99 |                 description: 'Path to the Dart/Flutter file',
100 |               },
101 |             },
102 |             required: ['file'],
103 |           },
104 |         },
105 |         {
106 |           name: 'apply_fixes',
107 |           description: 'Apply Dart fix suggestions to a file',
108 |           inputSchema: {
109 |             type: 'object',
110 |             properties: {
111 |               file: {
112 |                 type: 'string',
113 |                 description: 'Path to the Dart/Flutter file',
114 |               },
115 |             },
116 |             required: ['file'],
117 |           },
118 |         },
119 |       ],
120 |     }));
121 | 
122 |     this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
123 |       if (request.params.name !== 'get_diagnostics' && request.params.name !== 'apply_fixes') {
124 |         throw new McpError(
125 |           ErrorCode.MethodNotFound,
126 |           `Unknown tool: ${request.params.name}`
127 |         );
128 |       }
129 | 
130 |       await this.startFlutterDaemon();
131 | 
132 |       switch (request.params.name) {
133 |         case 'get_diagnostics': {
134 |           const filePath = String(request.params.arguments?.file);
135 |           if (!filePath) {
136 |             throw new McpError(
137 |               ErrorCode.InvalidParams,
138 |               'File path is required'
139 |             );
140 |           }
141 | 
142 |           const diagnostics = await this.getDiagnostics(filePath);
143 |           return {
144 |             jsonrpc: '2.0',
145 |             result: {
146 |               content: [{
147 |                 type: 'text',
148 |                 text: JSON.stringify(diagnostics, null, 2),
149 |               }],
150 |             },
151 |           };
152 |         }
153 | 
154 |         case 'apply_fixes': {
155 |           const filePath = String(request.params.arguments?.file);
156 |           if (!filePath) {
157 |             throw new McpError(
158 |               ErrorCode.InvalidParams,
159 |               'File path is required'
160 |             );
161 |           }
162 | 
163 |           const result = await this.applyFixes(filePath);
164 |           return {
165 |             jsonrpc: '2.0',
166 |             result: {
167 |               content: [{
168 |                 type: 'text',
169 |                 text: `Applied fixes to ${filePath}: ${result}`,
170 |               }],
171 |             },
172 |           };
173 |         }
174 | 
175 |         default:
176 |           throw new McpError(
177 |             ErrorCode.MethodNotFound,
178 |             `Unknown tool: ${request.params.name}`
179 |           );
180 |       }
181 |     });
182 |   }
183 | 
184 |   private async getDiagnostics(filePath: string): Promise<any[]> {
185 |     return new Promise((resolve, reject) => {
186 |       const flutterPath = this.findFlutterSdk();
187 |       const process = spawn(String(flutterPath), ['analyze', filePath, '--json'], {
188 |         name: 'xterm-color',
189 |         cols: 80,
190 |         rows: 30,
191 |       });
192 | 
193 |       let output = '';
194 |       const dataHandler = process.onData((data: string) => {
195 |         output += data;
196 |       });
197 | 
198 |       const cleanup = () => {
199 |         process.kill();
200 |         if (dataHandler) {
201 |           dataHandler.dispose();
202 |         }
203 |       };
204 | 
205 |       const timeout = setTimeout(() => {
206 |         cleanup();
207 |         reject(new Error('Timeout waiting for diagnostics'));
208 |       }, 30000);
209 | 
210 |       const interval = setInterval(() => {
211 |         try {
212 |           const diagnostics = JSON.parse(output);
213 |           clearTimeout(timeout);
214 |           clearInterval(interval);
215 |           cleanup();
216 |           resolve(diagnostics);
217 |         } catch {
218 |           // Keep waiting for complete output
219 |         }
220 |       }, 100);
221 |     });
222 |   }
223 | 
224 |   private async applyFixes(filePath: string): Promise<string> {
225 |     if (!this.state.flutterProcess) {
226 |       throw new Error('Flutter process not initialized');
227 |     }
228 | 
229 |     return new Promise<string>((resolve, reject) => {
230 |       let output = '';
231 |       const process = this.state.flutterProcess;
232 | 
233 |       if (!process) {
234 |         reject(new Error('Flutter process not available'));
235 |         return;
236 |       }
237 | 
238 |       process.write(`dart fix --apply ${filePath}\r`);
239 | 
240 |       const dataHandler = process.onData((data: string) => {
241 |         output += data;
242 |         if (output.includes('Applied')) {
243 |           dataHandler.dispose();
244 |           resolve(output.trim());
245 |         }
246 |       });
247 | 
248 |       setTimeout(() => {
249 |         if (dataHandler) {
250 |           dataHandler.dispose();
251 |         }
252 |         reject(new Error('Timeout waiting for dart fix to complete'));
253 |       }, 30000);
254 |     });
255 |   }
256 | 
257 |   async run() {
258 |     const transport = new StdioServerTransport();
259 |     await this.server.connect(transport);
260 |     console.error('Flutter Tools MCP server running on stdio');
261 |   }
262 | }
263 | 
264 | const server = new FlutterTools();
265 | server.run().catch((error: unknown) => {
266 |   const errorMessage = error instanceof Error ? error.message : String(error);
267 |   console.error('Server error:', errorMessage);
268 |   process.exit(1);
269 | });
270 | 
```