A Model Context Protocol (MCP) server focused on C#βbased Godot 4 development workflows.
This project provides tools for project scaffolding, build/export automation, static analysis, and editorβlevel operations β all tailored for C# developers building games with Godot.
This server does not provide runtime debugging or sceneβtree inspection.
For runtimeβfocused MCP tooling, see the excellent community project
godot4-runtime-mcp: https://github.com/minghuiliu/godot4-runtime-mcp
- Quick Start - Get up and running in minutes
- Tool Catalog - Complete list of available tools
- Examples - Common workflows and usage patterns
- Configuration - Environment variables and settings
- Security & Configuration - Workspace isolation and safety features
- Solution Layout - Project structure and dependencies
- .NET 10 SDK or later
# Clone the repository
git clone https://github.com/jongalloway/godot-csharp-dev-mcp.git
cd godot-csharp-dev-mcp
# Build the project
dotnet build --configuration Release# Run the MCP server (waits for JSON-RPC messages on stdin)
dotnet run --project src/GodotCSharpDevMcp.Server --configuration Release
# OR use the built-in CLI test harness for quick testing
dotnet run --project src/GodotCSharpDevMcp.Server -- --test list_project_files '{}'Try the list_project_files tool to verify the server is working:
# Using the CLI test harness
dotnet run --project src/GodotCSharpDevMcp.Server -- --test list_project_files '{}'
# Expected output: JSON list of files in current directoryCreate a simple C# script:
# Set workspace directory
export GODOT_WORKSPACE_ROOT=/tmp/my-godot-project
mkdir -p $GODOT_WORKSPACE_ROOT
# Create a player script
dotnet run --project src/GodotCSharpDevMcp.Server -- --test create_csharp_script \
'{"path":"Player.cs","className":"Player","baseType":"Node2D","generateReady":true}'See the Examples section for complete workflows.
A developerβworkflow MCP server designed to help AI assistants:
- Create and scaffold Godot C# projects
- Generate C# scripts using Roslyn templates
- Modify scenes and nodes deterministically
- Run MSBuild and Godot exports
- Analyze C# code with Roslyn
- Automate editorβlevel tasks
- Search and inspect project structure
The goal is to make Godot C# development faster, safer, and more reproducible.
To avoid overlap with existing community work, this project intentionally does not include:
- Runtime debugging
- Scene tree introspection during gameplay
- Log streaming
- GDScriptβfocused tooling
Those scenarios are already wellβserved by other MCP servers.
The current MVP focuses on file-based workflows. The following features are deferred to post-MVP:
- Hot-reload workflows: File changes require manual editor refresh
- Multi-project solutions: Single Godot project per workspace (no shared libraries/multi-project setups)
- In-editor integration: Server runs outside Godot; cannot directly interact with a running editor instance
See ADR 001: MVP Scope and Architecture for detailed rationale and roadmap.
Early development.
See the PRD (or issue #1) for the roadmap, architecture, and planned features.
The server is configured using environment variables:
| Variable | Description | Default |
|---|---|---|
GODOT_WORKSPACE_ROOT |
Path to the Godot project workspace | Current directory |
GODOT_WORKSPACE_READONLY |
Enable read-only mode (prevents all writes) | false |
GODOT_MAX_FILE_SIZE_BYTES |
Maximum file size for read/write operations | 10485760 (10 MB) |
Example:
# Set workspace to a specific directory
export GODOT_WORKSPACE_ROOT=/home/user/my-godot-game
# Enable read-only mode for safety
export GODOT_WORKSPACE_READONLY=true
# Increase max file size to 50 MB
export GODOT_MAX_FILE_SIZE_BYTES=52428800Safety Features:
- Workspace Isolation: All file operations are confined to the configured workspace root
- Read-Only Mode: Optional mode that prevents all write operations
- Path Validation: Cross-platform path normalization and boundary checking
- Size Limits: Configurable limits to prevent memory exhaustion
For detailed information, see Configuration & Safety Documentation.
This repo uses .NET 10 for the MCP server itself (a standalone tool you run from the command line). Thatβs generally fine because the server runs outside of Godot.
However, anything that must be loaded/executed by Godot (for example: the generated Godot game project, or any optional editor plugin code) must target the .NET runtime that your chosen Godot version supports.
Practical implications:
- The MCP server can target newer .NET because Godot does not load the server assembly.
- Tools that scaffold a Godot C# project should default to a Godot-supported target framework (and optionally a pinned
LangVersion), not necessarilynet10.0. - If/when we add an editor plugin, that plugin (and any shared libraries it loads) should target a Godot-supported framework.
Official references:
- Godot C# docs: https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/index.html#doc-c-sharp
- Godot feature/support matrix (including scripting): https://docs.godotengine.org/en/stable/about/list_of_features.html#scripting
The repository uses a standard .NET solution structure:
GodotCSharpDevMcp.slnx # Main solution file
βββ src/
β βββ GodotCSharpDevMcp.Server/ # MCP server host (console app)
β βββ GodotCSharpDevMcp.Core/ # Core library for shared logic
βββ tests/
β βββ GodotCSharpDevMcp.Tests/ # Unit tests (xUnit)
βββ Directory.Build.props # Shared build settings
- ModelContextProtocol (0.5.0-preview.1): MCP SDK for implementing the server protocol
- Microsoft.Extensions.Hosting (10.0.1): Host infrastructure for the MCP server
- xUnit (2.5.3): Testing framework
All projects target .NET 10 with nullable reference types enabled, implicit usings, and warnings treated as errors for code quality.
# Restore dependencies
dotnet restore ./GodotCSharpDevMcp.slnx
# Build the solution
dotnet build ./GodotCSharpDevMcp.slnx --configuration Release
# Run tests
dotnet test ./GodotCSharpDevMcp.slnx --configuration ReleaseThe MCP server uses stdio transport to communicate with MCP clients. To run the server:
# Run the server (it will wait for JSON-RPC messages on stdin)
dotnet run --project src/GodotCSharpDevMcp.ServerThe server implements the Model Context Protocol and communicates via JSON-RPC 2.0 over stdio. Clients should:
- Send an
initializerequest first - Call
tools/listto discover available tools - Use
tools/callto invoke tools
Example MCP interaction:
# Send initialize request
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test-client","version":"1.0"}}}' | \
dotnet run --project src/GodotCSharpDevMcp.Server 2>/dev/nullFor local development and smoke testing without requiring an MCP client, the server includes a built-in CLI test harness.
Full CLI Test Harness Documentation
# Basic usage
dotnet run --project src/GodotCSharpDevMcp.Server -- --test <tool_name> <json_input> [log_file]
# List all project files
dotnet run --project src/GodotCSharpDevMcp.Server -- --test list_project_files '{}'
# Read a file
dotnet run --project src/GodotCSharpDevMcp.Server -- --test read_file '{"path":"test.txt"}'
# Write a file
dotnet run --project src/GodotCSharpDevMcp.Server -- --test write_file '{"path":"new-file.txt","content":"Hello, World!"}'
# Run smoke test
bash scripts/cli-smoke-test.shKey Features:
- π§ Invoke any tool by name with JSON input
- π Stable, formatted JSON output for easy diffing
- π Optional log file recording for reproducibility
- π Same safety guarantees as MCP server (workspace isolation, read-only mode)
- β‘ Perfect for local development and automated smoke testing
All tools follow a standardized result format for consistent, machine-readable responses.
Enumerate project files with filtering, sorting, and depth control.
Purpose: List relevant files in the workspace with glob pattern support.
Input:
{
relativeRoot?: string; // Relative path root (default: workspace root)
includePatterns?: string[]; // Include glob patterns (e.g., ["**/*.cs"])
excludePatterns?: string[]; // Additional exclude patterns
maxDepth?: number; // Maximum directory depth
maxResults?: number; // Maximum number of results
includeModifiedTime?: boolean; // Include last modified timestamp
}Output:
{
entries: Array<{
relativePath: string; // Relative path from workspace root
type: "File" | "Directory"; // Entry type
sizeBytes?: number; // File size (null for directories)
lastModifiedUtc?: string; // Last modified timestamp (UTC)
}>;
totalCount: number; // Total number of entries found
isTruncated: boolean; // Whether results were truncated
}Example:
# List all C# scripts
dotnet run --project src/GodotCSharpDevMcp.Server -- \
--test list_project_files '{"includePatterns":["**/*.cs"]}'Read file contents as UTF-8 text with range support.
Purpose: Read files with support for partial reads (byte ranges, line ranges).
Input:
{
path: string; // Relative path to file
offset?: number; // Byte offset for partial read
length?: number; // Number of bytes to read
startLine?: number; // Starting line number (1-based)
endLine?: number; // Ending line number (1-based, inclusive)
}Output:
{
content: string; // File content
bytesRead: number; // Bytes read
totalFileSize: number;// Total file size
isTruncated: boolean; // Whether content was truncated
}Example:
# Read entire file
dotnet run --project src/GodotCSharpDevMcp.Server -- \
--test read_file '{"path":"Player.cs"}'
# Read lines 10-20
dotnet run --project src/GodotCSharpDevMcp.Server -- \
--test read_file '{"path":"Player.cs","startLine":10,"endLine":20}'Write UTF-8 text to a file atomically.
Purpose: Safely write files with atomic semantics.
Input:
{
path: string; // Relative path to file
content: string; // UTF-8 content to write
createParentDirectories?: boolean; // Create parent dirs (default: false)
}Output:
{
bytesWritten: number; // Number of bytes written
absolutePath: string; // Absolute path to file
}Example:
dotnet run --project src/GodotCSharpDevMcp.Server -- \
--test write_file '{"path":"test.txt","content":"Hello, World!"}'Search project files for text or regex patterns.
Purpose: Find matches across project files with context.
Input:
{
pattern: string; // Search pattern (literal or regex)
isRegex?: boolean; // Use regex mode (default: false)
caseSensitive?: boolean; // Case-sensitive matching (default: true)
includePatterns?: string[]; // File patterns to include
excludePatterns?: string[]; // File patterns to exclude
contextLines?: number; // Lines of context around matches
maxResults?: number; // Maximum number of results
}Output:
{
matches: Array<{
relativePath: string; // File path
lineNumber: number; // Line number of match
lineContent: string; // Line content
contextBefore?: string[]; // Lines before match
contextAfter?: string[]; // Lines after match
}>;
totalMatches: number; // Total matches found
isTruncated: boolean; // Whether results were truncated
}Example:
# Search for "Player" in all C# files
dotnet run --project src/GodotCSharpDevMcp.Server -- \
--test search_project '{"pattern":"Player","includePatterns":["**/*.cs"]}'Create deterministic Godot 4 scene files (.tscn).
Purpose: Generate valid .tscn files with stable ordering.
Input:
{
path: string; // Path to .tscn file
rootNodeName: string; // Root node name
rootNodeType: string; // Root node type (e.g., "Node2D")
nodes?: Array<{ // Optional child nodes
name: string;
type: string;
parent: string;
properties?: {[key: string]: string};
}>;
}Output:
{
absolutePath: string; // Absolute path to created scene
bytesWritten: number; // Bytes written
nodeCount: number; // Number of nodes in scene
message: string; // Success message
}Example:
dotnet run --project src/GodotCSharpDevMcp.Server -- \
--test create_scene '{"path":"main.tscn","rootNodeName":"Main","rootNodeType":"Node2D"}'Add a node to an existing .tscn file deterministically.
Purpose: Modify scene files while preserving structure.
Input:
{
scenePath: string; // Path to .tscn file
parentNodePath: string; // Parent path ("." for root)
nodeName: string; // Name of new node
nodeType: string; // Node type (e.g., "Sprite2D")
properties?: {[key: string]: string}; // Optional properties
}Output:
{
scenePath: string; // Path to modified scene
nodeName: string; // Name of added node
nodeFullPath: string; // Full path of added node
message: string; // Success message
}Example:
# Add player to root
dotnet run --project src/GodotCSharpDevMcp.Server -- \
--test add_node_to_scene \
'{"scenePath":"main.tscn","parentNodePath":".","nodeName":"Player","nodeType":"CharacterBody2D"}'Generate idiomatic Godot 4 C# scripts.
Purpose: Create C# scripts with proper Godot structure.
Input:
{
path: string; // Path to .cs file
className: string; // Class name
baseType: string; // Base type (e.g., "Node2D")
namespace?: string; // Optional namespace
generateReady?: boolean; // Generate _Ready method
generateProcess?: boolean; // Generate _Process method
createParents?: boolean; // Create parent directories (default: true)
}Output:
{
absolutePath: string; // Absolute path to created script
bytesWritten: number; // Bytes written
className: string; // Class name
baseType: string; // Base type
message: string; // Success message
}Example:
# Create player script with methods
dotnet run --project src/GodotCSharpDevMcp.Server -- \
--test create_csharp_script \
'{"path":"Player.cs","className":"Player","baseType":"CharacterBody2D","generateReady":true,"generateProcess":true}'Generated output:
using Godot;
public partial class Player : CharacterBody2D
{
public override void _Ready()
{
}
public override void _Process(double delta)
{
}
}This example creates a simple game scene with a player character.
# Set workspace
export GODOT_WORKSPACE_ROOT=/tmp/my-game
mkdir -p $GODOT_WORKSPACE_ROOT
# Note: Replace with the path where you cloned this repository
# For example: ~/godot-csharp-dev-mcp or /path/to/godot-csharp-dev-mcp
export PROJECT_ROOT=~/godot-csharp-dev-mcp
# 1. Create main scene
dotnet run --project $PROJECT_ROOT/src/GodotCSharpDevMcp.Server -- \
--test create_scene \
'{"path":"scenes/main.tscn","rootNodeName":"Main","rootNodeType":"Node2D"}'
# 2. Add a player node to the scene
dotnet run --project $PROJECT_ROOT/src/GodotCSharpDevMcp.Server -- \
--test add_node_to_scene \
'{"scenePath":"scenes/main.tscn","parentNodePath":".","nodeName":"Player","nodeType":"CharacterBody2D"}'
# 3. Create the player script
dotnet run --project $PROJECT_ROOT/src/GodotCSharpDevMcp.Server -- \
--test create_csharp_script \
'{"path":"scripts/Player.cs","className":"Player","baseType":"CharacterBody2D","namespace":"MyGame","generateReady":true,"generateProcess":true}'
# 4. List all files to verify
dotnet run --project $PROJECT_ROOT/src/GodotCSharpDevMcp.Server -- \
--test list_project_files '{}'Find all C# files that mention "Player" and read them.
# Search for "Player" in all C# files
dotnet run --project src/GodotCSharpDevMcp.Server -- \
--test search_project \
'{"pattern":"Player","includePatterns":["**/*.cs"],"contextLines":2}'
# Read a specific file
dotnet run --project src/GodotCSharpDevMcp.Server -- \
--test read_file '{"path":"scripts/Player.cs"}'Enable read-only mode to inspect a project without modifications.
# Enable read-only mode
export GODOT_WORKSPACE_READONLY=true
# List files (works)
dotnet run --project src/GodotCSharpDevMcp.Server -- \
--test list_project_files '{}'
# Try to write (fails with ReadOnlyMode error)
dotnet run --project src/GodotCSharpDevMcp.Server -- \
--test write_file '{"path":"test.txt","content":"Hello"}'
# Returns: {"success":false,"error":{"code":"ReadOnlyMode","message":"..."}}The server implements a robust safety model to ensure all operations are deterministic and cannot escape the workspace.
Key Features:
- Workspace Isolation: All file operations are confined to a configured workspace root
- Read-Only Mode: Optional mode that prevents all write operations
- File Size Limits: Configurable limits to prevent memory exhaustion
- Path Patterns: Allowlist/denylist support using glob patterns
- Path Validation: Cross-platform path normalization and boundary checking
For detailed information, see Configuration & Safety Documentation.
Known Limitations: Glob pattern ? single-character wildcard is not supported. See issue #70 for details and workarounds.
All MCP tools in this server use a standardized result envelope to ensure consistent, machine-readable responses.
When a tool executes successfully, it returns a result with success: true and the data:
{
"success": true,
"data": {
// Tool-specific response data
}
}When a tool fails, it returns a result with success: false and an error object:
{
"success": false,
"error": {
"code": "InvalidInput",
"message": "Human-readable error description",
"details": {
// Optional structured details for debugging
}
}
}The server defines standard error codes for common failure scenarios:
| Code | Description |
|---|---|
InvalidInput |
Invalid input parameters provided to the tool |
PathOutsideWorkspace |
Requested path is outside the workspace boundary |
ReadOnlyMode |
Operation failed because server is in read-only mode |
NotFound |
Requested resource was not found |
ExternalToolFailed |
An external tool (MSBuild, Godot CLI, etc.) failed |
InternalError |
An unexpected internal error occurred |
The optional details field can contain structured information to help diagnose failures:
{
"success": false,
"error": {
"code": "PathOutsideWorkspace",
"message": "Path is outside workspace boundary",
"details": {
"RequestedPath": "/forbidden/path",
"WorkspaceRoot": "/allowed/workspace"
}
}
}Note: Stack traces are not included by default. They may be enabled via server configuration for debugging, but production deployments should keep them disabled to avoid leaking sensitive information.