diff --git a/Endpoints/ChatEndpoint.cs b/Endpoints/ChatEndpoint.cs index ab89b79..3a6e070 100644 --- a/Endpoints/ChatEndpoint.cs +++ b/Endpoints/ChatEndpoint.cs @@ -245,7 +245,7 @@ public static class ChatEndpoint { if (anthropic.IsCliAvailable) { - await anthropic.StreamChatViaCli(res, ctx.ModelId, ctx.SystemPrompt, messages, ctx.ChainId, workspace: ctx.Workspace, allowUserChoice: ctx.Agent?.AllowUserChoice ?? true, planMode: body.PlanMode, webSearch: !string.IsNullOrEmpty(ctx.WebSearchVersion), sessionId: sessionId, agentId: ctx.AgentId, remoteMgr: remoteMgr, workCt: workCt, sseCt: sseCt); + await anthropic.StreamChatViaCli(res, ctx.ModelId, ctx.SystemPrompt, messages, ctx.ChainId, workspace: ctx.Workspace, allowUserChoice: ctx.Agent?.AllowUserChoice ?? true, planMode: body.PlanMode, webSearch: !string.IsNullOrEmpty(ctx.WebSearchVersion), sessionId: sessionId, agentId: ctx.AgentId, remoteMgr: remoteMgr, unityMcpUrl: ctx.Session?.UnityMcpUrl, unityMcpToken: ctx.Session?.UnityMcpToken, workCt: workCt, sseCt: sseCt); } else { diff --git a/Endpoints/SessionEndpoint.cs b/Endpoints/SessionEndpoint.cs index c6d13af..4087ccf 100644 --- a/Endpoints/SessionEndpoint.cs +++ b/Endpoints/SessionEndpoint.cs @@ -80,6 +80,8 @@ public static class SessionEndpoint s.MaxReviewIterations, s.FinalizerAgentId, s.PreparerAgentId, + s.UnityMcpUrl, + s.UnityMcpToken, s.IsDeleted, s.CreatedAt, s.UpdatedAt, diff --git a/Models/SessionRecord.cs b/Models/SessionRecord.cs index 483a428..c34ce99 100644 --- a/Models/SessionRecord.cs +++ b/Models/SessionRecord.cs @@ -23,6 +23,8 @@ public class SessionRecord public int MaxReviewIterations { get; set; } = 3; public string FinalizerAgentId { get; set; } public string PreparerAgentId { get; set; } + public string UnityMcpUrl { get; set; } + public string UnityMcpToken { get; set; } public string CreatedAt { get; set; } public string UpdatedAt { get; set; } } diff --git a/Services/AnthropicService.Cli.cs b/Services/AnthropicService.Cli.cs index b8dd81c..3443bae 100644 --- a/Services/AnthropicService.Cli.cs +++ b/Services/AnthropicService.Cli.cs @@ -31,6 +31,8 @@ public partial class AnthropicService string sessionId = null, string agentId = null, RemoteConnectionManager remoteMgr = null, + string unityMcpUrl = null, + string unityMcpToken = null, CancellationToken workCt = default, CancellationToken sseCt = default) { @@ -59,7 +61,7 @@ public partial class AnthropicService { // MCP-Config generieren (immer – für ask_user_choice, optional Memory-Tools + remote_execute) var hasRemote = remoteMgr != null && !string.IsNullOrEmpty(sessionId) && remoteMgr.GetBySessionId(sessionId) != null; - mcpConfigPath = GenerateMcpConfig(sessionId, agentId, hasRemote); + mcpConfigPath = GenerateMcpConfig(sessionId, agentId, hasRemote, unityMcpUrl, unityMcpToken); // System-Prompt als Text extrahieren var systemText = new StringBuilder(); @@ -210,6 +212,7 @@ public partial class AnthropicService planMode: planMode, webSearch: webSearch, mcpConfigPath: mcpConfigPath, + hasUnityMcp: !string.IsNullOrEmpty(unityMcpUrl), cancellationToken: workCt ); @@ -486,7 +489,7 @@ public partial class AnthropicService /// Generiert eine temporäre MCP-Config-Datei für die Claude CLI. /// Enthält den MCP-Server für remote_execute. /// - private string GenerateMcpConfig(string sessionId, string agentId = null, bool hasRemote = false) + private string GenerateMcpConfig(string sessionId, string agentId = null, bool hasRemote = false, string unityMcpUrl = null, string unityMcpToken = null) { // Port aus Konfiguration oder Default var port = 19000; @@ -509,19 +512,39 @@ public partial class AnthropicService ["GOTT_HAS_REMOTE"] = hasRemote ? "true" : "false" }; - var config = new + var mcpServers = new Dictionary { - mcpServers = new Dictionary + ["gott-remote"] = new { - ["gott-remote"] = new - { - command = "python3", - args = new[] { scriptPath }, - env - } + command = "python3", + args = new[] { scriptPath }, + env } }; + // Unity MCP Server (pro Session konfigurierbar) + // Claude CLI erfordert expliziten "type" – "http" für streamableHttp, "sse" für SSE + if (!string.IsNullOrEmpty(unityMcpUrl)) + { + // URL-basierte Typ-Erkennung: /sse → SSE-Transport, sonst HTTP (streamableHttp) + var mcpType = unityMcpUrl.TrimEnd('/').EndsWith("/sse", StringComparison.OrdinalIgnoreCase) ? "sse" : "http"; + var unityConfig = new Dictionary + { + ["type"] = mcpType, + ["url"] = unityMcpUrl + }; + if (!string.IsNullOrEmpty(unityMcpToken)) + { + unityConfig["headers"] = new Dictionary + { + ["Authorization"] = $"Bearer {unityMcpToken}" + }; + } + mcpServers["unity"] = unityConfig; + } + + var config = new { mcpServers }; + var json = System.Text.Json.JsonSerializer.Serialize(config, new System.Text.Json.JsonSerializerOptions { WriteIndented = true }); var tempPath = Path.Combine(Path.GetTempPath(), $"gott-mcp-{sessionId}.json"); File.WriteAllText(tempPath, json); diff --git a/Services/ClaudeCliService.cs b/Services/ClaudeCliService.cs index 4e86bd2..3625314 100644 --- a/Services/ClaudeCliService.cs +++ b/Services/ClaudeCliService.cs @@ -83,6 +83,7 @@ public class ClaudeCliService bool planMode = false, bool webSearch = false, string mcpConfigPath = null, + bool hasUnityMcp = false, CancellationToken cancellationToken = default) { if (!_isAvailable) @@ -126,6 +127,11 @@ public class ClaudeCliService tools.Add("mcp__gott-remote__memory_write"); tools.Add("mcp__gott-remote__memory_list"); } + // Unity MCP Tools erlauben (alle Tools vom unity MCP-Server) + if (hasUnityMcp) + { + tools.Add("mcp__unity__*"); + } args.Add("--allowed-tools"); args.Add(tools.Count > 0 ? string.Join(" ", tools) : "\"\""); diff --git a/Services/DatabaseService.Sessions.cs b/Services/DatabaseService.Sessions.cs index 4eadcd9..080d657 100644 --- a/Services/DatabaseService.Sessions.cs +++ b/Services/DatabaseService.Sessions.cs @@ -145,6 +145,8 @@ public partial class DatabaseService if (body.TryGetProperty("maxReviewIterations", out var mri)) { sets.Add("max_review_iterations = @maxReviewIterations"); p.Add("maxReviewIterations", mri.GetInt32()); } if (body.TryGetProperty("finalizerAgentId", out var fa)) { sets.Add("finalizer_agent_id = @finalizerAgentId"); p.Add("finalizerAgentId", fa.ValueKind == System.Text.Json.JsonValueKind.Null ? null : fa.GetString()); } if (body.TryGetProperty("preparerAgentId", out var pa)) { sets.Add("preparer_agent_id = @preparerAgentId"); p.Add("preparerAgentId", pa.ValueKind == System.Text.Json.JsonValueKind.Null ? null : pa.GetString()); } + if (body.TryGetProperty("unityMcpUrl", out var umu)) { sets.Add("unity_mcp_url = @unityMcpUrl"); p.Add("unityMcpUrl", umu.ValueKind == System.Text.Json.JsonValueKind.Null ? null : umu.GetString()); } + if (body.TryGetProperty("unityMcpToken", out var umt)) { sets.Add("unity_mcp_token = @unityMcpToken"); p.Add("unityMcpToken", umt.ValueKind == System.Text.Json.JsonValueKind.Null ? null : umt.GetString()); } await conn.ExecuteAsync($"UPDATE sessions SET {string.Join(", ", sets)} WHERE session_id = @sessionId", p); } diff --git a/Services/DatabaseService.cs b/Services/DatabaseService.cs index 92892de..12b6ae9 100644 --- a/Services/DatabaseService.cs +++ b/Services/DatabaseService.cs @@ -193,6 +193,8 @@ public partial class DatabaseService TryAddColumn(conn, "agents", "task_instruction", "TEXT"); TryAddColumn(conn, "chains", "plan_mode", "INTEGER NOT NULL DEFAULT 0"); TryAddColumn(conn, "sessions", "preparer_agent_id", "TEXT"); + TryAddColumn(conn, "sessions", "unity_mcp_url", "TEXT"); + TryAddColumn(conn, "sessions", "unity_mcp_token", "TEXT"); TryAddColumn(conn, "messages", "tag", "TEXT"); conn.Execute(@" diff --git a/wwwroot/index.html b/wwwroot/index.html index d87b6d4..b6193ae 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -158,6 +158,10 @@
+ + + +
Kein Bot zugewiesen
diff --git a/wwwroot/js/pages/session-page.js b/wwwroot/js/pages/session-page.js index aa75394..76bab02 100644 --- a/wwwroot/js/pages/session-page.js +++ b/wwwroot/js/pages/session-page.js @@ -35,6 +35,8 @@ registerPage('session', { const deleteBtn = document.getElementById('sessionDeleteBtn'); const commandsList = document.getElementById('customCommandsList'); const addCommandBtn = document.getElementById('addCommandBtn'); + const unityMcpUrlEl = document.getElementById('sessionUnityMcpUrl'); + const unityMcpTokenEl = document.getElementById('sessionUnityMcpToken'); const more = document.querySelector('.more'); // Avatar Editor initialisieren @@ -55,6 +57,10 @@ registerPage('session', { // Custom Commands laden loadCommandsIntoUI(commandsList, session.customCommands); + + // Unity MCP + unityMcpUrlEl.value = session.unityMcpUrl || ''; + unityMcpTokenEl.value = session.unityMcpToken || ''; } else { titleEl.textContent = 'Neue Session'; deleteBtn.hidden = true; @@ -168,7 +174,9 @@ registerPage('session', { customCommands: commands, avatarScale: _sessionAvatarEditor.getScale(), avatarOffsetX: _sessionAvatarEditor.getOffsetX(), - avatarOffsetY: _sessionAvatarEditor.getOffsetY() + avatarOffsetY: _sessionAvatarEditor.getOffsetY(), + unityMcpUrl: unityMcpUrlEl.value.trim() || null, + unityMcpToken: unityMcpTokenEl.value.trim() || null }; if (isNew) {