2026-03-28 02:08:47 +01:00
|
|
|
using System;
|
2026-03-30 23:14:28 +02:00
|
|
|
using MinecraftClient.Mapping;
|
2026-03-28 02:08:47 +01:00
|
|
|
using MinecraftClient.Mcp;
|
|
|
|
|
using MinecraftClient.Scripting;
|
|
|
|
|
using Tomlet.Attributes;
|
|
|
|
|
|
|
|
|
|
namespace MinecraftClient.ChatBots
|
|
|
|
|
{
|
|
|
|
|
public class McpServer : ChatBot
|
|
|
|
|
{
|
|
|
|
|
public static Configs Config = new();
|
|
|
|
|
|
|
|
|
|
[TomlDoNotInlineObject]
|
|
|
|
|
public class Configs
|
|
|
|
|
{
|
|
|
|
|
[NonSerialized]
|
|
|
|
|
private const string BotName = "McpServer";
|
|
|
|
|
|
|
|
|
|
[TomlInlineComment("$ChatBot.McpServer.Enabled$")]
|
|
|
|
|
public bool Enabled = false;
|
|
|
|
|
|
|
|
|
|
[TomlPrecedingComment("$ChatBot.McpServer.Transport$")]
|
|
|
|
|
public MccMcpTransportConfig Transport = new();
|
|
|
|
|
|
|
|
|
|
[TomlPrecedingComment("$ChatBot.McpServer.Capabilities$")]
|
|
|
|
|
public MccMcpCapabilityToggles Capabilities = new();
|
|
|
|
|
|
|
|
|
|
public void OnSettingUpdate()
|
|
|
|
|
{
|
|
|
|
|
Transport ??= new MccMcpTransportConfig();
|
|
|
|
|
Capabilities ??= new MccMcpCapabilityToggles();
|
|
|
|
|
|
|
|
|
|
if (Transport.Port is < 1 or > 65535)
|
|
|
|
|
Transport.Port = 33333;
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(Transport.BindHost))
|
|
|
|
|
Transport.BindHost = "127.0.0.1";
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(Transport.Route))
|
|
|
|
|
Transport.Route = "/mcp";
|
|
|
|
|
|
|
|
|
|
if (!Transport.Route.StartsWith('/'))
|
|
|
|
|
Transport.Route = "/" + Transport.Route;
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(Transport.AuthTokenEnvVar))
|
|
|
|
|
Transport.AuthTokenEnvVar = "MCC_MCP_AUTH_TOKEN";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private MccEmbeddedMcpHost? host;
|
|
|
|
|
|
|
|
|
|
public override void Initialize()
|
|
|
|
|
{
|
|
|
|
|
Config.OnSettingUpdate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void AfterGameJoined()
|
|
|
|
|
{
|
|
|
|
|
if (!Config.Enabled)
|
|
|
|
|
return;
|
|
|
|
|
|
2026-03-30 23:14:28 +02:00
|
|
|
ClearStores();
|
2026-03-28 02:08:47 +01:00
|
|
|
|
|
|
|
|
MccMcpConfig mcpConfig = new()
|
|
|
|
|
{
|
|
|
|
|
Enabled = Config.Enabled,
|
|
|
|
|
Transport = Config.Transport,
|
|
|
|
|
Capabilities = Config.Capabilities
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
host ??= new MccEmbeddedMcpHost(mcpConfig, new MccMcpCapabilities(() => Config.Capabilities));
|
|
|
|
|
|
|
|
|
|
if (host.IsRunning)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
LogToConsole(Translations.bot_mcpserver_starting);
|
|
|
|
|
if (!host.Start(out string? error))
|
|
|
|
|
{
|
|
|
|
|
if (error == "missing_auth_token")
|
|
|
|
|
LogToConsole(string.Format(Translations.bot_mcpserver_missing_auth_token, Config.Transport.AuthTokenEnvVar));
|
|
|
|
|
LogToConsole(string.Format(Translations.bot_mcpserver_start_failed, error ?? "unknown"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LogToConsole(string.Format(Translations.bot_mcpserver_started, host.Endpoint));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool OnDisconnect(DisconnectReason reason, string message)
|
|
|
|
|
{
|
2026-03-30 23:14:28 +02:00
|
|
|
MccMcpRecentEventStore.Add("disconnect", new
|
|
|
|
|
{
|
|
|
|
|
reason = reason.ToString(),
|
|
|
|
|
message
|
|
|
|
|
});
|
2026-03-28 02:08:47 +01:00
|
|
|
StopHost();
|
2026-03-30 23:14:28 +02:00
|
|
|
ClearStores();
|
2026-03-28 02:08:47 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnUnload()
|
|
|
|
|
{
|
|
|
|
|
StopHost();
|
2026-03-30 23:14:28 +02:00
|
|
|
ClearStores();
|
2026-03-28 02:08:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void GetText(string text, string? json)
|
|
|
|
|
{
|
|
|
|
|
string clean = GetVerbatim(text);
|
|
|
|
|
if (string.IsNullOrWhiteSpace(clean))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
string kind = "system";
|
|
|
|
|
string? sender = null;
|
|
|
|
|
string? message = null;
|
|
|
|
|
|
|
|
|
|
string parsedMessage = string.Empty;
|
|
|
|
|
string parsedSender = string.Empty;
|
|
|
|
|
if (IsPrivateMessage(clean, ref parsedMessage, ref parsedSender))
|
|
|
|
|
{
|
|
|
|
|
kind = "private";
|
|
|
|
|
sender = parsedSender;
|
|
|
|
|
message = parsedMessage;
|
|
|
|
|
}
|
|
|
|
|
else if (IsChatMessage(clean, ref parsedMessage, ref parsedSender))
|
|
|
|
|
{
|
|
|
|
|
kind = "chat";
|
|
|
|
|
sender = parsedSender;
|
|
|
|
|
message = parsedMessage;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MccMcpChatHistoryStore.Add(new MccMcpChatHistoryEntry
|
|
|
|
|
{
|
|
|
|
|
TimestampUtc = DateTimeOffset.UtcNow,
|
|
|
|
|
Kind = kind,
|
|
|
|
|
Text = clean,
|
|
|
|
|
Sender = sender,
|
|
|
|
|
Message = message,
|
|
|
|
|
Json = json
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 23:14:28 +02:00
|
|
|
public override void OnTimeUpdate(long WorldAge, long TimeOfDay)
|
|
|
|
|
{
|
|
|
|
|
MccMcpRuntimeStateStore.SetTime(WorldAge, TimeOfDay);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnRainLevelChange(float level)
|
|
|
|
|
{
|
|
|
|
|
MccMcpRuntimeStateStore.SetRainLevel(level);
|
|
|
|
|
MccMcpRecentEventStore.Add("weather_rain", new { level });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnThunderLevelChange(float level)
|
|
|
|
|
{
|
|
|
|
|
MccMcpRuntimeStateStore.SetThunderLevel(level);
|
|
|
|
|
MccMcpRecentEventStore.Add("weather_thunder", new { level });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnDeath()
|
|
|
|
|
{
|
|
|
|
|
MccMcpRecentEventStore.Add("death");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnRespawn()
|
|
|
|
|
{
|
|
|
|
|
MccMcpRecentEventStore.Add("respawn");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnPlayerJoin(Guid uuid, string name)
|
|
|
|
|
{
|
|
|
|
|
MccMcpRecentEventStore.Add("player_join", new
|
|
|
|
|
{
|
|
|
|
|
uuid,
|
|
|
|
|
name
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnPlayerLeave(Guid uuid, string? name)
|
|
|
|
|
{
|
|
|
|
|
MccMcpRecentEventStore.Add("player_leave", new
|
|
|
|
|
{
|
|
|
|
|
uuid,
|
|
|
|
|
name
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnInventoryOpen(int inventoryId)
|
|
|
|
|
{
|
|
|
|
|
MccMcpRecentEventStore.Add("inventory_open", new { inventoryId });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnInventoryClose(int inventoryId)
|
|
|
|
|
{
|
|
|
|
|
MccMcpRecentEventStore.Add("inventory_close", new { inventoryId });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnTitle(int action, string titletext, string subtitletext, string actionbartext, int fadein, int stay, int fadeout, string json)
|
|
|
|
|
{
|
|
|
|
|
if (action == 2)
|
|
|
|
|
{
|
|
|
|
|
MccMcpRecentEventStore.Add("actionbar", new
|
|
|
|
|
{
|
|
|
|
|
action,
|
|
|
|
|
text = actionbartext,
|
|
|
|
|
fadein,
|
|
|
|
|
stay,
|
|
|
|
|
fadeout,
|
|
|
|
|
json
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (action is 0 or 1)
|
|
|
|
|
{
|
|
|
|
|
MccMcpRecentEventStore.Add("title", new
|
|
|
|
|
{
|
|
|
|
|
action,
|
|
|
|
|
titleText = titletext,
|
|
|
|
|
subtitleText = subtitletext,
|
|
|
|
|
fadein,
|
|
|
|
|
stay,
|
|
|
|
|
fadeout,
|
|
|
|
|
json
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnBlockBreakAnimation(Entity entity, Location location, byte stage)
|
|
|
|
|
{
|
|
|
|
|
MccMcpRecentEventStore.Add("block_break_animation", new
|
|
|
|
|
{
|
|
|
|
|
entityId = entity.ID,
|
|
|
|
|
entityType = entity.Type.ToString(),
|
|
|
|
|
stage,
|
|
|
|
|
location = new
|
|
|
|
|
{
|
|
|
|
|
x = location.X,
|
|
|
|
|
y = location.Y,
|
|
|
|
|
z = location.Z
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void OnEntityAnimation(Entity entity, byte animation)
|
|
|
|
|
{
|
|
|
|
|
MccMcpRecentEventStore.Add("entity_animation", new
|
|
|
|
|
{
|
|
|
|
|
entityId = entity.ID,
|
|
|
|
|
entityType = entity.Type.ToString(),
|
|
|
|
|
animation,
|
|
|
|
|
name = entity.Name,
|
|
|
|
|
customName = entity.CustomName
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 02:08:47 +01:00
|
|
|
private void StopHost()
|
|
|
|
|
{
|
|
|
|
|
if (host is null || !host.IsRunning)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (host.Stop(out string? error))
|
|
|
|
|
LogToConsole(Translations.bot_mcpserver_stopped);
|
|
|
|
|
else
|
|
|
|
|
LogToConsole(string.Format(Translations.bot_mcpserver_stop_failed, error ?? "unknown"));
|
|
|
|
|
}
|
2026-03-30 23:14:28 +02:00
|
|
|
|
|
|
|
|
private static void ClearStores()
|
|
|
|
|
{
|
|
|
|
|
MccMcpChatHistoryStore.Clear();
|
|
|
|
|
MccMcpRuntimeStateStore.Clear();
|
|
|
|
|
MccMcpRecentEventStore.Clear();
|
|
|
|
|
}
|
2026-03-28 02:08:47 +01:00
|
|
|
}
|
|
|
|
|
}
|