Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
TypeScript
at incident-api
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { db, eq } from "@openstatus/db";import { page, pageComponent, pageSubscriber, privateLocation, statusReport, statusReportUpdate,} from "@openstatus/db/src/schema";import type { Scope, Workspace } from "@openstatus/db/src/schema";import type { ServiceContext } from "@openstatus/services";import { SEEDED_WORKSPACE_FREE_ID } from "@openstatus/services/test/fixtures";import { expectAuditRow, createWorkspaceFixture, readAuditLog, withTestTransaction,} from "@openstatus/services/test/helpers";import { expect } from "@std/expect";import { afterAll, beforeAll, describe, test } from "@std/testing/bdd";
import { toServiceCtx } from "../adapter";import { registerMaintenanceTools } from "./maintenance";import { registerPageTools } from "./page";import { registerPrivateLocationTools } from "./private-location";import { registerStatusReportTools } from "./status-report";
/** * Build an MCP-flavoured `ServiceContext` for a workspace, optionally * threading a transaction through `ctx.db` so writes can be rolled back * via `withTestTransaction`. Local to this test file โ distinct from * `makeMcpCtx` in `@openstatus/services/test/helpers`, which doesn't * thread a tx and exists for service-package tests. */function makeMcpToolCtx( workspace: Workspace, opts: { db?: ServiceContext["db"]; createdById?: number; scopes?: Scope[]; } = {},): ServiceContext { return { ...toServiceCtx({ workspace, apiKey: { id: "test-key", createdById: opts.createdById, scopes: opts.scopes ?? ["write"], }, requestId: "test-req", }), db: opts.db, };}
const TEST_PREFIX = "mcp-tool-test";
let teamWorkspace: Workspace;let testPageId: number;let testPageComponentId: number;
beforeAll(async () => { teamWorkspace = (await createWorkspaceFixture("team")).workspace; const pageRow = await db .insert(page) .values({ workspaceId: teamWorkspace.id, title: `${TEST_PREFIX}-page`, description: "test page", slug: `${TEST_PREFIX}-page-slug`, customDomain: "", }) .returning() .get(); testPageId = pageRow.id;
const componentRow = await db .insert(pageComponent) .values({ workspaceId: teamWorkspace.id, pageId: testPageId, name: `${TEST_PREFIX}-component`, type: "static", }) .returning() .get(); testPageComponentId = componentRow.id;});
afterAll(async () => { await db .delete(pageSubscriber) .where(eq(pageSubscriber.pageId, testPageId)) .catch(() => undefined); await db .delete(pageComponent) .where(eq(pageComponent.id, testPageComponentId)) .catch(() => undefined); await db .delete(page) .where(eq(page.id, testPageId)) .catch(() => undefined);});
/** Build a fresh McpServer + register a tool group, return the tool map. */function registered( group: "page" | "status-report" | "maintenance" | "private-location", ctx: ServiceContext,) { const server = new McpServer( { name: "test", version: "0.0.0" }, { capabilities: { tools: { listChanged: false } } }, ); switch (group) { case "page": return registerPageTools(server, ctx); case "status-report": return registerStatusReportTools(server, ctx); case "maintenance": return registerMaintenanceTools(server, ctx); case "private-location": return registerPrivateLocationTools(server, ctx); }}
/** Invoke a tool's registered handler with input args. Returns the CallToolResult. */async function callTool( toolMap: ReturnType<typeof registered>, name: string, args: Record<string, unknown>,): Promise<{ structuredContent?: unknown; isError?: boolean; content: unknown;}> { const tool = toolMap.get(name); if (!tool) throw new Error(`tool ${name} not registered`); // The SDK's `RegisteredTool.handler` signature accepts (args, extra) โ // we provide a minimal extra suitable for unit tests. const extra = { signal: new AbortController().signal, requestId: "test-req", sendNotification: async () => undefined, sendRequest: async () => undefined as never, } as any; return (tool.handler as any)(args, extra);}
describe("list_status_pages", () => { test("lists pages in the workspace, slim shape", async () => { await withTestTransaction(async (tx) => { const ctx = makeMcpToolCtx(teamWorkspace, { db: tx }); const tools = registered("page", ctx); const result = await callTool(tools, "list_status_pages", {}); expect(result.isError).toBeUndefined(); const items = (result.structuredContent as { items: unknown[] }).items; expect(Array.isArray(items)).toBe(true); const ids = items.map((i) => (i as { id: number }).id); expect(ids).toContain(testPageId); // slim shape: must NOT carry password / customDomain etc. const ourPage = items.find( (i) => (i as { id: number }).id === testPageId, ); expect(Object.keys(ourPage as object).sort()).toEqual([ "id", "slug", "title", ]); }); });
test("does not leak pages from another workspace", async () => { await withTestTransaction(async (tx) => { // Insert a page in the FREE workspace using the same tx. const otherPage = await tx .insert(page) .values({ workspaceId: SEEDED_WORKSPACE_FREE_ID, title: `${TEST_PREFIX}-other-ws`, description: "should be invisible", slug: `${TEST_PREFIX}-other-ws-slug`, customDomain: "", }) .returning() .get();
// Call list_status_pages as the TEAM workspace. const ctx = makeMcpToolCtx(teamWorkspace, { db: tx }); const tools = registered("page", ctx); const result = await callTool(tools, "list_status_pages", {}); const items = (result.structuredContent as { items: { id: number }[] }) .items; const ids = items.map((i) => i.id); expect(ids).not.toContain(otherPage.id); }); });});
describe("list_status_reports", () => { test("filters out resolved reports by default", async () => { await withTestTransaction(async (tx) => { // seed: one active + one resolved const active = await tx .insert(statusReport) .values({ workspaceId: teamWorkspace.id, pageId: testPageId, title: `${TEST_PREFIX}-active`, status: "investigating", }) .returning() .get(); const resolved = await tx .insert(statusReport) .values({ workspaceId: teamWorkspace.id, pageId: testPageId, title: `${TEST_PREFIX}-resolved`, status: "resolved", }) .returning() .get();
const ctx = makeMcpToolCtx(teamWorkspace, { db: tx }); const tools = registered("status-report", ctx); const result = await callTool(tools, "list_status_reports", { filter: "active", }); expect(result.isError).toBeUndefined(); const items = (result.structuredContent as { items: { id: number }[] }) .items; const ids = items.map((i) => i.id); expect(ids).toContain(active.id); expect(ids).not.toContain(resolved.id); }); });});
describe("create_status_report", () => { test("creates a report + initial update and emits audit with transport=mcp", async () => { await withTestTransaction(async (tx) => { const ctx = makeMcpToolCtx(teamWorkspace, { db: tx }); const tools = registered("status-report", ctx); const result = await callTool(tools, "create_status_report", { title: `${TEST_PREFIX}-create`, status: "investigating", message: "investigating slowdown", pageId: testPageId, pageComponentIds: [testPageComponentId], notify: false, }); expect(result.isError).toBeUndefined(); const out = result.structuredContent as { statusReport: { id: number; title: string }; initialUpdateId: number; }; expect(out.statusReport.id).toBeGreaterThan(0); expect(out.statusReport.title).toBe(`${TEST_PREFIX}-create`); expect(out.initialUpdateId).toBeGreaterThan(0);
await expectAuditRow({ workspaceId: teamWorkspace.id, action: "status_report.create", entityType: "status_report", entityId: out.statusReport.id, actorType: "mcp", db: tx, });
const rows = await readAuditLog({ workspaceId: teamWorkspace.id, entityType: "status_report", entityId: out.statusReport.id, db: tx, }); expect(rows[0]?.actorType).toBe("mcp"); expect(rows[0]?.actorId).toBe("test-key"); // No createdById on the test ctx โ actorUserId stays null. expect(rows[0]?.actorUserId).toBeNull(); // notify defaulted false โ no dispatch expect(out).toMatchObject({ notified: false }); }); });
test("propagates createdById to audit_log.actor_user_id", async () => { await withTestTransaction(async (tx) => { const ctx = makeMcpToolCtx(teamWorkspace, { db: tx, createdById: 1 }); const tools = registered("status-report", ctx); const result = await callTool(tools, "create_status_report", { title: `${TEST_PREFIX}-with-creator`, status: "investigating", message: "x", pageId: testPageId, pageComponentIds: [], notify: false, }); expect(result.isError).toBeUndefined(); const out = result.structuredContent as { statusReport: { id: number }; }; const rows = await readAuditLog({ workspaceId: teamWorkspace.id, entityType: "status_report", entityId: out.statusReport.id, db: tx, }); expect(rows[0]?.actorUserId).toBe(1); }); });
test("notify: true reports notified back to caller", async () => { await withTestTransaction(async (tx) => { const ctx = makeMcpToolCtx(teamWorkspace, { db: tx }); const tools = registered("status-report", ctx); const result = await callTool(tools, "create_status_report", { title: `${TEST_PREFIX}-create-notify`, status: "investigating", message: "investigating slowdown", pageId: testPageId, pageComponentIds: [], notify: true, }); expect(result.isError).toBeUndefined(); const out = result.structuredContent as { notified: boolean }; // The team workspace's plan may or may not enable status-subscribers, // but the notify call must run; the tool reports `notified: true` // regardless of plan-level no-op behaviour inside the service. expect(out.notified).toBe(true); }); });
test("returns isError: true when pageId is not in workspace (NOT_FOUND)", async () => { await withTestTransaction(async (tx) => { const ctx = makeMcpToolCtx(teamWorkspace, { db: tx }); const tools = registered("status-report", ctx); const result = await callTool(tools, "create_status_report", { title: `${TEST_PREFIX}-bad-page`, status: "investigating", message: "x", pageId: 9_999_999, pageComponentIds: [], notify: false, }); expect(result.isError).toBe(true); }); });
// The "rejects calls that omit `notify`" guarantee belongs to the // SDK's input-validation step โ exercised in handler.test.ts via the // real `tools/call` JSON-RPC envelope. A handler-direct invocation // (this file's pattern) bypasses that validation, so a unit test // here can't catch the missing-required-field case.});
describe("add_status_report_update", () => { test("appends an update and emits audit with transport=mcp", async () => { await withTestTransaction(async (tx) => { const ctx = makeMcpToolCtx(teamWorkspace, { db: tx }); const sr = await tx .insert(statusReport) .values({ workspaceId: teamWorkspace.id, pageId: testPageId, title: `${TEST_PREFIX}-add-update`, status: "investigating", }) .returning() .get();
const tools = registered("status-report", ctx); const result = await callTool(tools, "add_status_report_update", { statusReportId: sr.id, status: "identified", message: "found root cause", notify: false, }); expect(result.isError).toBeUndefined(); const out = result.structuredContent as { statusReportUpdateId: number }; expect(out.statusReportUpdateId).toBeGreaterThan(0);
const rows = await readAuditLog({ workspaceId: teamWorkspace.id, entityType: "status_report_update", entityId: out.statusReportUpdateId, db: tx, }); expect(rows[0]?.action).toBe("status_report_update.create"); expect(rows[0]?.actorType).toBe("mcp"); expect(rows[0]?.actorId).toBe("test-key"); }); });});
describe("update_status_report", () => { test("edits a report's title and emits audit with transport=mcp", async () => { await withTestTransaction(async (tx) => { const ctx = makeMcpToolCtx(teamWorkspace, { db: tx }); const sr = await tx .insert(statusReport) .values({ workspaceId: teamWorkspace.id, pageId: testPageId, title: `${TEST_PREFIX}-orig`, status: "investigating", }) .returning() .get();
const tools = registered("status-report", ctx); const result = await callTool(tools, "update_status_report", { statusReportId: sr.id, title: `${TEST_PREFIX}-edited`, }); expect(result.isError).toBeUndefined(); const out = result.structuredContent as { id: number; title: string }; expect(out.title).toBe(`${TEST_PREFIX}-edited`);
const rows = await readAuditLog({ workspaceId: teamWorkspace.id, entityType: "status_report", entityId: out.id, db: tx, }); expect(rows[0]?.action).toBe("status_report.update"); expect(rows[0]?.actorType).toBe("mcp"); expect(rows[0]?.actorId).toBe("test-key"); }); });
// The "rejects status: 'resolved'" guarantee is enforced by a Zod // refine on the input schema โ see handler.test.ts for the // integration test that exercises it via a real `tools/call`. A // handler-direct invocation here bypasses the SDK's input // validation step, so the refine doesn't fire.});
describe("resolve_status_report", () => { test("resolves an active report and emits audit with transport=mcp", async () => { await withTestTransaction(async (tx) => { const ctx = makeMcpToolCtx(teamWorkspace, { db: tx }); const sr = await tx .insert(statusReport) .values({ workspaceId: teamWorkspace.id, pageId: testPageId, title: `${TEST_PREFIX}-resolve`, status: "investigating", }) .returning() .get();
const tools = registered("status-report", ctx); const result = await callTool(tools, "resolve_status_report", { statusReportId: sr.id, message: "fixed", notify: false, }); expect(result.isError).toBeUndefined(); const out = result.structuredContent as { statusReportUpdateId: number }; expect(out.statusReportUpdateId).toBeGreaterThan(0);
// Resolution path goes through addStatusReportUpdate which writes // a status_report_update.create audit row. const rows = await readAuditLog({ workspaceId: teamWorkspace.id, entityType: "status_report_update", entityId: out.statusReportUpdateId, db: tx, }); expect(rows[0]?.actorType).toBe("mcp"); expect(rows[0]?.actorId).toBe("test-key"); // confirm the report itself flipped to resolved const after = await tx .select() .from(statusReport) .where(eq(statusReport.id, sr.id)) .get(); expect(after?.status).toBe("resolved"); // tidy up the inserted update row to keep the rolled-back tx tidy await tx .delete(statusReportUpdate) .where(eq(statusReportUpdate.statusReportId, sr.id)) .catch(() => undefined); }); });});
describe("list_maintenances", () => { test("returns items and pagination metadata", async () => { await withTestTransaction(async (tx) => { const ctx = makeMcpToolCtx(teamWorkspace, { db: tx }); const tools = registered("maintenance", ctx); const result = await callTool(tools, "list_maintenances", {}); expect(result.isError).toBeUndefined(); const out = result.structuredContent as { items: unknown[]; pagination: { page: number; perPage: number; totalSize: number; totalPages: number; }; }; expect(Array.isArray(out.items)).toBe(true); expect(out.pagination.page).toBe(1); expect(out.pagination.perPage).toBe(50); expect(typeof out.pagination.totalSize).toBe("number"); expect(out.pagination.totalPages).toBeGreaterThanOrEqual(1); }); });
test("respects page and perPage inputs", async () => { await withTestTransaction(async (tx) => { const ctx = makeMcpToolCtx(teamWorkspace, { db: tx }); const tools = registered("maintenance", ctx); const result = await callTool(tools, "list_maintenances", { page: 2, perPage: 10, }); expect(result.isError).toBeUndefined(); const out = result.structuredContent as { pagination: { page: number; perPage: number }; }; expect(out.pagination.page).toBe(2); expect(out.pagination.perPage).toBe(10); }); });});
describe("create_maintenance", () => { test("creates a maintenance window and emits audit with transport=mcp", async () => { await withTestTransaction(async (tx) => { const ctx = makeMcpToolCtx(teamWorkspace, { db: tx }); const tools = registered("maintenance", ctx); const from = new Date("2026-04-30T14:00:00Z").toISOString(); const to = new Date("2026-04-30T15:00:00Z").toISOString(); const result = await callTool(tools, "create_maintenance", { title: `${TEST_PREFIX}-mtc`, message: "scheduled work", from, to, pageId: testPageId, pageComponentIds: [testPageComponentId], notify: false, }); expect(result.isError).toBeUndefined(); const out = result.structuredContent as { id: number; title: string }; expect(out.id).toBeGreaterThan(0); expect(out.title).toBe(`${TEST_PREFIX}-mtc`);
const rows = await readAuditLog({ workspaceId: teamWorkspace.id, entityType: "maintenance", entityId: out.id, db: tx, }); expect(rows[0]?.action).toBe("maintenance.create"); expect(rows[0]?.actorType).toBe("mcp"); expect(rows[0]?.actorId).toBe("test-key"); }); });
test("returns isError: true when from > to (VALIDATION)", async () => { await withTestTransaction(async (tx) => { const ctx = makeMcpToolCtx(teamWorkspace, { db: tx }); const tools = registered("maintenance", ctx); const from = new Date("2026-04-30T15:00:00Z").toISOString(); const to = new Date("2026-04-30T14:00:00Z").toISOString(); const result = await callTool(tools, "create_maintenance", { title: `${TEST_PREFIX}-bad-range`, message: "x", from, to, pageId: testPageId, pageComponentIds: [], notify: false, }); expect(result.isError).toBe(true); }); });});
describe("list_private_locations", () => { test("never exposes the agent token", async () => { await withTestTransaction(async (tx) => { const ctx = makeMcpToolCtx(teamWorkspace, { db: tx }); await tx.insert(privateLocation).values({ name: `${TEST_PREFIX}-mcp-location`, token: "super-secret-agent-token", workspaceId: teamWorkspace.id, });
const tools = registered("private-location", ctx); const result = await callTool(tools, "list_private_locations", { page: 1, perPage: 50, });
expect(result.isError).toBeFalsy();
const serialized = JSON.stringify(result); expect(serialized).not.toContain("super-secret-agent-token"); expect(serialized).not.toContain("token"); }); });
test("returns locations for the workspace", async () => { await withTestTransaction(async (tx) => { const ctx = makeMcpToolCtx(teamWorkspace, { db: tx }); await tx.insert(privateLocation).values({ name: `${TEST_PREFIX}-mcp-listed`, token: "another-token", workspaceId: teamWorkspace.id, });
const tools = registered("private-location", ctx); const result = await callTool(tools, "list_private_locations", { page: 1, perPage: 50, });
const structured = result.structuredContent as { items: { name: string }[]; }; expect(structured.items.some((i) => i.name.includes("mcp-listed"))).toBe( true, ); }); });});
describe("scope filter", () => { test("read-only key sees read tools, no write tools", async () => { const ctx = makeMcpToolCtx(teamWorkspace, { scopes: ["read"] }); const pageTools = registered("page", ctx); const reportTools = registered("status-report", ctx); const maintenanceTools = registered("maintenance", ctx);
// Read-only is allowed: expect(pageTools.has("list_status_pages")).toBe(true); expect(reportTools.has("list_status_reports")).toBe(true); expect(maintenanceTools.has("list_maintenances")).toBe(true); expect( registered("private-location", ctx).has("list_private_locations"), ).toBe(true);
// Write tools must be filtered out โ registry doesn't carry them // and `tools/list` will see only the read tools. expect(reportTools.has("create_status_report")).toBe(false); expect(reportTools.has("add_status_report_update")).toBe(false); expect(reportTools.has("update_status_report")).toBe(false); expect(reportTools.has("resolve_status_report")).toBe(false); expect(maintenanceTools.has("create_maintenance")).toBe(false); });
test("write key sees both read and write tools", () => { const ctx = makeMcpToolCtx(teamWorkspace, { scopes: ["write"] }); const reportTools = registered("status-report", ctx); expect(reportTools.has("list_status_reports")).toBe(true); expect(reportTools.has("create_status_report")).toBe(true); });
test("'*' (super-admin) key sees every tool", () => { const ctx = makeMcpToolCtx(teamWorkspace, { scopes: ["*"] }); const reportTools = registered("status-report", ctx); expect(reportTools.has("list_status_reports")).toBe(true); expect(reportTools.has("create_status_report")).toBe(true); expect(reportTools.has("resolve_status_report")).toBe(true); });});