watsonx-openpages-mcp-server-expertlabs
v1.0.0
Published
MCP server for Watsonx Orchestrate / OpenPages integration
Downloads
25
Readme
Watsonx Orchestrate / OpenPages MCP Server
A Model Context Protocol (MCP) server for integrating with Watsonx Orchestrate and OpenPages APIs. This server provides tools to interact with these services and serves as a template for building additional integrations.
Features
- Echo Tool: A simple test tool that verifies the MCP server is running correctly
- Schema Discovery: Discover OpenPages database schema with localized names using
wxo_op_discover_schema - Type Safety: Built with TypeScript and Zod for runtime validation
Prerequisites
- Node.js 22 or higher
- npm or yarn package manager
- OpenPages instance with REST API v2 access
Installation
- Install dependencies:
npm install- Configure environment variables:
cp .env.example .env
# Edit .env with your OpenPages credentials:
# - OPENPAGES_BASE_URL: Your OpenPages instance URL (e.g., https://your-org.op.ibmcloud.com)
# - OPENPAGES_USERNAME: Your username
# - OPENPAGES_PASSWORD: Your password- Build the project:
npm run buildUsage
Running the Server
Start the server in production mode:
npm startOr run in development mode with auto-reload:
npm run devTesting Tools
Since this MCP server uses STDIO transport, you cannot interact with it directly in a terminal. Use the provided test script to test any tool:
List all available tools:
npm run test:listTest the echo tool:
npm run test:echo '{"message":"Hello MCP!"}'Test any tool with custom arguments:
npm run test:tool <tool_name> '<json_arguments>'Examples:
# Test echo with a custom message
npm run test:tool wxo_op_echo '{"message":"Testing the server"}'
# Test with complex JSON arguments
npm run test:tool wxo_op_create_user '{"username":"john_doe","email":"[email protected]","age":30}'
# Test with arrays and nested objects
npm run test:tool some_tool '{"tags":["tag1","tag2"],"preferences":{"theme":"dark"}}'Direct usage of test script:
# After building the project
node test-tool.js wxo_op_echo '{"message":"Hello!"}'
node test-tool.js --list
node test-tool.js --helpAvailable Tools
wxo_op_echo
A simple echo tool to verify the MCP server is running correctly.
Input:
message(string): The message to echo back (1-1000 characters)
Output:
{
"original_message": "Hello, MCP!",
"echoed_at": "2024-01-01T12:00:00.000Z",
"server": "Watsonx Orchestrate / OpenPages MCP Server",
"status": "success"
}Example Usage:
{
"message": "Testing the MCP server connection"
}wxo_op_discover_schema
Discovers OpenPages database schema with localized names. This tool helps LLMs understand the database structure by providing human-readable localized names alongside technical database names.
Input:
search_term(string, optional): Filter objects/fields by keyword (e.g., 'risk', 'assessment')object_name(string, optional): Get specific object details by localized or technical nameinclude_fields(boolean): Include field definitions (default: true)include_enums(boolean): Include enumerated values (default: true)limit(number): Max objects to return 1-100 (default: 20)language(string): Language code for localized names (default: 'en')
Output:
{
"matches": [{
"object_technical": "SOXRisk",
"object_localized": "Risk",
"description": "Risk assessment records",
"fields": [{
"technical": "OPSS_RISK_INHERENT_RATING_C",
"localized": "OPSS-Risk:Inherent Risk Rating",
"field_group": "OPSS-Risk",
"data_type": "enum",
"is_enum": true,
"enum_values": [
{"technical": "1", "localized": "Low"},
{"technical": "5", "localized": "High"}
]
}],
"primary_key": "ID"
}],
"total_available": 150,
"language_used": "en",
"execution_time_ms": 245
}Field Groups:
Fields in OpenPages often belong to field groups (e.g., "OPSS-Risk"). The localized name includes the group prefix: OPSS-Risk:Field Name. When querying, you can use either:
- Full name with group:
[OPSS-Risk:Residual Risk Rating] - Just the field name (if unique within the table):
[Residual Risk Rating]
Example Usage:
# Discover all objects
npm run test:tool wxo_op_discover_schema '{}'
# Search for risk objects
npm run test:tool wxo_op_discover_schema '{"search_term":"risk"}'
# Get specific object
npm run test:tool wxo_op_discover_schema '{"object_name":"SOXRisk"}'wxo_op_execute_query
Executes database queries using localized names in square brackets. This tool automatically resolves localized names to technical names and executes queries against OpenPages.
Input:
localized_sql(string, required): SQL query with bracketed localized names (e.g.,SELECT [Name] FROM [SOXRisk] WHERE [Rating] = 'High')parameters(object, optional): Query parameters for prepared statementsreturn_both_names(boolean): Return both localized and technical column names (default: true)max_rows(number): Max rows to return 1-1000 (default: 100)offset(number): Rows to skip for pagination (default: 0)language(string): Language code (default: 'en')
SQL Syntax:
- Use
[Localized Name]for tables, columns, and enum values - Fields with groups:
[OPSS-Risk:Residual Risk Rating]or[Residual Risk Rating] - Enum values in WHERE clauses:
WHERE [Rating] = 'Low'(maps to technical value) - Supports standard SQL: SELECT, FROM, WHERE, JOIN, ORDER BY, etc.
Output:
{
"success": true,
"query_resolved": {
"original_sql": "SELECT [Name], [OPSS-Risk:Residual Risk Rating] FROM [SOXRisk] WHERE [OPSS-Risk:Residual Risk Rating] = 'Low'",
"technical_sql": "SELECT NAME, OPSS_RISK_RESIDUAL_RATING_C FROM SOXRISK WHERE OPSS_RISK_RESIDUAL_RATING_C = '1'",
"name_mappings": [
{"bracketed_name": "[SOXRisk]", "technical_name": "SOXRISK", "type": "table", "confidence": "high"},
{"bracketed_name": "[Name]", "technical_name": "NAME", "type": "column", "confidence": "high"},
{"bracketed_name": "[OPSS-Risk:Residual Risk Rating]", "technical_name": "OPSS_RISK_RESIDUAL_RATING_C", "type": "column", "confidence": "high"},
{"bracketed_name": "'Low'", "technical_name": "'1'", "type": "enum_value", "confidence": "high"}
]
},
"results": [
{
"localized": {"Name": "Data Breach Risk", "OPSS-Risk:Residual Risk Rating": "Low"},
"technical": {"NAME": "Data Breach Risk", "OPSS_RISK_RESIDUAL_RATING_C": "1"}
}
],
"row_count": 1,
"has_more": false,
"execution_time_ms": 320
}Example Usage:
# Basic query
npm run test:tool wxo_op_execute_query '{"localized_sql":"SELECT [Name] FROM [SOXRisk]"}'
# Query with field group notation
npm run test:tool wxo_op_execute_query '{"localized_sql":"SELECT [Name], [OPSS-Risk:Residual Risk Rating] FROM [SOXRisk] WHERE [OPSS-Risk:Residual Risk Rating] = \'Low\'"}'
# Query with pagination
npm run test:tool wxo_op_execute_query '{"localized_sql":"SELECT * FROM [SOXRisk]", "max_rows": 50, "offset": 100}'Project Structure
openpages-mcp-server/
├── package.json # Project dependencies and scripts
├── tsconfig.json # TypeScript configuration
├── README.md # This file
├── src/
│ ├── index.ts # Main server entry point with tool registration
│ ├── tools/ # Tool implementations (add new tools here)
│ ├── services/ # API clients and shared utilities
│ └── schemas/ # Zod validation schemas
└── dist/ # Built JavaScript files (generated)Development
Adding New Tools
Follow these steps to add a new tool to the MCP server:
Create a new tool file in
src/tools/(e.g.,src/tools/userManagement.ts)Define your Zod schema with multiple data types for input validation:
import { z } from "zod";
// Define enums for constrained values
enum UserRole {
ADMIN = "admin",
USER = "user",
VIEWER = "viewer"
}
enum ResponseFormat {
MARKDOWN = "markdown",
JSON = "json"
}
// Create comprehensive input schema with various data types
const CreateUserInputSchema = z.object({
// String with validation
username: z.string()
.min(3, "Username must be at least 3 characters")
.max(50, "Username must not exceed 50 characters")
.regex(/^[a-zA-Z0-9_-]+$/, "Username can only contain letters, numbers, hyphens, and underscores")
.describe("Unique username for the new user"),
// Email validation
email: z.string()
.email("Invalid email format")
.describe("User's email address"),
// Number with constraints
age: z.number()
.int("Age must be a whole number")
.min(18, "User must be at least 18 years old")
.max(120, "Age cannot exceed 120")
.optional()
.describe("User's age (optional)"),
// Boolean
active: z.boolean()
.default(true)
.describe("Whether the user account is active"),
// Enum
role: z.nativeEnum(UserRole)
.default(UserRole.USER)
.describe("User role: 'admin', 'user', or 'viewer'"),
// Array of strings
tags: z.array(z.string())
.min(1, "At least one tag is required")
.max(10, "Cannot exceed 10 tags")
.default([])
.describe("Tags associated with the user"),
// Nested object
preferences: z.object({
theme: z.enum(["light", "dark"]).default("light"),
notifications: z.boolean().default(true),
language: z.string().default("en")
}).optional()
.describe("User preferences (optional)"),
// Response format
response_format: z.nativeEnum(ResponseFormat)
.default(ResponseFormat.MARKDOWN)
.describe("Output format: 'markdown' or 'json'")
}).strict();
type CreateUserInput = z.infer<typeof CreateUserInputSchema>;- Register the tool in
src/index.ts:
server.registerTool(
"wxo_op_create_user",
{
title: "Create User",
description: `Create a new user in the system with specified attributes.
This tool creates a user account with validation for all input fields.
It demonstrates handling multiple data types including strings, numbers,
booleans, enums, arrays, and nested objects.
Args:
- username (string): Unique username (3-50 chars, alphanumeric with hyphens/underscores)
- email (string): Valid email address
- age (number, optional): User's age (18-120)
- active (boolean): Whether account is active (default: true)
- role (enum): User role - 'admin', 'user', or 'viewer' (default: 'user')
- tags (string[]): Array of tags (1-10 items, default: [])
- preferences (object, optional): User preferences with theme, notifications, language
- response_format (enum): Output format - 'markdown' or 'json' (default: 'markdown')
Returns:
For JSON format:
{
"user_id": string,
"username": string,
"email": string,
"age": number | null,
"active": boolean,
"role": string,
"tags": string[],
"preferences": object | null,
"created_at": string,
"status": "success"
}
Examples:
- Basic user: { "username": "john_doe", "email": "[email protected]" }
- Full user: { "username": "admin_user", "email": "[email protected]",
"age": 30, "role": "admin", "tags": ["developer", "team-lead"],
"preferences": { "theme": "dark", "notifications": true } }`,
inputSchema: CreateUserInputSchema,
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
openWorldHint: false
}
},
async (params: CreateUserInput) => {
try {
// Simulate user creation (replace with actual API call)
const output = {
user_id: `user_${Date.now()}`,
username: params.username,
email: params.email,
age: params.age ?? null,
active: params.active,
role: params.role,
tags: params.tags,
preferences: params.preferences ?? null,
created_at: new Date().toISOString(),
status: "success"
};
// Format response based on requested format
let textContent: string;
if (params.response_format === ResponseFormat.MARKDOWN) {
textContent = `# User Created Successfully\n\n` +
`- **User ID**: ${output.user_id}\n` +
`- **Username**: ${output.username}\n` +
`- **Email**: ${output.email}\n` +
`- **Role**: ${output.role}\n` +
`- **Active**: ${output.active}\n` +
(output.age ? `- **Age**: ${output.age}\n` : '') +
(output.tags.length > 0 ? `- **Tags**: ${output.tags.join(', ')}\n` : '') +
`- **Created**: ${output.created_at}`;
} else {
textContent = JSON.stringify(output, null, 2);
}
return {
content: [{ type: "text", text: textContent }],
structuredContent: output
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [{
type: "text",
text: `Error creating user: ${errorMessage}`
}]
};
}
}
);- Test your tool
Key Points for New Tools
- Use
.strict()on Zod schemas to prevent unexpected fields - Add descriptive error messages for all validation rules
- Include
.describe()for each field to document the schema - Set appropriate defaults for optional fields
- Use enums for constrained string values
- Support multiple response formats (markdown and JSON)
- Return both text and structuredContent in responses
- Handle errors gracefully with clear, actionable messages
- Set correct annotations based on tool behavior:
readOnlyHint: true for read-only operationsdestructiveHint: true for delete/destructive operationsidempotentHint: true if repeated calls have same effectopenWorldHint: true if tool can work with arbitrary inputs
Building
# Clean previous build
npm run clean
# Build TypeScript to JavaScript
npm run buildScripts
npm start- Run the built servernpm run dev- Run in development mode with auto-reloadnpm run build- Compile TypeScript to JavaScriptnpm run clean- Remove the dist directory
Architecture
This MCP server follows best practices from the Node/TypeScript MCP Server Implementation Guide:
