Added inventory manipulation to the MCP, improved the test harness

This commit is contained in:
Anon 2026-03-28 16:44:22 +01:00
parent 7f9023e7bb
commit cf382122e9
5 changed files with 948 additions and 7 deletions

View file

@ -295,16 +295,53 @@ internal sealed class DeterministicCapabilities : IMccMcpCapabilities
public MccMcpResult LookAt(double x, double y, double z) =>
MccMcpResult.Ok(new { looked = true, x = C(x), y = C(y), z = C(z) });
public MccMcpResult ListInventories() =>
MccMcpResult.Ok(new
{
count = 2,
inventories = new object[]
{
new { id = 0, type = "PlayerInventory", title = "Player Inventory", slotCount = 46, nonEmptySlots = 1, active = false },
new { id = 1, type = "Generic_9x3", title = "Chest", slotCount = 63, nonEmptySlots = 2, active = true }
}
});
public MccMcpResult GetInventorySnapshot(int inventoryId) =>
MccMcpResult.Ok(new
{
id = inventoryId,
type = inventoryId == 0 ? "PlayerInventory" : "Generic_9x3",
title = inventoryId == 0 ? "Player Inventory" : "Chest",
slotCount = inventoryId == 0 ? 46 : 63,
slots = new[]
{
new { slot = 0, type = "Stone", count = 64 }
}
});
public MccMcpResult OpenContainerAt(int x, int y, int z, int timeoutMs, bool closeCurrent) =>
MccMcpResult.Ok(new
{
success = true,
openAccepted = true,
opened = true,
timeoutMs = timeoutMs <= 0 ? 5000 : timeoutMs,
x,
y,
z,
block = new { material = "Chest", typeLabel = "Chest", blockId = 0, blockMeta = 0 },
inventory = new { id = 1, type = "Generic_9x3", title = "Chest", slotCount = 63, nonEmptySlots = 2 }
});
public MccMcpResult CloseContainer(int inventoryId, int timeoutMs) =>
MccMcpResult.Ok(new
{
success = true,
closed = true,
inventoryId = inventoryId <= 0 ? 1 : inventoryId,
timeoutMs = timeoutMs <= 0 ? 5000 : timeoutMs
});
public MccMcpResult InventoryWindowAction(int inventoryId, int slotId, string actionType) =>
MccMcpResult.Ok(new { success = true, inventoryId, slotId, actionType });
@ -322,6 +359,42 @@ internal sealed class DeterministicCapabilities : IMccMcpCapabilities
preferStack
});
public MccMcpResult DepositContainerItem(string itemType, int count, int inventoryId, bool preferLargestStack) =>
MccMcpResult.Ok(new
{
success = true,
direction = "deposit",
itemType,
requestedCount = count,
movedCount = count,
beforePlayerCount = 64,
afterPlayerCount = Math.Max(0, 64 - count),
beforeContainerCount = 0,
afterContainerCount = count,
inventoryId = inventoryId <= 0 ? 1 : inventoryId,
containerType = "Generic_9x3",
touchedSourceSlots = new[] { 36 },
touchedTargetSlots = new[] { 0 }
});
public MccMcpResult WithdrawContainerItem(string itemType, int count, int inventoryId, bool preferLargestStack) =>
MccMcpResult.Ok(new
{
success = true,
direction = "withdraw",
itemType,
requestedCount = count,
movedCount = count,
beforePlayerCount = 0,
afterPlayerCount = count,
beforeContainerCount = 64,
afterContainerCount = Math.Max(0, 64 - count),
inventoryId = inventoryId <= 0 ? 1 : inventoryId,
containerType = "Generic_9x3",
touchedSourceSlots = new[] { 0 },
touchedTargetSlots = new[] { 36 }
});
public MccMcpResult QueryEntities(int maxCount) =>
MccMcpResult.Ok(new
{

View file

@ -8,7 +8,10 @@ using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient("openrouter");
builder.Services.AddHttpClient("openrouter", client =>
{
client.Timeout = TimeSpan.FromMinutes(15);
});
var app = builder.Build();
app.UseDefaultFiles();
@ -46,6 +49,7 @@ Todo policy
Tool-use policy
- Use MCP tools for MCC/game-state questions and actions.
- Prefer the most direct high-signal tool first.
- Prefer structured inventory/container tools over raw window-click tools for chest or container management.
- If a tool result says success=false or includes an errorCode, treat that as a failed observation even if the transport call itself succeeded.
- Do not guess tool arguments repeatedly. If a tool returns invalid_args:
- simplify to the minimum required arguments,
@ -73,6 +77,12 @@ Action-specific guidance
- dig in a sensible order,
- re-check remaining blocks,
- re-check inventory or nearby item entities before finishing.
- Container inventory:
- locate the target container block,
- open the container first,
- inspect player and container inventory state,
- use structured deposit or withdraw tools instead of raw window clicks,
- verify both player and container counts changed before finishing.
- Search:
- start with the most direct search tool,
- use the user's requested radius when supported,
@ -97,6 +107,13 @@ Good examples
Good:
- finish with a short greeting
- no MCP tools
4) User: "Put 5 diamonds in the chest."
Good:
- open the chest
- inspect inventory state
- deposit exactly 5 diamonds
- verify the chest count increased and player count decreased by 5
- then finish
Wrong examples
1) Wrong:
@ -165,9 +182,9 @@ app.MapPost("/api/chat/stream", async (ChatStreamRequest request, IHttpClientFac
}
string model = GetModel();
int maxIterations = GetBoundedInt("MCC_WEB_MAX_ITERATIONS", 24, 4, 80);
int maxToolCalls = GetBoundedInt("MCC_WEB_MAX_TOOL_CALLS", 80, 4, 256);
TimeSpan maxWallTime = TimeSpan.FromSeconds(GetBoundedInt("MCC_WEB_MAX_SECONDS", 120, 10, 300));
int maxIterations = GetBoundedInt("MCC_WEB_MAX_ITERATIONS", 96, 4, 256);
int maxToolCalls = GetBoundedInt("MCC_WEB_MAX_TOOL_CALLS", 320, 4, 1024);
TimeSpan maxWallTime = TimeSpan.FromSeconds(GetBoundedInt("MCC_WEB_MAX_SECONDS", 900, 10, 3600));
await using McpClient mcp = await CreateMcpClientAsync(cancellationToken);
IList<McpClientTool> mcpTools = await mcp.ListToolsAsync(cancellationToken: cancellationToken);
@ -1005,9 +1022,9 @@ Answer:
static bool ShouldInjectReminder(int iteration, int maxIterations, int toolCallCount, int maxToolCalls, TimeSpan elapsed, TimeSpan maxWallTime)
{
return iteration >= maxIterations - 2
|| toolCallCount >= maxToolCalls - 4
|| elapsed >= maxWallTime - TimeSpan.FromSeconds(10);
return iteration >= maxIterations - 6
|| toolCallCount >= maxToolCalls - 12
|| elapsed >= maxWallTime - TimeSpan.FromSeconds(45);
}
static string BuildForcedFinalAnswer(