diff --git a/.mcp.json b/.mcp.json
new file mode 100644
index 0000000..3ac1f28
--- /dev/null
+++ b/.mcp.json
@@ -0,0 +1,15 @@
+{
+ "mcpServers": {
+ "csharp-mcp": {
+ "type": "stdio",
+ "command": "docker",
+ "args": [
+ "run",
+ "-i",
+ "--rm",
+ "ghcr.io/infinityflowapp/csharp-mcp:latest"
+ ],
+ "env": {}
+ }
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 13f70b4..68a5556 100644
--- a/README.md
+++ b/README.md
@@ -113,6 +113,76 @@ The following namespaces are automatically available:
## MCP Configuration
+### Cursor
+
+Add to your Cursor settings (`.cursor/mcp_settings.json` or via Settings UI):
+
+```json
+{
+ "mcpServers": {
+ "csharp-eval": {
+ "command": "docker",
+ "args": ["run", "-i", "--rm", "ghcr.io/infinityflowapp/csharp-mcp:latest"],
+ "env": {
+ "CSX_ALLOWED_PATH": "/scripts"
+ }
+ }
+ }
+}
+```
+
+Or if installed as a dotnet tool:
+
+```json
+{
+ "mcpServers": {
+ "csharp-eval": {
+ "command": "infinityflow-csharp-eval",
+ "env": {
+ "CSX_ALLOWED_PATH": "${workspaceFolder}/scripts"
+ }
+ }
+ }
+}
+```
+
+### Claude Code
+
+Add to your Claude Code configuration (`claude_desktop_config.json`):
+
+- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
+- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
+- **Linux**: `~/.config/claude/claude_desktop_config.json`
+
+```json
+{
+ "mcpServers": {
+ "csharp-eval": {
+ "command": "docker",
+ "args": ["run", "-i", "--rm", "ghcr.io/infinityflowapp/csharp-mcp:latest"],
+ "env": {
+ "CSX_ALLOWED_PATH": "/scripts"
+ }
+ }
+ }
+}
+```
+
+Or if installed as a dotnet tool:
+
+```json
+{
+ "mcpServers": {
+ "csharp-eval": {
+ "command": "infinityflow-csharp-eval",
+ "env": {
+ "CSX_ALLOWED_PATH": "/Users/your-username/scripts"
+ }
+ }
+ }
+}
+```
+
### VS Code
Create `.vscode/mcp.json`:
diff --git a/src/InfinityFlow.CSharp.Eval/Tools/CSharpEvalTools.cs b/src/InfinityFlow.CSharp.Eval/Tools/CSharpEvalTools.cs
index b755542..e40544a 100644
--- a/src/InfinityFlow.CSharp.Eval/Tools/CSharpEvalTools.cs
+++ b/src/InfinityFlow.CSharp.Eval/Tools/CSharpEvalTools.cs
@@ -9,7 +9,9 @@ namespace InfinityFlow.CSharp.Eval.Tools;
///
/// MCP tool for evaluating and executing C# scripts using Roslyn.
///
-internal class CSharpEvalTools
+
+[McpServerToolType]
+public class CSharpEvalTools
{
[McpServerTool]
[Description("Evaluates and executes C# script code and returns the output. Can either execute code directly or from a file.")]
@@ -29,7 +31,7 @@ public async Task EvalCSharp(
}
string scriptCode;
-
+
try
{
if (!string.IsNullOrWhiteSpace(csxFile))
@@ -38,13 +40,13 @@ public async Task EvalCSharp(
try
{
var fullPath = Path.GetFullPath(csxFile);
-
+
// Ensure the file has .csx extension
if (!fullPath.EndsWith(".csx", StringComparison.OrdinalIgnoreCase))
{
return $"Error: Only .csx files are allowed. Provided: {csxFile}";
}
-
+
// Optional: Restrict to specific directories for additional security
// This can be configured via environment variable
var allowedPath = Environment.GetEnvironmentVariable("CSX_ALLOWED_PATH");
@@ -56,12 +58,12 @@ public async Task EvalCSharp(
return $"Error: File access is restricted to {normalizedAllowedPath}";
}
}
-
+
if (!File.Exists(fullPath))
{
return $"Error: File not found: {fullPath}";
}
-
+
scriptCode = await File.ReadAllTextAsync(fullPath);
}
catch (Exception ex)
@@ -101,7 +103,7 @@ public async Task EvalCSharp(
// Capture console output
var originalOut = Console.Out;
var outputBuilder = new StringBuilder();
-
+
try
{
using var stringWriter = new StringWriter(outputBuilder);
@@ -109,21 +111,21 @@ public async Task EvalCSharp(
// Execute the script with timeout
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds));
-
+
// Run script in a task so we can properly handle timeout
- var scriptTask = Task.Run(async () =>
- await CSharpScript.EvaluateAsync(scriptCode, scriptOptions, cancellationToken: cts.Token),
+ var scriptTask = Task.Run(async () =>
+ await CSharpScript.EvaluateAsync(scriptCode, scriptOptions, cancellationToken: cts.Token),
cts.Token);
-
+
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(timeoutSeconds));
var completedTask = await Task.WhenAny(scriptTask, timeoutTask);
-
+
if (completedTask == timeoutTask)
{
cts.Cancel();
throw new OperationCanceledException();
}
-
+
var result = await scriptTask;
// Add the result value if it's not null
@@ -149,18 +151,18 @@ await CSharpScript.EvaluateAsync(scriptCode, scriptOptions, cancellationToken: c
var errorBuilder = new StringBuilder();
errorBuilder.AppendLine("Compilation Error(s):");
errorBuilder.AppendLine();
-
+
foreach (var diagnostic in e.Diagnostics)
{
var lineSpan = diagnostic.Location.GetLineSpan();
var line = lineSpan.StartLinePosition.Line + 1; // Convert to 1-based
var column = lineSpan.StartLinePosition.Character + 1;
-
+
errorBuilder.AppendLine($" Line {line}, Column {column}: {diagnostic.Id} - {diagnostic.GetMessage()}");
-
+
// Try to show the problematic code if available
if (!diagnostic.Location.IsInSource) continue;
-
+
var sourceText = diagnostic.Location.SourceTree?.GetText();
if (sourceText != null)
{
@@ -168,7 +170,7 @@ await CSharpScript.EvaluateAsync(scriptCode, scriptOptions, cancellationToken: c
if (!string.IsNullOrWhiteSpace(lineText))
{
errorBuilder.AppendLine($" Code: {lineText.Trim()}");
-
+
// Add a pointer to the error position
if (column > 0 && column <= lineText.Length)
{
@@ -179,7 +181,7 @@ await CSharpScript.EvaluateAsync(scriptCode, scriptOptions, cancellationToken: c
}
errorBuilder.AppendLine();
}
-
+
return errorBuilder.ToString().TrimEnd();
}
catch (OperationCanceledException)
@@ -191,7 +193,7 @@ await CSharpScript.EvaluateAsync(scriptCode, scriptOptions, cancellationToken: c
var errorBuilder = new StringBuilder();
errorBuilder.AppendLine($"Runtime Error: {e.GetType().Name}");
errorBuilder.AppendLine($"Message: {e.Message}");
-
+
// Try to extract the line number from the stack trace if it's a script error
if (e.StackTrace != null && e.StackTrace.Contains("Submission#0"))
{
@@ -209,16 +211,16 @@ await CSharpScript.EvaluateAsync(scriptCode, scriptOptions, cancellationToken: c
}
}
}
-
+
if (e.InnerException != null)
{
errorBuilder.AppendLine($"Inner Exception: {e.InnerException.GetType().Name}: {e.InnerException.Message}");
}
-
+
errorBuilder.AppendLine();
errorBuilder.AppendLine("Stack Trace:");
errorBuilder.AppendLine(e.StackTrace);
-
+
return errorBuilder.ToString().TrimEnd();
}
}