# Directory Structure
```
├── .DS_Store
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
├── src
│ ├── index.ts
│ └── types
│ └── modelcontextprotocol.d.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
node_modules/
build/
*.log
.env*
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Flutter Tools MCP Server
## Overview
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.
## Tools
### get_diagnostics
**Description:** Get Flutter/Dart diagnostics for a file.
**Input Schema:**
```json
{
"type": "object",
"properties": {
"file": {
"type": "string",
"description": "Path to the Dart/Flutter file"
}
},
"required": ["file"]
}
```
**Example Usage:**
```json
{
"name": "get_diagnostics",
"arguments": {
"file": "/path/to/your/file.dart"
}
}
```
### apply_fixes
**Description:** Apply Dart fix suggestions to a file.
**Input Schema:**
```json
{
"type": "object",
"properties": {
"file": {
"type": "string",
"description": "Path to the Dart/Flutter file"
}
},
"required": ["file"]
}
```
**Example Usage:**
```json
{
"name": "apply_fixes",
"arguments": {
"file": "/path/to/your/file.dart"
}
}
```
## Dependencies
- `@modelcontextprotocol/sdk`: ^1.0.0
- `node-pty`: ^1.0.0
- `which`: ^4.0.0
## Dev Dependencies
- `@types/node`: ^18.19.0
- `@types/which`: ^3.0.3
- `typescript`: ^5.3.3
## Scripts
- `build`: Compiles the TypeScript code and sets the executable permissions on the compiled JavaScript file.
- `prepare`: Runs the `build` script.
- `watch`: Compiles the TypeScript code and watches for changes, recompiling automatically.
## Installation
To install the MCP server, add the following configuration to your MCP settings file:
```json
{
"mcpServers": {
"flutter-tools": {
"command": "node",
"args": ["/path/to/flutter-tools/build/index.js"],
"env": {}
}
}
}
```
Replace `/path/to/flutter-tools/build/index.js` with the actual path to the compiled JavaScript file.
## Usage
1. Ensure the Flutter SDK is installed and available in your PATH.
2. Start the MCP server using the configured command.
3. Use the `get_diagnostics` and `apply_fixes` tools as needed.
## Example
```bash
node /path/to/flutter-tools/build/index.js
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"allowJs": true,
"resolveJsonModule": true,
"baseUrl": ".",
"typeRoots": [
"./src/types",
"./node_modules/@types"
]
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "flutter-tools",
"version": "0.1.0",
"description": "A Model Context Protocol server for Flutter/Dart tools",
"private": true,
"type": "module",
"bin": {
"flutter-tools": "./build/index.js"
},
"files": [
"build"
],
"engines": {
"node": ">=16.0.0"
},
"scripts": {
"build": "tsc && node --experimental-modules -e \"import('fs').then(fs => fs.chmodSync('build/index.js', '755'))\"",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"node-pty": "^1.0.0",
"which": "^4.0.0"
},
"devDependencies": {
"@types/node": "^18.19.0",
"@types/which": "^3.0.3",
"typescript": "^5.3.3"
},
"exports": {
".": {
"import": "./build/index.js"
}
}
}
```
--------------------------------------------------------------------------------
/src/types/modelcontextprotocol.d.ts:
--------------------------------------------------------------------------------
```typescript
declare module '@modelcontextprotocol/sdk' {
export class Server {
constructor(info: { name: string; version: string }, config: { capabilities: { tools: {} } });
onerror: (error: Error) => void;
setRequestHandler<T>(schema: RequestSchema, handler: (request: T) => Promise<HandlerResponse>): void;
connect(transport: StdioServerTransport): Promise<void>;
close(): Promise<void>;
}
export class StdioServerTransport {
constructor();
}
export interface RequestSchema {
type: string;
properties: Record<string, any>;
required?: string[];
}
export interface HandlerResponse {
content: Array<{
type: string;
text: string;
}>;
}
export const CallToolRequestSchema: RequestSchema;
export const ListToolsRequestSchema: RequestSchema;
export class McpError extends Error {
constructor(code: ErrorCode, message: string);
}
export enum ErrorCode {
InternalError = 'InternalError',
InvalidParams = 'InvalidParams',
MethodNotFound = 'MethodNotFound'
}
export interface CallToolRequest {
params: {
name: string;
arguments?: {
file?: string;
[key: string]: any;
};
};
}
export interface Tool {
name: string;
description: string;
inputSchema: {
type: string;
properties: Record<string, any>;
required?: string[];
};
}
export interface ListToolsResponse extends HandlerResponse {
tools: Tool[];
}
}
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import { spawn } from 'node-pty';
import which from 'which';
interface FlutterToolsServer {
flutterProcess?: any;
}
interface McpRequest {
jsonrpc: '2.0';
id: number;
method: string;
params: any;
}
interface McpResponse {
jsonrpc: '2.0';
id: number;
result?: any;
error?: {
code: number;
message: string;
};
}
class FlutterTools {
private nextId = 1;
private state: FlutterToolsServer = {};
private server: Server;
constructor() {
this.server = new Server(
{
name: 'flutter-tools',
version: '0.1.0',
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
private async findFlutterSdk(): Promise<string> {
try {
return await which('flutter');
} catch (error) {
throw new Error('Flutter SDK not found in PATH. Please ensure Flutter is installed and in your PATH.');
}
}
private async startFlutterDaemon() {
if (!this.state.flutterProcess) {
const flutterPath = await this.findFlutterSdk();
this.state.flutterProcess = spawn(flutterPath, ['daemon'], {
name: 'xterm-color',
cols: 80,
rows: 30,
});
}
}
private async cleanup() {
if (this.state.flutterProcess) {
this.state.flutterProcess.kill();
}
process.exit(0);
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'get_diagnostics',
description: 'Get Flutter/Dart diagnostics for a file',
inputSchema: {
type: 'object',
properties: {
file: {
type: 'string',
description: 'Path to the Dart/Flutter file',
},
},
required: ['file'],
},
},
{
name: 'apply_fixes',
description: 'Apply Dart fix suggestions to a file',
inputSchema: {
type: 'object',
properties: {
file: {
type: 'string',
description: 'Path to the Dart/Flutter file',
},
},
required: ['file'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== 'get_diagnostics' && request.params.name !== 'apply_fixes') {
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
await this.startFlutterDaemon();
switch (request.params.name) {
case 'get_diagnostics': {
const filePath = String(request.params.arguments?.file);
if (!filePath) {
throw new McpError(
ErrorCode.InvalidParams,
'File path is required'
);
}
const diagnostics = await this.getDiagnostics(filePath);
return {
jsonrpc: '2.0',
result: {
content: [{
type: 'text',
text: JSON.stringify(diagnostics, null, 2),
}],
},
};
}
case 'apply_fixes': {
const filePath = String(request.params.arguments?.file);
if (!filePath) {
throw new McpError(
ErrorCode.InvalidParams,
'File path is required'
);
}
const result = await this.applyFixes(filePath);
return {
jsonrpc: '2.0',
result: {
content: [{
type: 'text',
text: `Applied fixes to ${filePath}: ${result}`,
}],
},
};
}
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
});
}
private async getDiagnostics(filePath: string): Promise<any[]> {
return new Promise((resolve, reject) => {
const flutterPath = this.findFlutterSdk();
const process = spawn(String(flutterPath), ['analyze', filePath, '--json'], {
name: 'xterm-color',
cols: 80,
rows: 30,
});
let output = '';
const dataHandler = process.onData((data: string) => {
output += data;
});
const cleanup = () => {
process.kill();
if (dataHandler) {
dataHandler.dispose();
}
};
const timeout = setTimeout(() => {
cleanup();
reject(new Error('Timeout waiting for diagnostics'));
}, 30000);
const interval = setInterval(() => {
try {
const diagnostics = JSON.parse(output);
clearTimeout(timeout);
clearInterval(interval);
cleanup();
resolve(diagnostics);
} catch {
// Keep waiting for complete output
}
}, 100);
});
}
private async applyFixes(filePath: string): Promise<string> {
if (!this.state.flutterProcess) {
throw new Error('Flutter process not initialized');
}
return new Promise<string>((resolve, reject) => {
let output = '';
const process = this.state.flutterProcess;
if (!process) {
reject(new Error('Flutter process not available'));
return;
}
process.write(`dart fix --apply ${filePath}\r`);
const dataHandler = process.onData((data: string) => {
output += data;
if (output.includes('Applied')) {
dataHandler.dispose();
resolve(output.trim());
}
});
setTimeout(() => {
if (dataHandler) {
dataHandler.dispose();
}
reject(new Error('Timeout waiting for dart fix to complete'));
}, 30000);
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Flutter Tools MCP server running on stdio');
}
}
const server = new FlutterTools();
server.run().catch((error: unknown) => {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error('Server error:', errorMessage);
process.exit(1);
});
```