feat: Enhance MCP tool generation with public status page tools

- Refactored ToolGenerator.ts to include generation of public status page tools.
- Added PublicStatusPageTools.ts with functions to create tools for querying public status pages.
- Implemented tools for getting overview, incidents, scheduled maintenance, announcements, and resolving status page domains.
- Updated logging and error handling for public status page tool execution.
This commit is contained in:
Nawaz Dhandala 2025-12-18 13:03:17 +00:00
parent 2cf23c203e
commit eecf87bd0f
No known key found for this signature in database
GPG key ID: 96C5DCA24769DBCA
4 changed files with 1510 additions and 849 deletions

View file

@ -4,317 +4,373 @@
*/
import {
CallToolRequestSchema,
CallToolRequest,
ErrorCode,
ListToolsRequestSchema,
McpError,
CallToolRequestSchema,
CallToolRequest,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from "@modelcontextprotocol/sdk/types.js";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { McpToolInfo, OneUptimeToolCallArgs, JSONSchema } from "../Types/McpTypes";
import {
McpToolInfo,
OneUptimeToolCallArgs,
JSONSchema,
} from "../Types/McpTypes";
import OneUptimeOperation from "../Types/OneUptimeOperation";
import OneUptimeApiService from "../Services/OneUptimeApiService";
import SessionManager from "../Server/SessionManager";
import { LIST_PREVIEW_LIMIT } from "../Config/ServerConfig";
import { isHelperTool, handleHelperTool } from "../Tools/HelperTools";
import {
isPublicStatusPageTool,
handlePublicStatusPageTool,
} from "../Tools/PublicStatusPageTools";
import logger from "Common/Server/Utils/Logger";
/**
* Register tool handlers on the MCP server
*/
export function registerToolHandlers(mcpServer: McpServer, tools: McpToolInfo[]): void {
// Register list tools handler
mcpServer.server.setRequestHandler(ListToolsRequestSchema, async () => {
return handleListTools(tools);
});
export function registerToolHandlers(
mcpServer: McpServer,
tools: McpToolInfo[],
): void {
// Register list tools handler
mcpServer.server.setRequestHandler(ListToolsRequestSchema, async () => {
return handleListTools(tools);
});
// Register call tool handler
mcpServer.server.setRequestHandler(
CallToolRequestSchema,
async (request: CallToolRequest) => {
return handleCallTool(request, tools);
}
);
// Register call tool handler
mcpServer.server.setRequestHandler(
CallToolRequestSchema,
async (request: CallToolRequest) => {
return handleCallTool(request, tools);
},
);
logger.info(`Registered handlers for ${tools.length} tools`);
logger.info(`Registered handlers for ${tools.length} tools`);
}
/**
* Handle list tools request
*/
function handleListTools(tools: McpToolInfo[]): {
tools: Array<{ name: string; description: string; inputSchema: JSONSchema }>;
tools: Array<{ name: string; description: string; inputSchema: JSONSchema }>;
} {
const mcpTools: Array<{
name: string;
description: string;
inputSchema: JSONSchema;
}> = tools.map((tool: McpToolInfo) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
}));
const mcpTools: Array<{
name: string;
description: string;
inputSchema: JSONSchema;
}> = tools.map((tool: McpToolInfo) => {
return {
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
};
});
logger.info(`Listing ${mcpTools.length} available tools`);
return { tools: mcpTools };
logger.info(`Listing ${mcpTools.length} available tools`);
return { tools: mcpTools };
}
/**
* Handle tool call request
*/
async function handleCallTool(
request: CallToolRequest,
tools: McpToolInfo[]
request: CallToolRequest,
tools: McpToolInfo[],
): Promise<{ content: Array<{ type: string; text: string }> }> {
const { name, arguments: args } = request.params;
const { name, arguments: args } = request.params;
try {
// Check if this is a helper tool (doesn't require API key)
if (isHelperTool(name)) {
logger.info(`Executing helper tool: ${name}`);
const responseText: string = handleHelperTool(
name,
(args || {}) as Record<string, unknown>,
tools.filter((t: McpToolInfo) => !isHelperTool(t.name))
);
return {
content: [
{
type: "text",
text: responseText,
},
],
};
}
// Find the tool by name
const tool: McpToolInfo | undefined = tools.find(
(t: McpToolInfo) => t.name === name
);
if (!tool) {
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}. Use 'oneuptime_help' to see available tools.`);
}
logger.info(`Executing tool: ${name} for model: ${tool.modelName}`);
// Validate API key is available for this session
const apiKey: string = SessionManager.getCurrentApiKey();
if (!apiKey) {
throw new McpError(
ErrorCode.InvalidRequest,
"API key is required. Please provide x-api-key header in your request. Use 'oneuptime_help' to learn more."
);
}
// Execute the OneUptime operation with the session's API key
const result: unknown = await OneUptimeApiService.executeOperation(
tool.tableName,
tool.operation,
tool.modelType,
tool.apiPath || "",
args as OneUptimeToolCallArgs,
apiKey
);
// Format the response
const responseText: string = formatToolResponse(
tool,
result,
args as OneUptimeToolCallArgs
);
return {
content: [
{
type: "text",
text: responseText,
},
],
};
} catch (error) {
logger.error(`Error executing tool ${name}: ${error}`);
if (error instanceof McpError) {
throw error;
}
throw new McpError(
ErrorCode.InternalError,
`Failed to execute ${name}: ${error}. Use 'oneuptime_help' for guidance.`
);
try {
// Check if this is a helper tool (doesn't require API key)
if (isHelperTool(name)) {
logger.info(`Executing helper tool: ${name}`);
const responseText: string = handleHelperTool(
name,
(args || {}) as Record<string, unknown>,
tools.filter((t: McpToolInfo) => {
return !isHelperTool(t.name) && !isPublicStatusPageTool(t.name);
}),
);
return {
content: [
{
type: "text",
text: responseText,
},
],
};
}
// Check if this is a public status page tool (doesn't require API key)
if (isPublicStatusPageTool(name)) {
logger.info(`Executing public status page tool: ${name}`);
const responseText: string = await handlePublicStatusPageTool(
name,
(args || {}) as Record<string, unknown>,
);
return {
content: [
{
type: "text",
text: responseText,
},
],
};
}
// Find the tool by name
const tool: McpToolInfo | undefined = tools.find((t: McpToolInfo) => {
return t.name === name;
});
if (!tool) {
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${name}. Use 'oneuptime_help' to see available tools.`,
);
}
logger.info(`Executing tool: ${name} for model: ${tool.modelName}`);
// Validate API key is available for this session
const apiKey: string = SessionManager.getCurrentApiKey();
if (!apiKey) {
throw new McpError(
ErrorCode.InvalidRequest,
"API key is required. Please provide x-api-key header in your request. Use 'oneuptime_help' to learn more.",
);
}
// Execute the OneUptime operation with the session's API key
const result: unknown = await OneUptimeApiService.executeOperation(
tool.tableName,
tool.operation,
tool.modelType,
tool.apiPath || "",
args as OneUptimeToolCallArgs,
apiKey,
);
// Format the response
const responseText: string = formatToolResponse(
tool,
result,
args as OneUptimeToolCallArgs,
);
return {
content: [
{
type: "text",
text: responseText,
},
],
};
} catch (error) {
logger.error(`Error executing tool ${name}: ${error}`);
if (error instanceof McpError) {
throw error;
}
throw new McpError(
ErrorCode.InternalError,
`Failed to execute ${name}: ${error}. Use 'oneuptime_help' for guidance.`,
);
}
}
/**
* Format tool response based on operation type
*/
export function formatToolResponse(
tool: McpToolInfo,
result: unknown,
args: OneUptimeToolCallArgs
tool: McpToolInfo,
result: unknown,
args: OneUptimeToolCallArgs,
): string {
const operation: OneUptimeOperation = tool.operation;
const modelName: string = tool.singularName;
const pluralName: string = tool.pluralName;
const operation: OneUptimeOperation = tool.operation;
const modelName: string = tool.singularName;
const pluralName: string = tool.pluralName;
switch (operation) {
case OneUptimeOperation.Create:
return formatCreateResponse(modelName, result);
switch (operation) {
case OneUptimeOperation.Create:
return formatCreateResponse(modelName, result);
case OneUptimeOperation.Read:
return formatReadResponse(modelName, result, args.id);
case OneUptimeOperation.Read:
return formatReadResponse(modelName, result, args.id);
case OneUptimeOperation.List:
return formatListResponse(modelName, pluralName, result);
case OneUptimeOperation.List:
return formatListResponse(modelName, pluralName, result);
case OneUptimeOperation.Update:
return formatUpdateResponse(modelName, result, args.id);
case OneUptimeOperation.Update:
return formatUpdateResponse(modelName, result, args.id);
case OneUptimeOperation.Delete:
return formatDeleteResponse(modelName, args.id);
case OneUptimeOperation.Delete:
return formatDeleteResponse(modelName, args.id);
case OneUptimeOperation.Count:
return formatCountResponse(pluralName, result);
case OneUptimeOperation.Count:
return formatCountResponse(pluralName, result);
default:
return `Operation ${operation} completed successfully: ${JSON.stringify(result, null, 2)}`;
}
default:
return `Operation ${operation} completed successfully: ${JSON.stringify(result, null, 2)}`;
}
}
function formatCreateResponse(modelName: string, result: unknown): string {
const response: Record<string, unknown> = {
success: true,
operation: "create",
resourceType: modelName,
message: `Successfully created ${modelName}`,
data: result,
};
return JSON.stringify(response, null, 2);
const response: Record<string, unknown> = {
success: true,
operation: "create",
resourceType: modelName,
message: `Successfully created ${modelName}`,
data: result,
};
return JSON.stringify(response, null, 2);
}
function formatReadResponse(
modelName: string,
result: unknown,
id: string | undefined
modelName: string,
result: unknown,
id: string | undefined,
): string {
if (result) {
const response: Record<string, unknown> = {
success: true,
operation: "read",
resourceType: modelName,
resourceId: id,
data: result,
};
return JSON.stringify(response, null, 2);
}
if (result) {
const response: Record<string, unknown> = {
success: false,
operation: "read",
resourceType: modelName,
resourceId: id,
error: `${modelName} not found with ID: ${id}`,
suggestion: `Use list_${modelName.toLowerCase().replace(/\s+/g, "_")}s to find valid IDs`,
success: true,
operation: "read",
resourceType: modelName,
resourceId: id,
data: result,
};
return JSON.stringify(response, null, 2);
}
const response: Record<string, unknown> = {
success: false,
operation: "read",
resourceType: modelName,
resourceId: id,
error: `${modelName} not found with ID: ${id}`,
suggestion: `Use list_${modelName.toLowerCase().replace(/\s+/g, "_")}s to find valid IDs`,
};
return JSON.stringify(response, null, 2);
}
function formatListResponse(
modelName: string,
pluralName: string,
result: unknown
modelName: string,
pluralName: string,
result: unknown,
): string {
const items: Array<unknown> = Array.isArray(result)
? result
: (result as { data?: Array<unknown> })?.data || [];
const count: number = items.length;
const items: Array<unknown> = Array.isArray(result)
? result
: (result as { data?: Array<unknown> })?.data || [];
const count: number = items.length;
const response: Record<string, unknown> = {
success: true,
operation: "list",
resourceType: pluralName,
totalReturned: count,
hasMore: count >= LIST_PREVIEW_LIMIT,
message: count === 0
? `No ${pluralName} found matching the criteria`
: `Found ${count} ${count === 1 ? modelName : pluralName}`,
data: items.slice(0, LIST_PREVIEW_LIMIT),
};
const response: Record<string, unknown> = {
success: true,
operation: "list",
resourceType: pluralName,
totalReturned: count,
hasMore: count >= LIST_PREVIEW_LIMIT,
message:
count === 0
? `No ${pluralName} found matching the criteria`
: `Found ${count} ${count === 1 ? modelName : pluralName}`,
data: items.slice(0, LIST_PREVIEW_LIMIT),
};
if (count > LIST_PREVIEW_LIMIT) {
response["note"] = `Showing first ${LIST_PREVIEW_LIMIT} results. Use 'skip' parameter to paginate.`;
}
if (count > LIST_PREVIEW_LIMIT) {
response["note"] =
`Showing first ${LIST_PREVIEW_LIMIT} results. Use 'skip' parameter to paginate.`;
}
return JSON.stringify(response, null, 2);
return JSON.stringify(response, null, 2);
}
function formatUpdateResponse(
modelName: string,
result: unknown,
id: string | undefined
modelName: string,
result: unknown,
id: string | undefined,
): string {
const response: Record<string, unknown> = {
success: true,
operation: "update",
resourceType: modelName,
resourceId: id,
message: `Successfully updated ${modelName}`,
data: result,
};
return JSON.stringify(response, null, 2);
const response: Record<string, unknown> = {
success: true,
operation: "update",
resourceType: modelName,
resourceId: id,
message: `Successfully updated ${modelName}`,
data: result,
};
return JSON.stringify(response, null, 2);
}
function formatDeleteResponse(modelName: string, id: string | undefined): string {
const response: Record<string, unknown> = {
success: true,
operation: "delete",
resourceType: modelName,
resourceId: id,
message: `Successfully deleted ${modelName} (ID: ${id})`,
};
return JSON.stringify(response, null, 2);
function formatDeleteResponse(
modelName: string,
id: string | undefined,
): string {
const response: Record<string, unknown> = {
success: true,
operation: "delete",
resourceType: modelName,
resourceId: id,
message: `Successfully deleted ${modelName} (ID: ${id})`,
};
return JSON.stringify(response, null, 2);
}
function formatCountResponse(pluralName: string, result: unknown): string {
let totalCount: number = 0;
let totalCount: number = 0;
if (result !== null && result !== undefined) {
if (typeof result === "number") {
totalCount = result;
} else if (typeof result === "object") {
const resultObj: Record<string, unknown> = result as Record<string, unknown>;
if (result !== null && result !== undefined) {
if (typeof result === "number") {
totalCount = result;
} else if (typeof result === "object") {
const resultObj: Record<string, unknown> = result as Record<
string,
unknown
>;
// Handle { count: number } format
if ("count" in resultObj) {
const countValue: unknown = resultObj["count"];
if (typeof countValue === "number") {
totalCount = countValue;
} else if (typeof countValue === "object" && countValue !== null) {
// Handle PositiveNumber or other objects with value/toNumber
const countObj: Record<string, unknown> = countValue as Record<string, unknown>;
if (typeof countObj["value"] === "number") {
totalCount = countObj["value"];
} else if (typeof (countObj as { toNumber?: () => number }).toNumber === "function") {
totalCount = (countObj as { toNumber: () => number }).toNumber();
}
}
}
// Handle { data: { count: number } } format
else if ("data" in resultObj && typeof resultObj["data"] === "object" && resultObj["data"] !== null) {
const dataObj: Record<string, unknown> = resultObj["data"] as Record<string, unknown>;
if ("count" in dataObj && typeof dataObj["count"] === "number") {
totalCount = dataObj["count"];
}
}
// Handle { count: number } format
if ("count" in resultObj) {
const countValue: unknown = resultObj["count"];
if (typeof countValue === "number") {
totalCount = countValue;
} else if (typeof countValue === "object" && countValue !== null) {
// Handle PositiveNumber or other objects with value/toNumber
const countObj: Record<string, unknown> = countValue as Record<
string,
unknown
>;
if (typeof countObj["value"] === "number") {
totalCount = countObj["value"];
} else if (
typeof (countObj as { toNumber?: () => number }).toNumber ===
"function"
) {
totalCount = (countObj as { toNumber: () => number }).toNumber();
}
}
}
// Handle { data: { count: number } } format
else if (
"data" in resultObj &&
typeof resultObj["data"] === "object" &&
resultObj["data"] !== null
) {
const dataObj: Record<string, unknown> = resultObj["data"] as Record<
string,
unknown
>;
if ("count" in dataObj && typeof dataObj["count"] === "number") {
totalCount = dataObj["count"];
}
}
}
}
const response: Record<string, unknown> = {
success: true,
operation: "count",
resourceType: pluralName,
count: totalCount,
message: `Total count of ${pluralName}: ${totalCount}`,
};
return JSON.stringify(response, null, 2);
const response: Record<string, unknown> = {
success: true,
operation: "count",
resourceType: pluralName,
count: totalCount,
message: `Total count of ${pluralName}: ${totalCount}`,
};
return JSON.stringify(response, null, 2);
}

View file

@ -8,69 +8,82 @@ import OneUptimeOperation from "../Types/OneUptimeOperation";
import ModelType from "../Types/ModelType";
export interface ResourceInfo {
name: string;
singularName: string;
pluralName: string;
description: string;
operations: string[];
name: string;
singularName: string;
pluralName: string;
description: string;
operations: string[];
}
/**
* Generate helper tools for MCP
*/
export function generateHelperTools(resourceTools: McpToolInfo[]): McpToolInfo[] {
// Extract unique resources from tools
const resources: Map<string, ResourceInfo> = new Map();
export function generateHelperTools(
resourceTools: McpToolInfo[],
): McpToolInfo[] {
// Extract unique resources from tools
const resources: Map<string, ResourceInfo> = new Map();
for (const tool of resourceTools) {
if (!resources.has(tool.tableName)) {
resources.set(tool.tableName, {
name: tool.tableName,
singularName: tool.singularName,
pluralName: tool.pluralName,
description: getResourceDescription(tool.singularName),
operations: [],
});
}
const resource: ResourceInfo | undefined = resources.get(tool.tableName);
if (resource) {
resource.operations.push(tool.operation);
}
for (const tool of resourceTools) {
if (!resources.has(tool.tableName)) {
resources.set(tool.tableName, {
name: tool.tableName,
singularName: tool.singularName,
pluralName: tool.pluralName,
description: getResourceDescription(tool.singularName),
operations: [],
});
}
const resource: ResourceInfo | undefined = resources.get(tool.tableName);
if (resource) {
resource.operations.push(tool.operation);
}
}
const resourceList: ResourceInfo[] = Array.from(resources.values());
const resourceList: ResourceInfo[] = Array.from(resources.values());
return [
createHelpTool(resourceList),
createResourceInfoTool(resourceList),
];
return [createHelpTool(resourceList), createResourceInfoTool(resourceList)];
}
function getResourceDescription(singularName: string): string {
const descriptions: Record<string, string> = {
"Incident": "Represents service disruptions or issues affecting your systems. Track incident lifecycle from creation to resolution.",
"Monitor": "Defines what to monitor (websites, APIs, servers) and how to check their health and availability.",
"Alert": "Notifications triggered when monitors detect issues. Configures who gets notified and how.",
"Project": "Top-level container for all your monitoring resources. Organizes monitors, incidents, and team members.",
"Status Page": "Public-facing page showing the status of your services to your customers.",
"Scheduled Maintenance": "Planned downtime events that inform users about expected service interruptions.",
"Team": "Groups of users with shared access to project resources.",
"On-Call Duty Policy": "Defines escalation rules and schedules for incident response.",
"Incident State": "Represents the lifecycle states of incidents (e.g., Created, Acknowledged, Resolved).",
"Monitor Status": "Represents the health states of monitors (e.g., Operational, Degraded, Offline).",
};
const descriptions: Record<string, string> = {
Incident:
"Represents service disruptions or issues affecting your systems. Track incident lifecycle from creation to resolution.",
Monitor:
"Defines what to monitor (websites, APIs, servers) and how to check their health and availability.",
Alert:
"Notifications triggered when monitors detect issues. Configures who gets notified and how.",
Project:
"Top-level container for all your monitoring resources. Organizes monitors, incidents, and team members.",
"Status Page":
"Public-facing page showing the status of your services to your customers.",
"Scheduled Maintenance":
"Planned downtime events that inform users about expected service interruptions.",
Team: "Groups of users with shared access to project resources.",
"On-Call Duty Policy":
"Defines escalation rules and schedules for incident response.",
"Incident State":
"Represents the lifecycle states of incidents (e.g., Created, Acknowledged, Resolved).",
"Monitor Status":
"Represents the health states of monitors (e.g., Operational, Degraded, Offline).",
};
return descriptions[singularName] || `Manages ${singularName} resources in OneUptime.`;
return (
descriptions[singularName] ||
`Manages ${singularName} resources in OneUptime.`
);
}
function createHelpTool(resources: ResourceInfo[]): McpToolInfo {
const resourceSummary: string = resources
.map((r: ResourceInfo) => `- ${r.pluralName}: ${r.description}`)
.join("\n");
const resourceSummary: string = resources
.map((r: ResourceInfo) => {
return `- ${r.pluralName}: ${r.description}`;
})
.join("\n");
return {
name: "oneuptime_help",
description: `Get help and guidance for using the OneUptime MCP server. Returns information about available resources and common operations. Use this tool first to understand what you can do with OneUptime.
return {
name: "oneuptime_help",
description: `Get help and guidance for using the OneUptime MCP server. Returns information about available resources and common operations. Use this tool first to understand what you can do with OneUptime.
AVAILABLE RESOURCES:
${resourceSummary}
@ -80,234 +93,300 @@ COMMON WORKFLOWS:
2. Create incident: Use create_incident with title and severity
3. Check monitor status: Use list_monitors to see all monitors and their status
4. Count resources: Use count_* tools to get totals without fetching all data`,
inputSchema: {
type: "object",
properties: {
topic: {
type: "string",
description: "Optional topic to get help on: 'resources', 'incidents', 'monitors', 'alerts', 'workflows', or 'examples'",
enum: ["resources", "incidents", "monitors", "alerts", "workflows", "examples"],
},
},
additionalProperties: false,
inputSchema: {
type: "object",
properties: {
topic: {
type: "string",
description:
"Optional topic to get help on: 'resources', 'incidents', 'monitors', 'alerts', 'workflows', or 'examples'",
enum: [
"resources",
"incidents",
"monitors",
"alerts",
"workflows",
"examples",
],
},
modelName: "Help",
operation: OneUptimeOperation.Read,
modelType: ModelType.Database,
singularName: "Help",
pluralName: "Help",
tableName: "Help",
apiPath: "",
};
},
additionalProperties: false,
},
modelName: "Help",
operation: OneUptimeOperation.Read,
modelType: ModelType.Database,
singularName: "Help",
pluralName: "Help",
tableName: "Help",
apiPath: "",
};
}
function createResourceInfoTool(_resources: ResourceInfo[]): McpToolInfo {
return {
name: "oneuptime_list_resources",
description: "List all available OneUptime resources and their supported operations. Use this to discover what resources you can manage through the MCP server.",
inputSchema: {
type: "object",
properties: {},
additionalProperties: false,
},
modelName: "ResourceInfo",
operation: OneUptimeOperation.List,
modelType: ModelType.Database,
singularName: "Resource",
pluralName: "Resources",
tableName: "ResourceInfo",
apiPath: "",
};
return {
name: "oneuptime_list_resources",
description:
"List all available OneUptime resources and their supported operations. Use this to discover what resources you can manage through the MCP server.",
inputSchema: {
type: "object",
properties: {},
additionalProperties: false,
},
modelName: "ResourceInfo",
operation: OneUptimeOperation.List,
modelType: ModelType.Database,
singularName: "Resource",
pluralName: "Resources",
tableName: "ResourceInfo",
apiPath: "",
};
}
/**
* Handle helper tool execution
*/
export function handleHelperTool(
toolName: string,
args: Record<string, unknown>,
resourceTools: McpToolInfo[]
toolName: string,
args: Record<string, unknown>,
resourceTools: McpToolInfo[],
): string {
// Extract unique resources from tools
const resources: Map<string, ResourceInfo> = new Map();
// Extract unique resources from tools
const resources: Map<string, ResourceInfo> = new Map();
for (const tool of resourceTools) {
if (!resources.has(tool.tableName)) {
resources.set(tool.tableName, {
name: tool.tableName,
singularName: tool.singularName,
pluralName: tool.pluralName,
description: getResourceDescription(tool.singularName),
operations: [],
});
}
const resource: ResourceInfo | undefined = resources.get(tool.tableName);
if (resource) {
resource.operations.push(tool.operation);
}
for (const tool of resourceTools) {
if (!resources.has(tool.tableName)) {
resources.set(tool.tableName, {
name: tool.tableName,
singularName: tool.singularName,
pluralName: tool.pluralName,
description: getResourceDescription(tool.singularName),
operations: [],
});
}
const resourceList: ResourceInfo[] = Array.from(resources.values());
if (toolName === "oneuptime_help") {
return handleHelpTool(args, resourceList);
} else if (toolName === "oneuptime_list_resources") {
return handleListResourcesTool(resourceList);
const resource: ResourceInfo | undefined = resources.get(tool.tableName);
if (resource) {
resource.operations.push(tool.operation);
}
}
return JSON.stringify({ error: "Unknown helper tool" });
const resourceList: ResourceInfo[] = Array.from(resources.values());
if (toolName === "oneuptime_help") {
return handleHelpTool(args, resourceList);
} else if (toolName === "oneuptime_list_resources") {
return handleListResourcesTool(resourceList);
}
return JSON.stringify({ error: "Unknown helper tool" });
}
function handleHelpTool(args: Record<string, unknown>, resourceList: ResourceInfo[]): string {
const topic: string = (args["topic"] as string) || "general";
function handleHelpTool(
args: Record<string, unknown>,
resourceList: ResourceInfo[],
): string {
const topic: string = (args["topic"] as string) || "general";
const response: Record<string, unknown> = {
success: true,
topic,
data: {} as Record<string, unknown>,
};
const response: Record<string, unknown> = {
success: true,
topic,
data: {} as Record<string, unknown>,
};
switch (topic) {
case "resources":
(response["data"] as Record<string, unknown>)["resources"] = resourceList.map((r: ResourceInfo) => ({
name: r.name,
singularName: r.singularName,
pluralName: r.pluralName,
description: r.description,
availableOperations: r.operations,
}));
(response["data"] as Record<string, unknown>)["hint"] = "Use the specific tool for each operation. For example: list_incidents, create_incident, get_incident, update_incident, delete_incident, count_incidents";
break;
case "incidents":
(response["data"] as Record<string, unknown>)["description"] = "Incidents represent service disruptions or issues. They have states (Created, Acknowledged, Resolved) and severities.";
(response["data"] as Record<string, unknown>)["commonOperations"] = [
{ tool: "list_incidents", description: "List all incidents, optionally filtered by state or severity" },
{ tool: "create_incident", description: "Create a new incident when an issue is detected" },
{ tool: "update_incident", description: "Update incident state, severity, or add notes" },
{ tool: "count_incidents", description: "Get count of incidents by state" },
];
(response["data"] as Record<string, unknown>)["example"] = {
createIncident: {
title: "Database connection failure",
description: "Production database is not responding to queries",
incidentSeverityId: "<severity-uuid>",
},
};
break;
case "monitors":
(response["data"] as Record<string, unknown>)["description"] = "Monitors check the health and availability of your services (websites, APIs, servers).";
(response["data"] as Record<string, unknown>)["commonOperations"] = [
{ tool: "list_monitors", description: "List all monitors and their current status" },
{ tool: "create_monitor", description: "Create a new monitor to watch a service" },
{ tool: "update_monitor", description: "Update monitor configuration or enable/disable" },
{ tool: "count_monitors", description: "Get total number of monitors" },
];
break;
case "alerts":
(response["data"] as Record<string, unknown>)["description"] = "Alerts are notifications sent when monitors detect issues.";
(response["data"] as Record<string, unknown>)["commonOperations"] = [
{ tool: "list_alerts", description: "List all alerts and their status" },
{ tool: "count_alerts", description: "Get count of alerts" },
];
break;
case "workflows":
(response["data"] as Record<string, unknown>)["workflows"] = [
{
name: "Check system status",
steps: [
"1. Use count_incidents to see if there are active incidents",
"2. Use list_monitors with query to find any monitors with issues",
"3. Use list_incidents to get details of any active incidents",
],
},
{
name: "Create and manage incident",
steps: [
"1. Use list_incident_states to get available states",
"2. Use create_incident with title, description, and severity",
"3. Use update_incident to change state as incident progresses",
],
},
{
name: "Incident summary report",
steps: [
"1. Use count_incidents to get total count",
"2. Use list_incidents with sort by createdAt descending",
"3. Group and summarize the results",
],
},
];
break;
case "examples":
(response["data"] as Record<string, unknown>)["examples"] = {
listRecentIncidents: {
tool: "list_incidents",
args: { limit: 10, sort: { createdAt: -1 } },
},
countActiveIncidents: {
tool: "count_incidents",
args: { query: {} },
},
getSpecificIncident: {
tool: "get_incident",
args: { id: "<incident-uuid>" },
},
updateIncidentTitle: {
tool: "update_incident",
args: { id: "<incident-uuid>", title: "Updated title" },
},
};
break;
default:
(response["data"] as Record<string, unknown>)["welcome"] = "Welcome to OneUptime MCP Server!";
(response["data"] as Record<string, unknown>)["description"] = "OneUptime is an open-source monitoring platform. This MCP server lets you manage incidents, monitors, alerts, and more.";
(response["data"] as Record<string, unknown>)["availableTopics"] = ["resources", "incidents", "monitors", "alerts", "workflows", "examples"];
(response["data"] as Record<string, unknown>)["quickStart"] = [
"1. Use 'oneuptime_list_resources' to see all available resources",
"2. Use 'list_*' tools to browse existing data",
"3. Use 'count_*' tools to get quick summaries",
"4. Use 'create_*' tools to add new items",
];
(response["data"] as Record<string, unknown>)["resourceCount"] = resourceList.length;
break;
}
return JSON.stringify(response, null, 2);
}
function handleListResourcesTool(resources: ResourceInfo[]): string {
const response: Record<string, unknown> = {
success: true,
totalResources: resources.length,
resources: resources.map((r: ResourceInfo) => ({
switch (topic) {
case "resources":
(response["data"] as Record<string, unknown>)["resources"] =
resourceList.map((r: ResourceInfo) => {
return {
name: r.name,
singularName: r.singularName,
pluralName: r.pluralName,
description: r.description,
operations: r.operations,
tools: {
create: `create_${r.singularName.toLowerCase().replace(/\s+/g, "_")}`,
get: `get_${r.singularName.toLowerCase().replace(/\s+/g, "_")}`,
list: `list_${r.pluralName.toLowerCase().replace(/\s+/g, "_")}`,
update: `update_${r.singularName.toLowerCase().replace(/\s+/g, "_")}`,
delete: `delete_${r.singularName.toLowerCase().replace(/\s+/g, "_")}`,
count: `count_${r.pluralName.toLowerCase().replace(/\s+/g, "_")}`,
},
})),
};
availableOperations: r.operations,
};
});
(response["data"] as Record<string, unknown>)["hint"] =
"Use the specific tool for each operation. For example: list_incidents, create_incident, get_incident, update_incident, delete_incident, count_incidents";
break;
return JSON.stringify(response, null, 2);
case "incidents":
(response["data"] as Record<string, unknown>)["description"] =
"Incidents represent service disruptions or issues. They have states (Created, Acknowledged, Resolved) and severities.";
(response["data"] as Record<string, unknown>)["commonOperations"] = [
{
tool: "list_incidents",
description:
"List all incidents, optionally filtered by state or severity",
},
{
tool: "create_incident",
description: "Create a new incident when an issue is detected",
},
{
tool: "update_incident",
description: "Update incident state, severity, or add notes",
},
{
tool: "count_incidents",
description: "Get count of incidents by state",
},
];
(response["data"] as Record<string, unknown>)["example"] = {
createIncident: {
title: "Database connection failure",
description: "Production database is not responding to queries",
incidentSeverityId: "<severity-uuid>",
},
};
break;
case "monitors":
(response["data"] as Record<string, unknown>)["description"] =
"Monitors check the health and availability of your services (websites, APIs, servers).";
(response["data"] as Record<string, unknown>)["commonOperations"] = [
{
tool: "list_monitors",
description: "List all monitors and their current status",
},
{
tool: "create_monitor",
description: "Create a new monitor to watch a service",
},
{
tool: "update_monitor",
description: "Update monitor configuration or enable/disable",
},
{ tool: "count_monitors", description: "Get total number of monitors" },
];
break;
case "alerts":
(response["data"] as Record<string, unknown>)["description"] =
"Alerts are notifications sent when monitors detect issues.";
(response["data"] as Record<string, unknown>)["commonOperations"] = [
{
tool: "list_alerts",
description: "List all alerts and their status",
},
{ tool: "count_alerts", description: "Get count of alerts" },
];
break;
case "workflows":
(response["data"] as Record<string, unknown>)["workflows"] = [
{
name: "Check system status",
steps: [
"1. Use count_incidents to see if there are active incidents",
"2. Use list_monitors with query to find any monitors with issues",
"3. Use list_incidents to get details of any active incidents",
],
},
{
name: "Create and manage incident",
steps: [
"1. Use list_incident_states to get available states",
"2. Use create_incident with title, description, and severity",
"3. Use update_incident to change state as incident progresses",
],
},
{
name: "Incident summary report",
steps: [
"1. Use count_incidents to get total count",
"2. Use list_incidents with sort by createdAt descending",
"3. Group and summarize the results",
],
},
];
break;
case "examples":
(response["data"] as Record<string, unknown>)["examples"] = {
listRecentIncidents: {
tool: "list_incidents",
args: { limit: 10, sort: { createdAt: -1 } },
},
countActiveIncidents: {
tool: "count_incidents",
args: { query: {} },
},
getSpecificIncident: {
tool: "get_incident",
args: { id: "<incident-uuid>" },
},
updateIncidentTitle: {
tool: "update_incident",
args: { id: "<incident-uuid>", title: "Updated title" },
},
};
break;
default:
(response["data"] as Record<string, unknown>)["welcome"] =
"Welcome to OneUptime MCP Server!";
(response["data"] as Record<string, unknown>)["description"] =
"OneUptime is an open-source monitoring platform. This MCP server lets you manage incidents, monitors, alerts, and more.";
(response["data"] as Record<string, unknown>)["availableTopics"] = [
"resources",
"incidents",
"monitors",
"alerts",
"workflows",
"examples",
];
(response["data"] as Record<string, unknown>)["quickStart"] = [
"1. Use 'oneuptime_list_resources' to see all available resources",
"2. Use 'list_*' tools to browse existing data",
"3. Use 'count_*' tools to get quick summaries",
"4. Use 'create_*' tools to add new items",
];
(response["data"] as Record<string, unknown>)["resourceCount"] =
resourceList.length;
break;
}
return JSON.stringify(response, null, 2);
}
function handleListResourcesTool(resources: ResourceInfo[]): string {
const response: Record<string, unknown> = {
success: true,
totalResources: resources.length,
resources: resources.map((r: ResourceInfo) => {
return {
name: r.name,
singularName: r.singularName,
pluralName: r.pluralName,
description: r.description,
operations: r.operations,
tools: {
create: `create_${r.singularName.toLowerCase().replace(/\s+/g, "_")}`,
get: `get_${r.singularName.toLowerCase().replace(/\s+/g, "_")}`,
list: `list_${r.pluralName.toLowerCase().replace(/\s+/g, "_")}`,
update: `update_${r.singularName.toLowerCase().replace(/\s+/g, "_")}`,
delete: `delete_${r.singularName.toLowerCase().replace(/\s+/g, "_")}`,
count: `count_${r.pluralName.toLowerCase().replace(/\s+/g, "_")}`,
},
};
}),
};
return JSON.stringify(response, null, 2);
}
/**
* Check if a tool is a helper tool
* Check if a tool is a helper tool (doesn't require API key)
*/
export function isHelperTool(toolName: string): boolean {
return toolName === "oneuptime_help" || toolName === "oneuptime_list_resources";
return (
toolName === "oneuptime_help" || toolName === "oneuptime_list_resources"
);
}
/**
* Check if a tool doesn't require API key (helper or public status page tool)
*/
export function isPublicTool(toolName: string): boolean {
// Import check is done in ToolHandler to avoid circular dependencies
return isHelperTool(toolName);
}

View file

@ -0,0 +1,472 @@
/**
* Public Status Page Tools
* Provides tools for querying public status pages without authentication
* These tools can be used with either a status page ID or domain name
*/
import { McpToolInfo, JSONSchema } from "../Types/McpTypes";
import OneUptimeOperation from "../Types/OneUptimeOperation";
import ModelType from "../Types/ModelType";
import MCPLogger from "../Utils/MCPLogger";
import API from "Common/Utils/API";
import URL from "Common/Types/API/URL";
import Route from "Common/Types/API/Route";
import Headers from "Common/Types/API/Headers";
import HTTPResponse from "Common/Types/API/HTTPResponse";
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
import { JSONObject } from "Common/Types/JSON";
import { getApiUrl } from "../Config/ServerConfig";
// Common input schema for status page identifier
const statusPageIdentifierSchema: JSONSchema = {
type: "object",
properties: {
statusPageIdOrDomain: {
type: "string",
description:
"The status page ID (UUID) or domain name (e.g., 'status.company.com'). Use domain for public status pages with custom domains.",
},
},
required: ["statusPageIdOrDomain"],
additionalProperties: false,
};
/**
* Generate public status page tools
*/
export function generatePublicStatusPageTools(): McpToolInfo[] {
return [
createGetOverviewTool(),
createGetIncidentsTool(),
createGetScheduledMaintenanceTool(),
createGetAnnouncementsTool(),
createResolveStatusPageTool(),
];
}
function createGetOverviewTool(): McpToolInfo {
return {
name: "get_public_status_page_overview",
description: `Get the complete overview of a public status page including current status, resources, active incidents, scheduled maintenance, and announcements.
This tool does NOT require an API key and works with public status pages.
USAGE:
- By domain: statusPageIdOrDomain = "status.company.com"
- By ID: statusPageIdOrDomain = "550e8400-e29b-41d4-a716-446655440000"
RETURNS:
- Status page metadata (name, description, branding)
- Resources and their current status
- Active incidents
- Upcoming scheduled maintenance
- Active announcements
- Monitor status history`,
inputSchema: statusPageIdentifierSchema,
modelName: "StatusPageOverview",
operation: OneUptimeOperation.Read,
modelType: ModelType.Database,
singularName: "Status Page Overview",
pluralName: "Status Page Overviews",
tableName: "StatusPageOverview",
apiPath: "/status-page",
};
}
function createGetIncidentsTool(): McpToolInfo {
return {
name: "get_public_status_page_incidents",
description: `Get incidents from a public status page.
This tool does NOT require an API key and works with public status pages.
USAGE:
- By domain: statusPageIdOrDomain = "status.company.com"
- By ID: statusPageIdOrDomain = "550e8400-e29b-41d4-a716-446655440000"
RETURNS:
- List of incidents (active and recent history)
- Incident details (title, description, severity)
- Incident timeline and state changes
- Public notes/updates for each incident`,
inputSchema: {
type: "object",
properties: {
statusPageIdOrDomain: {
type: "string",
description:
"The status page ID (UUID) or domain name (e.g., 'status.company.com')",
},
incidentId: {
type: "string",
description: "Optional: Specific incident ID to fetch details for",
},
},
required: ["statusPageIdOrDomain"],
additionalProperties: false,
},
modelName: "StatusPageIncidents",
operation: OneUptimeOperation.List,
modelType: ModelType.Database,
singularName: "Status Page Incident",
pluralName: "Status Page Incidents",
tableName: "StatusPageIncidents",
apiPath: "/status-page",
};
}
function createGetScheduledMaintenanceTool(): McpToolInfo {
return {
name: "get_public_status_page_scheduled_maintenance",
description: `Get scheduled maintenance events from a public status page.
This tool does NOT require an API key and works with public status pages.
USAGE:
- By domain: statusPageIdOrDomain = "status.company.com"
- By ID: statusPageIdOrDomain = "550e8400-e29b-41d4-a716-446655440000"
RETURNS:
- List of scheduled maintenance events (upcoming and ongoing)
- Maintenance details (title, description, scheduled times)
- Maintenance timeline and state changes
- Public notes/updates for each maintenance event`,
inputSchema: {
type: "object",
properties: {
statusPageIdOrDomain: {
type: "string",
description:
"The status page ID (UUID) or domain name (e.g., 'status.company.com')",
},
scheduledMaintenanceId: {
type: "string",
description:
"Optional: Specific scheduled maintenance ID to fetch details for",
},
},
required: ["statusPageIdOrDomain"],
additionalProperties: false,
},
modelName: "StatusPageScheduledMaintenance",
operation: OneUptimeOperation.List,
modelType: ModelType.Database,
singularName: "Status Page Scheduled Maintenance",
pluralName: "Status Page Scheduled Maintenances",
tableName: "StatusPageScheduledMaintenance",
apiPath: "/status-page",
};
}
function createGetAnnouncementsTool(): McpToolInfo {
return {
name: "get_public_status_page_announcements",
description: `Get announcements from a public status page.
This tool does NOT require an API key and works with public status pages.
USAGE:
- By domain: statusPageIdOrDomain = "status.company.com"
- By ID: statusPageIdOrDomain = "550e8400-e29b-41d4-a716-446655440000"
RETURNS:
- List of active announcements
- Announcement details (title, description, dates)`,
inputSchema: {
type: "object",
properties: {
statusPageIdOrDomain: {
type: "string",
description:
"The status page ID (UUID) or domain name (e.g., 'status.company.com')",
},
announcementId: {
type: "string",
description:
"Optional: Specific announcement ID to fetch details for",
},
},
required: ["statusPageIdOrDomain"],
additionalProperties: false,
},
modelName: "StatusPageAnnouncements",
operation: OneUptimeOperation.List,
modelType: ModelType.Database,
singularName: "Status Page Announcement",
pluralName: "Status Page Announcements",
tableName: "StatusPageAnnouncements",
apiPath: "/status-page",
};
}
function createResolveStatusPageTool(): McpToolInfo {
return {
name: "resolve_status_page_domain",
description: `Resolve a status page domain to get the status page ID and basic information.
This tool does NOT require an API key.
USAGE:
- statusPageIdOrDomain = "status.company.com"
RETURNS:
- Status page ID
- Page title
- Page description`,
inputSchema: statusPageIdentifierSchema,
modelName: "StatusPageResolve",
operation: OneUptimeOperation.Read,
modelType: ModelType.Database,
singularName: "Status Page",
pluralName: "Status Pages",
tableName: "StatusPageResolve",
apiPath: "/status-page",
};
}
/**
* Check if a tool is a public status page tool
*/
export function isPublicStatusPageTool(toolName: string): boolean {
return (
toolName === "get_public_status_page_overview" ||
toolName === "get_public_status_page_incidents" ||
toolName === "get_public_status_page_scheduled_maintenance" ||
toolName === "get_public_status_page_announcements" ||
toolName === "resolve_status_page_domain"
);
}
/**
* Handle public status page tool execution
*/
export async function handlePublicStatusPageTool(
toolName: string,
args: Record<string, unknown>,
): Promise<string> {
const statusPageIdOrDomain: string = args["statusPageIdOrDomain"] as string;
if (!statusPageIdOrDomain) {
return JSON.stringify({
success: false,
error: "statusPageIdOrDomain is required",
});
}
try {
switch (toolName) {
case "get_public_status_page_overview":
return await getStatusPageOverview(statusPageIdOrDomain);
case "get_public_status_page_incidents":
return await getStatusPageIncidents(
statusPageIdOrDomain,
args["incidentId"] as string | undefined,
);
case "get_public_status_page_scheduled_maintenance":
return await getStatusPageScheduledMaintenance(
statusPageIdOrDomain,
args["scheduledMaintenanceId"] as string | undefined,
);
case "get_public_status_page_announcements":
return await getStatusPageAnnouncements(
statusPageIdOrDomain,
args["announcementId"] as string | undefined,
);
case "resolve_status_page_domain":
return await resolveStatusPage(statusPageIdOrDomain);
default:
return JSON.stringify({
success: false,
error: `Unknown public status page tool: ${toolName}`,
});
}
} catch (error) {
MCPLogger.error(
`Error executing public status page tool ${toolName}: ${error}`,
);
return JSON.stringify({
success: false,
error: `Failed to execute ${toolName}: ${error}`,
});
}
}
/**
* Get status page overview
* The backend now accepts both statusPageId and domain directly
*/
async function getStatusPageOverview(
statusPageIdOrDomain: string,
): Promise<string> {
const response: JSONObject = await makeStatusPageApiRequest(
"POST",
`/api/status-page/overview/${statusPageIdOrDomain}`,
);
return JSON.stringify(
{
success: true,
operation: "get_overview",
statusPageIdOrDomain,
data: response,
},
null,
2,
);
}
/**
* Get status page incidents
* The backend now accepts both statusPageId and domain directly
*/
async function getStatusPageIncidents(
statusPageIdOrDomain: string,
incidentId?: string,
): Promise<string> {
let route: string = `/api/status-page/incidents/${statusPageIdOrDomain}`;
if (incidentId) {
route = `/api/status-page/incidents/${statusPageIdOrDomain}/${incidentId}`;
}
const response: JSONObject = await makeStatusPageApiRequest("POST", route);
return JSON.stringify(
{
success: true,
operation: "get_incidents",
statusPageIdOrDomain,
incidentId: incidentId || null,
data: response,
},
null,
2,
);
}
/**
* Get status page scheduled maintenance events
* The backend now accepts both statusPageId and domain directly
*/
async function getStatusPageScheduledMaintenance(
statusPageIdOrDomain: string,
scheduledMaintenanceId?: string,
): Promise<string> {
let route: string = `/api/status-page/scheduled-maintenance-events/${statusPageIdOrDomain}`;
if (scheduledMaintenanceId) {
route = `/api/status-page/scheduled-maintenance-events/${statusPageIdOrDomain}/${scheduledMaintenanceId}`;
}
const response: JSONObject = await makeStatusPageApiRequest("POST", route);
return JSON.stringify(
{
success: true,
operation: "get_scheduled_maintenance",
statusPageIdOrDomain,
scheduledMaintenanceId: scheduledMaintenanceId || null,
data: response,
},
null,
2,
);
}
/**
* Get status page announcements
* The backend now accepts both statusPageId and domain directly
*/
async function getStatusPageAnnouncements(
statusPageIdOrDomain: string,
announcementId?: string,
): Promise<string> {
let route: string = `/api/status-page/announcements/${statusPageIdOrDomain}`;
if (announcementId) {
route = `/api/status-page/announcements/${statusPageIdOrDomain}/${announcementId}`;
}
const response: JSONObject = await makeStatusPageApiRequest("POST", route);
return JSON.stringify(
{
success: true,
operation: "get_announcements",
statusPageIdOrDomain,
announcementId: announcementId || null,
data: response,
},
null,
2,
);
}
/**
* Resolve status page and get basic info
*/
async function resolveStatusPage(
statusPageIdOrDomain: string,
): Promise<string> {
const seoData: JSONObject = await makeStatusPageApiRequest(
"GET",
`/api/status-page/seo/${statusPageIdOrDomain}`,
);
return JSON.stringify(
{
success: true,
operation: "resolve_status_page",
statusPageIdOrDomain,
data: {
statusPageId: seoData["_id"],
title: seoData["title"],
description: seoData["description"],
},
},
null,
2,
);
}
/**
* Make a request to the StatusPage API
*/
async function makeStatusPageApiRequest(
method: "GET" | "POST",
path: string,
data?: JSONObject,
): Promise<JSONObject> {
const apiUrl: string = getApiUrl();
const url: URL = URL.fromString(apiUrl);
const route: Route = new Route(path);
const fullUrl: URL = new URL(url.protocol, url.hostname, route);
const headers: Headers = {
"Content-Type": "application/json",
Accept: "application/json",
};
MCPLogger.info(`Making ${method} request to ${fullUrl.toString()}`);
let response: HTTPResponse<JSONObject> | HTTPErrorResponse;
if (method === "GET") {
response = await API.get({ url: fullUrl, headers });
} else {
response = await API.post({ url: fullUrl, headers, data: data || {} });
}
if (response instanceof HTTPErrorResponse) {
MCPLogger.error(
`API request failed: ${response.statusCode} - ${response.message}`,
);
throw new Error(
`API request failed: ${response.statusCode} - ${response.message}`,
);
}
return response.data as JSONObject;
}

View file

@ -9,479 +9,533 @@ import DatabaseBaseModel from "Common/Models/DatabaseModels/DatabaseBaseModel/Da
import AnalyticsBaseModel from "Common/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel";
import { ModelSchema, ModelSchemaType } from "Common/Utils/Schema/ModelSchema";
import {
AnalyticsModelSchema,
AnalyticsModelSchemaType,
AnalyticsModelSchema,
AnalyticsModelSchemaType,
} from "Common/Utils/Schema/AnalyticsModelSchema";
import { McpToolInfo, ModelToolsResult } from "../Types/McpTypes";
import OneUptimeOperation from "../Types/OneUptimeOperation";
import ModelType from "../Types/ModelType";
import { zodToJsonSchema, sanitizeToolName, ZodToJsonSchemaResult } from "./SchemaConverter";
import {
zodToJsonSchema,
sanitizeToolName,
ZodToJsonSchemaResult,
} from "./SchemaConverter";
import { generateHelperTools } from "./HelperTools";
import { generatePublicStatusPageTools } from "./PublicStatusPageTools";
import MCPLogger from "../Utils/MCPLogger";
/**
* Generate all MCP tools for all OneUptime models
*/
export function generateAllTools(): McpToolInfo[] {
const allTools: McpToolInfo[] = [];
const allTools: McpToolInfo[] = [];
// Generate tools for Database Models
const databaseTools: McpToolInfo[] = generateDatabaseModelTools();
allTools.push(...databaseTools);
// Generate tools for Database Models
const databaseTools: McpToolInfo[] = generateDatabaseModelTools();
allTools.push(...databaseTools);
// Generate tools for Analytics Models
const analyticsTools: McpToolInfo[] = generateAnalyticsModelTools();
allTools.push(...analyticsTools);
// Generate tools for Analytics Models
const analyticsTools: McpToolInfo[] = generateAnalyticsModelTools();
allTools.push(...analyticsTools);
// Generate helper tools for discovery and guidance
const helperTools: McpToolInfo[] = generateHelperTools(allTools);
allTools.push(...helperTools);
// Generate helper tools for discovery and guidance
const helperTools: McpToolInfo[] = generateHelperTools(allTools);
allTools.push(...helperTools);
MCPLogger.info(`Generated ${allTools.length} MCP tools for OneUptime models (including ${helperTools.length} helper tools)`);
return allTools;
// Generate public status page tools (no API key required)
const publicStatusPageTools: McpToolInfo[] = generatePublicStatusPageTools();
allTools.push(...publicStatusPageTools);
MCPLogger.info(
`Generated ${allTools.length} MCP tools for OneUptime models (including ${helperTools.length} helper tools and ${publicStatusPageTools.length} public status page tools)`,
);
return allTools;
}
/**
* Generate tools for all database models
*/
function generateDatabaseModelTools(): McpToolInfo[] {
const tools: McpToolInfo[] = [];
const tools: McpToolInfo[] = [];
for (const ModelClass of DatabaseModels) {
try {
const model: DatabaseBaseModel = new ModelClass();
const result: ModelToolsResult = generateToolsForDatabaseModel(model, ModelClass);
tools.push(...result.tools);
} catch (error) {
MCPLogger.error(
`Error generating tools for database model ${ModelClass.name}: ${error}`
);
}
for (const ModelClass of DatabaseModels) {
try {
const model: DatabaseBaseModel = new ModelClass();
const result: ModelToolsResult = generateToolsForDatabaseModel(
model,
ModelClass,
);
tools.push(...result.tools);
} catch (error) {
MCPLogger.error(
`Error generating tools for database model ${ModelClass.name}: ${error}`,
);
}
}
return tools;
return tools;
}
/**
* Generate tools for all analytics models
*/
function generateAnalyticsModelTools(): McpToolInfo[] {
const tools: McpToolInfo[] = [];
const tools: McpToolInfo[] = [];
for (const ModelClass of AnalyticsModels) {
try {
const model: AnalyticsBaseModel = new ModelClass();
const result: ModelToolsResult = generateToolsForAnalyticsModel(model, ModelClass);
tools.push(...result.tools);
} catch (error) {
MCPLogger.error(
`Error generating tools for analytics model ${ModelClass.name}: ${error}`
);
}
for (const ModelClass of AnalyticsModels) {
try {
const model: AnalyticsBaseModel = new ModelClass();
const result: ModelToolsResult = generateToolsForAnalyticsModel(
model,
ModelClass,
);
tools.push(...result.tools);
} catch (error) {
MCPLogger.error(
`Error generating tools for analytics model ${ModelClass.name}: ${error}`,
);
}
}
return tools;
return tools;
}
/**
* Generate MCP tools for a specific database model
*/
export function generateToolsForDatabaseModel(
model: DatabaseBaseModel,
ModelClass: { new (): DatabaseBaseModel }
model: DatabaseBaseModel,
ModelClass: { new (): DatabaseBaseModel },
): ModelToolsResult {
const modelName: string = model.tableName || ModelClass.name;
const singularName: string = model.singularName || modelName;
const pluralName: string = model.pluralName || `${singularName}s`;
const apiPath: string | undefined = model.crudApiPath?.toString();
const modelName: string = model.tableName || ModelClass.name;
const singularName: string = model.singularName || modelName;
const pluralName: string = model.pluralName || `${singularName}s`;
const apiPath: string | undefined = model.crudApiPath?.toString();
const modelInfo = {
tableName: modelName,
singularName,
pluralName,
modelType: ModelType.Database,
...(apiPath && { apiPath }),
};
const modelInfo = {
tableName: modelName,
singularName,
pluralName,
modelType: ModelType.Database,
...(apiPath && { apiPath }),
};
// Skip if model doesn't have required properties or MCP is disabled
if (!modelName || !model.enableMCP || !apiPath) {
return { tools: [], modelInfo };
}
// Skip if model doesn't have required properties or MCP is disabled
if (!modelName || !model.enableMCP || !apiPath) {
return { tools: [], modelInfo };
}
// Generate schemas using ModelSchema
const createSchema: ModelSchemaType = ModelSchema.getCreateModelSchema({
modelType: ModelClass,
});
const updateSchema: ModelSchemaType = ModelSchema.getUpdateModelSchema({
modelType: ModelClass,
});
const querySchema: ModelSchemaType = ModelSchema.getQueryModelSchema({
modelType: ModelClass,
});
const sortSchema: ModelSchemaType = ModelSchema.getSortModelSchema({
modelType: ModelClass,
});
// Generate schemas using ModelSchema
const createSchema: ModelSchemaType = ModelSchema.getCreateModelSchema({
modelType: ModelClass,
});
const updateSchema: ModelSchemaType = ModelSchema.getUpdateModelSchema({
modelType: ModelClass,
});
const querySchema: ModelSchemaType = ModelSchema.getQueryModelSchema({
modelType: ModelClass,
});
const sortSchema: ModelSchemaType = ModelSchema.getSortModelSchema({
modelType: ModelClass,
});
const tools: McpToolInfo[] = [
createCreateTool(modelName, singularName, pluralName, apiPath, createSchema),
createReadTool(modelName, singularName, pluralName, apiPath),
createListTool(modelName, singularName, pluralName, apiPath, querySchema, sortSchema),
createUpdateTool(modelName, singularName, pluralName, apiPath, updateSchema),
createDeleteTool(modelName, singularName, pluralName, apiPath),
createCountTool(modelName, singularName, pluralName, apiPath, querySchema),
];
const tools: McpToolInfo[] = [
createCreateTool(
modelName,
singularName,
pluralName,
apiPath,
createSchema,
),
createReadTool(modelName, singularName, pluralName, apiPath),
createListTool(
modelName,
singularName,
pluralName,
apiPath,
querySchema,
sortSchema,
),
createUpdateTool(
modelName,
singularName,
pluralName,
apiPath,
updateSchema,
),
createDeleteTool(modelName, singularName, pluralName, apiPath),
createCountTool(modelName, singularName, pluralName, apiPath, querySchema),
];
return { tools, modelInfo };
return { tools, modelInfo };
}
/**
* Generate MCP tools for a specific analytics model
*/
export function generateToolsForAnalyticsModel(
model: AnalyticsBaseModel,
ModelClass: { new (): AnalyticsBaseModel }
model: AnalyticsBaseModel,
ModelClass: { new (): AnalyticsBaseModel },
): ModelToolsResult {
const modelName: string = model.tableName || ModelClass.name;
const singularName: string = model.singularName || modelName;
const pluralName: string = model.pluralName || `${singularName}s`;
const apiPath: string | undefined = model.crudApiPath?.toString();
const modelName: string = model.tableName || ModelClass.name;
const singularName: string = model.singularName || modelName;
const pluralName: string = model.pluralName || `${singularName}s`;
const apiPath: string | undefined = model.crudApiPath?.toString();
const modelInfo = {
tableName: modelName,
singularName,
pluralName,
modelType: ModelType.Analytics,
apiPath,
};
const modelInfo = {
tableName: modelName,
singularName,
pluralName,
modelType: ModelType.Analytics,
apiPath,
};
// Skip if model doesn't have required properties or MCP is disabled
if (!modelName || !model.enableMCP || !apiPath) {
return { tools: [], modelInfo };
}
// Skip if model doesn't have required properties or MCP is disabled
if (!modelName || !model.enableMCP || !apiPath) {
return { tools: [], modelInfo };
}
// Generate schemas using AnalyticsModelSchema
const createSchema: AnalyticsModelSchemaType = AnalyticsModelSchema.getCreateModelSchema({
modelType: ModelClass,
disableOpenApiSchema: true,
// Generate schemas using AnalyticsModelSchema
const createSchema: AnalyticsModelSchemaType =
AnalyticsModelSchema.getCreateModelSchema({
modelType: ModelClass,
disableOpenApiSchema: true,
});
const querySchema: AnalyticsModelSchemaType = AnalyticsModelSchema.getQueryModelSchema({
modelType: ModelClass,
disableOpenApiSchema: true,
const querySchema: AnalyticsModelSchemaType =
AnalyticsModelSchema.getQueryModelSchema({
modelType: ModelClass,
disableOpenApiSchema: true,
});
const selectSchema: AnalyticsModelSchemaType = AnalyticsModelSchema.getSelectModelSchema({
modelType: ModelClass,
const selectSchema: AnalyticsModelSchemaType =
AnalyticsModelSchema.getSelectModelSchema({
modelType: ModelClass,
});
const sortSchema: AnalyticsModelSchemaType = AnalyticsModelSchema.getSortModelSchema({
modelType: ModelClass,
disableOpenApiSchema: true,
const sortSchema: AnalyticsModelSchemaType =
AnalyticsModelSchema.getSortModelSchema({
modelType: ModelClass,
disableOpenApiSchema: true,
});
const tools: McpToolInfo[] = [
createAnalyticsCreateTool(modelName, singularName, pluralName, apiPath, createSchema),
createAnalyticsListTool(
modelName,
singularName,
pluralName,
apiPath,
querySchema,
selectSchema,
sortSchema
),
createAnalyticsCountTool(modelName, singularName, pluralName, apiPath, querySchema),
];
const tools: McpToolInfo[] = [
createAnalyticsCreateTool(
modelName,
singularName,
pluralName,
apiPath,
createSchema,
),
createAnalyticsListTool(
modelName,
singularName,
pluralName,
apiPath,
querySchema,
selectSchema,
sortSchema,
),
createAnalyticsCountTool(
modelName,
singularName,
pluralName,
apiPath,
querySchema,
),
];
return { tools, modelInfo };
return { tools, modelInfo };
}
// Database Model Tool Creators
function createCreateTool(
modelName: string,
singularName: string,
pluralName: string,
apiPath: string,
createSchema: ModelSchemaType
modelName: string,
singularName: string,
pluralName: string,
apiPath: string,
createSchema: ModelSchemaType,
): McpToolInfo {
const schemaProperties: ZodToJsonSchemaResult = zodToJsonSchema(createSchema);
const schemaProperties: ZodToJsonSchemaResult = zodToJsonSchema(createSchema);
return {
name: `create_${sanitizeToolName(singularName)}`,
description: `Create a new ${singularName} in OneUptime. Returns the created ${singularName} object with its ID and all fields. Use this to add new ${pluralName} to your project.`,
inputSchema: {
type: "object",
properties: schemaProperties.properties || {},
required: schemaProperties.required || [],
additionalProperties: false,
},
modelName,
operation: OneUptimeOperation.Create,
modelType: ModelType.Database,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
return {
name: `create_${sanitizeToolName(singularName)}`,
description: `Create a new ${singularName} in OneUptime. Returns the created ${singularName} object with its ID and all fields. Use this to add new ${pluralName} to your project.`,
inputSchema: {
type: "object",
properties: schemaProperties.properties || {},
required: schemaProperties.required || [],
additionalProperties: false,
},
modelName,
operation: OneUptimeOperation.Create,
modelType: ModelType.Database,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
}
function createReadTool(
modelName: string,
singularName: string,
pluralName: string,
apiPath: string
modelName: string,
singularName: string,
pluralName: string,
apiPath: string,
): McpToolInfo {
return {
name: `get_${sanitizeToolName(singularName)}`,
description: `Retrieve a single ${singularName} by its unique ID from OneUptime. Returns the complete ${singularName} object with all its fields. Use list_${sanitizeToolName(pluralName)} first if you need to find the ID.`,
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: `The unique identifier (UUID) of the ${singularName} to retrieve. Example: "550e8400-e29b-41d4-a716-446655440000"`,
},
},
required: ["id"],
additionalProperties: false,
return {
name: `get_${sanitizeToolName(singularName)}`,
description: `Retrieve a single ${singularName} by its unique ID from OneUptime. Returns the complete ${singularName} object with all its fields. Use list_${sanitizeToolName(pluralName)} first if you need to find the ID.`,
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: `The unique identifier (UUID) of the ${singularName} to retrieve. Example: "550e8400-e29b-41d4-a716-446655440000"`,
},
modelName,
operation: OneUptimeOperation.Read,
modelType: ModelType.Database,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
},
required: ["id"],
additionalProperties: false,
},
modelName,
operation: OneUptimeOperation.Read,
modelType: ModelType.Database,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
}
function createListTool(
modelName: string,
singularName: string,
pluralName: string,
apiPath: string,
querySchema: ModelSchemaType,
sortSchema: ModelSchemaType
modelName: string,
singularName: string,
pluralName: string,
apiPath: string,
querySchema: ModelSchemaType,
sortSchema: ModelSchemaType,
): McpToolInfo {
return {
name: `list_${sanitizeToolName(pluralName)}`,
description: `List and search ${pluralName} from OneUptime with optional filtering, pagination, and sorting. Returns an array of ${singularName} objects. Use the 'query' parameter to filter results by specific field values. Supports pagination via 'skip' and 'limit' parameters.`,
inputSchema: {
type: "object",
properties: {
query: {
...zodToJsonSchema(querySchema),
description: `Filter criteria for ${pluralName}. Each field can be used to filter results. Example: {"title": "My ${singularName}"} to find by title.`,
},
skip: {
type: "number",
description: "Number of records to skip for pagination. Default: 0. Example: skip=10 to start from the 11th record.",
},
limit: {
type: "number",
description: "Maximum number of records to return. Default: 10, Maximum: 100. Example: limit=25 to get 25 records.",
},
sort: {
...zodToJsonSchema(sortSchema),
description: `Sort order for results. Use 1 for ascending, -1 for descending. Example: {"createdAt": -1} to sort by newest first.`,
},
},
additionalProperties: false,
return {
name: `list_${sanitizeToolName(pluralName)}`,
description: `List and search ${pluralName} from OneUptime with optional filtering, pagination, and sorting. Returns an array of ${singularName} objects. Use the 'query' parameter to filter results by specific field values. Supports pagination via 'skip' and 'limit' parameters.`,
inputSchema: {
type: "object",
properties: {
query: {
...zodToJsonSchema(querySchema),
description: `Filter criteria for ${pluralName}. Each field can be used to filter results. Example: {"title": "My ${singularName}"} to find by title.`,
},
modelName,
operation: OneUptimeOperation.List,
modelType: ModelType.Database,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
skip: {
type: "number",
description:
"Number of records to skip for pagination. Default: 0. Example: skip=10 to start from the 11th record.",
},
limit: {
type: "number",
description:
"Maximum number of records to return. Default: 10, Maximum: 100. Example: limit=25 to get 25 records.",
},
sort: {
...zodToJsonSchema(sortSchema),
description: `Sort order for results. Use 1 for ascending, -1 for descending. Example: {"createdAt": -1} to sort by newest first.`,
},
},
additionalProperties: false,
},
modelName,
operation: OneUptimeOperation.List,
modelType: ModelType.Database,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
}
function createUpdateTool(
modelName: string,
singularName: string,
pluralName: string,
apiPath: string,
updateSchema: ModelSchemaType
modelName: string,
singularName: string,
pluralName: string,
apiPath: string,
updateSchema: ModelSchemaType,
): McpToolInfo {
const schemaProperties: ZodToJsonSchemaResult = zodToJsonSchema(updateSchema);
const schemaProperties: ZodToJsonSchemaResult = zodToJsonSchema(updateSchema);
return {
name: `update_${sanitizeToolName(singularName)}`,
description: `Update an existing ${singularName} in OneUptime. Only include the fields you want to change - unspecified fields will remain unchanged. Returns the updated ${singularName} object.`,
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: `The unique identifier (UUID) of the ${singularName} to update. Required. Use list_${sanitizeToolName(pluralName)} to find IDs.`,
},
...(schemaProperties.properties || {}),
},
required: ["id"],
additionalProperties: false,
return {
name: `update_${sanitizeToolName(singularName)}`,
description: `Update an existing ${singularName} in OneUptime. Only include the fields you want to change - unspecified fields will remain unchanged. Returns the updated ${singularName} object.`,
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: `The unique identifier (UUID) of the ${singularName} to update. Required. Use list_${sanitizeToolName(pluralName)} to find IDs.`,
},
modelName,
operation: OneUptimeOperation.Update,
modelType: ModelType.Database,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
...(schemaProperties.properties || {}),
},
required: ["id"],
additionalProperties: false,
},
modelName,
operation: OneUptimeOperation.Update,
modelType: ModelType.Database,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
}
function createDeleteTool(
modelName: string,
singularName: string,
pluralName: string,
apiPath: string
modelName: string,
singularName: string,
pluralName: string,
apiPath: string,
): McpToolInfo {
return {
name: `delete_${sanitizeToolName(singularName)}`,
description: `Permanently delete a ${singularName} from OneUptime. This action cannot be undone. Returns a confirmation message upon successful deletion.`,
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: `The unique identifier (UUID) of the ${singularName} to delete. This action is irreversible.`,
},
},
required: ["id"],
additionalProperties: false,
return {
name: `delete_${sanitizeToolName(singularName)}`,
description: `Permanently delete a ${singularName} from OneUptime. This action cannot be undone. Returns a confirmation message upon successful deletion.`,
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: `The unique identifier (UUID) of the ${singularName} to delete. This action is irreversible.`,
},
modelName,
operation: OneUptimeOperation.Delete,
modelType: ModelType.Database,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
},
required: ["id"],
additionalProperties: false,
},
modelName,
operation: OneUptimeOperation.Delete,
modelType: ModelType.Database,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
}
function createCountTool(
modelName: string,
singularName: string,
pluralName: string,
apiPath: string,
querySchema: ModelSchemaType
modelName: string,
singularName: string,
pluralName: string,
apiPath: string,
querySchema: ModelSchemaType,
): McpToolInfo {
return {
name: `count_${sanitizeToolName(pluralName)}`,
description: `Count the total number of ${pluralName} in OneUptime, optionally filtered by query criteria. Returns a single number. Useful for dashboards, reports, or checking if records exist before listing.`,
inputSchema: {
type: "object",
properties: {
query: {
...zodToJsonSchema(querySchema),
description: `Optional filter criteria. If omitted, counts all ${pluralName}. Example: {"currentIncidentStateId": "..."} to count incidents in a specific state.`,
},
},
additionalProperties: false,
return {
name: `count_${sanitizeToolName(pluralName)}`,
description: `Count the total number of ${pluralName} in OneUptime, optionally filtered by query criteria. Returns a single number. Useful for dashboards, reports, or checking if records exist before listing.`,
inputSchema: {
type: "object",
properties: {
query: {
...zodToJsonSchema(querySchema),
description: `Optional filter criteria. If omitted, counts all ${pluralName}. Example: {"currentIncidentStateId": "..."} to count incidents in a specific state.`,
},
modelName,
operation: OneUptimeOperation.Count,
modelType: ModelType.Database,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
},
additionalProperties: false,
},
modelName,
operation: OneUptimeOperation.Count,
modelType: ModelType.Database,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
}
// Analytics Model Tool Creators
function createAnalyticsCreateTool(
modelName: string,
singularName: string,
pluralName: string,
apiPath: string,
createSchema: AnalyticsModelSchemaType
modelName: string,
singularName: string,
pluralName: string,
apiPath: string,
createSchema: AnalyticsModelSchemaType,
): McpToolInfo {
const schemaProperties: ZodToJsonSchemaResult = zodToJsonSchema(createSchema);
const schemaProperties: ZodToJsonSchemaResult = zodToJsonSchema(createSchema);
return {
name: `create_${sanitizeToolName(singularName)}`,
description: `Create a new ${singularName} analytics record in OneUptime`,
inputSchema: {
type: "object",
properties: schemaProperties.properties || {},
required: schemaProperties.required || [],
additionalProperties: false,
},
modelName,
operation: OneUptimeOperation.Create,
modelType: ModelType.Analytics,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
return {
name: `create_${sanitizeToolName(singularName)}`,
description: `Create a new ${singularName} analytics record in OneUptime`,
inputSchema: {
type: "object",
properties: schemaProperties.properties || {},
required: schemaProperties.required || [],
additionalProperties: false,
},
modelName,
operation: OneUptimeOperation.Create,
modelType: ModelType.Analytics,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
}
function createAnalyticsListTool(
modelName: string,
singularName: string,
pluralName: string,
apiPath: string,
querySchema: AnalyticsModelSchemaType,
selectSchema: AnalyticsModelSchemaType,
sortSchema: AnalyticsModelSchemaType
modelName: string,
singularName: string,
pluralName: string,
apiPath: string,
querySchema: AnalyticsModelSchemaType,
selectSchema: AnalyticsModelSchemaType,
sortSchema: AnalyticsModelSchemaType,
): McpToolInfo {
return {
name: `list_${sanitizeToolName(pluralName)}`,
description: `Query ${pluralName} analytics data from OneUptime`,
inputSchema: {
type: "object",
properties: {
query: zodToJsonSchema(querySchema),
select: zodToJsonSchema(selectSchema),
skip: {
type: "number",
description: "Number of records to skip",
},
limit: {
type: "number",
description: "Maximum number of records to return",
},
sort: zodToJsonSchema(sortSchema),
},
additionalProperties: false,
return {
name: `list_${sanitizeToolName(pluralName)}`,
description: `Query ${pluralName} analytics data from OneUptime`,
inputSchema: {
type: "object",
properties: {
query: zodToJsonSchema(querySchema),
select: zodToJsonSchema(selectSchema),
skip: {
type: "number",
description: "Number of records to skip",
},
modelName,
operation: OneUptimeOperation.List,
modelType: ModelType.Analytics,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
limit: {
type: "number",
description: "Maximum number of records to return",
},
sort: zodToJsonSchema(sortSchema),
},
additionalProperties: false,
},
modelName,
operation: OneUptimeOperation.List,
modelType: ModelType.Analytics,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
}
function createAnalyticsCountTool(
modelName: string,
singularName: string,
pluralName: string,
apiPath: string,
querySchema: AnalyticsModelSchemaType
modelName: string,
singularName: string,
pluralName: string,
apiPath: string,
querySchema: AnalyticsModelSchemaType,
): McpToolInfo {
return {
name: `count_${sanitizeToolName(pluralName)}`,
description: `Count ${pluralName} analytics records in OneUptime`,
inputSchema: {
type: "object",
properties: {
query: zodToJsonSchema(querySchema),
},
additionalProperties: false,
},
modelName,
operation: OneUptimeOperation.Count,
modelType: ModelType.Analytics,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
return {
name: `count_${sanitizeToolName(pluralName)}`,
description: `Count ${pluralName} analytics records in OneUptime`,
inputSchema: {
type: "object",
properties: {
query: zodToJsonSchema(querySchema),
},
additionalProperties: false,
},
modelName,
operation: OneUptimeOperation.Count,
modelType: ModelType.Analytics,
singularName,
pluralName,
tableName: modelName,
apiPath,
};
}