refactor: remove OpenAPIParser, StringUtils, and Types modules

- Deleted OpenAPIParser.ts, StringUtils.ts, and Types.ts as part of the refactoring process.
- Removed associated methods and interfaces that were no longer needed.
- Cleaned up the MCP server generation script and README documentation to reflect these changes.
- Updated the publish script to streamline the publishing process.
This commit is contained in:
Simon Larsen
2025-06-27 12:52:26 +01:00
parent e6c33c8e6d
commit f2de3cc8a8
9 changed files with 0 additions and 2102 deletions

3
.gitignore vendored
View File

@@ -124,6 +124,3 @@ Terraform/**
TerraformTest/**
terraform-provider-example/**
MCP/**
MCP/**

View File

@@ -1,45 +0,0 @@
import fs from "fs";
import path from "path";
export class FileGenerator {
private outputDir: string;
public constructor(outputDir: string) {
this.outputDir = outputDir;
}
public async writeFile(filePath: string, content: string): Promise<void> {
const fullPath: string = path.join(this.outputDir, filePath);
const dir: string = path.dirname(fullPath);
// Create directory if it doesn't exist
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(fullPath, content, "utf8");
}
public async writeFileInDir(
subDir: string,
fileName: string,
content: string,
): Promise<void> {
const fullDir: string = path.join(this.outputDir, subDir);
// Create directory if it doesn't exist
if (!fs.existsSync(fullDir)) {
fs.mkdirSync(fullDir, { recursive: true });
}
const fullPath: string = path.join(fullDir, fileName);
fs.writeFileSync(fullPath, content, "utf8");
}
public ensureDirectoryExists(dirPath: string): void {
const fullPath: string = path.join(this.outputDir, dirPath);
if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath, { recursive: true });
}
}
}

View File

@@ -1,711 +0,0 @@
import { MCPServerConfig, OpenAPISpec, MCPTool } from "./Types";
import { FileGenerator } from "./FileGenerator";
import { StringUtils } from "./StringUtils";
import { OpenAPIParser } from "./OpenAPIParser";
export class MCPServerGenerator {
private config: MCPServerConfig;
private spec: OpenAPISpec;
private fileGenerator: FileGenerator;
public constructor(config: MCPServerConfig, spec: OpenAPISpec) {
this.config = config;
this.spec = spec;
this.fileGenerator = new FileGenerator(config.outputDir);
}
public async generateServer(): Promise<void> {
await this.generatePackageJson();
await this.generateIndexFile();
await this.generateMCPService();
await this.generateAPIClient();
await this.generateConfigUtils();
await this.generateReadme();
await this.generateTsConfig();
await this.generateNodemonConfig();
await this.generateDockerfile();
}
private async generatePackageJson(): Promise<void> {
const packageJson: any = {
name: this.config.npmPackageName,
version: this.config.serverVersion,
description: this.config.description,
main: "build/Index.js",
bin: {
[this.config.serverName]: "./build/Index.js",
},
files: ["build", "README.md", "package.json"],
scripts: {
start:
"export NODE_OPTIONS='--max-old-space-size=8096' && node --require ts-node/register Index.ts",
build: "rm -rf build && tsc",
compile: "npm run build",
dev: "npx nodemon",
"clear-modules":
"rm -rf node_modules && rm package-lock.json && npm install",
audit: "npm audit --audit-level=low",
"dep-check":
"npm install -g depcheck && depcheck ./ --skip-missing=true",
test: "jest --detectOpenHandles --passWithNoTests",
coverage: "jest --detectOpenHandles --coverage",
prepublishOnly: "npm run build && chmod +x build/Index.js",
},
keywords: [
"mcp",
"model-context-protocol",
"oneuptime",
"api",
"monitoring",
"observability",
],
author: "OneUptime <hello@oneuptime.com> (https://oneuptime.com/)",
license: "Apache-2.0",
repository: {
type: "git",
url: "https://github.com/OneUptime/oneuptime.git",
directory: "MCP",
},
bugs: {
url: "https://github.com/OneUptime/oneuptime/issues",
},
homepage: "https://oneuptime.com",
dependencies: {
"@modelcontextprotocol/sdk": "^1.12.0",
axios: "^1.6.0",
dotenv: "^16.3.1",
},
devDependencies: {
"@types/jest": "^29.5.11",
"@types/node": "^20.0.0",
jest: "^29.0.0",
nodemon: "^3.0.0",
"ts-node": "^10.9.0",
typescript: "^5.0.0",
},
};
await this.fileGenerator.writeFile(
"package.json",
JSON.stringify(packageJson, null, 2),
);
}
private async generateIndexFile(): Promise<void> {
const indexContent: string = [
"#!/usr/bin/env node",
"",
'import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";',
'import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";',
'import { ServerConfig } from "./Utils/Config.js";',
'import { MCPService } from "./Service/MCP.js";',
'import dotenv from "dotenv";',
"",
"// Load environment variables",
"dotenv.config();",
"",
"async function main(): Promise<void> {",
" try {",
" // Create server instance",
" const server: McpServer = new McpServer({",
" name: ServerConfig.name,",
" version: ServerConfig.version,",
" capabilities: {",
" tools: {},",
" resources: {},",
" },",
" });",
"",
" // Add tools to server",
" const mcpService = new MCPService();",
" await mcpService.addToolsToServer(server);",
"",
" const transport: StdioServerTransport = new StdioServerTransport();",
" await server.connect(transport);",
' console.error("OneUptime MCP Server running on stdio");',
" } catch (error) {",
' console.error("Fatal error in main():");',
" console.error(error);",
" process.exit(1);",
" }",
"}",
"",
"// Handle graceful shutdown",
'process.on("SIGINT", () => {',
' console.error("Received SIGINT, shutting down gracefully...");',
" process.exit(0);",
"});",
"",
'process.on("SIGTERM", () => {',
' console.error("Received SIGTERM, shutting down gracefully...");',
" process.exit(0);",
"});",
"",
"main().catch((error: Error) => {",
' console.error("Fatal error in main():");',
" console.error(error);",
" process.exit(1);",
"});",
].join("\n");
await this.fileGenerator.writeFile("Index.ts", indexContent);
}
private async generateMCPService(): Promise<void> {
const parser: OpenAPIParser = new OpenAPIParser();
parser.setSpec(this.spec);
const tools: any[] = parser.getMCPTools();
const toolRegistrations: string = tools
.map((tool: any) => {
return [
` server.tool(`,
` "${tool.name}",`,
` "${StringUtils.sanitizeDescription(tool.description)}",`,
` ${JSON.stringify(tool.inputSchema, null, 6).replace(/^/gm, " ")},`,
` async (args: any) => {`,
` return await this.${StringUtils.toCamelCase(tool.name)}(args);`,
` }`,
` );`,
].join("\n");
})
.join("\n\n");
const toolMethods: string = tools
.map((tool: any) => {
return this.generateToolMethod(tool);
})
.join("\n\n");
const serviceContent: string = [
'import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";',
'import { OneUptimeAPIClient } from "./APIClient.js";',
"",
"export class MCPService {",
" private apiClient: OneUptimeAPIClient;",
"",
" public constructor() {",
" this.apiClient = new OneUptimeAPIClient();",
" }",
"",
" public async addToolsToServer(server: McpServer): Promise<void> {",
" // Register all tools",
toolRegistrations,
" }",
"",
toolMethods,
"}",
].join("\n");
this.fileGenerator.ensureDirectoryExists("Service");
await this.fileGenerator.writeFile("Service/MCP.ts", serviceContent);
}
private generateToolMethod(tool: MCPTool): string {
const methodName: string = StringUtils.toCamelCase(tool.name);
const operation: any = tool.operation;
// Extract path parameters from the operation
const pathParams: string[] = [];
if (operation.parameters) {
for (const param of operation.parameters) {
if (param.in === "path") {
pathParams.push(StringUtils.toCamelCase(param.name));
}
}
}
// Generate parameter validation
const requiredParams = tool.inputSchema?.required || [];
const paramValidation = requiredParams.length > 0
? [
" // Validate required parameters",
` const requiredParams = [${requiredParams.map((p: string) => `"${p}"`).join(", ")}];`,
" for (const param of requiredParams) {",
" if (args[param] === undefined || args[param] === null) {",
" throw new Error(`Missing required parameter: ${param}`);",
" }",
" }",
].join("\n")
: "";
// Generate path replacement logic
let pathReplacement: string;
if (pathParams.length > 0) {
const replacements = pathParams
.map((param) => `replace(/{${StringUtils.fromCamelCase(param)}}/g, args.${param})`)
.join(".");
pathReplacement = `"${operation.path}".${replacements}`;
} else {
pathReplacement = `"${operation.path}"`;
}
// Separate path parameters from other data
const dataProcessing = pathParams.length > 0
? [
" // Extract path parameters from args",
` const pathParams = [${pathParams.map(p => `"${p}"`).join(", ")}];`,
" const requestData = { ...args };",
" pathParams.forEach(param => delete requestData[param]);",
].join("\n")
: " const requestData = args;";
return [
` private async ${methodName}(args: any): Promise<any> {`,
" try {",
paramValidation,
paramValidation ? "" : "",
dataProcessing,
"",
" const response = await this.apiClient.request({",
` method: "${operation.method.toUpperCase()}",`,
` path: ${pathReplacement},`,
" data: requestData,",
" });",
"",
" return {",
" content: [",
" {",
' type: "text",',
" text: JSON.stringify(response.data, null, 2),",
" },",
" ],",
" };",
" } catch (error) {",
' throw new Error(`API request failed: ${error instanceof Error ? error.message : "Unknown error"}`);',
" }",
" }",
].join("\n");
}
private async generateAPIClient(): Promise<void> {
const clientContent: string = [
'import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";',
"",
"export interface APIRequestConfig {",
" method: string;",
" path: string;",
" data?: any;",
" params?: any;",
" headers?: Record<string, string>;",
"}",
"",
"export class OneUptimeAPIClient {",
" private client: AxiosInstance;",
" private baseURL: string;",
" private apiKey: string;",
"",
" public constructor() {",
" this.baseURL = this.getBaseURL();",
" this.apiKey = this.getAPIKey();",
"",
" this.client = axios.create({",
" baseURL: this.baseURL,",
" timeout: 30000,",
" headers: {",
' "Content-Type": "application/json",',
' "Accept": "application/json",',
' "User-Agent": "OneUptime MCP Server/1.0.0",',
" },",
" });",
"",
" // Add request interceptor for authentication",
" this.client.interceptors.request.use((config) => {",
" if (this.apiKey) {",
' config.headers["APIKey"] = this.apiKey;',
" }",
" return config;",
" });",
"",
" // Add response interceptor for error handling",
" this.client.interceptors.response.use(",
" (response) => response,",
" (error) => {",
" if (error.response) {",
" const errorMessage = error.response.data?.message || error.response.statusText;",
" throw new Error(`HTTP ${error.response.status}: ${errorMessage}`);",
" } else if (error.request) {",
' throw new Error("Network error: No response received from server");',
" } else {",
" throw new Error(`Request error: ${error.message}`);",
" }",
" }",
" );",
" }",
"",
" private getBaseURL(): string {",
' const url = process.env.ONEUPTIME_URL || process.env.ONEUPTIME_API_URL || "https://oneuptime.com";',
" ",
" // Ensure the URL has the correct scheme",
' const normalizedURL = url.startsWith("http") ? url : `https://${url}`;',
" ",
" // Append /api if not present",
' return normalizedURL.endsWith("/api") ? normalizedURL : `${normalizedURL.replace(/\\/$/, "")}/api`;',
" }",
"",
" private getAPIKey(): string {",
" const apiKey = process.env.ONEUPTIME_API_KEY || process.env.API_KEY;",
" if (!apiKey) {",
" throw new Error(",
' "OneUptime API key is required. Set ONEUPTIME_API_KEY or API_KEY environment variable."',
" );",
" }",
" return apiKey;",
" }",
"",
" public async request(config: APIRequestConfig): Promise<AxiosResponse> {",
" const requestConfig: AxiosRequestConfig = {",
" method: config.method.toLowerCase() as any,",
" url: this.interpolatePath(config.path, config.data || config.params),",
' data: config.method.toUpperCase() !== "GET" ? config.data : undefined,',
' params: config.method.toUpperCase() === "GET" ? config.params : undefined,',
" headers: config.headers || {},",
" };",
"",
" return await this.client.request(requestConfig);",
" }",
"",
" private interpolatePath(path: string, data: any): string {",
" if (!data) return path;",
"",
" return path.replace(/\\{([^}]+)\\}/g, (match, paramName) => {",
" const value = data[paramName];",
" if (value === undefined) {",
" throw new Error(`Missing required path parameter: ${paramName}`);",
" }",
" return encodeURIComponent(value.toString());",
" });",
" }",
"",
" public async get(path: string, params?: any): Promise<AxiosResponse> {",
' return this.request({ method: "GET", path, params });',
" }",
"",
" public async post(path: string, data?: any): Promise<AxiosResponse> {",
' return this.request({ method: "POST", path, data });',
" }",
"",
" public async put(path: string, data?: any): Promise<AxiosResponse> {",
' return this.request({ method: "PUT", path, data });',
" }",
"",
" public async patch(path: string, data?: any): Promise<AxiosResponse> {",
' return this.request({ method: "PATCH", path, data });',
" }",
"",
" public async delete(path: string): Promise<AxiosResponse> {",
' return this.request({ method: "DELETE", path });',
" }",
"}",
].join("\n");
this.fileGenerator.ensureDirectoryExists("Service");
await this.fileGenerator.writeFile("Service/APIClient.ts", clientContent);
}
private async generateConfigUtils(): Promise<void> {
const configContent: string = [
"export const ServerConfig = {",
` name: "${this.config.serverName}",`,
` version: "${this.config.serverVersion}",`,
` description: "${this.config.description}",`,
"} as const;",
"",
"export const EnvironmentVariables = {",
' ONEUPTIME_URL: "ONEUPTIME_URL",',
' ONEUPTIME_API_URL: "ONEUPTIME_API_URL",',
' ONEUPTIME_API_KEY: "ONEUPTIME_API_KEY",',
' API_KEY: "API_KEY",',
"} as const;",
"",
"export function validateEnvironment(): void {",
" const apiKey = process.env.ONEUPTIME_API_KEY || process.env.API_KEY;",
" ",
" if (!apiKey) {",
" throw new Error(",
' "OneUptime API key is required. Please set one of the following environment variables:\\n" +',
' "- ONEUPTIME_API_KEY\\n" +',
' "- API_KEY"',
" );",
" }",
"}",
"",
"export function getEnvironmentInfo(): Record<string, string | undefined> {",
" return {",
" ONEUPTIME_URL: process.env.ONEUPTIME_URL,",
" ONEUPTIME_API_URL: process.env.ONEUPTIME_API_URL,",
' ONEUPTIME_API_KEY: process.env.ONEUPTIME_API_KEY ? "[REDACTED]" : undefined,',
' API_KEY: process.env.API_KEY ? "[REDACTED]" : undefined,',
" NODE_ENV: process.env.NODE_ENV,",
" };",
"}",
].join("\n");
this.fileGenerator.ensureDirectoryExists("Utils");
await this.fileGenerator.writeFile("Utils/Config.ts", configContent);
}
private async generateReadme(): Promise<void> {
const parser: OpenAPIParser = new OpenAPIParser();
parser.setSpec(this.spec);
const tools: any[] = parser.getMCPTools();
const resourceTags: string[] = parser.getResourceTags();
const toolList: string = tools
.slice(0, 20)
.map((tool: any) => {
return `- **${tool.name}**: ${tool.description}`;
})
.join("\n");
const resourceList: string = resourceTags
.map((tag: string) => {
return `- **${StringUtils.toPascalCase(tag)}**`;
})
.join("\n");
const additionalToolsNote: string =
tools.length > 20 ? `\n...and ${tools.length - 20} more tools` : "";
const readmeContent: string = `# ${this.config.serverName}
${this.config.description}
This is a Model Context Protocol (MCP) server that provides access to OneUptime's APIs, allowing LLMs to interact with your OneUptime instance for monitoring, incident management, and observability operations.
## Features
- **Complete API Coverage**: Access to ${tools.length} OneUptime API endpoints
- **Resource Management**: Manage ${resourceTags.length} different resource types including ${resourceTags.slice(0, 5).join(", ")}${resourceTags.length > 5 ? ", and more" : ""}
- **Real-time Operations**: Create, read, update, and delete resources in your OneUptime instance
- **Type-safe**: Fully typed interface with comprehensive input validation
- **Error Handling**: Robust error handling with detailed error messages
- **Authentication**: Secure API key-based authentication
## Installation
### Via NPM
\`\`\`bash
npm install -g ${this.config.npmPackageName}
\`\`\`
### From Source
\`\`\`bash
git clone https://github.com/OneUptime/oneuptime.git
npm run generate-mcp-server
cd oneuptime/MCP
npm install
npm run build
\`\`\`
## Configuration
### Environment Variables
The MCP server requires the following environment variables:
| Variable | Description | Required | Example |
|----------|-------------|----------|---------|
| \`ONEUPTIME_API_KEY\` | Your OneUptime API key | Yes | \`xxxxxxxx-xxxx-xxxx-xxxx\` |
| \`ONEUPTIME_URL\` | Your OneUptime instance URL | No | \`https://oneuptime.com\` (default) |
### Getting Your API Key
1. **For OneUptime Cloud**:
- Go to [OneUptime Cloud](https://oneuptime.com) and log in
- Navigate to **Settings** → **API Keys**
- Click **Create API Key**
- Name it "MCP Server" and select appropriate permissions
- Copy the generated API key
2. **For Self-Hosted OneUptime**:
- Access your OneUptime instance
- Navigate to **Settings** → **API Keys**
- Click **Create API Key**
- Name it "MCP Server" and select appropriate permissions
- Copy the generated API key
## Usage
### With Claude Desktop
Add the following to your Claude Desktop configuration file:
\`\`\`json
{
"mcpServers": {
"oneuptime": {
"command": "${this.config.serverName}",
"env": {
"ONEUPTIME_API_KEY": "your-api-key-here",
"ONEUPTIME_URL": "https://oneuptime.com"
}
}
}
}
\`\`\`
### With Other MCP Clients
The server can be used with any MCP-compatible client by running:
\`\`\`bash
${this.config.serverName}
\`\`\`
Ensure the environment variables are set before running the server.
## Available Tools
The MCP server provides access to the following OneUptime operations:
${toolList}${additionalToolsNote}
## Resource Types
You can manage the following OneUptime resources:
${resourceList}
## Development
### Prerequisites
- Node.js 18+
- TypeScript 5+
- OneUptime API access
### Setup
1. Clone the repository and navigate to the generated MCP directory
2. Install dependencies: \`npm install\`
3. Set up environment variables
4. Start development server: \`npm run dev\`
### Building
\`\`\`bash
npm run build
\`\`\`
### Testing
\`\`\`bash
npm test
\`\`\`
## License
This project is licensed under the Apache 2.0 License.
---
Generated from OneUptime OpenAPI specification v${this.spec.info.version}
`;
await this.fileGenerator.writeFile("README.md", readmeContent);
}
private async generateTsConfig(): Promise<void> {
const tsConfigContent: string = `{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"lib": ["ES2022"],
"outDir": "./build",
"rootDir": "./",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"removeComments": false,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"useUnknownInCatchVariables": true,
"allowUnusedLabels": false,
"allowUnreachableCode": false,
"exactOptionalPropertyTypes": false,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": false,
"noUncheckedIndexedAccess": false
},
"include": [
"**/*.ts"
],
"exclude": [
"node_modules",
"build",
"**/*.test.ts",
"**/*.spec.ts"
]
}
`;
await this.fileGenerator.writeFile("tsconfig.json", tsConfigContent);
}
private async generateNodemonConfig(): Promise<void> {
const nodemonContent: string = `{
"watch": ["**/*.ts"],
"ext": "ts",
"ignore": ["build/**/*", "node_modules/**/*"],
"exec": "ts-node Index.ts",
"env": {
"NODE_ENV": "development"
}
}
`;
await this.fileGenerator.writeFile("nodemon.json", nodemonContent);
}
private async generateDockerfile(): Promise<void> {
const dockerContent: string = `FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy source code
COPY . .
# Build the application
RUN npm run build
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S oneuptime -u 1001
# Change ownership of the app directory
RUN chown -R oneuptime:nodejs /app
USER oneuptime
# Expose port (if needed for debugging)
EXPOSE 3000
# Set environment variables
ENV NODE_ENV=production
# Start the application
CMD ["node", "build/Index.js"]
`;
await this.fileGenerator.writeFile("Dockerfile", dockerContent);
}
}

View File

@@ -1,227 +0,0 @@
import fs from "fs";
import { OpenAPISpec, OpenAPIOperation, MCPTool, OpenAPISchema } from "./Types";
import { StringUtils } from "./StringUtils";
export class OpenAPIParser {
public spec: OpenAPISpec | null = null;
public async parseOpenAPISpec(filePath: string): Promise<OpenAPISpec> {
try {
const content: string = fs.readFileSync(filePath, "utf-8");
this.spec = JSON.parse(content) as OpenAPISpec;
return this.spec;
} catch (error) {
throw new Error(
`Failed to parse OpenAPI spec: ${error instanceof Error ? error.message : "Unknown error"}`,
);
}
}
public setSpec(spec: OpenAPISpec): void {
this.spec = spec;
}
public getMCPTools(): MCPTool[] {
if (!this.spec) {
throw new Error("OpenAPI spec not loaded. Call parseOpenAPISpec first.");
}
const tools: MCPTool[] = [];
// Group operations by resource/tag
for (const [path, pathItem] of Object.entries(this.spec.paths)) {
for (const [method, operation] of Object.entries(pathItem)) {
if (
!operation.operationId ||
!operation.tags ||
operation.tags.length === 0
) {
continue;
}
const tool: MCPTool = this.createMCPTool(path, method, operation);
tools.push(tool);
}
}
return tools;
}
private createMCPTool(
path: string,
method: string,
operation: OpenAPIOperation,
): MCPTool {
const toolName: string = this.generateToolName(operation);
const description: string =
operation.description ||
operation.summary ||
`${method.toUpperCase()} ${path}`;
const inputSchema: any = this.generateInputSchema(operation);
return {
name: toolName,
description: description,
operation: {
...operation,
method,
path,
},
inputSchema: inputSchema,
};
}
private generateToolName(operation: OpenAPIOperation): string {
if (operation.operationId) {
return StringUtils.toCamelCase(operation.operationId);
}
// Fallback to tag + summary
const tag: string = operation.tags?.[0] || "api";
const summary: string = operation.summary || "operation";
return StringUtils.toCamelCase(`${tag}_${summary}`);
}
private generateInputSchema(operation: OpenAPIOperation): any {
const properties: any = {};
const required: string[] = [];
// Add path parameters
if (operation.parameters) {
for (const param of operation.parameters) {
if (
param.in === "path" ||
param.in === "query" ||
param.in === "header"
) {
const paramName: string = StringUtils.toCamelCase(param.name);
properties[paramName] = this.convertOpenAPISchemaToJsonSchema(
param.schema,
);
properties[paramName].description = param.description || "";
if (param.required || param.in === "path") {
required.push(paramName);
}
}
}
}
// Add request body
if (operation.requestBody) {
const content: any = operation.requestBody.content;
const jsonContent: any = content["application/json"];
if (jsonContent && jsonContent.schema) {
if (jsonContent.schema.properties) {
// Flatten the request body properties into the main properties
Object.assign(
properties,
this.convertOpenAPISchemaToJsonSchema(jsonContent.schema)
.properties,
);
if (jsonContent.schema.required) {
required.push(...jsonContent.schema.required);
}
} else {
// If it's a reference or complex schema, add as 'data' property
properties.data = this.convertOpenAPISchemaToJsonSchema(
jsonContent.schema,
);
if (operation.requestBody.required) {
required.push("data");
}
}
}
}
return {
type: "object",
properties: properties,
required: required.length > 0 ? required : undefined,
};
}
private convertOpenAPISchemaToJsonSchema(schema: OpenAPISchema): any {
if (schema.$ref) {
const resolvedSchema: OpenAPISchema = this.resolveSchemaRef(schema.$ref);
return this.convertOpenAPISchemaToJsonSchema(resolvedSchema);
}
const jsonSchema: any = {
type: schema.type || "string",
};
if (schema.description) {
jsonSchema.description = schema.description;
}
if (schema.example !== undefined) {
jsonSchema.example = schema.example;
}
if (schema.format) {
jsonSchema.format = schema.format;
}
if (schema.type === "array" && schema.items) {
jsonSchema.items = this.convertOpenAPISchemaToJsonSchema(schema.items);
}
if (schema.properties) {
jsonSchema.properties = {};
for (const [propName, propSchema] of Object.entries(schema.properties)) {
jsonSchema.properties[propName] =
this.convertOpenAPISchemaToJsonSchema(propSchema);
}
}
if (schema.required) {
jsonSchema.required = schema.required;
}
return jsonSchema;
}
private resolveSchemaRef(ref: string): OpenAPISchema {
if (!this.spec) {
throw new Error("OpenAPI spec not loaded");
}
// Handle #/components/schemas/SchemeName format
const refParts: string[] = ref.split("/");
if (
refParts[0] === "#" &&
refParts[1] === "components" &&
refParts[2] === "schemas"
) {
const schemaName: string | undefined = refParts[3];
if (schemaName && this.spec.components?.schemas?.[schemaName]) {
return this.spec.components.schemas[schemaName]!;
}
}
throw new Error(`Could not resolve schema reference: ${ref}`);
}
public getResourceTags(): string[] {
if (!this.spec) {
return [];
}
const tags: Set<string> = new Set<string>();
for (const [, pathItem] of Object.entries(this.spec.paths)) {
for (const [, operation] of Object.entries(pathItem)) {
if (operation.tags) {
operation.tags.forEach((tag: string) => {
return tags.add(tag);
});
}
}
}
return Array.from(tags);
}
}

View File

@@ -1,60 +0,0 @@
export class StringUtils {
public static toCamelCase(str: string): string {
return str
.replace(/(?:^\w|[A-Z]|\b\w)/g, (word: string, index: number) => {
return index === 0 ? word.toLowerCase() : word.toUpperCase();
})
.replace(/\s+/g, "");
}
public static fromCamelCase(str: string): string {
// Convert camelCase back to original parameter format (kebab-case or snake_case)
return str
.replace(/([a-z])([A-Z])/g, "$1-$2")
.toLowerCase();
}
public static toPascalCase(str: string): string {
return str
.replace(/(?:^\w|[A-Z]|\b\w)/g, (word: string) => {
return word.toUpperCase();
})
.replace(/\s+/g, "");
}
public static toKebabCase(str: string): string {
return str
.replace(/([a-z])([A-Z])/g, "$1-$2")
.replace(/[\s_]+/g, "-")
.toLowerCase();
}
public static toSnakeCase(str: string): string {
return str
.replace(/([a-z])([A-Z])/g, "$1_$2")
.replace(/[\s-]+/g, "_")
.toLowerCase();
}
public static toConstantCase(str: string): string {
return this.toSnakeCase(str).toUpperCase();
}
public static capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
public static sanitizeDescription(description: string): string {
return description
.replace(/"/g, '\\"')
.replace(/\n/g, "\\n")
.replace(/\r/g, "\\r")
.replace(/\t/g, "\\t");
}
public static extractJsonSchema(schema: any): string {
return JSON.stringify(schema, null, 2)
.replace(/"/g, '\\"')
.replace(/\n/g, "\\n");
}
}

View File

@@ -1,97 +0,0 @@
export interface MCPServerConfig {
outputDir: string;
serverName: string;
serverVersion: string;
npmPackageName: string;
description: string;
}
export interface OpenAPIOperation {
operationId: string;
method: string;
path: string;
summary?: string;
description?: string;
tags?: string[];
parameters?: OpenAPIParameter[];
requestBody?: OpenAPIRequestBody;
responses?: Record<string, OpenAPIResponse>;
}
export interface OpenAPIParameter {
name: string;
in: "query" | "path" | "header" | "cookie";
required?: boolean;
schema: OpenAPISchema;
description?: string;
}
export interface OpenAPIRequestBody {
content: Record<string, OpenAPIMediaType>;
required?: boolean;
description?: string;
}
export interface OpenAPIResponse {
description: string;
content?: Record<string, OpenAPIMediaType>;
}
export interface OpenAPIMediaType {
schema: OpenAPISchema;
}
export interface OpenAPISchema {
type?: string;
format?: string;
description?: string;
example?: any;
items?: OpenAPISchema;
properties?: Record<string, OpenAPISchema>;
required?: string[];
$ref?: string;
allOf?: OpenAPISchema[];
oneOf?: OpenAPISchema[];
anyOf?: OpenAPISchema[];
}
export interface OpenAPISpec {
openapi: string;
info: {
title: string;
version: string;
description?: string;
};
servers: Array<{
url: string;
description?: string;
}>;
paths: Record<string, Record<string, OpenAPIOperation>>;
components: {
schemas: Record<string, OpenAPISchema>;
securitySchemes?: Record<string, any>;
};
}
export interface MCPTool {
name: string;
description: string;
operation: OpenAPIOperation;
inputSchema: any;
outputSchema?: any;
}
export interface MCPResource {
name: string;
description: string;
uri: string;
mimeType: string;
}
export interface MCPToolParameter {
name: string;
type: string;
description?: string;
required: boolean;
example?: any;
}

View File

@@ -1,380 +0,0 @@
import { generateOpenAPISpec } from "../OpenAPI/GenerateSpec";
import path from "path";
import fs from "fs";
import Logger from "Common/Server/Utils/Logger";
import { MCPServerGenerator } from "./Core/MCPServerGenerator";
import { OpenAPIParser } from "./Core/OpenAPIParser";
async function main(): Promise<void> {
Logger.info("🚀 Starting MCP Server Generation Process...");
// Define paths
const mcpDir: string = path.resolve(__dirname, "../../MCP");
const openApiSpecPath: string = path.resolve(mcpDir, "openapi.json");
try {
// Step 1: Clean up existing MCP directory
if (fs.existsSync(mcpDir)) {
Logger.info("🗑️ Removing existing MCP directory...");
fs.rmSync(mcpDir, { recursive: true, force: true });
}
// Create MCP directory
fs.mkdirSync(mcpDir, { recursive: true });
// Step 2: Generate OpenAPI spec
Logger.info("📄 Step 1: Generating OpenAPI specification...");
await generateOpenAPISpec(openApiSpecPath);
// Step 3: Parse OpenAPI spec
Logger.info("🔍 Step 2: Parsing OpenAPI specification...");
const parser: OpenAPIParser = new OpenAPIParser();
const apiSpec: any = await parser.parseOpenAPISpec(openApiSpecPath);
// Step 4: Initialize MCP server generator
Logger.info("⚙️ Step 3: Initializing MCP server generator...");
const generator: MCPServerGenerator = new MCPServerGenerator(
{
outputDir: mcpDir,
serverName: "oneuptime-mcp",
serverVersion: "1.0.0",
npmPackageName: "@oneuptime/mcp-server",
description:
"OneUptime Model Context Protocol (MCP) Server - Provides access to OneUptime APIs for LLMs",
},
apiSpec,
);
// Step 5: Generate MCP server
Logger.info("🏗️ Step 4: Generating MCP server files...");
await generator.generateServer();
// Step 6: Create additional documentation files
Logger.info("📚 Step 5: Creating additional documentation...");
await createAdditionalFiles(mcpDir);
Logger.info("✅ MCP server generation completed successfully!");
Logger.info(`📁 MCP server generated at: ${mcpDir}`);
Logger.info("🎯 Next steps:");
Logger.info(" 1. cd MCP");
Logger.info(" 2. npm install");
Logger.info(" 3. Set up your environment variables");
Logger.info(" 4. npm run build");
Logger.info(" 5. Test with npm start");
} catch (error) {
Logger.error("💥 MCP server generation failed:");
Logger.error(error instanceof Error ? error.message : "Unknown error");
process.exit(1);
}
}
async function createAdditionalFiles(mcpDir: string): Promise<void> {
// Create .env.example
const envExample: string = `# OneUptime MCP Server Configuration
# Required: Your OneUptime API key
ONEUPTIME_API_KEY=your-api-key-here
# Optional: Your OneUptime instance URL (defaults to https://oneuptime.com)
ONEUPTIME_URL=https://oneuptime.com
# Optional: Alternative environment variable names
# ONEUPTIME_API_URL=https://oneuptime.com/api
# API_KEY=your-api-key-here
# Development settings
NODE_ENV=development
`;
fs.writeFileSync(path.join(mcpDir, ".env.example"), envExample);
// Create .gitignore
const gitignore: string = `# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build output
build/
dist/
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
# IDE files
.vscode/
.idea/
*.swp
*.swo
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# TypeScript
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
`;
fs.writeFileSync(path.join(mcpDir, ".gitignore"), gitignore);
// Create CHANGELOG.md
const changelog: string = `# Changelog
All notable changes to the OneUptime MCP Server will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.0] - ${new Date().toISOString().split("T")[0]}
### Added
- Initial release of OneUptime MCP Server
- Complete API coverage with auto-generated tools from OpenAPI specification
- Support for all OneUptime resources and operations
- Comprehensive error handling and validation
- Docker support for containerized deployment
- TypeScript support with full type safety
- Environment-based configuration
- Detailed documentation and examples
### Features
- Model Context Protocol (MCP) 1.12+ compatibility
- Automatic OpenAPI specification parsing
- Dynamic tool generation from API endpoints
- Secure API key authentication
- Request/response validation
- Comprehensive logging and error reporting
`;
fs.writeFileSync(path.join(mcpDir, "CHANGELOG.md"), changelog);
// Create LICENSE
const license: string = `Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(which shall not include Communication that is conspicuously
marked or otherwise designated in writing by the copyright owner
as "Not a Work of the License").
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based upon (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and derivative works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control
systems, and issue tracking systems that are managed by, or on behalf
of, the Licensor for the purpose of discussing and improving the Work,
but excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to use, reproduce, modify, publicly display,
publicly perform, sublicense, and distribute the Work and such Derivative
Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright notice to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Support. You may choose to offer, and to
charge a fee for, warranty, support, indemnity or other liability
obligations and/or rights consistent with this License. However, in
accepting such obligations, You may act only on Your own behalf and on
Your sole responsibility, not on behalf of any other Contributor, and
only if You agree to indemnify, defend, and hold each Contributor
harmless for any liability incurred by, or claims asserted against,
such Contributor by reason of your accepting any such warranty or support.
END OF TERMS AND CONDITIONS
Copyright 2024 OneUptime, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
`;
fs.writeFileSync(path.join(mcpDir, "LICENSE"), license);
}
main().catch((err: Error) => {
Logger.error(`💥 Unexpected error: ${err.message}`);
process.exit(1);
});

View File

@@ -1,285 +0,0 @@
# OneUptime MCP Server Generator
## Overview
This project provides a **dynamic MCP (Model Context Protocol) server generator** that automatically creates a complete, production-ready MCP server from OneUptime's OpenAPI specification. The generator is written in TypeScript and produces a fully functional MCP server that allows LLMs to interact with OneUptime APIs.
## 🚀 Features
### ✅ Dynamic Generation
- **Automatic tool discovery** from OpenAPI operations and endpoints
- **Schema generation** from API request/response models
- **Type-safe** TypeScript code generation with full type definitions
- **Input validation** using JSON schemas derived from OpenAPI spec
- **Error handling** with comprehensive error messages and validation
### ✅ Complete MCP Server Structure
- **Main server file** with MCP SDK integration
- **API client** with authentication and request handling
- **Tool registry** with all discovered API endpoints as MCP tools
- **Configuration utilities** for environment-based setup
- **Documentation** auto-generated with usage examples
- **Docker support** for containerized deployment
### ✅ Build System
- **NPM package** with proper dependencies and scripts
- **TypeScript compilation** with source maps and declarations
- **Build scripts** for development and production
- **Testing infrastructure** with Jest setup
- **Publishing scripts** for NPM and Docker registries
## 📁 Generated Structure
```
MCP/
├── package.json # NPM package configuration
├── Index.ts # Main MCP server entry point
├── README.md # Complete usage documentation
├── tsconfig.json # TypeScript configuration
├── nodemon.json # Development configuration
├── Dockerfile # Container deployment
├── .env.example # Environment variables template
├── .gitignore # Git ignore rules
├── CHANGELOG.md # Version history
├── LICENSE # Apache 2.0 license
├── Service/
│ ├── MCP.ts # MCP tool registry and handlers
│ └── APIClient.ts # OneUptime API client
└── Utils/
└── Config.ts # Configuration utilities
```
## 🔧 Usage
### Generate the MCP Server
```bash
# Generate the complete MCP server
npm run generate-mcp-server
```
### Build and Test
```bash
# Navigate to the generated server
cd MCP
# Install dependencies
npm install
# Build the server
npm run build
# Test the server (optional)
npm test
# Run in development mode
npm run dev
```
### Use the MCP Server
```bash
# Set your OneUptime API key
export ONEUPTIME_API_KEY=your-api-key-here
# Optional: Set your OneUptime instance URL (defaults to https://oneuptime.com)
export ONEUPTIME_URL=https://oneuptime.com
# Run the MCP server
npm start
```
### Docker Usage
```bash
# Build Docker image
cd MCP
docker build -t oneuptime-mcp .
# Run with environment variables
docker run -e ONEUPTIME_API_KEY=your-key oneuptime-mcp
```
## 🏗️ Architecture
### Core Components
1. **OpenAPIParser** - Parses OpenAPI spec and extracts MCP tool definitions
2. **MCPServerGenerator** - Generates complete MCP server project structure
3. **FileGenerator** - Handles file I/O operations and directory management
4. **StringUtils** - Utility functions for naming conversions and sanitization
5. **GenerateMCPServer** - Main orchestration script that coordinates generation
### Generation Flow
```
OpenAPI Spec → Parser → Tool Discovery → Code Generation → MCP Server
↓ ↓ ↓ ↓ ↓
JSON Schema → Operations → MCP Tools → TypeScript Files → NPM Package
```
## 📋 Generated MCP Tools
The generator automatically creates MCP tools for all OneUptime API endpoints, including:
- **Projects** - Project management and organization
- **Monitors** - Service monitoring and health checks
- **Incidents** - Incident management and tracking
- **Alerts** - Alert rules and notification management
- **Status Pages** - Public status page management
- **Teams** - Team organization and permissions
- **Users** - User management and authentication
- **Workflows** - Automation and workflow management
- **Service Catalog** - Service discovery and documentation
- **And 700+ more tools** covering the complete OneUptime API
Each tool includes:
- **Input validation** using JSON schemas
- **Type-safe parameters** with TypeScript definitions
- **Comprehensive documentation** with examples
- **Error handling** with detailed error messages
## 🔐 Authentication
The MCP server supports OneUptime API key authentication:
```bash
# Set your API key
export ONEUPTIME_API_KEY=your-api-key-here
# Optional: Set custom OneUptime URL. Replace with your instance if self-hosted
# Defaults to https://oneuptime.com
export ONEUPTIME_URL=https://oneuptime.com
```
Environment variables supported:
- `ONEUPTIME_API_KEY` - Your OneUptime API key (required)
- `ONEUPTIME_URL` - Your OneUptime instance URL (optional, defaults to oneuptime.com)
- `API_KEY` - Alternative API key variable name
- `ONEUPTIME_API_URL` - Alternative full API URL
## 🧪 Testing
```bash
# Run unit tests
npm test
# Run with coverage
npm run coverage
# Run in watch mode during development
npm run dev
```
## 📦 Publishing
### Manual Publishing
```bash
# Publish to NPM
npm run publish-mcp-server -- --version 1.0.0
# Dry run (test without publishing)
npm run publish-mcp-server -- --version 1.0.0 --dry-run
# With NPM token
npm run publish-mcp-server -- --version 1.0.0 --npm-token YOUR_TOKEN
```
### Automated Publishing
The project includes GitHub Actions workflows for automated publishing:
1. **MCP Server Generation** - Validates generation on every PR/push
2. **Publish MCP Server** - Publishes to NPM and Docker on releases
## 🔄 Dynamic Updates
The generator is designed to be **completely dynamic**:
- **New API endpoints** are automatically discovered and added as MCP tools
- **Schema changes** are reflected in the generated code
- **No manual updates** required when the OneUptime API evolves
- **Regenerate anytime** with a single command
## 🎯 Benefits
1. **Always Up-to-Date** - MCP server automatically includes new API features
2. **Type Safety** - Generated TypeScript code is type-safe and follows best practices
3. **Complete Coverage** - All API endpoints become MCP tools for LLM integration
4. **Production Ready** - Includes error handling, logging, validation, and documentation
5. **Standards Compliant** - Follows MCP protocol specifications and TypeScript conventions
6. **Easy Maintenance** - Single source of truth (OpenAPI spec)
## 🛠️ Development
To modify the generator:
1. Edit TypeScript files in `Scripts/MCPProvider/Core/`
2. Run `npm run generate-mcp-server` to test changes
3. Check generated code in `MCP/`
4. Iterate and improve
### Key Files
- `Scripts/MCPProvider/Core/Types.ts` - Type definitions
- `Scripts/MCPProvider/Core/OpenAPIParser.ts` - OpenAPI parsing logic
- `Scripts/MCPProvider/Core/MCPServerGenerator.ts` - Main generation logic
- `Scripts/MCPProvider/GenerateMCPServer.ts` - Generation orchestration
## 🌟 Example Usage with LLMs
Once the MCP server is running, LLMs can use it to interact with OneUptime:
```typescript
// Example: Create a new monitor
{
"tool": "createMonitor",
"arguments": {
"name": "Website Health Check",
"type": "http",
"url": "https://example.com",
"projectId": "project-id-here"
}
}
// Example: Get incident details
{
"tool": "getIncident",
"arguments": {
"incidentId": "incident-id-here"
}
}
// Example: Update status page
{
"tool": "updateStatusPage",
"arguments": {
"statusPageId": "status-page-id",
"status": "operational"
}
}
```
## 📚 Documentation
- **Generated README** provides complete usage instructions for each generated server
- **API Documentation** auto-generated from OpenAPI specifications
- **Examples** show common usage patterns and integrations
- **Type Definitions** provide IntelliSense support in IDEs
## 🤝 Contributing
1. This is part of the main OneUptime repository
2. The MCP server is generated automatically from the OpenAPI specification
3. To add new features, update the underlying OneUptime APIs
4. The generator will automatically include new endpoints in the next generation
## 📄 License
Apache License 2.0 - see [LICENSE](LICENSE) for details.
---
This generator provides a complete, maintainable solution for creating MCP servers from OpenAPI specifications, ensuring that your LLM integration tools stay synchronized with your API evolution.

View File

@@ -1,294 +0,0 @@
#!/bin/bash
# OneUptime MCP Server Publishing Script
# This script automates the process of generating and publishing the OneUptime MCP Server
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to show help
show_help() {
cat << EOF
OneUptime MCP Server Publishing Script
Usage: $0 [options]
Options:
--version VERSION Set the version number (e.g., 1.0.0)
--dry-run Run in dry-run mode (no actual publishing)
--npm-token TOKEN NPM authentication token
--skip-build Skip the build step
--skip-tests Skip the test step
--help Show this help message
Environment Variables:
NPM_TOKEN NPM authentication token (alternative to --npm-token)
ONEUPTIME_VERSION Version to publish (alternative to --version)
Examples:
$0 --version 1.0.0
$0 --version 1.2.3 --dry-run
$0 --version 1.0.0 --npm-token YOUR_TOKEN
EOF
}
# Default values
VERSION=""
DRY_RUN=false
NPM_TOKEN="${NPM_TOKEN:-}"
SKIP_BUILD=false
SKIP_TESTS=false
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--version)
VERSION="$2"
shift 2
;;
--dry-run)
DRY_RUN=true
shift
;;
--npm-token)
NPM_TOKEN="$2"
shift 2
;;
--skip-build)
SKIP_BUILD=true
shift
;;
--skip-tests)
SKIP_TESTS=true
shift
;;
--help)
show_help
exit 0
;;
*)
print_error "Unknown option: $1"
show_help
exit 1
;;
esac
done
# Use environment variable if version not provided
if [ -z "$VERSION" ]; then
VERSION="${ONEUPTIME_VERSION:-}"
fi
# Validate version
if [ -z "$VERSION" ]; then
print_error "Version is required. Use --version VERSION or set ONEUPTIME_VERSION environment variable."
show_help
exit 1
fi
# Validate version format (basic semantic versioning)
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)*$ ]]; then
print_error "Invalid version format. Please use semantic versioning (e.g., 1.0.0, 1.2.3-beta.1)"
exit 1
fi
print_status "OneUptime MCP Server Publishing Script"
print_status "Version: $VERSION"
print_status "Dry Run: $DRY_RUN"
# Check if we're in the right directory
if [ ! -f "package.json" ] || [ ! -f "Scripts/MCPProvider/GenerateMCPServer.ts" ]; then
print_error "This script must be run from the OneUptime project root directory"
exit 1
fi
# Check for required tools
command -v node >/dev/null 2>&1 || { print_error "Node.js is required but not installed. Aborting."; exit 1; }
command -v npm >/dev/null 2>&1 || { print_error "npm is required but not installed. Aborting."; exit 1; }
# Determine NPM token
if [ -z "$NPM_TOKEN" ]; then
print_warning "No NPM token provided. This will be required for publishing."
print_warning "Set NPM_TOKEN environment variable or use --npm-token option."
if [ "$DRY_RUN" = false ]; then
print_error "NPM token is required for actual publishing."
exit 1
fi
fi
print_status "Step 1: Installing dependencies..."
npm install --silent
print_status "Step 2: Installing Common dependencies..."
cd Common && npm install --silent && cd ..
print_status "Step 3: Installing Scripts dependencies..."
cd Scripts && npm install --silent && cd ..
print_status "Step 4: Generating MCP server..."
npm run generate-mcp-server
# Check if MCP server was generated
MCP_DIR="./MCP"
if [ ! -d "$MCP_DIR" ]; then
print_error "MCP server generation failed - directory not created"
exit 1
fi
print_success "MCP server generated successfully"
# Change to MCP directory
cd "$MCP_DIR"
print_status "Step 5: Updating package.json version..."
if [ "$DRY_RUN" = false ]; then
npm version "$VERSION" --no-git-tag-version
else
print_status "(Dry run) Would update version to $VERSION"
fi
if [ "$SKIP_BUILD" = false ]; then
print_status "Step 6: Installing MCP server dependencies..."
npm install --silent
print_status "Step 7: Building MCP server..."
npm run build
print_success "MCP server built successfully"
else
print_warning "Skipping build step"
fi
if [ "$SKIP_TESTS" = false ]; then
print_status "Step 8: Running tests..."
if npm run test --silent >/dev/null 2>&1; then
print_success "Tests passed"
else
print_warning "Tests failed or no tests found, continuing..."
fi
else
print_warning "Skipping tests"
fi
# Publishing
if [ "$DRY_RUN" = true ]; then
print_status "Step 9: Dry run - Simulating npm publish..."
npm pack --dry-run
print_success "Dry run completed successfully"
print_status "Would publish @oneuptime/mcp-server@$VERSION"
else
print_status "Step 9: Publishing to npm..."
# Configure npm with token
if [ -n "$NPM_TOKEN" ]; then
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc
fi
# Publish to npm
npm publish --access public
print_success "Successfully published @oneuptime/mcp-server@$VERSION to npm!"
# Clean up npm token
if [ -n "$NPM_TOKEN" ]; then
rm -f ~/.npmrc
fi
fi
# Back to root directory
cd ..
print_status "Step 10: Creating GitHub release (if not dry run)..."
if [ "$DRY_RUN" = false ]; then
# Check if GitHub CLI is available
if command -v gh >/dev/null 2>&1; then
# Create a release on GitHub
RELEASE_NOTES="MCP Server v$VERSION
## What's Changed
This release includes the OneUptime Model Context Protocol (MCP) Server generated from the OneUptime API specification.
### Features
- Complete API coverage with $(find MCP -name "*.ts" | wc -l) generated TypeScript files
- Auto-generated from OpenAPI specification
- Full support for OneUptime monitoring and incident management features
- Type-safe MCP tools for LLM integration
### Installation
\`\`\`bash
npm install -g @oneuptime/mcp-server@$VERSION
\`\`\`
### Usage
\`\`\`bash
# Set your OneUptime API key
export ONEUPTIME_API_KEY=your-api-key-here
# Run the MCP server
oneuptime-mcp
\`\`\`
### Docker Usage
\`\`\`bash
docker run -e ONEUPTIME_API_KEY=your-api-key-here oneuptime/mcp-server:$VERSION
\`\`\`
For more information, see the [README](https://github.com/OneUptime/oneuptime/blob/main/MCP/README.md).
"
echo "$RELEASE_NOTES" > release-notes.md
# Create the release
gh release create "mcp-v$VERSION" \
--title "MCP Server v$VERSION" \
--notes-file release-notes.md \
MCP/*.tgz 2>/dev/null || echo "Note: No package files to attach to release"
rm -f release-notes.md
print_success "GitHub release created: mcp-v$VERSION"
else
print_warning "GitHub CLI not found. Skipping GitHub release creation."
fi
else
print_status "(Dry run) Would create GitHub release: mcp-v$VERSION"
fi
print_success "🎉 MCP Server publishing process completed!"
print_status "📦 Package: @oneuptime/mcp-server@$VERSION"
if [ "$DRY_RUN" = false ]; then
print_status "🔗 NPM: https://www.npmjs.com/package/@oneuptime/mcp-server"
print_status "🐙 GitHub: https://github.com/OneUptime/oneuptime/releases/tag/mcp-v$VERSION"
fi
print_status "📚 Documentation: See MCP/README.md for usage instructions"