mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
236 lines
8.5 KiB
C#
236 lines
8.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace MinecraftClient.Mcp;
|
|
|
|
public sealed class MccMcpGuidanceProvider
|
|
{
|
|
private const string EmbeddedPromptResourceSuffix = "MccMcpOperatorPrompt.md";
|
|
private const string BestPracticesHeading = "## Best Practices";
|
|
private const string ExampleScenariosHeading = "## Example Scenarios";
|
|
|
|
private readonly MccMcpConfig config;
|
|
private readonly Lazy<GuidanceDocument> guidanceDocument;
|
|
|
|
public MccMcpGuidanceProvider(MccMcpConfig config)
|
|
{
|
|
this.config = config;
|
|
guidanceDocument = new Lazy<GuidanceDocument>(LoadGuidanceDocument);
|
|
}
|
|
|
|
public string PromptName => "mcc_operator_prompt";
|
|
|
|
public string GetSystemPrompt()
|
|
{
|
|
GuidanceDocument document = guidanceDocument.Value;
|
|
MccMcpAgentCapabilityStatus capabilityStatus = BuildCapabilityStatus();
|
|
StringBuilder builder = new();
|
|
builder.AppendLine("You are an external agent controlling Minecraft Console Client (MCC) through its built-in MCP server.");
|
|
builder.AppendLine("Use the following MCP Operator Prompt as your system prompt. Treat the capability snapshot as authoritative and do not invent unsupported actions.");
|
|
builder.AppendLine();
|
|
builder.AppendLine(document.BodyMarkdown);
|
|
builder.AppendLine();
|
|
builder.AppendLine("Current capability snapshot");
|
|
builder.AppendLine($"- sessionStatus: {FormatCapability(capabilityStatus.SessionStatus)}");
|
|
builder.AppendLine($"- chatAndCommands: {FormatCapability(capabilityStatus.ChatAndCommands)}");
|
|
builder.AppendLine($"- movement: {FormatCapability(capabilityStatus.Movement)}");
|
|
builder.AppendLine($"- inventory: {FormatCapability(capabilityStatus.Inventory)}");
|
|
builder.AppendLine($"- entityWorld: {FormatCapability(capabilityStatus.EntityWorld)}");
|
|
return builder.ToString().Trim();
|
|
}
|
|
|
|
public MccMcpAgentGuidancePayload GetToolPayload()
|
|
{
|
|
GuidanceDocument document = guidanceDocument.Value;
|
|
return new MccMcpAgentGuidancePayload
|
|
{
|
|
PromptName = PromptName,
|
|
PromptMarkdown = document.PromptMarkdown,
|
|
SystemPrompt = GetSystemPrompt(),
|
|
BestPractices = document.BestPractices,
|
|
ExampleScenarios = document.ExampleScenarios,
|
|
CapabilityStatus = BuildCapabilityStatus()
|
|
};
|
|
}
|
|
|
|
private GuidanceDocument LoadGuidanceDocument()
|
|
{
|
|
Assembly assembly = typeof(MccMcpGuidanceProvider).Assembly;
|
|
string resourceName = assembly.GetManifestResourceNames()
|
|
.FirstOrDefault(name => name.EndsWith(EmbeddedPromptResourceSuffix, StringComparison.Ordinal))
|
|
?? throw new InvalidOperationException($"Embedded MCP operator prompt resource '{EmbeddedPromptResourceSuffix}' was not found.");
|
|
|
|
using Stream? stream = assembly.GetManifestResourceStream(resourceName);
|
|
if (stream is null)
|
|
throw new InvalidOperationException($"Embedded MCP operator prompt resource '{resourceName}' could not be opened.");
|
|
|
|
using StreamReader reader = new(stream, Encoding.UTF8);
|
|
string promptMarkdown = reader.ReadToEnd();
|
|
string bodyMarkdown = StripFrontmatter(promptMarkdown);
|
|
string bestPracticesSection = ExtractSection(bodyMarkdown, BestPracticesHeading);
|
|
string exampleScenariosSection = ExtractSection(bodyMarkdown, ExampleScenariosHeading);
|
|
|
|
return new GuidanceDocument(
|
|
promptMarkdown.Replace("\r\n", "\n").Trim(),
|
|
bodyMarkdown,
|
|
ExtractBulletList(bestPracticesSection),
|
|
ExtractExampleScenarios(exampleScenariosSection));
|
|
}
|
|
|
|
private MccMcpAgentCapabilityStatus BuildCapabilityStatus()
|
|
{
|
|
return new MccMcpAgentCapabilityStatus
|
|
{
|
|
SessionStatus = config.Capabilities.SessionStatus,
|
|
ChatAndCommands = config.Capabilities.ChatAndCommands,
|
|
Movement = config.Capabilities.Movement,
|
|
Inventory = config.Capabilities.Inventory,
|
|
EntityWorld = config.Capabilities.EntityWorld
|
|
};
|
|
}
|
|
|
|
private static string StripFrontmatter(string markdown)
|
|
{
|
|
string normalized = markdown.Replace("\r\n", "\n");
|
|
if (!normalized.StartsWith("---\n", StringComparison.Ordinal))
|
|
return normalized.Trim();
|
|
|
|
int endOfFrontmatter = normalized.IndexOf("\n---\n", 4, StringComparison.Ordinal);
|
|
if (endOfFrontmatter < 0)
|
|
return normalized.Trim();
|
|
|
|
return normalized[(endOfFrontmatter + 5)..].Trim();
|
|
}
|
|
|
|
private static string ExtractSection(string markdownBody, string heading)
|
|
{
|
|
int headingIndex = markdownBody.IndexOf(heading, StringComparison.Ordinal);
|
|
if (headingIndex < 0)
|
|
return string.Empty;
|
|
|
|
int sectionStart = headingIndex + heading.Length;
|
|
int nextHeading = markdownBody.IndexOf("\n## ", sectionStart, StringComparison.Ordinal);
|
|
string section = nextHeading >= 0
|
|
? markdownBody[sectionStart..nextHeading]
|
|
: markdownBody[sectionStart..];
|
|
|
|
return section.Trim();
|
|
}
|
|
|
|
private static string[] ExtractBulletList(string section)
|
|
{
|
|
return section
|
|
.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
|
.Where(line => line.StartsWith("- ", StringComparison.Ordinal))
|
|
.Select(line => line[2..].Trim())
|
|
.Where(line => line.Length > 0)
|
|
.ToArray();
|
|
}
|
|
|
|
private static MccMcpAgentScenario[] ExtractExampleScenarios(string section)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(section))
|
|
return [];
|
|
|
|
List<MccMcpAgentScenario> scenarios = [];
|
|
string? currentTitle = null;
|
|
List<string> currentBodyLines = [];
|
|
|
|
foreach (string rawLine in section.Split('\n'))
|
|
{
|
|
string line = rawLine.TrimEnd();
|
|
if (line.StartsWith("### ", StringComparison.Ordinal))
|
|
{
|
|
AddScenario(scenarios, currentTitle, currentBodyLines);
|
|
currentTitle = line[4..].Trim();
|
|
currentBodyLines = [];
|
|
continue;
|
|
}
|
|
|
|
if (currentTitle is not null)
|
|
currentBodyLines.Add(line);
|
|
}
|
|
|
|
AddScenario(scenarios, currentTitle, currentBodyLines);
|
|
return scenarios.ToArray();
|
|
}
|
|
|
|
private static void AddScenario(List<MccMcpAgentScenario> scenarios, string? title, List<string> bodyLines)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(title))
|
|
return;
|
|
|
|
string guidance = string.Join('\n', bodyLines)
|
|
.Trim();
|
|
|
|
scenarios.Add(new MccMcpAgentScenario
|
|
{
|
|
Title = title,
|
|
Guidance = guidance
|
|
});
|
|
}
|
|
|
|
private static string FormatCapability(bool enabled)
|
|
{
|
|
return enabled ? "enabled" : "disabled";
|
|
}
|
|
|
|
private sealed record GuidanceDocument(
|
|
string PromptMarkdown,
|
|
string BodyMarkdown,
|
|
string[] BestPractices,
|
|
MccMcpAgentScenario[] ExampleScenarios);
|
|
}
|
|
|
|
public sealed class MccMcpAgentGuidancePayload
|
|
{
|
|
[JsonPropertyName("promptName")]
|
|
public string PromptName { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("promptMarkdown")]
|
|
public string PromptMarkdown { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("systemPrompt")]
|
|
public string SystemPrompt { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("bestPractices")]
|
|
public string[] BestPractices { get; init; } = [];
|
|
|
|
[JsonPropertyName("exampleScenarios")]
|
|
public MccMcpAgentScenario[] ExampleScenarios { get; init; } = [];
|
|
|
|
[JsonPropertyName("capabilityStatus")]
|
|
public MccMcpAgentCapabilityStatus CapabilityStatus { get; init; } = new();
|
|
}
|
|
|
|
public sealed class MccMcpAgentScenario
|
|
{
|
|
[JsonPropertyName("title")]
|
|
public string Title { get; init; } = string.Empty;
|
|
|
|
[JsonPropertyName("guidance")]
|
|
public string Guidance { get; init; } = string.Empty;
|
|
}
|
|
|
|
public sealed class MccMcpAgentCapabilityStatus
|
|
{
|
|
[JsonPropertyName("sessionStatus")]
|
|
public bool SessionStatus { get; init; }
|
|
|
|
[JsonPropertyName("chatAndCommands")]
|
|
public bool ChatAndCommands { get; init; }
|
|
|
|
[JsonPropertyName("movement")]
|
|
public bool Movement { get; init; }
|
|
|
|
[JsonPropertyName("inventory")]
|
|
public bool Inventory { get; init; }
|
|
|
|
[JsonPropertyName("entityWorld")]
|
|
public bool EntityWorld { get; init; }
|
|
}
|