Live User Count
rybbit_live_usersRetrieve the current number of live users on a website by providing the site ID. Get real-time active user count for analytics.
Instructions
Get the current number of live/active users on a site in real-time
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| siteId | Yes | Site ID (numeric ID or domain identifier) |
Implementation Reference
- src/tools/overview.ts:41-62 (handler)The handler function for the rybbit_live_users tool. It calls client.get on /sites/{siteId}/live-user-count to fetch the live user count from the API, and returns the response wrapped in MCP content.
async (args) => { try { const count = await client.get<number>( `/sites/${args.siteId}/live-user-count` ); return { content: [ { type: "text" as const, text: truncateResponse({ liveUsers: count }), }, ], }; } catch (err) { const message = err instanceof Error ? err.message : String(err); return { content: [{ type: "text" as const, text: `Error: ${message}` }], isError: true, }; } } - src/tools/overview.ts:27-39 (schema)The input schema for rybbit_live_users. It defines siteId using the shared siteIdSchema, plus title, description, and annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint).
{ title: "Live User Count", description: "Get the current number of live/active users on a site in real-time", inputSchema: { siteId: siteIdSchema, }, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true, }, - src/tools/overview.ts:25-63 (registration)Registration of the rybbit_live_users tool via server.registerTool() with name 'rybbit_live_users', its schema, and the handler function. This is inside the registerOverviewTools() function.
server.registerTool( "rybbit_live_users", { title: "Live User Count", description: "Get the current number of live/active users on a site in real-time", inputSchema: { siteId: siteIdSchema, }, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true, }, }, async (args) => { try { const count = await client.get<number>( `/sites/${args.siteId}/live-user-count` ); return { content: [ { type: "text" as const, text: truncateResponse({ liveUsers: count }), }, ], }; } catch (err) { const message = err instanceof Error ? err.message : String(err); return { content: [{ type: "text" as const, text: `Error: ${message}` }], isError: true, }; } } ); - src/index.ts:39-48 (registration)Top-level registration: registerOverviewTools(server, client) is called in the main entrypoint, which wires up the rybbit_live_users tool as part of the MCP server.
registerOverviewTools(server, client); registerMetricsTools(server, client); registerSessionsTools(server, client); registerUsersTools(server, client); registerEventsTools(server, client); registerErrorsTools(server, client); registerPerformanceTools(server, client); registerFunnelsTools(server, client); registerGoalsTools(server, client); registerJourneysTools(server, client); - src/client.ts:23-144 (helper)The RybbitClient.get<T>() method used by the handler to make the HTTP GET request to /sites/{siteId}/live-user-count.
async get<T>(path: string, params?: QueryParams): Promise<T> { const url = this.buildUrl(path, params); return this.request<T>("GET", url); } async post<T>(path: string, body?: unknown, params?: QueryParams): Promise<T> { const url = this.buildUrl(path, params); return this.request<T>("POST", url, body); } async put<T>(path: string, body?: unknown, params?: QueryParams): Promise<T> { const url = this.buildUrl(path, params); return this.request<T>("PUT", url, body); } async delete<T>(path: string, params?: QueryParams): Promise<T> { const url = this.buildUrl(path, params); return this.request<T>("DELETE", url); } private buildUrl(path: string, params?: QueryParams): string { const base = `${this.config.baseUrl}/api${path}`; if (!params) return base; const searchParams = new URLSearchParams(); for (const [key, value] of Object.entries(params)) { if (value !== undefined && value !== null && value !== "") { searchParams.set(key, String(value)); } } const qs = searchParams.toString(); return qs ? `${base}?${qs}` : base; } private async request<T>( method: string, url: string, body?: unknown, isRetry = false ): Promise<T> { const auth = await getAuthHeaders(this.config, this.sessionCookie); const headers = auth.headers; this.sessionCookie = auth.sessionCookie; // Remove Content-Type for requests without body (DELETE, GET) if (!body) { delete headers["Content-Type"]; } const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); let res: Response; try { res = await fetch(url, { method, headers, body: body ? JSON.stringify(body) : undefined, signal: controller.signal, }); } catch (err) { clearTimeout(timeout); if (err instanceof DOMException && err.name === "AbortError") { throw new Error( `Request timed out after ${REQUEST_TIMEOUT_MS / 1000}s. Try narrowing the date range or adding filters.` ); } throw err; } finally { clearTimeout(timeout); } if (res.status === 401 && !isRetry && this.config.email) { this.sessionCookie = null; return this.request<T>(method, url, body, true); } if (!res.ok) { const text = await res.text().catch(() => ""); throw new Error(formatApiError(res.status, text)); } const contentType = res.headers.get("content-type") ?? ""; if (contentType.includes("application/json")) { return (await res.json()) as T; } return (await res.text()) as unknown as T; } buildAnalyticsParams(options: { startDate?: string; endDate?: string; timeZone?: string; filters?: FilterParam[]; pastMinutesStart?: number; pastMinutesEnd?: number; bucket?: string; page?: number; limit?: number; offset?: number; }): QueryParams { const params: QueryParams = {}; if (options.startDate) params.start_date = options.startDate; if (options.endDate) params.end_date = options.endDate; if (options.timeZone) params.time_zone = options.timeZone; if (options.filters && options.filters.length > 0) { params.filters = JSON.stringify(options.filters); } if (options.pastMinutesStart !== undefined) params.past_minutes_start = options.pastMinutesStart; if (options.pastMinutesEnd !== undefined) params.past_minutes_end = options.pastMinutesEnd; if (options.bucket) params.bucket = options.bucket; if (options.page !== undefined) params.page = options.page; if (options.limit !== undefined) params.limit = options.limit; if (options.offset !== undefined) params.offset = options.offset; return params; }