# 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 |
```