#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import axios, { AxiosInstance } from "axios";
const BASE_URL = "https://operator.opus.com";
interface OpusConfig {
serviceKey: string;
}
class OpusMCPServer {
private server: Server;
private axiosInstance: AxiosInstance;
private config: OpusConfig;
constructor() {
this.config = {
serviceKey: process.env.OPUS_SERVICE_KEY || "",
};
if (!this.config.serviceKey) {
console.error(
"ERROR: OPUS_SERVICE_KEY environment variable is required"
);
process.exit(1);
}
this.axiosInstance = axios.create({
baseURL: BASE_URL,
headers: {
"x-service-key": this.config.serviceKey,
"Content-Type": "application/json",
},
});
this.server = new Server(
{
name: "opus-mcp-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
this.setupErrorHandling();
}
private setupErrorHandling(): void {
this.server.onerror = (error) => {
console.error("[MCP Error]", error);
};
process.on("SIGINT", async () => {
await this.server.close();
process.exit(0);
});
}
private setupToolHandlers(): void {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: this.getTools(),
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "get_workflow_details":
return await this.getWorkflowDetails(args);
case "initiate_job":
return await this.initiateJob(args);
case "generate_file_upload_url":
return await this.generateFileUploadUrl(args);
case "execute_job":
return await this.executeJob(args);
case "get_job_status":
return await this.getJobStatus(args);
case "get_job_results":
return await this.getJobResults(args);
case "get_job_audit_log":
return await this.getJobAuditLog(args);
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error: any) {
const errorMessage =
error.response?.data?.message || error.message || "Unknown error";
return {
content: [
{
type: "text",
text: `Error: ${errorMessage}`,
},
],
isError: true,
};
}
});
}
private getTools(): Tool[] {
return [
{
name: "get_workflow_details",
description:
"Get workflow details including jobPayloadSchema that defines required inputs for a workflow",
inputSchema: {
type: "object",
properties: {
workflowId: {
type: "string",
description: "The ID of the workflow to retrieve details for",
},
},
required: ["workflowId"],
},
},
{
name: "initiate_job",
description:
"Initiate a new job for a workflow. Returns jobExecutionId required for subsequent operations",
inputSchema: {
type: "object",
properties: {
workflowId: {
type: "string",
description: "The ID of the workflow to execute",
},
title: {
type: "string",
description: "Title for the job",
},
description: {
type: "string",
description: "Description for the job",
},
},
required: ["workflowId", "title", "description"],
},
},
{
name: "generate_file_upload_url",
description:
"Generate presigned URL for file upload. Returns presignedUrl (for uploading) and fileUrl (to reference in job execution)",
inputSchema: {
type: "object",
properties: {
fileExtension: {
type: "string",
description:
"File extension with dot (e.g., .pdf, .jpeg, .png, .docx, .csv, .xlsx, .txt, .json, .html, .xml)",
},
accessScope: {
type: "string",
enum: ["all", "user", "workspace", "organization"],
description: "Access scope for the file",
default: "organization",
},
},
required: ["fileExtension"],
},
},
{
name: "execute_job",
description:
"Execute a job with populated input values. Use jobPayloadSchema from get_workflow_details to structure inputs correctly",
inputSchema: {
type: "object",
properties: {
jobExecutionId: {
type: "string",
description:
"The job execution ID from initiate_job response",
},
jobPayloadSchemaInstance: {
type: "object",
description:
"Job payload with all inputs populated according to workflow schema",
},
},
required: ["jobExecutionId", "jobPayloadSchemaInstance"],
},
},
{
name: "get_job_status",
description:
"Get the current status of a job execution (e.g., IN PROGRESS, COMPLETED, FAILED)",
inputSchema: {
type: "object",
properties: {
jobExecutionId: {
type: "string",
description: "The job execution ID to check status for",
},
},
required: ["jobExecutionId"],
},
},
{
name: "get_job_results",
description:
"Get the results of a completed job execution. Only works when job status is COMPLETED",
inputSchema: {
type: "object",
properties: {
jobExecutionId: {
type: "string",
description: "The job execution ID to retrieve results for",
},
},
required: ["jobExecutionId"],
},
},
{
name: "get_job_audit_log",
description:
"Get detailed audit log of all system actions during job execution",
inputSchema: {
type: "object",
properties: {
jobExecutionId: {
type: "string",
description: "The job execution ID to retrieve audit log for",
},
},
required: ["jobExecutionId"],
},
},
];
}
private async getWorkflowDetails(args: any) {
const { workflowId } = args;
const response = await this.axiosInstance.get(
`/workflow/${workflowId}`
);
return {
content: [
{
type: "text",
text: JSON.stringify(response.data, null, 2),
},
],
};
}
private async initiateJob(args: any) {
const { workflowId, title, description } = args;
const response = await this.axiosInstance.post("/job/initiate", {
workflowId,
title,
description,
});
return {
content: [
{
type: "text",
text: JSON.stringify(response.data, null, 2),
},
],
};
}
private async generateFileUploadUrl(args: any) {
const { fileExtension, accessScope = "organization" } = args;
const response = await this.axiosInstance.post("/job/file/upload", {
fileExtension,
accessScope,
});
return {
content: [
{
type: "text",
text: JSON.stringify(
{
presignedUrl: response.data.presignedUrl,
fileUrl: response.data.fileUrl,
instructions:
"Use presignedUrl to upload file via PUT request (no auth headers). Use fileUrl in job execution payload.",
},
null,
2
),
},
],
};
}
private async executeJob(args: any) {
const { jobExecutionId, jobPayloadSchemaInstance } = args;
const response = await this.axiosInstance.post("/job/execute", {
jobExecutionId,
jobPayloadSchemaInstance,
});
return {
content: [
{
type: "text",
text: JSON.stringify(response.data, null, 2),
},
],
};
}
private async getJobStatus(args: any) {
const { jobExecutionId } = args;
const response = await this.axiosInstance.get(
`/job/${jobExecutionId}/status`
);
return {
content: [
{
type: "text",
text: JSON.stringify(response.data, null, 2),
},
],
};
}
private async getJobResults(args: any) {
const { jobExecutionId } = args;
const response = await this.axiosInstance.get(
`/job/${jobExecutionId}/results`
);
return {
content: [
{
type: "text",
text: JSON.stringify(response.data, null, 2),
},
],
};
}
private async getJobAuditLog(args: any) {
const { jobExecutionId } = args;
const response = await this.axiosInstance.get(
`/job/${jobExecutionId}/audit`
);
return {
content: [
{
type: "text",
text: JSON.stringify(response.data, null, 2),
},
],
};
}
async run(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("Opus MCP Server running on stdio");
}
}
const server = new OpusMCPServer();
server.run().catch(console.error);