import { expect, it } from "@effect/vitest"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { DeviceHostUnavailableError, EnvironmentId, ProviderInstanceId, ThreadId, } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import { McpSchema, McpServer } from "effect/unstable/ai"; import * as ServerConfig from "../config.ts"; import * as DeviceService from "../device/DeviceService.ts"; import * as McpHttpServer from "./McpHttpServer.ts"; import * as McpInvocationContext from "./McpInvocationContext.ts"; const environmentId = EnvironmentId.make("environment-device-test"); const threadId = ThreadId.make("thread-device-test"); const invocation = (capabilities: ReadonlyArray) => ({ environmentId, threadId, providerSessionId: "provider-session-device-test", providerInstanceId: ProviderInstanceId.make("codex"), capabilities: new Set(capabilities), issuedAt: 1, }); const client = McpSchema.McpServerClient.of({ clientId: 1, clientCapabilities: {}, clientInfo: { name: "mcp-test", version: "1.0.0" }, protocolVersion: "2025-06-18", initializePayload: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "mcp-test", version: "1.0.0" }, }, getClient: Effect.die("unused"), }); const device = { hostId: "local", id: "UDID-1", platform: "ios" as const, name: "iPhone 17 Pro", version: "iOS 27.0", booted: true, physical: false, }; const state = { hosts: [ { id: "local", kind: "local" as const, label: "This machine", platforms: [ { platform: "ios" as const, available: true }, { platform: "android" as const, available: false, reason: "No SDK" }, ], hubInstalled: true, agentDeviceInstalled: true, }, ], hostStatus: "ready" as const, hostStatuses: { local: { status: "ready" as const } }, devices: [device], sessions: [], onboardingCompleted: true, agentAccessEnabled: true, hubBasePath: "/api/device-hub", revision: 1, }; const png = new Uint8Array(24); new DataView(png.buffer).setUint32(0, 0x89504e47); new DataView(png.buffer).setUint32(4, 0x0d0a1a0a); new DataView(png.buffer).setUint32(12, 0x49484452); new DataView(png.buffer).setUint32(16, 1206); new DataView(png.buffer).setUint32(20, 2622); const DeviceServiceMock = Layer.mock(DeviceService.DeviceService)({ state: Effect.succeed(state), list: Effect.succeed(state), open: (input) => Effect.succeed({ threadId: input.threadId, hostId: "local", deviceId: input.deviceId, platform: input.platform, openedAt: "2026-09-08T00:00:00.000Z", }), sessionsForThread: () => Effect.succeed([]), screenshot: () => Effect.succeed({ device, png }), close: () => Effect.void, agentCli: Effect.succeed("/cli"), testHost: () => Effect.die("not used"), agentTarget: () => Effect.succeed(["--config", "/host.json", "--session", "thread-device"]), }); const TestLayer = McpHttpServer.DeviceToolkitRegistrationLive.pipe( Layer.provideMerge(McpServer.McpServer.layer), Layer.provideMerge(DeviceServiceMock), Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-mcp-device-toolkit-test-" })), Layer.provide(NodeServices.layer), ); it.effect("registers the device tools and returns the screenshot as image content", () => Effect.scoped( Effect.gen(function* () { const server = yield* McpServer.McpServer; const names = server.tools.map(({ tool }) => tool.name).toSorted(); expect(names).toEqual(["device_close", "device_list", "device_open", "device_screenshot"]); const callWith = (capabilities: ReadonlyArray) => Effect.provideService(McpInvocationContext.McpInvocationContext, invocation(capabilities)); const opened = yield* server .callTool({ name: "device_open", arguments: { platform: "ios" } }) .pipe(callWith(["device"]), Effect.provideService(McpSchema.McpServerClient, client)); expect(opened.isError).toBe(false); const openedContent = opened.structuredContent as { quickStart: string }; expect(openedContent.quickStart).toContain("--udid UDID-1"); const shot = yield* server .callTool({ name: "device_screenshot", arguments: { deviceId: "UDID-1" } }) .pipe(callWith(["device"]), Effect.provideService(McpSchema.McpServerClient, client)); expect(shot.isError).toBe(false); expect(shot.content.map((entry) => entry.type)).toEqual(["text", "image"]); expect(shot.structuredContent).toMatchObject({ screenshot: { mimeType: "image/png", width: 1206, height: 2622 }, }); const denied = yield* server .callTool({ name: "device_list", arguments: {} }) .pipe(callWith(["preview"]), Effect.provideService(McpSchema.McpServerClient, client)); expect(denied.isError).toBe(true); }), ).pipe(Effect.provide(TestLayer)), ); it.effect("rejects unavailable agent access before booting or opening a device", () => { const unavailable = Layer.mock(DeviceService.DeviceService)({ list: Effect.succeed(state), agentTarget: () => Effect.fail( new DeviceHostUnavailableError({ hostId: "local", reason: "Agent access is disabled." }), ), open: () => Effect.die("Must not boot or register a device when agent access fails"), }); return Effect.gen(function* () { const server = yield* McpServer.McpServer; const result = yield* server .callTool({ name: "device_open", arguments: { platform: "ios" } }) .pipe( Effect.provideService(McpInvocationContext.McpInvocationContext, invocation(["device"])), Effect.provideService(McpSchema.McpServerClient, client), ); expect(result.isError).toBe(true); expect(result.content).toEqual( expect.arrayContaining([ expect.objectContaining({ type: "text", text: expect.stringContaining("Agent access is disabled."), }), ]), ); }).pipe( Effect.scoped, Effect.provide( McpHttpServer.DeviceToolkitRegistrationLive.pipe( Layer.provideMerge(McpServer.McpServer.layer), Layer.provide(unavailable), Layer.provide(NodeServices.layer), ), ), ); });