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<McpInvocationContext.McpCapability>) => ({ 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<McpInvocationContext.McpCapability>) => 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), ), ), );});
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<McpInvocationContext.McpCapability>) => ({
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<McpInvocationContext.McpCapability>) =>
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),
),
),
);
});