diff --git a/apps/api/MCP_TOOLS.md b/apps/api/MCP_TOOLS.md index f6074babcf0..8fbf5f9d834 100644 --- a/apps/api/MCP_TOOLS.md +++ b/apps/api/MCP_TOOLS.md @@ -14,7 +14,7 @@ API key passed via `X-API-Key` header. Key encodes: Device commands can target **any device in the organization**: - If `deviceId` not specified, defaults to `defaultDeviceId` from API key - Any org member can run commands on any org device (permissions can be added later) -- Device must be online (heartbeat within last 60s) to receive commands +- Device must be online (seen within last 10 minutes) to receive commands ## Tool Categories diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useDevicePresence/useDevicePresence.ts b/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useDevicePresence/useDevicePresence.ts index 7bd89efed4c..ee6d8e334cd 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useDevicePresence/useDevicePresence.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useDevicePresence/useDevicePresence.ts @@ -1,33 +1,39 @@ -import { useEffect, useRef } from "react"; +import { useEffect } from "react"; import { apiTrpcClient } from "renderer/lib/api-trpc-client"; import { authClient } from "renderer/lib/auth-client"; import { electronTrpc } from "renderer/lib/electron-trpc"; +const PRESENCE_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes + /** - * Registers this device once on startup so MCP can verify ownership. - * No polling — just a single upsert into device_presence. + * Keeps this device visible to MCP by periodically refreshing + * its presence. Registers immediately on startup, then re-registers + * every 5 minutes so `list_devices` continues to report the device + * as online. */ export function useDevicePresence() { const { data: session } = authClient.useSession(); const { data: deviceInfo } = electronTrpc.auth.getDeviceInfo.useQuery(); - const registeredScopeRef = useRef(null); useEffect(() => { const orgId = session?.session?.activeOrganizationId; if (!deviceInfo || !orgId) return; - if (registeredScopeRef.current === orgId) return; - registeredScopeRef.current = orgId; - apiTrpcClient.device.registerDevice - .mutate({ - deviceId: deviceInfo.deviceId, - deviceName: deviceInfo.deviceName, - deviceType: "desktop", - }) - .catch(() => { - // Registration can fail when offline — will retry on next app launch - registeredScopeRef.current = null; - }); + const register = () => { + apiTrpcClient.device.registerDevice + .mutate({ + deviceId: deviceInfo.deviceId, + deviceName: deviceInfo.deviceName, + deviceType: "desktop", + }) + .catch((err) => { + console.debug("[device-presence] Registration failed:", err); + }); + }; + + register(); + const interval = setInterval(register, PRESENCE_INTERVAL_MS); + return () => clearInterval(interval); }, [deviceInfo, session?.session?.activeOrganizationId]); return { diff --git a/packages/mcp/src/tools/devices/list-devices/list-devices.test.ts b/packages/mcp/src/tools/devices/list-devices/list-devices.test.ts index 562b323d9d1..cadc3553099 100644 --- a/packages/mcp/src/tools/devices/list-devices/list-devices.test.ts +++ b/packages/mcp/src/tools/devices/list-devices/list-devices.test.ts @@ -17,7 +17,7 @@ let fetchedDevices = [ deviceId: "device-offline", deviceName: "Grace's iPhone", deviceType: "mobile", - lastSeenAt: new Date(Date.now() - 120_000), + lastSeenAt: new Date(Date.now() - 15 * 60 * 1000), ownerId: "user-2", ownerName: "Grace", ownerEmail: "grace@example.com", @@ -114,7 +114,7 @@ describe("list_devices MCP tool", () => { deviceId: "device-offline", deviceName: "Grace's iPhone", deviceType: "mobile", - lastSeenAt: new Date(Date.now() - 120_000), + lastSeenAt: new Date(Date.now() - 15 * 60 * 1000), ownerId: "user-2", ownerName: "Grace", ownerEmail: "grace@example.com", diff --git a/packages/mcp/src/tools/devices/list-devices/list-devices.ts b/packages/mcp/src/tools/devices/list-devices/list-devices.ts index dc06856f4c9..db973a49004 100644 --- a/packages/mcp/src/tools/devices/list-devices/list-devices.ts +++ b/packages/mcp/src/tools/devices/list-devices/list-devices.ts @@ -5,14 +5,14 @@ import { desc, eq } from "drizzle-orm"; import { z } from "zod"; import { getMcpContext } from "../../utils"; -const DEVICE_ONLINE_WINDOW_MS = 60_000; +const DEVICE_ONLINE_WINDOW_MS = 10 * 60 * 1000; // 10 minutes export function register(server: McpServer) { server.registerTool( "list_devices", { description: - "List devices in the organization. By default, only devices seen within the last 60 seconds are returned.", + "List devices in the organization. By default, only devices seen within the last 10 minutes are returned.", inputSchema: { includeOffline: z .boolean()