mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
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:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -124,6 +124,3 @@ Terraform/**
|
||||
TerraformTest/**
|
||||
|
||||
terraform-provider-example/**
|
||||
|
||||
MCP/**
|
||||
MCP/**
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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.
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user