Get device mix (mobile / desktop / tablet)
grips_get_devicesRetrieve device-level revenue, sessions, transactions, conversion rate, and average order value for competitor domains to compare mobile and desktop performance over a date window.
Instructions
Returns device-level revenue, sessions, transactions, CR, and AOV for one or more domains aggregated over a date window. Use this to understand where a competitor's traffic and conversions come from — mobile vs desktop split, AOV by device, etc.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| domains | Yes | One or more domains (e.g. ['adidas.com', 'nike.com']). Protocol and trailing slash are stripped automatically. | |
| date_from | Yes | Start of the reporting window, inclusive, as YYYY-MM-DD. Example: '2024-01-01'. | |
| date_to | Yes | End of the reporting window, inclusive, as YYYY-MM-DD. Example: '2024-12-31'. | |
| country | No | Optional country filter. Defaults to the server's GRIPS_DEFAULT_COUNTRY (usually 'US'). | |
| format | No | Response format. 'markdown' is human-readable; 'json' is machine-parseable. | markdown |
Implementation Reference
- src/tools/devices.ts:48-131 (handler)Main handler function `runDevices` for the grips_get_devices tool. Queries the Grips API for device-mix data, handles two possible response shapes (dict and array), deduplicates results, and formats output as JSON or Markdown.
export async function runDevices( client: GripsApiClient, args: DevicesInput, defaultCountry: string, ): Promise<string> { const country = args.country ?? defaultCountry; const variables = buildFilters({ domains: args.domains, date_from: args.date_from, date_to: args.date_to, country, }); let raw: { data?: unknown; aggregated?: unknown; } = {}; try { raw = await client.query<{ data?: unknown; aggregated?: unknown }>({ query: DEVICES_QUERY, variables, }); } catch (err) { const msg = formatUpstreamError(err); const hint = errorHint(err); return `Error: ${msg}${hint ? `\n\nHint: ${hint}` : ""}`; } // Grips devices has been observed in two shapes: // (a) { data: { mobile: {...}, desktop: {...} } } (per public docs) // (b) { aggregated: [{ device: 'mobile', ... }, ...] } (observed at runtime) // Handle both robustly. const shapeA = toObject<Record<string, DeviceMetrics>>(raw.data); const shapeB = toArray<{ device: string } & DeviceMetrics>(raw.aggregated); const deviceRows: Array<{ device: string } & DeviceMetrics> = []; for (const [device, metrics] of Object.entries(shapeA)) { const m = toObject<DeviceMetrics>(metrics); deviceRows.push({ device, ...m }); } for (const row of shapeB) { const m = toObject<{ device?: string } & DeviceMetrics>(row); const device = typeof m.device === "string" ? m.device : "(unknown)"; // Don't double-count if a device was already in shapeA. if (deviceRows.some((r) => r.device === device)) continue; deviceRows.push({ device, ...m }); } if (deviceRows.length === 0) { return `**Grips device mix — ${args.domains.join(", ")} (${country}, ${args.date_from} → ${args.date_to})**\n\n_No device data returned by Grips for this filter set._`; } if (args.format === "json") { return toJson({ domains: args.domains, country, date_from: args.date_from, date_to: args.date_to, devices: deviceRows.map((r) => ({ device: r.device, sessions: safeNumberOrNull(r.sessions), transactions: safeNumberOrNull(r.transactions), cr: safeNumberOrNull(r.cr), aov: safeNumberOrNull(r.aov), transactionrevenue: safeNumberOrNull(r.transactionrevenue), })), }); } const table = toMarkdownTable( deviceRows.map((r) => ({ Device: r.device, Revenue: formatCurrency(r.transactionrevenue), Sessions: formatInt(r.sessions), Transactions: formatInt(r.transactions), CR: formatPercent(r.cr), AOV: formatCurrency(r.aov), })), ); const header = `**Grips device mix — ${args.domains.join(", ")} (${country}, ${args.date_from} → ${args.date_to})**`; return truncateIfNeeded(`${header}\n\n${table}`); } - src/schemas/common.ts:41-55 (schema)Input schema definition `baseFilterFields` shared by all tools, including grips_get_devices. Defines domains, date_from, date_to, country, and format fields.
export const baseFilterFields = { domains: domainsArray.describe( "One or more domains (e.g. ['adidas.com', 'nike.com']). Protocol and trailing slash are stripped automatically.", ), date_from: isoDate.describe( "Start of the reporting window, inclusive, as YYYY-MM-DD. Example: '2024-01-01'.", ), date_to: isoDate.describe( "End of the reporting window, inclusive, as YYYY-MM-DD. Example: '2024-12-31'.", ), country: countryEnum .optional() .describe("Optional country filter. Defaults to the server's GRIPS_DEFAULT_COUNTRY (usually 'US')."), format: outputFormat, }; - src/types.ts:79-85 (schema)Type definition `DeviceMetrics` for per-device metrics (sessions, transactions, CR, AOV, revenue).
export interface DeviceMetrics { sessions?: number | string | null; transactions?: number | string | null; cr?: number | string | null; aov?: number | string | null; transactionrevenue?: number | string | null; } - src/index.ts:114-123 (registration)Registration of the grips_get_devices tool using `server.registerTool()` with devicesToolDef.name, schema, and handler.
server.registerTool( devicesToolDef.name, { title: devicesToolDef.title, description: devicesToolDef.description, inputSchema: devicesToolDef.inputSchema, annotations: devicesToolDef.annotations, }, async (args) => asText(await runDevices(client, args as any, defaultCountry)), ); - src/tools/devices.ts:34-46 (schema)Tool definition object `devicesToolDef` with name 'grips_get_devices', title, description, inputSchema, and annotations.
export const devicesToolDef = { name: "grips_get_devices", title: "Get device mix (mobile / desktop / tablet)", description: "Returns device-level revenue, sessions, transactions, CR, and AOV for one or more domains aggregated over a date window. Use this to understand where a competitor's traffic and conversions come from — mobile vs desktop split, AOV by device, etc.", inputSchema: baseFilterFields, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true, }, };