|
| 1 | +import AdaEngine |
| 2 | +import MCP |
| 3 | + |
| 4 | +extension AdaMCPRuntime { |
| 5 | + func entityByIDPayload(arguments: [String: Value]) throws -> Value { |
| 6 | + guard let entityID = arguments["id"]?.intValue else { |
| 7 | + throw AdaMCPError.invalidArguments("Argument 'id' is required.") |
| 8 | + } |
| 9 | + let worldName = arguments["world"]?.stringValue ?? AppWorldName.main.rawValue |
| 10 | + return try self.entityPayload(worldName: worldName, entityID: entityID) |
| 11 | + } |
| 12 | + |
| 13 | + func entityByNamePayload(arguments: [String: Value]) throws -> Value { |
| 14 | + guard let entityName = arguments["name"]?.stringValue, !entityName.isEmpty else { |
| 15 | + throw AdaMCPError.invalidArguments("Argument 'name' is required.") |
| 16 | + } |
| 17 | + let resolved = try self.resolveWorld(named: arguments["world"]?.stringValue) |
| 18 | + guard let entity = resolved.world.main.getEntityByName(entityName) else { |
| 19 | + throw AdaMCPError.entityNamedNotFound(world: resolved.name, entityName: entityName) |
| 20 | + } |
| 21 | + return try self.makeEntityPayload(worldName: resolved.name, world: resolved.world.main, entity: entity) |
| 22 | + } |
| 23 | + |
| 24 | + func findEntitiesPayload(arguments: [String: Value]) throws -> Value { |
| 25 | + let resolved = try self.resolveWorld(named: arguments["world"]?.stringValue) |
| 26 | + let nameQuery = arguments["name"]?.stringValue?.lowercased() |
| 27 | + let active = arguments["active"]?.boolValue |
| 28 | + let componentType = arguments["componentType"]?.stringValue |
| 29 | + |
| 30 | + let entities = try resolved.world.main.getEntities().filter { entity in |
| 31 | + if let nameQuery, !entity.name.lowercased().contains(nameQuery) { |
| 32 | + return false |
| 33 | + } |
| 34 | + if let active, entity.isActive != active { |
| 35 | + return false |
| 36 | + } |
| 37 | + if let componentType, !resolved.world.main.hasComponent(named: componentType, in: entity.id) { |
| 38 | + return false |
| 39 | + } |
| 40 | + return true |
| 41 | + } |
| 42 | + .map { entity in |
| 43 | + try self.makeEntityPayload(worldName: resolved.name, world: resolved.world.main, entity: entity) |
| 44 | + } |
| 45 | + |
| 46 | + return ["entities": .array(entities)] |
| 47 | + } |
| 48 | + |
| 49 | + func entityComponentsPayload(arguments: [String: Value]) throws -> Value { |
| 50 | + guard let entityID = arguments["entityId"]?.intValue else { |
| 51 | + throw AdaMCPError.invalidArguments("Argument 'entityId' is required.") |
| 52 | + } |
| 53 | + let resolved = try self.resolveWorld(named: arguments["world"]?.stringValue) |
| 54 | + guard let entity = resolved.world.main.getEntityByID(entityID) else { |
| 55 | + throw AdaMCPError.entityNotFound(world: resolved.name, entityID: entityID) |
| 56 | + } |
| 57 | + let components = try self.inspectComponents(world: resolved.world.main, entity: entity) |
| 58 | + return ["components": .array(components.payloads), "diagnostics": .array(components.diagnostics)] |
| 59 | + } |
| 60 | + |
| 61 | + func componentPayload(arguments: [String: Value]) throws -> Value { |
| 62 | + guard let entityID = arguments["entityId"]?.intValue else { |
| 63 | + throw AdaMCPError.invalidArguments("Argument 'entityId' is required.") |
| 64 | + } |
| 65 | + guard let componentType = arguments["componentType"]?.stringValue, !componentType.isEmpty else { |
| 66 | + throw AdaMCPError.invalidArguments("Argument 'componentType' is required.") |
| 67 | + } |
| 68 | + let resolved = try self.resolveWorld(named: arguments["world"]?.stringValue) |
| 69 | + guard resolved.world.main.getEntityByID(entityID) != nil else { |
| 70 | + throw AdaMCPError.entityNotFound(world: resolved.name, entityID: entityID) |
| 71 | + } |
| 72 | + guard let component = resolved.world.main.getComponent(named: componentType, from: entityID) else { |
| 73 | + throw AdaMCPError.componentNotFound( |
| 74 | + world: resolved.name, |
| 75 | + entityID: entityID, |
| 76 | + componentType: componentType |
| 77 | + ) |
| 78 | + } |
| 79 | + return try self.inspectValue(component) |
| 80 | + } |
| 81 | + |
| 82 | + func entityPayload(worldName: String, entityID: Int) throws -> Value { |
| 83 | + let resolved = try self.resolveWorld(named: worldName) |
| 84 | + guard let entity = resolved.world.main.getEntityByID(entityID) else { |
| 85 | + throw AdaMCPError.entityNotFound(world: resolved.name, entityID: entityID) |
| 86 | + } |
| 87 | + return try self.makeEntityPayload(worldName: resolved.name, world: resolved.world.main, entity: entity) |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +private extension AdaMCPRuntime { |
| 92 | + func makeEntityPayload(worldName: String, world: World, entity: Entity) throws -> Value { |
| 93 | + let components = try self.inspectComponents(world: world, entity: entity) |
| 94 | + return .object([ |
| 95 | + "type": "entity", |
| 96 | + "id": .int(entity.id), |
| 97 | + "name": .string(entity.name), |
| 98 | + "world": .string(worldName), |
| 99 | + "summary": .object([ |
| 100 | + "isActive": .bool(entity.isActive), |
| 101 | + "componentCount": .int(entity.components.count), |
| 102 | + "inspectableComponentCount": .int(components.payloads.count) |
| 103 | + ]), |
| 104 | + "fields": .object([ |
| 105 | + "isActive": .bool(entity.isActive) |
| 106 | + ]), |
| 107 | + "components": .array(components.payloads), |
| 108 | + "diagnostics": .array(components.diagnostics) |
| 109 | + ]) |
| 110 | + } |
| 111 | + |
| 112 | + func inspectComponents( |
| 113 | + world: World, |
| 114 | + entity: Entity |
| 115 | + ) throws -> (payloads: [Value], diagnostics: [Value]) { |
| 116 | + var payloads: [Value] = [] |
| 117 | + var diagnostics: [Value] = [] |
| 118 | + |
| 119 | + for (typeName, component) in world.getComponents(for: entity.id) { |
| 120 | + do { |
| 121 | + payloads.append(try self.inspectValue(component)) |
| 122 | + } catch { |
| 123 | + diagnostics.append(self.diagnosticValue( |
| 124 | + code: "not_inspectable", |
| 125 | + message: "Component \(typeName) on entity \(entity.id) is not inspectable." |
| 126 | + )) |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + return (payloads, diagnostics) |
| 131 | + } |
| 132 | +} |
0 commit comments