Added a Skill for MCP

This commit is contained in:
Anon 2026-03-29 20:57:16 +02:00
parent cf382122e9
commit 7b415d5388
7 changed files with 367 additions and 3 deletions

View file

@ -0,0 +1,94 @@
---
name: mcc-mcp-operator
description: Operate Minecraft Console Client through the built-in MCP server. Use this whenever the user wants an agent to inspect MCC state, move, search the world, interact with players or entities, dig, pick up items, manage containers, or carry out Minecraft tasks through MCP tools, even if they do not explicitly say "use MCP" or "control MCC". Prefer this skill over ad hoc tool guessing for agentic MCC and Minecraft control work.
---
# MCC MCP Operator
Use the MCC MCP toolset as the source of truth for game state and action results.
Do not guess what happened from intent alone.
## Operating Loop
1. Inspect the current situation before acting.
2. Make the shortest plan that can succeed.
3. Use the smallest set of high-signal tools needed to act.
4. Verify the outcome with fresh tool calls.
5. Report only what is verified, and clearly label anything inferred or still unknown.
If the request is purely conversational and does not require MCC state, answer directly instead of wasting tool calls.
## Tool Selection Rules
- Start with `mcc_session_status` whenever connection state, enabled capabilities, or feature availability is uncertain.
- Prefer direct inspection tools such as `mcc_player_state`, `mcc_players_list`, `mcc_entities_list`, `mcc_blocks_find`, `mcc_items_list`, and `mcc_inventory_snapshot` before taking physical actions.
- Prefer purpose-built action tools over low-level escape hatches.
- Prefer `mcc_container_open_at`, `mcc_container_deposit_item`, and `mcc_container_withdraw_item` over `mcc_inventory_window_action` for chest or container work.
- Use `mcc_can_reach_position` or a locating tool before pathing when reachability is uncertain.
- Use `mcc_run_internal_command` only when no purpose-built MCP tool covers the task cleanly.
- Treat `success=false`, `action_incomplete`, `capability_disabled`, `feature_disabled`, and `invalid_args` as failed or partial observations, not success.
- After `invalid_args`, simplify the call and try at most one nearby variant. Do not spam near-duplicate guesses.
## Verification Rules
- Movement is not complete just because a move request was accepted. Confirm `arrived=true` or verify the new location with a fresh state read.
- Digging is not complete just because `mcc_dig_block` was invoked. Re-check the target block or nearby block search results.
- Item pickup is not complete just because the bot moved over an item. Re-check inventory state or nearby dropped-item entities.
- Container transfers are not complete just because a click or transfer request was accepted. Verify the resulting counts after the transfer.
- Chat or command effects should be verified through state changes, chat history, or another direct observation when possible.
- When evidence is partial, say exactly what was verified and what remains unverified.
## Best Practices
- Query first, act second, verify third.
- Keep plans short and concrete. Long speculative tool chains usually make the result worse.
- Prefer high-signal tools that answer the real question directly.
- Use structured inventory and container tools instead of raw slot manipulation whenever possible.
- Do not claim success from acceptance alone. Always pair actions with a follow-up observation.
- Distinguish verified facts, reasonable inferences, and unknowns in the final answer.
- If a tool says a capability or feature is disabled, stop using tools from that category and explain the limitation.
- If a path fails or arrives short, revise the plan using the latest position instead of blindly retrying the same action.
- Use `mcc_quit_client` to stop MCC. Do not send bare `quit` or `exit` through chat.
- Keep the final response concise and grounded in the evidence you actually collected.
## Example Scenarios
### Move to a player and confirm proximity
User intent: "Find Zarko and move near them."
Good flow:
- call `mcc_player_locate` or `mcc_players_list` to confirm the player is known
- if needed, call `mcc_can_reach_position` for the target area
- call `mcc_move_to_player`
- verify `arrived=true` or confirm the new position with `mcc_player_state`
- report whether proximity was verified or only partially achieved
### Open a chest, move an exact item count, and verify the result
User intent: "Put 5 diamonds in the chest at 11000 64 11021."
Good flow:
- call `mcc_container_open_at`
- inspect current state with `mcc_inventory_snapshot` if item availability is unclear
- call `mcc_container_deposit_item` or `mcc_container_withdraw_item`
- verify the resulting counts from the transfer result and, when useful, a fresh inventory snapshot
- report the exact verified delta, not just that the action was attempted
### Collect nearby dropped items or dig target blocks and verify the outcome
User intent: "Pick up nearby apples" or "Break those logs and collect them."
Good flow:
- call `mcc_items_list` or `mcc_blocks_find` to locate the target
- move only if the target is not already reachable from the current position
- call `mcc_items_pickup` for dropped items, or `mcc_dig_block` in a sensible order for blocks
- verify the result with `mcc_items_list`, `mcc_inventory_snapshot`, or a fresh block query
- if the result is partial, say what changed and what still remains
## Output Style
- Lead with the outcome the user cares about.
- Include the small set of observations that justify the answer.
- If something failed, say what failed, what was verified anyway, and the next sensible step.
- Do not embellish uncertain results.

View file

@ -10,10 +10,13 @@ builder.Logging.AddConsole(options =>
options.LogToStandardErrorThreshold = LogLevel.Trace;
});
builder.Services.AddSingleton(new MccMcpConfig());
builder.Services.AddSingleton<IMccMcpCapabilities, DeterministicCapabilities>();
builder.Services.AddSingleton<MccMcpGuidanceProvider>();
builder.Services.AddMcpServer()
.WithStdioServerTransport()
.WithTools<MccMcpToolSet>();
.WithTools<MccMcpToolSet>()
.WithPrompts<MccMcpPromptSet>();
await builder.Build().RunAsync();

View file

@ -66,9 +66,11 @@ public sealed class MccEmbeddedMcpHost
builder.Logging.AddFilter(_ => false);
builder.Services.AddSingleton(capabilities);
builder.Services.AddSingleton(config);
builder.Services.AddSingleton<MccMcpGuidanceProvider>();
builder.Services.AddMcpServer()
.WithHttpTransport()
.WithTools<MccMcpToolSet>();
.WithTools<MccMcpToolSet>()
.WithPrompts<MccMcpPromptSet>();
builder.WebHost.UseUrls($"http://{bindHost}:{config.Transport.Port}");
WebApplication builtApp = builder.Build();

View file

@ -0,0 +1,236 @@
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 EmbeddedSkillResourceSuffix = "MccMcpOperatorSkill.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 SkillName => "mcc-mcp-operator";
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 operator guide 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
{
SkillName = SkillName,
SkillMarkdown = document.SkillMarkdown,
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(EmbeddedSkillResourceSuffix, StringComparison.Ordinal))
?? throw new InvalidOperationException($"Embedded MCP skill resource '{EmbeddedSkillResourceSuffix}' was not found.");
using Stream? stream = assembly.GetManifestResourceStream(resourceName);
if (stream is null)
throw new InvalidOperationException($"Embedded MCP skill resource '{resourceName}' could not be opened.");
using StreamReader reader = new(stream, Encoding.UTF8);
string skillMarkdown = reader.ReadToEnd();
string bodyMarkdown = StripFrontmatter(skillMarkdown);
string bestPracticesSection = ExtractSection(bodyMarkdown, BestPracticesHeading);
string exampleScenariosSection = ExtractSection(bodyMarkdown, ExampleScenariosHeading);
return new GuidanceDocument(
skillMarkdown.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 SkillMarkdown,
string BodyMarkdown,
string[] BestPractices,
MccMcpAgentScenario[] ExampleScenarios);
}
public sealed class MccMcpAgentGuidancePayload
{
[JsonPropertyName("skillName")]
public string SkillName { get; init; } = string.Empty;
[JsonPropertyName("skillMarkdown")]
public string SkillMarkdown { 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; }
}

View file

@ -0,0 +1,20 @@
using System.ComponentModel;
using ModelContextProtocol.Server;
namespace MinecraftClient.Mcp;
public sealed class MccMcpPromptSet
{
private readonly MccMcpGuidanceProvider guidanceProvider;
public MccMcpPromptSet(MccMcpGuidanceProvider guidanceProvider)
{
this.guidanceProvider = guidanceProvider;
}
[McpServerPrompt(Name = "mcc_operator_guide"), Description("Get the canonical MCC operator guidance prompt for external agents using this MCP server.")]
public string OperatorGuide()
{
return guidanceProvider.GetSystemPrompt();
}
}

View file

@ -7,10 +7,12 @@ namespace MinecraftClient.Mcp;
public sealed class MccMcpToolSet
{
private readonly IMccMcpCapabilities capabilities;
private readonly MccMcpGuidanceProvider guidanceProvider;
public MccMcpToolSet(IMccMcpCapabilities capabilities)
public MccMcpToolSet(IMccMcpCapabilities capabilities, MccMcpGuidanceProvider guidanceProvider)
{
this.capabilities = capabilities;
this.guidanceProvider = guidanceProvider;
}
[McpServerTool(Name = "mcc_session_status"), Description("Get current MCC session and feature status.")]
@ -49,6 +51,12 @@ public sealed class MccMcpToolSet
return capabilities.GetInternalCommands();
}
[McpServerTool(Name = "mcc_agent_guidance"), Description("Get the canonical MCC operator guidance bundle for external agents using this MCP server.")]
public object AgentGuidance()
{
return guidanceProvider.GetToolPayload();
}
[McpServerTool(Name = "mcc_materials_list"), Description("List known MCC material names with optional filtering.")]
public object MaterialsList(string? filter = null, int maxCount = 500)
{

View file

@ -20,6 +20,7 @@
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Physics\BlockShapeData.json" LogicalName="BlockShapeData.json" />
<EmbeddedResource Include="..\.skills\mcc-mcp-operator\SKILL.md" Link="Mcp\EmbeddedSkills\SKILL.md" LogicalName="MccMcpOperatorSkill.md" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Protocol\Handlers\Compression\**" />