mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -1,12 +1,16 @@
|
||||
# Environment variables for OneUptime MCP Hello World Server
|
||||
# OneUptime MCP Server Configuration
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=info
|
||||
# OneUptime Instance Configuration
|
||||
ONEUPTIME_HOSTNAME=localhost:3002
|
||||
ONEUPTIME_PROTOCOL=http
|
||||
ONEUPTIME_BASE_ROUTE=/api/v1
|
||||
|
||||
# Server Configuration
|
||||
# Authentication (Required for production)
|
||||
ONEUPTIME_API_KEY=your_oneuptime_api_key_here
|
||||
ONEUPTIME_PROJECT_ID=your_project_id_here
|
||||
|
||||
# Server Configuration
|
||||
NODE_ENV=development
|
||||
PORT=3002
|
||||
|
||||
# OneUptime Configuration
|
||||
APP_NAME=mcp-hello-world
|
||||
LOG_LEVEL=info
|
||||
APP_NAME=oneuptime-mcp-server
|
||||
APP_VERSION=1.0.0
|
||||
|
||||
251
MCP/Index.ts
251
MCP/Index.ts
@@ -8,16 +8,24 @@ import {
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import dotenv from "dotenv";
|
||||
import DynamicToolGenerator from "./Utils/DynamicToolGenerator";
|
||||
import OneUptimeApiService, { OneUptimeApiConfig } from "./Services/OneUptimeApiService";
|
||||
import { McpToolInfo, OneUptimeToolCallArgs } from "./Types/McpTypes";
|
||||
import OneUptimeOperation from "./Types/OneUptimeOperation";
|
||||
import ModelType from "./Types/ModelType";
|
||||
import Protocol from "Common/Types/API/Protocol";
|
||||
import Route from "Common/Types/API/Route";
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
const APP_NAME: string = "mcp-hello-world";
|
||||
const APP_NAME: string = "oneuptime-mcp-server";
|
||||
|
||||
logger.info("OneUptime Hello World MCP Server is starting...");
|
||||
logger.info("OneUptime MCP Server is starting...");
|
||||
|
||||
class HelloWorldMCPServer {
|
||||
class OneUptimeMCPServer {
|
||||
private server: Server;
|
||||
private tools: McpToolInfo[] = [];
|
||||
|
||||
constructor() {
|
||||
this.server = new Server(
|
||||
@@ -32,52 +40,46 @@ class HelloWorldMCPServer {
|
||||
}
|
||||
);
|
||||
|
||||
this.initializeServices();
|
||||
this.generateTools();
|
||||
this.setupHandlers();
|
||||
}
|
||||
|
||||
private initializeServices(): void {
|
||||
// Initialize OneUptime API Service
|
||||
const config: OneUptimeApiConfig = {
|
||||
hostname: process.env.ONEUPTIME_HOSTNAME || "localhost:3002",
|
||||
protocol: process.env.ONEUPTIME_PROTOCOL === "http" ? Protocol.HTTP : Protocol.HTTPS,
|
||||
apiKey: process.env.ONEUPTIME_API_KEY,
|
||||
projectId: process.env.ONEUPTIME_PROJECT_ID,
|
||||
baseRoute: new Route(process.env.ONEUPTIME_BASE_ROUTE || "/api/v1"),
|
||||
};
|
||||
|
||||
OneUptimeApiService.initialize(config);
|
||||
logger.info("OneUptime API Service initialized");
|
||||
}
|
||||
|
||||
private generateTools(): void {
|
||||
try {
|
||||
this.tools = DynamicToolGenerator.generateAllTools();
|
||||
logger.info(`Generated ${this.tools.length} OneUptime MCP tools`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to generate tools: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private setupHandlers(): void {
|
||||
// List available tools
|
||||
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: "hello",
|
||||
description: "Say hello with a personalized greeting",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: {
|
||||
type: "string",
|
||||
description: "Name of the person to greet",
|
||||
},
|
||||
},
|
||||
required: ["name"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get_time",
|
||||
description: "Get the current server time",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "echo",
|
||||
description: "Echo back any message",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
message: {
|
||||
type: "string",
|
||||
description: "Message to echo back",
|
||||
},
|
||||
},
|
||||
required: ["message"],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const mcpTools = this.tools.map((tool) => ({
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
inputSchema: tool.inputSchema,
|
||||
}));
|
||||
|
||||
logger.info(`Listing ${mcpTools.length} available tools`);
|
||||
return { tools: mcpTools };
|
||||
});
|
||||
|
||||
// Handle tool calls
|
||||
@@ -85,89 +87,120 @@ class HelloWorldMCPServer {
|
||||
const { name, arguments: args } = request.params;
|
||||
|
||||
try {
|
||||
switch (name) {
|
||||
case "hello": {
|
||||
const personName = args?.name as string;
|
||||
if (!personName) {
|
||||
throw new McpError(
|
||||
ErrorCode.InvalidParams,
|
||||
"Name parameter is required"
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Saying hello to: ${personName}`);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Hello, ${personName}! Welcome to OneUptime's Hello World MCP Server! 🚀`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case "get_time": {
|
||||
const currentTime = new Date().toISOString();
|
||||
logger.info(`Returning current time: ${currentTime}`);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Current server time: ${currentTime}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
case "echo": {
|
||||
const message = args?.message as string;
|
||||
if (!message) {
|
||||
throw new McpError(
|
||||
ErrorCode.InvalidParams,
|
||||
"Message parameter is required"
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Echoing message: ${message}`);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Echo: ${message}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
throw new McpError(
|
||||
ErrorCode.MethodNotFound,
|
||||
`Unknown tool: ${name}`
|
||||
);
|
||||
// Find the tool by name
|
||||
const tool = this.tools.find((t) => t.name === name);
|
||||
if (!tool) {
|
||||
throw new McpError(
|
||||
ErrorCode.MethodNotFound,
|
||||
`Unknown tool: ${name}`
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Executing tool: ${name} for model: ${tool.modelName}`);
|
||||
|
||||
// Execute the OneUptime operation
|
||||
const result = await OneUptimeApiService.executeOperation(
|
||||
tool.modelName,
|
||||
tool.operation,
|
||||
tool.modelType,
|
||||
tool.apiPath || "",
|
||||
args as OneUptimeToolCallArgs
|
||||
);
|
||||
|
||||
// Format the response
|
||||
const responseText = this.formatToolResponse(tool, result, args as OneUptimeToolCallArgs);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: responseText,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`Error executing tool ${name}:`, error);
|
||||
throw error;
|
||||
logger.error(`Error executing tool ${name}: ${error}`);
|
||||
|
||||
if (error instanceof McpError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new McpError(
|
||||
ErrorCode.InternalError,
|
||||
`Failed to execute ${name}: ${error}`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private formatToolResponse(tool: McpToolInfo, result: any, args: OneUptimeToolCallArgs): string {
|
||||
const operation = tool.operation;
|
||||
const modelName = tool.singularName;
|
||||
const pluralName = tool.pluralName;
|
||||
|
||||
switch (operation) {
|
||||
case OneUptimeOperation.Create:
|
||||
return `✅ Successfully created ${modelName}: ${JSON.stringify(result, null, 2)}`;
|
||||
|
||||
case OneUptimeOperation.Read:
|
||||
if (result) {
|
||||
return `📋 Retrieved ${modelName} (ID: ${args.id}): ${JSON.stringify(result, null, 2)}`;
|
||||
} else {
|
||||
return `❌ ${modelName} not found with ID: ${args.id}`;
|
||||
}
|
||||
|
||||
case OneUptimeOperation.List:
|
||||
const items = Array.isArray(result) ? result : result?.data || [];
|
||||
const count = items.length;
|
||||
const summary = `📊 Found ${count} ${count === 1 ? modelName : pluralName}`;
|
||||
|
||||
if (count === 0) {
|
||||
return `${summary}. No items match the criteria.`;
|
||||
}
|
||||
|
||||
const limitedItems = items.slice(0, 5); // Show first 5 items
|
||||
const itemsText = limitedItems.map((item: any, index: number) =>
|
||||
`${index + 1}. ${JSON.stringify(item, null, 2)}`
|
||||
).join('\n');
|
||||
|
||||
const hasMore = count > 5 ? `\n... and ${count - 5} more items` : '';
|
||||
return `${summary}:\n${itemsText}${hasMore}`;
|
||||
|
||||
case OneUptimeOperation.Update:
|
||||
return `✅ Successfully updated ${modelName} (ID: ${args.id}): ${JSON.stringify(result, null, 2)}`;
|
||||
|
||||
case OneUptimeOperation.Delete:
|
||||
return `🗑️ Successfully deleted ${modelName} (ID: ${args.id})`;
|
||||
|
||||
case OneUptimeOperation.Count:
|
||||
const totalCount = result?.count || result || 0;
|
||||
return `📊 Total count of ${pluralName}: ${totalCount}`;
|
||||
|
||||
default:
|
||||
return `✅ Operation ${operation} completed successfully: ${JSON.stringify(result, null, 2)}`;
|
||||
}
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
const transport = new StdioServerTransport();
|
||||
await this.server.connect(transport);
|
||||
|
||||
logger.info("OneUptime Hello World MCP Server is running!");
|
||||
logger.info("Available tools: hello, get_time, echo");
|
||||
logger.info("OneUptime MCP Server is running!");
|
||||
logger.info(`Available tools: ${this.tools.length} total`);
|
||||
|
||||
// Log some example tools
|
||||
const exampleTools = this.tools.slice(0, 5).map(t => t.name);
|
||||
logger.info(`Example tools: ${exampleTools.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Start the server
|
||||
async function main(): Promise<void> {
|
||||
try {
|
||||
const mcpServer = new HelloWorldMCPServer();
|
||||
const mcpServer = new OneUptimeMCPServer();
|
||||
await mcpServer.run();
|
||||
} catch (error) {
|
||||
logger.error("Failed to start MCP server:", error);
|
||||
logger.error(`Failed to start MCP server: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -185,6 +218,6 @@ process.on("SIGTERM", () => {
|
||||
|
||||
// Start the server
|
||||
main().catch((error) => {
|
||||
logger.error("Unhandled error:", error);
|
||||
logger.error(`Unhandled error: ${error}`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
191
MCP/README.md
191
MCP/README.md
@@ -1,92 +1,171 @@
|
||||
# OneUptime Hello World MCP Server
|
||||
# OneUptime MCP Server
|
||||
|
||||
A basic Hello World implementation of a Model Context Protocol (MCP) server for OneUptime.
|
||||
A production-ready Model Context Protocol (MCP) server for OneUptime that provides dynamic tools for all OneUptime models and operations.
|
||||
|
||||
## What is this?
|
||||
## Features
|
||||
|
||||
This is a simple MCP server that demonstrates how to create a Model Context Protocol server within the OneUptime ecosystem. It provides basic tools that can be used by AI assistants like Claude to interact with the server.
|
||||
- **Dynamic Tool Generation**: Automatically generates MCP tools for all OneUptime models (Database and Analytics)
|
||||
- **Full CRUD Operations**: Supports Create, Read, Update, Delete, List, and Count operations
|
||||
- **Production Ready**: Built with proper error handling, logging, and configuration
|
||||
- **Extensible**: Automatically supports new models as they are added to OneUptime
|
||||
- **Type Safe**: Fully typed with TypeScript
|
||||
|
||||
## Available Tools
|
||||
## Available Operations
|
||||
|
||||
1. **hello** - Say hello with a personalized greeting
|
||||
- Parameters: `name` (string, required)
|
||||
- Example: Returns "Hello, [name]! Welcome to OneUptime's Hello World MCP Server! 🚀"
|
||||
The MCP server automatically generates tools for each OneUptime model with the following operations:
|
||||
|
||||
2. **get_time** - Get the current server time
|
||||
- Parameters: None
|
||||
- Example: Returns current ISO timestamp
|
||||
### Database Models
|
||||
- `oneuptime_create{ModelName}` - Create a new record
|
||||
- `oneuptime_get{ModelName}` - Retrieve a record by ID
|
||||
- `oneuptime_list{ModelName}s` - List records with filtering
|
||||
- `oneuptime_update{ModelName}` - Update a record
|
||||
- `oneuptime_delete{ModelName}` - Delete a record
|
||||
- `oneuptime_count{ModelName}s` - Count records
|
||||
|
||||
3. **echo** - Echo back any message
|
||||
- Parameters: `message` (string, required)
|
||||
- Example: Returns "Echo: [your message]"
|
||||
### Analytics Models
|
||||
- `oneuptime_create{ModelName}` - Create analytics data
|
||||
- `oneuptime_list{ModelName}s` - Query analytics data
|
||||
- `oneuptime_count{ModelName}s` - Count analytics records
|
||||
|
||||
## Development
|
||||
## Supported Models
|
||||
|
||||
### Prerequisites
|
||||
The server automatically generates tools for all OneUptime models including:
|
||||
|
||||
- Node.js 18+
|
||||
- npm or yarn
|
||||
- TypeScript
|
||||
**Database Models**: Incident, Alert, Monitor, Project, User, Team, StatusPage, and 100+ more
|
||||
**Analytics Models**: Log, Metric, Span, TelemetryAttribute, ExceptionInstance, MonitorLog
|
||||
|
||||
### Setup
|
||||
## Configuration
|
||||
|
||||
1. Install dependencies:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Start development server:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
3. Start production server:
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
Build and run with Docker:
|
||||
Copy `.env.example` to `.env` and configure:
|
||||
|
||||
```bash
|
||||
# Build the Docker image
|
||||
docker build -f Dockerfile.tpl -t oneuptime-mcp-hello-world .
|
||||
# OneUptime Instance Configuration
|
||||
ONEUPTIME_HOSTNAME=localhost:3002
|
||||
ONEUPTIME_PROTOCOL=http
|
||||
ONEUPTIME_BASE_ROUTE=/api/v1
|
||||
|
||||
# Run the container
|
||||
docker run -it oneuptime-mcp-hello-world
|
||||
# Authentication (Required for production)
|
||||
ONEUPTIME_API_KEY=your_oneuptime_api_key_here
|
||||
ONEUPTIME_PROJECT_ID=your_project_id_here
|
||||
|
||||
# Server Configuration
|
||||
NODE_ENV=development
|
||||
LOG_LEVEL=info
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install dependencies:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Configure environment variables:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env with your OneUptime configuration
|
||||
```
|
||||
|
||||
3. Build the server:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
This MCP server communicates over stdio and is designed to be used with MCP-compatible clients like Claude Desktop or other AI assistants that support the Model Context Protocol.
|
||||
### Development
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Example Configuration for Claude Desktop
|
||||
### Production
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
Add this to your Claude Desktop MCP settings:
|
||||
## Example Tool Usage
|
||||
|
||||
### List Incidents
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"oneuptime": {
|
||||
"command": "node",
|
||||
"args": ["--require", "ts-node/register", "/path/to/mcp-hello-world/Index.ts"]
|
||||
"name": "oneuptime_listIncidents",
|
||||
"arguments": {
|
||||
"query": {"projectId": "your-project-id"},
|
||||
"limit": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Create Alert
|
||||
```json
|
||||
{
|
||||
"name": "oneuptime_createAlert",
|
||||
"arguments": {
|
||||
"data": {
|
||||
"title": "High CPU Usage",
|
||||
"description": "CPU usage above 90%",
|
||||
"projectId": "your-project-id"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Get Monitor by ID
|
||||
```json
|
||||
{
|
||||
"name": "oneuptime_getMonitor",
|
||||
"arguments": {
|
||||
"id": "monitor-id-here"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Query Logs
|
||||
```json
|
||||
{
|
||||
"name": "oneuptime_listLogs",
|
||||
"arguments": {
|
||||
"query": {
|
||||
"serviceId": "service-id",
|
||||
"severity": "error"
|
||||
},
|
||||
"limit": 50,
|
||||
"sort": {"time": -1}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
The server is built using:
|
||||
- **@modelcontextprotocol/sdk**: Official MCP SDK for TypeScript
|
||||
- **OneUptime Common**: Shared utilities and logging from OneUptime
|
||||
- **TypeScript**: For type safety and better development experience
|
||||
- **DynamicToolGenerator**: Automatically discovers and generates tools for all OneUptime models
|
||||
- **OneUptimeApiService**: Handles API communication with OneUptime instance
|
||||
- **Type Definitions**: Provides type safety and IntelliSense support
|
||||
- **Error Handling**: Comprehensive error handling and user-friendly messages
|
||||
|
||||
## Contributing
|
||||
## Development
|
||||
|
||||
This is part of the OneUptime project. Follow the standard OneUptime development practices and coding standards.
|
||||
### Adding New Models
|
||||
|
||||
New models are automatically supported! When new models are added to OneUptime:
|
||||
|
||||
1. Database models added to `Common/Models/DatabaseModels/Index.ts`
|
||||
2. Analytics models added to `Common/Models/AnalyticsModels/Index.ts`
|
||||
|
||||
The MCP server will automatically generate tools for them on the next restart.
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
### Linting
|
||||
|
||||
```bash
|
||||
npm run audit
|
||||
npm run dep-check
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0 - see the OneUptime project license for details.
|
||||
Apache-2.0 - See LICENSE file for details.
|
||||
|
||||
186
MCP/Services/OneUptimeApiService.ts
Normal file
186
MCP/Services/OneUptimeApiService.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
import OneUptimeOperation from "../Types/OneUptimeOperation";
|
||||
import ModelType from "../Types/ModelType";
|
||||
import { OneUptimeToolCallArgs } from "../Types/McpTypes";
|
||||
import Logger from "Common/Server/Utils/Logger";
|
||||
import API from "Common/Utils/API";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import Protocol from "Common/Types/API/Protocol";
|
||||
import Hostname from "Common/Types/API/Hostname";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import Headers from "Common/Types/API/Headers";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
|
||||
export interface OneUptimeApiConfig {
|
||||
hostname: string;
|
||||
protocol?: Protocol;
|
||||
apiKey?: string;
|
||||
projectId?: string;
|
||||
baseRoute?: Route;
|
||||
}
|
||||
|
||||
export default class OneUptimeApiService {
|
||||
private static api: API;
|
||||
private static config: OneUptimeApiConfig;
|
||||
|
||||
public static initialize(config: OneUptimeApiConfig): void {
|
||||
this.config = config;
|
||||
|
||||
const protocol = config.protocol || Protocol.HTTPS;
|
||||
const hostname = new Hostname(config.hostname);
|
||||
const baseRoute = config.baseRoute || new Route("/api/v1");
|
||||
|
||||
this.api = new API(protocol, hostname, baseRoute);
|
||||
|
||||
Logger.info(`OneUptime API Service initialized with: ${protocol}://${hostname}${baseRoute}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a OneUptime API operation
|
||||
*/
|
||||
public static async executeOperation(
|
||||
modelName: string,
|
||||
operation: OneUptimeOperation,
|
||||
modelType: ModelType,
|
||||
apiPath: string,
|
||||
args: OneUptimeToolCallArgs
|
||||
): Promise<any> {
|
||||
if (!this.api) {
|
||||
throw new Error("OneUptime API Service not initialized. Please call initialize() first.");
|
||||
}
|
||||
|
||||
this.validateOperationArgs(operation, args);
|
||||
|
||||
const route = this.buildApiRoute(apiPath, operation, args.id);
|
||||
const headers = this.getHeaders();
|
||||
const data = this.getRequestData(operation, args);
|
||||
|
||||
Logger.info(`Executing ${operation} operation for ${modelName} at ${route.toString()}`);
|
||||
|
||||
try {
|
||||
let response: HTTPResponse<any> | HTTPErrorResponse;
|
||||
|
||||
switch (operation) {
|
||||
case OneUptimeOperation.Create:
|
||||
response = await this.api.post(route, data, headers);
|
||||
break;
|
||||
case OneUptimeOperation.Read:
|
||||
case OneUptimeOperation.List:
|
||||
case OneUptimeOperation.Count:
|
||||
response = await this.api.get(route, data, headers);
|
||||
break;
|
||||
case OneUptimeOperation.Update:
|
||||
response = await this.api.put(route, data, headers);
|
||||
break;
|
||||
case OneUptimeOperation.Delete:
|
||||
response = await this.api.delete(route, data, headers);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported operation: ${operation}`);
|
||||
}
|
||||
|
||||
if (response instanceof HTTPErrorResponse) {
|
||||
throw new Error(`API request failed: ${response.statusCode} - ${response.message}`);
|
||||
}
|
||||
|
||||
Logger.info(`Successfully executed ${operation} operation for ${modelName}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
Logger.error(`Error executing ${operation} operation for ${modelName}: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private static buildApiRoute(apiPath: string, operation: OneUptimeOperation, id?: string): Route {
|
||||
let route = new Route(apiPath);
|
||||
|
||||
switch (operation) {
|
||||
case OneUptimeOperation.Read:
|
||||
case OneUptimeOperation.Update:
|
||||
case OneUptimeOperation.Delete:
|
||||
if (id) {
|
||||
route = route.addRoute(new Route(`/${id}`));
|
||||
}
|
||||
break;
|
||||
case OneUptimeOperation.Count:
|
||||
route = route.addRoute(new Route('/count'));
|
||||
break;
|
||||
case OneUptimeOperation.List:
|
||||
case OneUptimeOperation.Create:
|
||||
default:
|
||||
// No additional path needed
|
||||
break;
|
||||
}
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
private static getRequestData(operation: OneUptimeOperation, args: OneUptimeToolCallArgs): JSONObject | undefined {
|
||||
switch (operation) {
|
||||
case OneUptimeOperation.Create:
|
||||
return args.data;
|
||||
case OneUptimeOperation.Update:
|
||||
return args.data;
|
||||
case OneUptimeOperation.List:
|
||||
case OneUptimeOperation.Count:
|
||||
return {
|
||||
query: args.query || {},
|
||||
select: args.select,
|
||||
skip: args.skip,
|
||||
limit: args.limit,
|
||||
sort: args.sort,
|
||||
} as JSONObject;
|
||||
case OneUptimeOperation.Read:
|
||||
case OneUptimeOperation.Delete:
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private static getHeaders(): Headers {
|
||||
const headers: Headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
|
||||
if (this.config.apiKey) {
|
||||
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
||||
}
|
||||
|
||||
if (this.config.projectId) {
|
||||
headers['X-Project-ID'] = this.config.projectId;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate arguments for a specific operation
|
||||
*/
|
||||
public static validateOperationArgs(operation: OneUptimeOperation, args: OneUptimeToolCallArgs): void {
|
||||
switch (operation) {
|
||||
case OneUptimeOperation.Create:
|
||||
if (!args.data) {
|
||||
throw new Error('Data is required for create operation');
|
||||
}
|
||||
break;
|
||||
case OneUptimeOperation.Read:
|
||||
case OneUptimeOperation.Update:
|
||||
case OneUptimeOperation.Delete:
|
||||
if (!args.id) {
|
||||
throw new Error(`ID is required for ${operation} operation`);
|
||||
}
|
||||
if (operation === OneUptimeOperation.Update && !args.data) {
|
||||
throw new Error('Data is required for update operation');
|
||||
}
|
||||
break;
|
||||
case OneUptimeOperation.List:
|
||||
case OneUptimeOperation.Count:
|
||||
// No required arguments for list/count operations
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown operation: ${operation}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
MCP/Types/McpTypes.ts
Normal file
36
MCP/Types/McpTypes.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import OneUptimeOperation from "./OneUptimeOperation";
|
||||
import ModelType from "./ModelType";
|
||||
|
||||
export interface McpToolInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
inputSchema: any;
|
||||
modelName: string;
|
||||
operation: OneUptimeOperation;
|
||||
modelType: ModelType;
|
||||
singularName: string;
|
||||
pluralName: string;
|
||||
tableName: string;
|
||||
apiPath?: string;
|
||||
}
|
||||
|
||||
export interface ModelToolsResult {
|
||||
tools: McpToolInfo[];
|
||||
modelInfo: {
|
||||
tableName: string;
|
||||
singularName: string;
|
||||
pluralName: string;
|
||||
modelType: ModelType;
|
||||
apiPath?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface OneUptimeToolCallArgs {
|
||||
id?: string;
|
||||
data?: any;
|
||||
query?: any;
|
||||
select?: any;
|
||||
skip?: number;
|
||||
limit?: number;
|
||||
sort?: any;
|
||||
}
|
||||
6
MCP/Types/ModelType.ts
Normal file
6
MCP/Types/ModelType.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export enum ModelType {
|
||||
Database = "database",
|
||||
Analytics = "analytics",
|
||||
}
|
||||
|
||||
export default ModelType;
|
||||
10
MCP/Types/OneUptimeOperation.ts
Normal file
10
MCP/Types/OneUptimeOperation.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export enum OneUptimeOperation {
|
||||
Create = "create",
|
||||
Read = "read",
|
||||
List = "list",
|
||||
Update = "update",
|
||||
Delete = "delete",
|
||||
Count = "count",
|
||||
}
|
||||
|
||||
export default OneUptimeOperation;
|
||||
364
MCP/Utils/DynamicToolGenerator.ts
Normal file
364
MCP/Utils/DynamicToolGenerator.ts
Normal file
@@ -0,0 +1,364 @@
|
||||
import DatabaseModels from "Common/Models/DatabaseModels/Index";
|
||||
import AnalyticsModels from "Common/Models/AnalyticsModels/Index";
|
||||
import DatabaseBaseModel from "Common/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
||||
import AnalyticsBaseModel from "Common/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel";
|
||||
import OneUptimeOperation from "../Types/OneUptimeOperation";
|
||||
import ModelType from "../Types/ModelType";
|
||||
import { McpToolInfo, ModelToolsResult } from "../Types/McpTypes";
|
||||
import Logger from "Common/Server/Utils/Logger";
|
||||
|
||||
export default class DynamicToolGenerator {
|
||||
|
||||
/**
|
||||
* Generate all MCP tools for all OneUptime models
|
||||
*/
|
||||
public static generateAllTools(): McpToolInfo[] {
|
||||
const allTools: McpToolInfo[] = [];
|
||||
|
||||
// Generate tools for Database Models
|
||||
for (const ModelClass of DatabaseModels) {
|
||||
try {
|
||||
const model: DatabaseBaseModel = new ModelClass();
|
||||
const tools = this.generateToolsForDatabaseModel(model, ModelClass);
|
||||
allTools.push(...tools.tools);
|
||||
} catch (error) {
|
||||
Logger.error(`Error generating tools for database model ${ModelClass.name}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate tools for Analytics Models
|
||||
for (const ModelClass of AnalyticsModels) {
|
||||
try {
|
||||
const model: AnalyticsBaseModel = new ModelClass();
|
||||
const tools = this.generateToolsForAnalyticsModel(model, ModelClass);
|
||||
allTools.push(...tools.tools);
|
||||
} catch (error) {
|
||||
Logger.error(`Error generating tools for analytics model ${ModelClass.name}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.info(`Generated ${allTools.length} MCP tools for OneUptime models`);
|
||||
return allTools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate MCP tools for a specific database model
|
||||
*/
|
||||
public static generateToolsForDatabaseModel(
|
||||
model: DatabaseBaseModel,
|
||||
ModelClass: { new (): DatabaseBaseModel }
|
||||
): ModelToolsResult {
|
||||
const tools: McpToolInfo[] = [];
|
||||
const modelName = model.tableName || ModelClass.name;
|
||||
const singularName = model.singularName || modelName;
|
||||
const pluralName = model.pluralName || `${singularName}s`;
|
||||
const apiPath = model.crudApiPath?.toString();
|
||||
|
||||
// Skip if model doesn't have required properties or documentation is disabled
|
||||
if (!modelName || !model.enableDocumentation || !apiPath) {
|
||||
return {
|
||||
tools: [],
|
||||
modelInfo: {
|
||||
tableName: modelName,
|
||||
singularName,
|
||||
pluralName,
|
||||
modelType: ModelType.Database,
|
||||
apiPath
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const baseToolName = singularName.toLowerCase().replace(/\s+/g, '_');
|
||||
|
||||
// CREATE Tool
|
||||
tools.push({
|
||||
name: `oneuptime_create${singularName.replace(/\s+/g, '')}`,
|
||||
description: `Create a new ${singularName} in OneUptime`,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
data: {
|
||||
type: "object",
|
||||
description: `${singularName} data to create`,
|
||||
}
|
||||
},
|
||||
required: ["data"]
|
||||
},
|
||||
modelName,
|
||||
operation: OneUptimeOperation.Create,
|
||||
modelType: ModelType.Database,
|
||||
singularName,
|
||||
pluralName,
|
||||
tableName: modelName,
|
||||
apiPath
|
||||
});
|
||||
|
||||
// READ Tool
|
||||
tools.push({
|
||||
name: `oneuptime_get${singularName.replace(/\s+/g, '')}`,
|
||||
description: `Retrieve a single ${singularName} by ID from OneUptime`,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
description: `ID of the ${singularName} to retrieve`,
|
||||
}
|
||||
},
|
||||
required: ["id"]
|
||||
},
|
||||
modelName,
|
||||
operation: OneUptimeOperation.Read,
|
||||
modelType: ModelType.Database,
|
||||
singularName,
|
||||
pluralName,
|
||||
tableName: modelName,
|
||||
apiPath
|
||||
});
|
||||
|
||||
// LIST Tool
|
||||
tools.push({
|
||||
name: `oneuptime_list${pluralName.replace(/\s+/g, '')}`,
|
||||
description: `List all ${pluralName} from OneUptime`,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "object",
|
||||
description: `Query filters for ${pluralName}`,
|
||||
},
|
||||
select: {
|
||||
type: "object",
|
||||
description: "Fields to select",
|
||||
},
|
||||
skip: {
|
||||
type: "number",
|
||||
description: "Number of records to skip",
|
||||
},
|
||||
limit: {
|
||||
type: "number",
|
||||
description: "Maximum number of records to return",
|
||||
},
|
||||
sort: {
|
||||
type: "object",
|
||||
description: "Sort order",
|
||||
}
|
||||
}
|
||||
},
|
||||
modelName,
|
||||
operation: OneUptimeOperation.List,
|
||||
modelType: ModelType.Database,
|
||||
singularName,
|
||||
pluralName,
|
||||
tableName: modelName,
|
||||
apiPath
|
||||
});
|
||||
|
||||
// UPDATE Tool
|
||||
tools.push({
|
||||
name: `oneuptime_update${singularName.replace(/\s+/g, '')}`,
|
||||
description: `Update an existing ${singularName} in OneUptime`,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
description: `ID of the ${singularName} to update`,
|
||||
},
|
||||
data: {
|
||||
type: "object",
|
||||
description: `Updated ${singularName} data`,
|
||||
}
|
||||
},
|
||||
required: ["id", "data"]
|
||||
},
|
||||
modelName,
|
||||
operation: OneUptimeOperation.Update,
|
||||
modelType: ModelType.Database,
|
||||
singularName,
|
||||
pluralName,
|
||||
tableName: modelName,
|
||||
apiPath
|
||||
});
|
||||
|
||||
// DELETE Tool
|
||||
tools.push({
|
||||
name: `oneuptime_delete${singularName.replace(/\s+/g, '')}`,
|
||||
description: `Delete a ${singularName} from OneUptime`,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
description: `ID of the ${singularName} to delete`,
|
||||
}
|
||||
},
|
||||
required: ["id"]
|
||||
},
|
||||
modelName,
|
||||
operation: OneUptimeOperation.Delete,
|
||||
modelType: ModelType.Database,
|
||||
singularName,
|
||||
pluralName,
|
||||
tableName: modelName,
|
||||
apiPath
|
||||
});
|
||||
|
||||
// COUNT Tool
|
||||
tools.push({
|
||||
name: `oneuptime_count${pluralName.replace(/\s+/g, '')}`,
|
||||
description: `Count the number of ${pluralName} in OneUptime`,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "object",
|
||||
description: `Query filters for counting ${pluralName}`,
|
||||
}
|
||||
}
|
||||
},
|
||||
modelName,
|
||||
operation: OneUptimeOperation.Count,
|
||||
modelType: ModelType.Database,
|
||||
singularName,
|
||||
pluralName,
|
||||
tableName: modelName,
|
||||
apiPath
|
||||
});
|
||||
|
||||
return {
|
||||
tools,
|
||||
modelInfo: {
|
||||
tableName: modelName,
|
||||
singularName,
|
||||
pluralName,
|
||||
modelType: ModelType.Database,
|
||||
apiPath
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate MCP tools for a specific analytics model
|
||||
*/
|
||||
public static generateToolsForAnalyticsModel(
|
||||
model: AnalyticsBaseModel,
|
||||
ModelClass: { new (): AnalyticsBaseModel }
|
||||
): ModelToolsResult {
|
||||
const tools: McpToolInfo[] = [];
|
||||
const modelName = model.tableName || ModelClass.name;
|
||||
const singularName = model.singularName || modelName;
|
||||
const pluralName = model.pluralName || `${singularName}s`;
|
||||
const apiPath = model.crudApiPath?.toString();
|
||||
|
||||
// Skip if model doesn't have required properties
|
||||
if (!modelName || !apiPath) {
|
||||
return {
|
||||
tools: [],
|
||||
modelInfo: {
|
||||
tableName: modelName,
|
||||
singularName,
|
||||
pluralName,
|
||||
modelType: ModelType.Analytics,
|
||||
apiPath
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const baseToolName = singularName.toLowerCase().replace(/\s+/g, '_');
|
||||
|
||||
// CREATE Tool for Analytics
|
||||
tools.push({
|
||||
name: `oneuptime_create${singularName.replace(/\s+/g, '')}`,
|
||||
description: `Create a new ${singularName} analytics record in OneUptime`,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
data: {
|
||||
type: "object",
|
||||
description: `${singularName} analytics data to create`,
|
||||
}
|
||||
},
|
||||
required: ["data"]
|
||||
},
|
||||
modelName,
|
||||
operation: OneUptimeOperation.Create,
|
||||
modelType: ModelType.Analytics,
|
||||
singularName,
|
||||
pluralName,
|
||||
tableName: modelName,
|
||||
apiPath
|
||||
});
|
||||
|
||||
// LIST Tool for Analytics (most common operation)
|
||||
tools.push({
|
||||
name: `oneuptime_list${pluralName.replace(/\s+/g, '')}`,
|
||||
description: `Query ${pluralName} analytics data from OneUptime`,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "object",
|
||||
description: `Query filters for ${pluralName} analytics data`,
|
||||
},
|
||||
select: {
|
||||
type: "object",
|
||||
description: "Fields to select",
|
||||
},
|
||||
skip: {
|
||||
type: "number",
|
||||
description: "Number of records to skip",
|
||||
},
|
||||
limit: {
|
||||
type: "number",
|
||||
description: "Maximum number of records to return",
|
||||
},
|
||||
sort: {
|
||||
type: "object",
|
||||
description: "Sort order",
|
||||
}
|
||||
}
|
||||
},
|
||||
modelName,
|
||||
operation: OneUptimeOperation.List,
|
||||
modelType: ModelType.Analytics,
|
||||
singularName,
|
||||
pluralName,
|
||||
tableName: modelName,
|
||||
apiPath
|
||||
});
|
||||
|
||||
// COUNT Tool for Analytics
|
||||
tools.push({
|
||||
name: `oneuptime_count${pluralName.replace(/\s+/g, '')}`,
|
||||
description: `Count ${pluralName} analytics records in OneUptime`,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "object",
|
||||
description: `Query filters for counting ${pluralName} analytics data`,
|
||||
}
|
||||
}
|
||||
},
|
||||
modelName,
|
||||
operation: OneUptimeOperation.Count,
|
||||
modelType: ModelType.Analytics,
|
||||
singularName,
|
||||
pluralName,
|
||||
tableName: modelName,
|
||||
apiPath
|
||||
});
|
||||
|
||||
return {
|
||||
tools,
|
||||
modelInfo: {
|
||||
tableName: modelName,
|
||||
singularName,
|
||||
pluralName,
|
||||
modelType: ModelType.Analytics,
|
||||
apiPath
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
16994
MCP/package-lock.json
generated
Normal file
16994
MCP/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,6 @@
|
||||
"version": "1.0.0",
|
||||
"description": "OneUptime MCP Server",
|
||||
"main": "Index.ts",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"oneuptime-mcp-server": "./build/Index.js"
|
||||
},
|
||||
@@ -23,11 +22,13 @@
|
||||
"Common": "file:../Common",
|
||||
"@modelcontextprotocol/sdk": "^0.6.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"dotenv": "^16.4.5"
|
||||
"dotenv": "^16.4.5",
|
||||
"node-fetch": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.5.0",
|
||||
"@types/node": "^17.0.31",
|
||||
"@types/node-fetch": "^2.6.4",
|
||||
"jest": "^28.1.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"ts-jest": "^28.0.2",
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"lib": ["es6"],
|
||||
"lib": ["es6", "es2017", "es2020"],
|
||||
"allowJs": true,
|
||||
"outDir": "./build",
|
||||
"rootDir": "./",
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": "./",
|
||||
"baseUrl": "../",
|
||||
"paths": {
|
||||
"Common/*": ["../Common/*"]
|
||||
"Common/*": ["Common/*"]
|
||||
},
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
@@ -21,7 +20,19 @@
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
"**/*.ts",
|
||||
"../Common/Models/**/*.ts",
|
||||
"../Common/Types/API/**/*.ts",
|
||||
"../Common/Types/Database/**/*.ts",
|
||||
"../Common/Types/AnalyticsDatabase/**/*.ts",
|
||||
"../Common/Types/JSON.ts",
|
||||
"../Common/Types/ObjectID.ts",
|
||||
"../Common/Types/Permission.ts",
|
||||
"../Common/Types/Dictionary.ts",
|
||||
"../Common/Types/Sleep.ts",
|
||||
"../Common/Types/Exception/**/*.ts",
|
||||
"../Common/Server/Utils/Logger.ts",
|
||||
"../Common/Utils/API.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
|
||||
Reference in New Issue
Block a user