generate static method

String generate(
  1. List<Map<String, dynamic>> tools,
  2. int port,
  3. String address
)

Generates the HTTP server Dart code.

tools - List of tool definitions with parameters and metadata port - The network port to listen on (e.g., 3000, 8080) address - The bind address (e.g., '127.0.0.1', '0.0.0.0')

Returns the complete server code as a Dart string.

Implementation

static String generate(
  List<Map<String, dynamic>> tools,
  int port,
  String address,
) {
  // Collect unique imports for custom List inner types
  final listInnerImports = _collectListInnerImports(tools);
  final listInnerImportStatements = listInnerImports
      .map((uri) => "import '$uri';")
      .join('\n');

  // Determine the address expression to use
  final addressExpression = address == '127.0.0.1'
      ? 'io.InternetAddress.loopbackIPv4'
      : "'$address'";

  // Collect unique per-tool source imports with aliases
  final sourceImports = <String, String>{};
  for (final tool in tools) {
    final sourceImport = tool['sourceImport'] as String?;
    final sourceAlias = tool['sourceAlias'] as String?;
    if (sourceImport != null && sourceAlias != null) {
      sourceImports[sourceImport] = sourceAlias;
    }
  }
  final sourceImportStatements = sourceImports.entries
      .map((e) => "import '${e.key}' as ${e.value};")
      .join('\n');

  final toolRegistrations = tools
      .map((t) {
        final name = t['name'] as String;
        final schema = SchemaBuilder.buildObjectSchema(
          (t['parameters'] as List<Map<String, dynamic>>? ?? []),
        );
        return '''
  registerTool(
    Tool(
      name: '$name',
      description: '${t['description'] ?? 'Tool $name'}',
      inputSchema: $schema,
    ),
    _$name,
  );''';
      })
      .join('\n');

  final toolHandlers = tools
      .map((t) {
        final name = t['name'] as String;
        final params = t['parameters'] as List<Map<String, dynamic>>? ?? [];
        final paramExtractions = params
            .map((p) {
              final paramName = p['name'] as String;
              final paramType = p['type'] as String;
              final isOptional = p['isOptional'] == true;
              final dartType = _dartType(paramType);
              if (isOptional) {
                final nullableType = dartType.endsWith('?')
                    ? dartType
                    : '$dartType?';
                return "    final $paramName = request.arguments?['$paramName'] as $nullableType;";
              }
              return "    final $paramName = request.arguments!['$paramName'] as $dartType;";
            })
            .join('\n');

        // Generate conversion code for List parameters with custom inner types
        final paramConversions = params
            .where((p) => _needsListConversion(p['type'] as String))
            .map((p) {
              final paramName = p['name'] as String;
              final paramType = p['type'] as String;
              final innerType = _extractListInnerType(paramType);
              final isOptional = p['isOptional'] == true;
              if (isOptional) {
                return '    final ${paramName}Converted = $paramName?.map((e) => $innerType.fromJson(e as Map<String, dynamic>)).toList();';
              }
              return '    final ${paramName}Converted = $paramName.map((e) => $innerType.fromJson(e as Map<String, dynamic>)).toList();';
            })
            .join('\n');

        final isAsync = t['isAsync'] == true;
        final className = t['className'] as String?;
        final isStatic = t['isStatic'] == true;
        final sourceAlias = t['sourceAlias'] as String? ?? 'lib';

        String call;
        final methodName = t['methodName'] as String? ?? name;
        if (className != null && isStatic) {
          call = isAsync
              ? 'await $sourceAlias.$className.$methodName(${_callArgsWithConversion(params)})'
              : '$sourceAlias.$className.$methodName(${_callArgsWithConversion(params)})';
        } else if (className != null) {
          call = isAsync
              ? 'await $sourceAlias.$className().$methodName(${_callArgsWithConversion(params)})'
              : '$sourceAlias.$className().$methodName(${_callArgsWithConversion(params)})';
        } else {
          call = isAsync
              ? 'await $sourceAlias.$methodName(${_callArgsWithConversion(params)})'
              : '$sourceAlias.$methodName(${_callArgsWithConversion(params)})';
        }

        return '''
FutureOr<CallToolResult> _$name(CallToolRequest request) async {
  try {
$paramExtractions
$paramConversions
    final result = $call;
    return CallToolResult(
      content: [TextContent(text: _serializeResult(result))],
    );
  } catch (e) {
    return CallToolResult(
      content: [TextContent(text: 'An error occurred while processing the request.')],
      isError: true,
    );
  }
}''';
      })
      .join('\n');

  final ioImport = address == '127.0.0.1' ? "import 'dart:io' as io;" : '';

  return '''
// Generated MCP HTTP server
// DO NOT EDIT - automatically generated by mcp_generator

import 'dart:async';
import 'dart:convert';
$ioImport

import 'package:dart_mcp/server.dart';
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:stream_channel/stream_channel.dart';

$listInnerImportStatements
$sourceImportStatements

Future<void> main() async {
// Create stream controllers for bidirectional communication
final clientToServer = StreamController<String>();
final serverToClient = StreamController<String>.broadcast();

// Create the StreamChannel that MCPServer expects
final channel = StreamChannel<String>(
  clientToServer.stream,
  serverToClient.sink,
);

final server = MCPServerWithTools(channel);

// Buffer responses from the MCP server using a queue of completers
final responseQueue = <Completer<String>>[];
serverToClient.stream.listen((response) {
  if (responseQueue.isNotEmpty) {
    responseQueue.removeAt(0).complete(response);
  }
});

Future<shelf.Response> handleRequest(shelf.Request request) async {
  if (request.method != 'POST') {
    return shelf.Response(405, body: 'Method not allowed');
  }

  final body = await request.readAsString();
  final completer = Completer<String>();
  responseQueue.add(completer);
  clientToServer.add(body);

  final response = await completer.future;
  return shelf.Response.ok(
    response,
    headers: {'Content-Type': 'application/json'},
  );
}

final httpServer = await shelf_io.serve(
  handleRequest,
  $addressExpression,
  $port,
);

print('MCP HTTP server listening on port \${httpServer.port}');

// Wait for server to complete and then clean up
await server.done;
await httpServer.close();
await clientToServer.close();
await serverToClient.close();
}

base class MCPServerWithTools extends MCPServer with ToolsSupport {
MCPServerWithTools(super.channel)
  : super.fromStreamChannel(
      implementation: Implementation(
        name: 'mcp-server',
        version: '1.0.0',
      ),
      instructions: 'Auto-generated MCP server on port $port',
    ) {
$toolRegistrations
}

$toolHandlers

String _serializeResult(dynamic result) {
  if (result == null) return 'null';
  try {
    if (result is List) {
      final items = result.map((e) {
        if (e == null) return null;
        final toJson = e.toJson;
        if (toJson != null && toJson is Function) return toJson();
        return e.toString();
      }).where((e) => e != null).toList();
      return jsonEncode(items);
    }
    final toJson = result.toJson;
    if (toJson != null && toJson is Function) return jsonEncode(toJson());
    return result.toString();
  } catch (_) {
    return result.toString();
  }
}
}
''';
}