2026-03-28 04:12:37 +01:00
using Microsoft.Extensions.DependencyInjection ;
using Microsoft.Extensions.Hosting ;
using Microsoft.Extensions.Logging ;
using MinecraftClient.Mcp ;
using ModelContextProtocol.Server ;
var builder = Host . CreateApplicationBuilder ( args ) ;
builder . Logging . AddConsole ( options = >
{
options . LogToStandardErrorThreshold = LogLevel . Trace ;
} ) ;
2026-03-29 20:57:16 +02:00
builder . Services . AddSingleton ( new MccMcpConfig ( ) ) ;
2026-03-28 04:12:37 +01:00
builder . Services . AddSingleton < IMccMcpCapabilities , DeterministicCapabilities > ( ) ;
2026-03-29 20:57:16 +02:00
builder . Services . AddSingleton < MccMcpGuidanceProvider > ( ) ;
2026-03-28 04:12:37 +01:00
builder . Services . AddMcpServer ( )
. WithStdioServerTransport ( )
2026-03-29 20:57:16 +02:00
. WithTools < MccMcpToolSet > ( )
. WithPrompts < MccMcpPromptSet > ( ) ;
2026-03-28 04:12:37 +01:00
await builder . Build ( ) . RunAsync ( ) ;
internal sealed class DeterministicCapabilities : IMccMcpCapabilities
{
private static double C ( double value ) = > Math . Round ( value , 2 , MidpointRounding . AwayFromZero ) ;
2026-03-30 23:14:28 +02:00
private readonly List < RecentEvent > recentEvents = [ ] ;
private long nextEventId = 1 ;
private double playerX = C ( 0.5 ) ;
private double playerY = C ( 80.0 ) ;
private double playerZ = C ( 0.5 ) ;
private float yaw ;
private float pitch ;
private int currentSlot = 1 ;
private bool sneaking ;
private bool sprinting ;
private float health = 20.0f ;
private bool disconnecting ;
public DeterministicCapabilities ( )
{
AddRecentEvent ( "player_join" , new { name = "HarnessBot" } ) ;
AddRecentEvent ( "inventory_open" , new { inventoryId = 1 , type = "Generic_9x3" , title = "Chest" } ) ;
AddRecentEvent ( "weather_rain" , new { level = 1.0 } ) ;
AddRecentEvent ( "title" , new { text = "mcp_title" } ) ;
AddRecentEvent ( "actionbar" , new { text = "mcp_actionbar" } ) ;
}
2026-03-28 04:12:37 +01:00
public MccMcpResult GetSessionStatus ( ) = >
MccMcpResult . Ok ( new
{
2026-03-30 23:14:28 +02:00
connected = ! disconnecting ,
2026-03-28 04:12:37 +01:00
host = "deterministic.local" ,
port = 25565 ,
username = "HarnessBot" ,
2026-03-30 23:14:28 +02:00
location = new { x = playerX , y = playerY , z = playerZ }
2026-03-28 04:12:37 +01:00
} ) ;
public MccMcpResult GetServerInfo ( ) = >
MccMcpResult . Ok ( new
{
host = "deterministic.local" ,
port = 25565 ,
tps = 20.0
} ) ;
public MccMcpResult GetPlayerState ( ) = >
MccMcpResult . Ok ( new
{
nickname = "HarnessBot" ,
username = "HarnessBot" ,
2026-03-30 23:14:28 +02:00
health ,
2026-03-28 04:12:37 +01:00
saturation = 20 ,
gamemode = 1 ,
2026-03-30 23:14:28 +02:00
currentSlot ,
yaw ,
pitch ,
location = new { x = playerX , y = playerY , z = playerZ } ,
2026-03-28 04:12:37 +01:00
effects = new object [ 0 ]
} ) ;
2026-03-30 23:14:28 +02:00
public MccMcpResult GetWorldState ( ) = >
MccMcpResult . Ok ( new
{
connected = ! disconnecting ,
host = "deterministic.local" ,
port = 25565 ,
username = "HarnessBot" ,
protocol = 769 ,
terrainEnabled = true ,
inventoryEnabled = true ,
entityHandlingEnabled = true ,
location = new { x = playerX , y = playerY , z = playerZ } ,
tps = 20.0 ,
dimension = "minecraft:overworld" ,
loadedChunkCount = 9 ,
pendingChunkCount = 0 ,
totalChunkCount = 9 ,
loadRatio = 1.0 ,
worldAge = 12000L ,
timeOfDay = 6000L ,
rainLevel = 1.0 ,
thunderLevel = 0.0
} ) ;
public MccMcpResult GetChunkStatus ( double? x , double? y , double? z )
{
double resolvedX = x ? ? playerX ;
double resolvedY = y ? ? playerY ;
double resolvedZ = z ? ? playerZ ;
int chunkX = ( int ) Math . Floor ( resolvedX ) > > 4 ;
int chunkZ = ( int ) Math . Floor ( resolvedZ ) > > 4 ;
return MccMcpResult . Ok ( new
{
location = new { x = C ( resolvedX ) , y = C ( resolvedY ) , z = C ( resolvedZ ) } ,
chunk = new { x = chunkX , z = chunkZ } ,
loaded = true ,
fullyLoaded = true ,
loadedChunkCount = 9 ,
pendingChunkCount = 0 ,
totalChunkCount = 9 ,
loadRatio = 1.0
} ) ;
}
public MccMcpResult RaycastBlock ( double maxDistance , bool includeNeighbors )
{
object? neighbors = includeNeighbors
? new
{
north = new { x = 0 , y = 79 , z = - 1 , material = "Air" , typeLabel = "Air" } ,
south = new { x = 0 , y = 79 , z = 1 , material = "Air" , typeLabel = "Air" } ,
east = new { x = 1 , y = 79 , z = 0 , material = "Air" , typeLabel = "Air" } ,
west = new { x = - 1 , y = 79 , z = 0 , material = "Air" , typeLabel = "Air" } ,
above = new { x = 0 , y = 80 , z = 0 , material = "Air" , typeLabel = "Air" } ,
below = new { x = 0 , y = 78 , z = 0 , material = "Stone" , typeLabel = "Stone" }
}
: null ;
return MccMcpResult . Ok ( new
{
hit = true ,
maxDistance ,
playerLocation = new { x = playerX , y = playerY , z = playerZ } ,
eyeLocation = new { x = playerX , y = C ( playerY + 1.62 ) , z = playerZ } ,
location = new { x = 0 , y = 79 , z = 0 } ,
block = new { material = "Stone" , typeLabel = "Stone" , blockId = 1 , blockMeta = 0 } ,
distance = 1.12 ,
eyeDistance = 2.03 ,
neighbors
} ) ;
}
public MccMcpResult PreviewPath ( double x , double y , double z , bool allowUnsafe , int maxOffset , int minOffset , int timeoutMs , int maxWaypoints )
{
object [ ] waypoints =
[
new { x = playerX , y = playerY , z = playerZ } ,
new { x = C ( ( playerX + x ) / 2 ) , y = C ( ( playerY + y ) / 2 ) , z = C ( ( playerZ + z ) / 2 ) } ,
new { x = C ( x ) , y = C ( y ) , z = C ( z ) }
] ;
return MccMcpResult . Ok ( new
{
pathFound = true ,
exactReachable = true ,
target = new { x = C ( x ) , y = C ( y ) , z = C ( z ) } ,
startLocation = new { x = playerX , y = playerY , z = playerZ } ,
finalWaypoint = new { x = C ( x ) , y = C ( y ) , z = C ( z ) } ,
finalDistance = 0.0 ,
waypointCount = waypoints . Length ,
truncated = waypoints . Length > Math . Max ( 1 , maxWaypoints ) ,
waypoints = waypoints . Take ( Math . Max ( 1 , maxWaypoints ) ) . ToArray ( ) ,
allowUnsafe ,
maxOffset ,
minOffset ,
timeoutMs = timeoutMs < = 0 ? 5000 : timeoutMs
} ) ;
}
2026-03-28 04:12:37 +01:00
public MccMcpResult GetPlayersList ( ) = >
MccMcpResult . Ok ( new
{
players = new [ ] { "HarnessBot" , "PlayerOne" }
} ) ;
2026-03-30 23:14:28 +02:00
public MccMcpResult GetPlayersDetailed ( bool includeSelf , bool includeCoordinates )
{
List < object > players = [ ] ;
if ( includeSelf )
{
players . Add ( new
{
name = "HarnessBot" ,
uuid = Guid . Parse ( "11111111-1111-1111-1111-111111111111" ) ,
ping = 5 ,
gamemode = 1 ,
listed = true ,
displayName = "HarnessBot" ,
entityId = 1 ,
x = includeCoordinates ? playerX : ( double? ) null ,
y = includeCoordinates ? playerY : ( double? ) null ,
z = includeCoordinates ? playerZ : ( double? ) null
} ) ;
}
players . Add ( new
{
name = "PlayerOne" ,
uuid = Guid . Parse ( "22222222-2222-2222-2222-222222222222" ) ,
ping = 12 ,
gamemode = 1 ,
listed = true ,
displayName = "PlayerOne" ,
entityId = 2 ,
x = includeCoordinates ? C ( 3.5 ) : ( double? ) null ,
y = includeCoordinates ? C ( 80.0 ) : ( double? ) null ,
z = includeCoordinates ? C ( 0.5 ) : ( double? ) null
} ) ;
return MccMcpResult . Ok ( new
{
count = players . Count ,
players = players . ToArray ( )
} ) ;
}
public MccMcpResult GetPlayerStats ( ) = >
MccMcpResult . Ok ( new
{
health ,
saturation = 20 ,
level = 12 ,
totalExperience = 245 ,
gamemode = 1 ,
playerEntityId = 1 ,
currentSlot ,
yaw ,
pitch ,
sneaking ,
sprinting ,
location = new { x = playerX , y = playerY , z = playerZ } ,
tps = 20.0
} ) ;
public MccMcpResult GetStatusEffects ( ) = >
MccMcpResult . Ok ( new
{
count = 0 ,
effects = Array . Empty < object > ( )
} ) ;
public MccMcpResult GetRecentEvents ( long afterId , int maxCount , string? typeFilter )
{
RecentEvent [ ] events = recentEvents
. Where ( e = > e . Id > afterId )
. Where ( e = > string . IsNullOrWhiteSpace ( typeFilter ) | | string . Equals ( e . Type , typeFilter , StringComparison . OrdinalIgnoreCase ) )
. Take ( Math . Max ( 1 , maxCount ) )
. ToArray ( ) ;
return MccMcpResult . Ok ( new
{
afterId ,
latestId = recentEvents . Count > 0 ? recentEvents [ ^ 1 ] . Id : 0 ,
count = events . Length ,
events = events . Select ( e = > new
{
id = e . Id ,
timestampUtc = e . TimestampUtc ,
type = e . Type ,
data = e . Data
} ) . ToArray ( )
} ) ;
}
public MccMcpResult GetLoadedBots ( ) = >
MccMcpResult . Ok ( new
{
count = 2 ,
bots = new object [ ]
{
new { name = "McpServer" , fullTypeName = "MinecraftClient.ChatBots.McpServer" , isScript = false } ,
new { name = "HarnessScript" , fullTypeName = "MinecraftClient.ChatBots.Script" , isScript = true }
}
} ) ;
2026-03-28 04:12:37 +01:00
public MccMcpResult GetChatHistory ( int maxCount , bool includeJson ) = >
MccMcpResult . Ok ( new
{
count = 2 ,
entries = new object [ ]
{
new { timestampUtc = DateTimeOffset . UtcNow . AddSeconds ( - 10 ) , kind = "chat" , text = "<PlayerOne> hello" , sender = "PlayerOne" , message = "hello" , json = includeJson ? "{}" : null } ,
new { timestampUtc = DateTimeOffset . UtcNow . AddSeconds ( - 5 ) , kind = "system" , text = "HarnessBot joined the game" , sender = ( string? ) null , message = ( string? ) null , json = includeJson ? "{}" : null }
}
} ) ;
public MccMcpResult GetInternalCommands ( ) = >
MccMcpResult . Ok ( new
{
count = 4 ,
commands = new [ ]
{
new { name = "debug" , usage = "debug [on|off|state]" , description = "Toggle debug or print state." } ,
new { name = "move" , usage = "move <x> <y> <z>" , description = "Move to location." } ,
new { name = "useitem" , usage = "useitem [x] [y] [z]" , description = "Use current held item." } ,
new { name = "dig" , usage = "dig <x> <y> <z> [duration]" , description = "Dig block at location." }
}
} ) ;
public MccMcpResult GetMaterialsList ( string? filter , int maxCount ) = >
MccMcpResult . Ok ( new
{
total = 3 ,
count = 3 ,
filter ,
materials = new [ ]
{
new { name = "Air" , typeLabel = "Air" } ,
new { name = "GrassBlock" , typeLabel = "Grass Block" } ,
new { name = "OakLog" , typeLabel = "Oak Log" }
}
} ) ;
public MccMcpResult GetBlockTypesList ( string? filter , int maxCount ) = >
MccMcpResult . Ok ( new
{
total = 3 ,
count = 3 ,
filter ,
blockTypes = new [ ]
{
new { name = "Air" , typeLabel = "Air" } ,
new { name = "GrassBlock" , typeLabel = "Grass Block" } ,
new { name = "OakLog" , typeLabel = "Oak Log" }
}
} ) ;
public MccMcpResult GetEntityTypesList ( string? filter , int maxCount ) = >
MccMcpResult . Ok ( new
{
total = 3 ,
count = 3 ,
filter ,
entityTypes = new [ ]
{
new { name = "Player" , typeLabel = "Player" } ,
new { name = "Item" , typeLabel = "Item" } ,
new { name = "Villager" , typeLabel = "Villager" }
}
} ) ;
public MccMcpResult SendChat ( string text ) = >
MccMcpResult . Ok ( new { echoed = text } ) ;
public MccMcpResult QuitClient ( ) = >
MccMcpResult . Ok ( new { quitting = true } ) ;
2026-03-30 23:14:28 +02:00
public MccMcpResult DisconnectClient ( )
{
disconnecting = true ;
AddRecentEvent ( "disconnect" , new { reason = "requested" , message = "Disconnect requested by test client." } ) ;
return MccMcpResult . Ok ( new { disconnecting = true } ) ;
}
2026-03-28 04:12:37 +01:00
public MccMcpResult RunInternalCommand ( string command ) = >
MccMcpResult . Ok ( new { command , status = "Done" , output = "deterministic" } ) ;
public MccMcpResult UseItemOnHand ( ) = >
MccMcpResult . Ok ( new { success = true , action = "use_item_on_hand" } ) ;
2026-03-30 23:14:28 +02:00
public MccMcpResult ChangeHotbarSlot ( int slot )
{
currentSlot = slot ;
return MccMcpResult . Ok ( new { success = true , slot } ) ;
}
public MccMcpResult SelectHotbarItem ( string itemType , bool preferLowestSlot )
{
currentSlot = string . Equals ( itemType , "DiamondSword" , StringComparison . OrdinalIgnoreCase ) ? 2 : 1 ;
return MccMcpResult . Ok ( new
{
success = true ,
itemType ,
inventorySlot = currentSlot - 1 ,
selectedSlot = currentSlot ,
count = string . Equals ( itemType , "DiamondSword" , StringComparison . OrdinalIgnoreCase ) ? 1 : 32 ,
preferLowestSlot
} ) ;
}
2026-03-28 04:12:37 +01:00
public MccMcpResult UseItemOnBlock ( double x , double y , double z ) = >
MccMcpResult . Ok ( new { success = true , x = C ( x ) , y = C ( y ) , z = C ( z ) , action = "useitem" } ) ;
public MccMcpResult DigBlock ( double x , double y , double z , double durationSeconds ) = >
MccMcpResult . Ok ( new
{
success = true ,
target = new { x = C ( x ) , y = C ( y ) , z = C ( z ) } ,
beforeBlock = new { material = "OakLog" , typeLabel = "Oak Log" , blockId = 137 , blockMeta = 0 } ,
afterBlock = new { material = "Air" , typeLabel = "Air" , blockId = 0 , blockMeta = 0 } ,
commandAccepted = true ,
changed = true ,
destroyed = true ,
attempts = 1 ,
attemptedDurationsSeconds = new [ ] { durationSeconds > 0 ? durationSeconds : 1.5 } ,
distance = 1.5 ,
playerLocation = new { x = C ( 0.5 ) , y = C ( 80.0 ) , z = C ( 0.5 ) }
} ) ;
public MccMcpResult PlaceBlock ( int x , int y , int z , string face , string hand , bool lookAtBlock ) = >
MccMcpResult . Ok ( new { success = true , x , y , z , face , hand , lookAtBlock , action = "place_block" } ) ;
public MccMcpResult InteractEntity ( int entityId , string interaction , string hand ) = >
MccMcpResult . Ok ( new { success = true , entityId , interaction , hand } ) ;
2026-03-30 23:14:28 +02:00
public MccMcpResult AttackEntity ( int entityId ) = >
MccMcpResult . Ok ( new { success = true , entityId , interaction = "Attack" } ) ;
public MccMcpResult FindNearestEntity ( string? typeFilter , string? nameFilter , double radius , bool includePlayers )
{
bool wantsArmorStand = string . IsNullOrWhiteSpace ( typeFilter )
| | string . Equals ( typeFilter , "ArmorStand" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( typeFilter , "Armor Stand" , StringComparison . OrdinalIgnoreCase ) ;
if ( wantsArmorStand & & radius > = 4.0 )
{
return MccMcpResult . Ok ( new
{
id = 7 ,
type = "ArmorStand" ,
typeLabel = "Armor Stand" ,
uuid = Guid . Parse ( "33333333-3333-3333-3333-333333333333" ) ,
name = "Armor Stand" ,
customName = ( string? ) null ,
x = C ( 2.5 ) ,
y = C ( 80.0 ) ,
z = C ( 0.5 ) ,
distance = 2.0 ,
health = 20.0f ,
pose = "Standing" ,
latency = 0
} ) ;
}
if ( includePlayers & & radius > = 3.0 )
{
return MccMcpResult . Ok ( new
{
id = 2 ,
type = "Player" ,
typeLabel = "Player" ,
uuid = Guid . Parse ( "22222222-2222-2222-2222-222222222222" ) ,
name = string . IsNullOrWhiteSpace ( nameFilter ) ? "PlayerOne" : nameFilter ,
customName = ( string? ) null ,
x = C ( 3.5 ) ,
y = C ( 80.0 ) ,
z = C ( 0.5 ) ,
distance = 3.0 ,
health = 20.0f ,
pose = "Standing" ,
latency = 12
} ) ;
}
return MccMcpResult . Fail ( "invalid_state" , data : new { typeFilter , nameFilter , radius , includePlayers } ) ;
}
2026-03-28 04:12:37 +01:00
public MccMcpResult ScanNearbyBlocks ( int radius , int maxCount , string? materialFilter ) = >
MccMcpResult . Ok ( new
{
center = new { x = 0 , y = 79 , z = 0 } ,
radius ,
count = 1 ,
blocks = new [ ]
{
new { x = 0 , y = 79 , z = 0 , material = materialFilter ? ? "GrassBlock" , blockId = 9 , blockMeta = 0 , distance = 0.0 }
}
} ) ;
public MccMcpResult FindBlocks ( string? query , int radius , int maxCount , bool exactMatch ) = >
MccMcpResult . Ok ( new
{
center = new { x = 0 , y = 79 , z = 0 } ,
radius ,
query ,
exactMatch ,
count = 2 ,
blocks = new object [ ]
{
new { x = 1 , y = 79 , z = 0 , material = "GrassBlock" , typeLabel = "Grass Block" , blockId = 9 , blockMeta = 0 , distance = 1.0 } ,
new { x = 2 , y = 79 , z = 0 , material = "Dirt" , typeLabel = "Dirt" , blockId = 10 , blockMeta = 0 , distance = 2.0 }
}
} ) ;
public MccMcpResult IsPlayerNearby ( string? playerName , double radius , bool includeSelf ) = >
MccMcpResult . Ok ( new
{
radius ,
playerName ,
includeSelf ,
anyNearby = true ,
count = 1 ,
players = new object [ ]
{
new
{
entityId = 1 ,
uuid = Guid . Empty ,
name = "PlayerOne" ,
customName = ( string? ) null ,
x = C ( 3.5 ) ,
y = C ( 80.0 ) ,
z = C ( 0.5 ) ,
distance = 3.0 ,
latency = 5
}
}
} ) ;
public MccMcpResult LocatePlayer ( string playerName , bool includeSelf ) = >
MccMcpResult . Ok ( new
{
playerName ,
matchedName = "PlayerOne" ,
entityId = 1 ,
uuid = Guid . Empty ,
x = C ( 3.5 ) ,
y = C ( 80.0 ) ,
z = C ( 0.5 ) ,
distance = 3.0
} ) ;
public MccMcpResult CanReachPosition ( double x , double y , double z , bool allowUnsafe , int maxOffset , int minOffset , int timeoutMs ) = >
MccMcpResult . Ok ( new
{
reachable = true ,
exactReachable = true ,
target = new { x = C ( x ) , y = C ( y ) , z = C ( z ) } ,
startLocation = new { x = C ( 0.5 ) , y = C ( 80.0 ) , z = C ( 0.5 ) } ,
finalWaypoint = new { x = C ( x ) , y = C ( y ) , z = C ( z ) } ,
finalDistance = 0.0 ,
waypointCount = 4 ,
allowUnsafe ,
maxOffset ,
minOffset ,
timeoutMs = timeoutMs < = 0 ? 5000 : timeoutMs
} ) ;
public MccMcpResult MoveTo ( double x , double y , double z , bool allowUnsafe , bool allowDirectTeleport , int maxOffset , int minOffset , int timeoutMs ) = >
MccMcpResult . Ok ( new
{
pathFound = true ,
arrived = true ,
tolerance = 1.5 ,
verifyWaitMs = 250 ,
target = new { x = C ( x ) , y = C ( y ) , z = C ( z ) } ,
startLocation = new { x = C ( 0.5 ) , y = C ( 80.0 ) , z = C ( 0.5 ) } ,
finalLocation = new { x = C ( x ) , y = C ( y ) , z = C ( z ) } ,
finalDistance = 0.0 ,
distanceMoved = 3.0 ,
allowUnsafe ,
allowDirectTeleport ,
maxOffset ,
minOffset ,
timeoutMs
} ) ;
public MccMcpResult MoveToPlayer ( string playerName , bool allowUnsafe , bool allowDirectTeleport , int maxOffset , int minOffset , int timeoutMs ) = >
MccMcpResult . Ok ( new
{
pathFound = true ,
arrived = true ,
tolerance = 1.5 ,
verifyWaitMs = 250 ,
target = new
{
playerName = "PlayerOne" ,
entityId = 1 ,
x = C ( 3.5 ) ,
y = C ( 80.0 ) ,
z = C ( 0.5 )
} ,
startLocation = new { x = C ( 0.5 ) , y = C ( 80.0 ) , z = C ( 0.5 ) } ,
finalLocation = new { x = C ( 3.5 ) , y = C ( 80.0 ) , z = C ( 0.5 ) } ,
finalDistance = 0.0 ,
distanceMoved = 3.0 ,
allowUnsafe ,
allowDirectTeleport ,
maxOffset ,
minOffset ,
timeoutMs
} ) ;
public MccMcpResult LookAt ( double x , double y , double z ) = >
MccMcpResult . Ok ( new { looked = true , x = C ( x ) , y = C ( y ) , z = C ( z ) } ) ;
2026-03-30 23:14:28 +02:00
public MccMcpResult LookDirection ( string direction )
{
switch ( direction . Trim ( ) . ToLowerInvariant ( ) )
{
case "up" :
yaw = 0.0f ;
pitch = - 90.0f ;
break ;
case "down" :
yaw = 0.0f ;
pitch = 90.0f ;
break ;
case "north" :
yaw = 180.0f ;
pitch = 0.0f ;
break ;
case "south" :
yaw = 0.0f ;
pitch = 0.0f ;
break ;
case "east" :
yaw = - 90.0f ;
pitch = 0.0f ;
break ;
case "west" :
yaw = 90.0f ;
pitch = 0.0f ;
break ;
}
return MccMcpResult . Ok ( new { success = true , direction , yaw , pitch } ) ;
}
public MccMcpResult LookAngles ( float yaw , float pitch )
{
this . yaw = yaw ;
this . pitch = pitch ;
return MccMcpResult . Ok ( new { success = true , yaw , pitch } ) ;
}
public MccMcpResult PlayAnimation ( string hand ) = >
MccMcpResult . Ok ( new { success = true , hand } ) ;
public MccMcpResult ToggleSneak ( bool enabled )
{
sneaking = enabled ;
return MccMcpResult . Ok ( new { success = true , enabled = sneaking } ) ;
}
public MccMcpResult ToggleSprint ( bool enabled )
{
sprinting = enabled ;
return MccMcpResult . Ok ( new { success = true , enabled = sprinting } ) ;
}
2026-03-28 16:44:22 +01:00
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 }
}
} ) ;
2026-03-28 04:12:37 +01:00
public MccMcpResult GetInventorySnapshot ( int inventoryId ) = >
MccMcpResult . Ok ( new
{
id = inventoryId ,
2026-03-28 16:44:22 +01:00
type = inventoryId = = 0 ? "PlayerInventory" : "Generic_9x3" ,
title = inventoryId = = 0 ? "Player Inventory" : "Chest" ,
slotCount = inventoryId = = 0 ? 46 : 63 ,
2026-03-28 04:12:37 +01:00
slots = new [ ]
{
new { slot = 0 , type = "Stone" , count = 64 }
}
} ) ;
2026-03-30 23:14:28 +02:00
public MccMcpResult SearchInventories ( string query , int maxCount , bool exactMatch , bool includeContainers )
{
List < object > matches = [ ] ;
if ( query . Contains ( "stone" , StringComparison . OrdinalIgnoreCase ) )
{
matches . Add ( new
{
inventoryId = 0 ,
inventoryType = "PlayerInventory" ,
inventoryTitle = "Player Inventory" ,
slot = 0 ,
itemType = "Stone" ,
typeLabel = "Stone" ,
count = 32 ,
isPlayerInventory = true ,
hotbarSlot = 1
} ) ;
}
if ( query . Contains ( "diamond" , StringComparison . OrdinalIgnoreCase ) | | query . Contains ( "sword" , StringComparison . OrdinalIgnoreCase ) )
{
matches . Add ( new
{
inventoryId = 0 ,
inventoryType = "PlayerInventory" ,
inventoryTitle = "Player Inventory" ,
slot = 1 ,
itemType = "DiamondSword" ,
typeLabel = "Diamond Sword" ,
count = 1 ,
isPlayerInventory = true ,
hotbarSlot = 2
} ) ;
}
if ( includeContainers )
{
matches . Add ( new
{
inventoryId = 1 ,
inventoryType = "Generic_9x3" ,
inventoryTitle = "Chest" ,
slot = 0 ,
itemType = "Stone" ,
typeLabel = "Stone" ,
count = 16 ,
isPlayerInventory = false ,
hotbarSlot = ( int? ) null
} ) ;
}
object [ ] result = matches . Take ( Math . Max ( 1 , maxCount ) ) . ToArray ( ) ;
return MccMcpResult . Ok ( new
{
query ,
exactMatch ,
includeContainers ,
count = result . Length ,
matches = result
} ) ;
}
public MccMcpResult OpenContainerAt ( int x , int y , int z , int timeoutMs , bool closeCurrent )
{
AddRecentEvent ( "inventory_open" , new { inventoryId = 1 , type = "Generic_9x3" , title = "Chest" , x , y , z } ) ;
return MccMcpResult . Ok ( new
2026-03-28 16:44:22 +01:00
{
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 }
} ) ;
2026-03-30 23:14:28 +02:00
}
2026-03-28 16:44:22 +01:00
2026-03-30 23:14:28 +02:00
public MccMcpResult CloseContainer ( int inventoryId , int timeoutMs )
{
int resolvedInventoryId = inventoryId < = 0 ? 1 : inventoryId ;
AddRecentEvent ( "inventory_close" , new { inventoryId = resolvedInventoryId } ) ;
return MccMcpResult . Ok ( new
2026-03-28 16:44:22 +01:00
{
success = true ,
closed = true ,
2026-03-30 23:14:28 +02:00
inventoryId = resolvedInventoryId ,
2026-03-28 16:44:22 +01:00
timeoutMs = timeoutMs < = 0 ? 5000 : timeoutMs
} ) ;
2026-03-30 23:14:28 +02:00
}
2026-03-28 16:44:22 +01:00
2026-03-28 04:12:37 +01:00
public MccMcpResult InventoryWindowAction ( int inventoryId , int slotId , string actionType ) = >
MccMcpResult . Ok ( new { success = true , inventoryId , slotId , actionType } ) ;
public MccMcpResult DropInventoryItem ( string itemType , int count , int inventoryId , bool preferStack ) = >
MccMcpResult . Ok ( new
{
success = true ,
itemType ,
requestedCount = count ,
droppedCount = count ,
beforeCount = 64 ,
afterCount = Math . Max ( 0 , 64 - count ) ,
inventoryId ,
touchedSlots = new [ ] { 36 } ,
preferStack
} ) ;
2026-03-28 16:44:22 +01:00
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 }
} ) ;
2026-03-28 04:12:37 +01:00
public MccMcpResult QueryEntities ( int maxCount ) = >
MccMcpResult . Ok ( new
{
count = 1 ,
entities = new [ ]
{
new { id = 1 , type = "Player" , x = C ( 0.5 ) , y = C ( 80.0 ) , z = C ( 0.5 ) }
}
} ) ;
public MccMcpResult ListEntities ( int maxCount , string? typeFilter , double radius ) = >
MccMcpResult . Ok ( new
{
totalTracked = 1 ,
count = 1 ,
entities = new [ ]
{
new
{
id = 1 ,
type = "Player" ,
typeLabel = "Player" ,
uuid = Guid . Empty ,
name = "HarnessBot" ,
customName = ( string? ) null ,
x = C ( 0.5 ) ,
y = C ( 80.0 ) ,
z = C ( 0.5 ) ,
distance = 0.0 ,
health = 20.0f ,
pose = "Standing" ,
latency = 5
}
}
} ) ;
public MccMcpResult GetEntityInfo ( int entityId , bool includeMetadata , bool includeEquipment , bool includeEffects ) = >
MccMcpResult . Ok ( new
{
id = entityId ,
type = "Player" ,
typeLabel = "Player" ,
uuid = Guid . Empty ,
name = "HarnessBot" ,
customName = ( string? ) null ,
customNameVisible = false ,
x = C ( 0.5 ) ,
y = C ( 80.0 ) ,
z = C ( 0.5 ) ,
yaw = 0.0f ,
pitch = 0.0f ,
health = 20.0f ,
pose = "Standing" ,
latency = 5 ,
objectData = - 1 ,
metadata = includeMetadata ? new { flags = 0 } : null ,
equipment = includeEquipment ? new [ ] { new { slot = 0 , type = "Stone" , count = 1 } } : null ,
activeEffects = includeEffects ? new object [ 0 ] : null
} ) ;
public MccMcpResult FindSigns ( string text , bool exactMatch , int radius , int maxCount , bool includeBackText ) = >
MccMcpResult . Ok ( new
{
text ,
exactMatch ,
radius ,
includeBackText ,
count = 1 ,
signs = new [ ]
{
new
{
x = 2 ,
y = 80 ,
z = 1 ,
material = "OakSign" ,
typeLabel = "Oak Sign" ,
distance = 1.8 ,
isWaxed = false ,
frontText = new [ ] { "home" , "storage" } ,
backText = includeBackText ? new [ ] { "north wall" } : Array . Empty < string > ( ) ,
matchedLines = new [ ] { text }
}
}
} ) ;
public MccMcpResult ListItemEntities ( string? itemType , double radius , int maxCount ) = >
MccMcpResult . Ok ( new
{
itemType = itemType ? ? "OakLog" ,
radius ,
count = 1 ,
items = new [ ]
{
new
{
entityId = 99 ,
itemType = "OakLog" ,
typeLabel = "Oak Log" ,
count = 3 ,
x = C ( 2.5 ) ,
y = C ( 80.0 ) ,
z = C ( 1.5 ) ,
distance = 2.24
}
}
} ) ;
public MccMcpResult PickupItems ( string itemType , double radius , int maxItems , bool allowUnsafe , int timeoutMs ) = >
MccMcpResult . Ok ( new
{
itemType ,
radius ,
maxItems ,
allowUnsafe ,
timeoutMs = timeoutMs < = 0 ? 2500 : timeoutMs ,
attempted = 1 ,
successfulPickups = 1 ,
collectedCount = 3 ,
initialInventoryCount = 0 ,
finalInventoryCount = 3 ,
remainingNearby = 0 ,
attempts = new object [ ]
{
new
{
entityId = 99 ,
itemType ,
typeLabel = "Oak Log" ,
expectedCount = 3 ,
target = new { x = C ( 2.5 ) , y = C ( 80.0 ) , z = C ( 1.5 ) } ,
pathFound = true ,
arrived = true ,
entityGone = true ,
inventoryDelta = 3 ,
startLocation = new { x = C ( 0.5 ) , y = C ( 80.0 ) , z = C ( 0.5 ) } ,
finalLocation = new { x = C ( 2.5 ) , y = C ( 80.0 ) , z = C ( 1.5 ) } ,
finalDistance = 0.0
}
}
} ) ;
2026-03-30 23:14:28 +02:00
public MccMcpResult Respawn ( )
{
health = 20.0f ;
AddRecentEvent ( "respawn" , new { location = new { x = playerX , y = playerY , z = playerZ } } ) ;
return MccMcpResult . Ok ( new { success = true , respawned = true } ) ;
}
2026-03-28 04:12:37 +01:00
public MccMcpResult GetWorldBlockAt ( int x , int y , int z ) = >
MccMcpResult . Ok ( new { x , y , z , material = "Air" , blockId = 0 , blockMeta = 0 } ) ;
2026-03-30 23:14:28 +02:00
private void AddRecentEvent ( string type , object? data )
{
recentEvents . Add ( new RecentEvent ( nextEventId + + , DateTimeOffset . UtcNow , type , data ) ) ;
if ( recentEvents . Count > 100 )
recentEvents . RemoveAt ( 0 ) ;
}
private sealed record RecentEvent ( long Id , DateTimeOffset TimestampUtc , string Type , object? Data ) ;
2026-03-28 04:12:37 +01:00
}