-
+
-
+
diff --git a/apps/web/src/content/pages/docs/reference/mcp-server.mdx b/apps/web/src/content/pages/docs/reference/mcp-server.mdx
index c0e3600a1..e1d240f0f 100644
--- a/apps/web/src/content/pages/docs/reference/mcp-server.mdx
+++ b/apps/web/src/content/pages/docs/reference/mcp-server.mdx
@@ -62,7 +62,7 @@ For AI agents that should only observe state — health summaries, paging on-cal
## Tools
-The server exposes 39 tools (37 on plans without the `audit-log` feature), grouped by resource: 35 scoped to the workspace tied to your credential, plus 4 public content tools that read openstatus.dev pages on any credential and any scope (see [Content](#content)). Mutations write to the audit log with `actor_type = "mcp"` (see [Audit log](#audit-log)).
+The server exposes 40 tools (38 on plans without the `audit-log` feature), grouped by resource: 36 scoped to the workspace tied to your credential, plus 4 public content tools that read openstatus.dev pages on any credential and any scope (see [Content](#content)). Mutations write to the audit log with `actor_type = "mcp"` (see [Audit log](#audit-log)).
### Pages
@@ -134,6 +134,12 @@ These tools act on declared incidents, not on monitor downtime, and are internal
|--------------------------|------|---------|
| `list_private_locations` | read | List the workspace's private locations with their name, status (`active` / `error`), metadata, and `lastSeenAt`. Agent tokens are never exposed. |
+### Workspace
+
+| Tool | Type | Purpose |
+|-----------------|------|---------|
+| `get_workspace` | read | The workspace's name, slug, plan, trial end, and usage counts (monitors, status pages, components, notification channels, status reports). Plan limits and billing identifiers are never exposed. |
+
### Audit
Registered only when the workspace plan includes the `audit-log` feature — otherwise these tools are absent from `tools/list`.
diff --git a/packages/api/src/router/tinybird/index.ts b/packages/api/src/router/tinybird/index.ts
index e9928a4f2..7d5ccc1fb 100644
--- a/packages/api/src/router/tinybird/index.ts
+++ b/packages/api/src/router/tinybird/index.ts
@@ -18,12 +18,12 @@ const types = ["http", "tcp", "dns", "icmp", "grpc"] as const;
type Period = (typeof periods)[number];
type Type = (typeof types)[number];
-// 30d/90d windows are paid-only; free workspaces are blocked server-side.
+// Free workspaces only get up to 7d; longer windows are blocked server-side.
function assertPaidPeriod(plan: string, period: Period) {
- if (plan === "free" && (period === "30d" || period === "90d")) {
+ if (plan === "free" && period !== "1d" && period !== "7d") {
throw new TRPCError({
code: "FORBIDDEN",
- message: "Upgrade to a paid plan to access 30d and 90d data",
+ message: "Upgrade to a paid plan to access more than 7 days of data",
});
}
}
@@ -40,9 +40,37 @@ const periodToMinInterval: Record
= {
"90d": 1440,
};
+// Coarsest useful resolution per period — mirrors the dashboard's periodToMaxInterval.
+const periodToMaxInterval: Record = {
+ "1d": 240,
+ "7d": 1440,
+ "14d": 1440,
+ "30d": 1440,
+ "90d": 1440,
+};
+
function clampInterval(period: Period, interval?: number) {
if (interval == null) return interval;
- return Math.max(interval, periodToMinInterval[period]);
+ return Math.min(
+ Math.max(interval, periodToMinInterval[period]),
+ periodToMaxInterval[period],
+ );
+}
+
+// latency pipes read fixed-window MVs (14d → 30d, 1d/7d → 14d, dns 1d → 7d)
+// and ignore the dates, so trim to the requested window here
+function trimToWindow(
+ rows: T[],
+ fromDate?: string,
+ toDate?: string,
+) {
+ const from = fromDate ? new Date(fromDate).getTime() : undefined;
+ const to = toDate ? new Date(toDate).getTime() : undefined;
+ return rows.filter(
+ (row) =>
+ (from === undefined || row.timestamp >= from) &&
+ (to === undefined || row.timestamp <= to),
+ );
}
// NEW: workspace-level counters helper
@@ -563,8 +591,9 @@ export const tinybirdRouter = createTRPCRouter({
period: z.enum(periods),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
type: z.enum(types).prefault("http"),
- fromDate: z.string().optional(),
- toDate: z.string().optional(),
+ interval: z.int().optional(),
+ fromDate: z.iso.datetime().optional(),
+ toDate: z.iso.datetime().optional(),
}),
)
.query(async (opts) => {
@@ -590,11 +619,20 @@ export const tinybirdRouter = createTRPCRouter({
assertPaidPeriod(opts.ctx.workspace.plan, opts.input.period);
+ opts.input.interval = clampInterval(
+ opts.input.period,
+ opts.input.interval,
+ );
+
const procedure = getMetricsLatencyProcedure(
opts.input.period,
opts.input.type,
);
- return await procedure(opts.input);
+ const result = await procedure(opts.input);
+ return {
+ ...result,
+ data: trimToWindow(result.data, opts.input.fromDate, opts.input.toDate),
+ };
}),
metricsTimingPhases: protectedProcedure
@@ -605,6 +643,8 @@ export const tinybirdRouter = createTRPCRouter({
interval: z.int().optional(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
type: z.literal("http"),
+ fromDate: z.iso.datetime().optional(),
+ toDate: z.iso.datetime().optional(),
}),
)
.query(async (opts) => {
@@ -647,7 +687,11 @@ export const tinybirdRouter = createTRPCRouter({
});
}
- return await procedure(opts.input);
+ const result = await procedure(opts.input);
+ return {
+ ...result,
+ data: trimToWindow(result.data, opts.input.fromDate, opts.input.toDate),
+ };
}),
workspace30d: protectedProcedure
diff --git a/packages/services/src/agent-tools/index.ts b/packages/services/src/agent-tools/index.ts
index 90516666c..bd354a46a 100644
--- a/packages/services/src/agent-tools/index.ts
+++ b/packages/services/src/agent-tools/index.ts
@@ -47,6 +47,7 @@ import {
updateStatusReportUpdateTool,
} from "./status-report";
import type { AnyAgentTool } from "./types";
+import { getWorkspaceTool } from "./workspace";
export { getAuditLogTool, listAuditLogsTool } from "./audit";
export { getContentPageTool, searchContentTool } from "./content";
@@ -82,6 +83,7 @@ export { listNotificationsTool } from "./notification";
export { listStatusPagesTool } from "./page";
export { listPageComponentsTool } from "./page-component";
export { listPrivateLocationsTool } from "./private-location";
+export { getWorkspaceTool } from "./workspace";
export {
addStatusReportUpdateTool,
createStatusReportTool,
@@ -152,6 +154,7 @@ export const agentTools = {
get_response_log: getResponseLogTool,
list_notifications: listNotificationsTool,
list_private_locations: listPrivateLocationsTool,
+ get_workspace: getWorkspaceTool,
list_audit_logs: listAuditLogsTool,
get_audit_log: getAuditLogTool,
search_docs: searchDocsTool,
diff --git a/packages/services/src/agent-tools/workspace.ts b/packages/services/src/agent-tools/workspace.ts
new file mode 100644
index 000000000..2ab130f07
--- /dev/null
+++ b/packages/services/src/agent-tools/workspace.ts
@@ -0,0 +1,51 @@
+import { z } from "zod";
+
+import { getWorkspaceUsage } from "../workspace";
+import type { AgentTool } from "./types";
+
+const GetWorkspaceInput = z.object({}).strict();
+const GetWorkspaceOutput = z.object({
+ id: z.number().int(),
+ slug: z.string(),
+ name: z.string().nullable(),
+ plan: z.string(),
+ trialEndsAt: z.string().nullable(),
+ usage: z.object({
+ monitors: z.number().int(),
+ statusPages: z.number().int(),
+ pageComponents: z.number().int(),
+ notificationChannels: z.number().int(),
+ statusReports: z.number().int(),
+ }),
+});
+
+export const getWorkspaceTool: AgentTool<
+ z.infer,
+ z.infer
+> = {
+ name: "get_workspace",
+ description:
+ "The current workspace: name, slug, plan, trial end and usage counts for monitors, status pages, components, notification channels and status reports. Use to answer which-plan or how-many questions. Plan limits and billing identifiers are NOT exposed.",
+ scope: "read",
+ destructive: false,
+ inputSchema: GetWorkspaceInput,
+ outputSchema: GetWorkspaceOutput,
+ async run({ ctx }) {
+ const { workspace } = ctx;
+ const usage = await getWorkspaceUsage({ ctx });
+ return {
+ id: workspace.id,
+ slug: workspace.slug,
+ name: workspace.name ?? null,
+ plan: workspace.plan ?? "free",
+ trialEndsAt: workspace.trialEndsAt?.toISOString() ?? null,
+ usage: {
+ monitors: usage.monitors,
+ statusPages: usage.pages,
+ pageComponents: usage.pageComponents,
+ notificationChannels: usage.notifications,
+ statusReports: usage.statusReports,
+ },
+ };
+ },
+};
diff --git a/packages/tinybird/src/client.ts b/packages/tinybird/src/client.ts
index fde1f7af5..8f4634b01 100644
--- a/packages/tinybird/src/client.ts
+++ b/packages/tinybird/src/client.ts
@@ -1781,6 +1781,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -1794,6 +1795,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -1807,6 +1809,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -1820,6 +1823,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -2265,6 +2269,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -2278,6 +2283,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -2291,6 +2297,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -2304,6 +2311,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -2781,6 +2789,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -2801,6 +2810,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -2842,6 +2852,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -2862,6 +2873,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -3069,6 +3081,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -3444,6 +3457,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -3464,6 +3478,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -3484,6 +3499,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -3504,6 +3520,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -3524,6 +3541,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),
@@ -3544,6 +3562,7 @@ export class OSTinybird {
parameters: z.object({
monitorId: z.string(),
regions: z.array(z.enum(monitorRegions).or(z.string())).optional(),
+ interval: z.int().optional(),
fromDate: z.string().optional(),
toDate: z.string().optional(),
}),