npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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

  1. Install dependencies:
npm install
  1. 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
  1. Build the project:
npm run build

Usage

Running the Server

Start the server in production mode:

npm start

Or run in development mode with auto-reload:

npm run dev

Testing 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:list

Test 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 --help

Available 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 name
  • include_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 statements
  • return_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:

  1. Create a new tool file in src/tools/ (e.g., src/tools/userManagement.ts)

  2. 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>;
  1. 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}`
        }]
      };
    }
  }
);
  1. 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 operations
    • destructiveHint: true for delete/destructive operations
    • idempotentHint: true if repeated calls have same effect
    • openWorldHint: true if tool can work with arbitrary inputs

Building

# Clean previous build
npm run clean

# Build TypeScript to JavaScript
npm run build

Scripts

  • npm start - Run the built server
  • npm run dev - Run in development mode with auto-reload
  • npm run build - Compile TypeScript to JavaScript
  • npm run clean - Remove the dist directory

Architecture

This MCP server follows best practices from the Node/TypeScript MCP Server Implementation Guide: