#
tokens: 2941/50000 6/6 files
lines: off (toggle) GitHub
raw markdown copy
# 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);
});

```