feat: Unity MCP-Server Integration pro Session

Sessions können jetzt einen Unity MCP-Server (URL + Token) konfigurieren.
Die CLI erhält automatisch die MCP-Config mit korrektem Transport-Typ
(URL-basierte Erkennung: /sse → SSE, sonst HTTP für streamableHttp).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MelekiandClaude Opus 4.6 committed 2026-03-22 18:34:47 +01:00
1 parent 9c507e4716
commit 500c00f948
9 files changed
+61 -12

No files matched your search

+1 -1
View File
@@ -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
{
+2
View File
@@ -80,6 +80,8 @@ public static class SessionEndpoint
s.MaxReviewIterations,
s.FinalizerAgentId,
s.PreparerAgentId,
s.UnityMcpUrl,
s.UnityMcpToken,
s.IsDeleted,
s.CreatedAt,
s.UpdatedAt,
+2
View File
@@ -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; }
}
+33 -10
View File
@@ -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.
/// </summary>
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<string, object>
{
mcpServers = new Dictionary<string, object>
["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<string, object>
{
["type"] = mcpType,
["url"] = unityMcpUrl
};
if (!string.IsNullOrEmpty(unityMcpToken))
{
unityConfig["headers"] = new Dictionary<string, string>
{
["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);
+6
View File
@@ -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) : "\"\"");
+2
View File
@@ -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);
}
+2
View File
@@ -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(@"
+4
View File
@@ -158,6 +158,10 @@
<label>Benutzerdefinierte Befehle</label>
<div class="custom-commands" id="customCommandsList"></div>
<button type="button" id="addCommandBtn" class="add-cmd-btn">+ Befehl hinzufügen</button>
<label>Unity MCP URL <small>(optional)</small></label>
<input type="text" id="sessionUnityMcpUrl" placeholder="z.B. http://localhost:8080" autocomplete="off">
<label>Unity MCP Token <small>(optional)</small></label>
<input type="password" id="sessionUnityMcpToken" placeholder="MCP-Server Token" autocomplete="off">
<label>Telegram Bot</label>
<div id="telegramBotSelectCard" class="agent-select-card">
<div class="agent-select-empty"><i class="fa fa-paper-plane"></i> Kein Bot zugewiesen</div>
+9 -1
View File
@@ -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) {