Skip to content

MCP Server Development Standards (Optum)

Standards, patterns, and guardrails for building Model Context Protocol (MCP) servers compatible with Wall-E, VS Code Copilot, and enterprise systems.

experimental
IDE:
claude
codex
vscode
Version:
1.0.0
Owner:epic-platform-sre
mcp
sdk
wall-e
security
optum

MCP Server Development Standards

Overview

Model Context Protocol (MCP) servers provide standardized interfaces for LLM agents to interact with enterprise systems. These standards ensure security, maintainability, and compatibility with Wall-E and VS Code Copilot.

Architecture Requirements

Server Structure

MUST organize MCP servers with this structure:

mcp-server-{name}/
├── src/
│   ├── index.ts           # Entry point with server setup
│   ├── tools/             # Tool implementations
│   │   ├── index.ts       # Tool registry
│   │   └── {tool-name}.ts # Individual tool files
│   ├── resources/         # Resource implementations
│   │   ├── index.ts       # Resource registry
│   │   └── {resource}.ts  # Individual resource files
│   ├── prompts/           # Prompt templates
│   ├── types/             # TypeScript type definitions
│   └── lib/               # Shared utilities
├── schemas/               # JSON Schema definitions
├── tests/                 # Test files
├── package.json
├── tsconfig.json
└── README.md

Naming Conventions

MUST follow these naming conventions:

ElementConventionExample
Server namemcp-server-{domain}mcp-server-servicenow
Tool nameskebab-caseget-incident-details
Resource URIs{domain}://{type}/{id}servicenow://incident/INC0012345
Prompt nameskebab-casetriage-incident

NEVER rename tool or resource identifiers after publication—they are API surface.

Tool Development

Tool Classification

MUST classify every tool by risk level:

type ToolRiskLevel = 'read-only' | 'low-risk-write' | 'high-risk-write';

interface ToolMetadata {
  name: string;
  description: string;
  riskLevel: ToolRiskLevel;
  requiresApproval: boolean;
  auditRequired: boolean;
}
Risk LevelDescriptionExamples
read-onlyNo state changesget-incident, list-nodes, query-logs
low-risk-writeReversible changesadd-comment, update-tag, create-draft
high-risk-writeIrreversible or sensitiveclose-incident, delete-resource, execute-command

Tool Implementation Pattern

MUST implement tools with this pattern:

import { z } from 'zod';
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';

// 1. Define input schema with Zod
const GetIncidentInputSchema = z.object({
  incidentId: z
    .string()
    .regex(/^INC\d{7,10}$/, 'Must be valid incident ID format')
    .describe('ServiceNow incident ID (e.g., INC0012345)'),
  includeComments: z.boolean().default(false).describe('Include incident comments in response'),
});

type GetIncidentInput = z.infer<typeof GetIncidentInputSchema>;

// 2. Implement tool handler
export async function getIncident(
  input: GetIncidentInput,
  context: ToolContext,
): Promise<ToolResult> {
  // Validate input (Zod already validated schema)
  const { incidentId, includeComments } = input;

  // Log tool invocation for audit
  context.logger.info('tool_invocation', {
    tool: 'get-incident-details',
    incidentId,
    correlationId: context.correlationId,
    userId: context.userId,
  });

  try {
    // Call external service
    const incident = await context.servicenow.getIncident(incidentId, {
      includeComments,
    });

    if (!incident) {
      throw new McpError(ErrorCode.InvalidParams, `Incident ${incidentId} not found`);
    }

    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(incident, null, 2),
        },
      ],
      isError: false,
    };
  } catch (error) {
    context.logger.error('tool_error', {
      tool: 'get-incident-details',
      error: error.message,
      correlationId: context.correlationId,
    });
    throw error;
  }
}

// 3. Register tool with metadata
export const getIncidentTool: ToolDefinition = {
  name: 'get-incident-details',
  description: 'Retrieve details of a ServiceNow incident by ID',
  inputSchema: zodToJsonSchema(GetIncidentInputSchema),
  metadata: {
    riskLevel: 'read-only',
    requiresApproval: false,
    auditRequired: true,
  },
  handler: getIncident,
};

Write Operation Safeguards

MUST implement safeguards for write operations:

// For low-risk-write tools
export async function addIncidentComment(
  input: AddCommentInput,
  context: ToolContext,
): Promise<ToolResult> {
  // 1. Validate user has permission
  if (!context.permissions.canComment) {
    throw new McpError(ErrorCode.InvalidRequest, 'User lacks permission to add comments');
  }

  // 2. Log before action
  context.logger.info('write_operation_start', {
    tool: 'add-incident-comment',
    incidentId: input.incidentId,
    correlationId: context.correlationId,
  });

  // 3. Perform operation
  const result = await context.servicenow.addComment(input.incidentId, input.comment);

  // 4. Log after action
  context.logger.info('write_operation_complete', {
    tool: 'add-incident-comment',
    incidentId: input.incidentId,
    commentId: result.commentId,
    correlationId: context.correlationId,
  });

  return { content: [{ type: 'text', text: 'Comment added successfully' }] };
}

// For high-risk-write tools
export async function closeIncident(
  input: CloseIncidentInput,
  context: ToolContext,
): Promise<ToolResult> {
  // 1. REQUIRE explicit approval token for high-risk operations
  if (!input.approvalToken) {
    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify({
            status: 'approval_required',
            message: 'Closing an incident requires explicit approval',
            approvalPrompt: `Confirm closing ${input.incidentId} with resolution: ${input.resolution}`,
            action: 'close-incident',
            params: { incidentId: input.incidentId },
          }),
        },
      ],
      isError: false,
    };
  }

  // 2. Validate approval token
  const isValid = await context.approvalService.validate(input.approvalToken);
  if (!isValid) {
    throw new McpError(ErrorCode.InvalidRequest, 'Invalid approval token');
  }

  // 3. Proceed with operation
  // ...
}

Resource Development

Resource URI Format

MUST use consistent URI format:

// URI format: {protocol}://{type}/{identifier}[/{subresource}]
const resourceUri = 'servicenow://incident/INC0012345';
const subResourceUri = 'servicenow://incident/INC0012345/comments';

// Parse URI helper
function parseResourceUri(uri: string): ResourceRef {
  const url = new URL(uri);
  return {
    protocol: url.protocol.replace(':', ''),
    type: url.hostname,
    id: url.pathname.split('/')[1],
    subresource: url.pathname.split('/')[2] || null,
  };
}

Resource Implementation

export const incidentResource: ResourceDefinition = {
  uriTemplate: 'servicenow://incident/{incidentId}',
  name: 'ServiceNow Incident',
  description: 'A ServiceNow incident record',
  mimeType: 'application/json',

  async read(uri: string, context: ResourceContext): Promise<ResourceContent> {
    const { id } = parseResourceUri(uri);

    const incident = await context.servicenow.getIncident(id);
    if (!incident) {
      throw new McpError(ErrorCode.InvalidParams, `Incident ${id} not found`);
    }

    return {
      uri,
      mimeType: 'application/json',
      text: JSON.stringify(incident, null, 2),
    };
  },

  async list(context: ResourceContext): Promise<ResourceRef[]> {
    const incidents = await context.servicenow.listRecentIncidents(50);
    return incidents.map((inc) => ({
      uri: `servicenow://incident/${inc.number}`,
      name: `${inc.number}: ${inc.short_description}`,
    }));
  },
};

Security Requirements

Authentication and Authorization

MUST implement enterprise authentication:

// server.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';

const server = new Server(
  { name: 'mcp-server-servicenow', version: '1.0.0' },
  { capabilities: { tools: {}, resources: {} } },
);

// Middleware: Validate auth on every request
server.setRequestHandler(async (request, context) => {
  // 1. Extract credentials from transport
  const authHeader = context.transport.headers?.authorization;
  if (!authHeader) {
    throw new McpError(ErrorCode.InvalidRequest, 'Missing authorization');
  }

  // 2. Validate token with enterprise auth service
  const user = await authService.validateToken(authHeader);
  if (!user) {
    throw new McpError(ErrorCode.InvalidRequest, 'Invalid token');
  }

  // 3. Check RBAC permissions for requested operation
  const hasPermission = await rbacService.checkPermission(user, request.method, request.params);
  if (!hasPermission) {
    throw new McpError(ErrorCode.InvalidRequest, 'Insufficient permissions');
  }

  // 4. Add user context for downstream use
  context.userId = user.id;
  context.permissions = user.permissions;

  return next(request, context);
});

Secrets Management

NEVER embed secrets in code or prompts:

// ❌ NEVER do this
const apiKey = 'sk-1234567890abcdef'; // pragma: allowlist secret

// ✅ ALWAYS use environment variables
const apiKey = process.env.SERVICENOW_API_KEY;
if (!apiKey) {
  throw new Error('SERVICENOW_API_KEY environment variable required');
}

// ✅ PREFER secrets manager integration
import { SecretClient } from '@azure/keyvault-secrets';

async function getApiKey(): Promise<string> {
  const client = new SecretClient(process.env.KEYVAULT_URL!, new DefaultAzureCredential());
  const secret = await client.getSecret('servicenow-api-key');
  return secret.value!;
}

Input Sanitization

MUST sanitize all user inputs:

import { z } from 'zod';
import DOMPurify from 'isomorphic-dompurify';

// Sanitize string inputs
const sanitizedString = z.string().transform((val) => {
  // Remove HTML/script injection
  return DOMPurify.sanitize(val, { ALLOWED_TAGS: [] });
});

// Validate and constrain queries
const QueryInputSchema = z.object({
  table: z.enum(['incident', 'change_request', 'problem']),
  query: z
    .string()
    .max(1000)
    .refine((q) => !q.includes(';'), 'Query cannot contain semicolons'),
  limit: z.number().int().min(1).max(100).default(25),
});

Observability Requirements

Structured Logging

MUST emit structured logs:

interface LogContext {
  correlationId: string;
  userId: string;
  tool?: string;
  resource?: string;
  duration?: number;
  error?: string;
}

const logger = {
  info(event: string, context: LogContext) {
    console.log(
      JSON.stringify({
        timestamp: new Date().toISOString(),
        level: 'info',
        event,
        ...context,
      }),
    );
  },

  error(event: string, context: LogContext) {
    console.error(
      JSON.stringify({
        timestamp: new Date().toISOString(),
        level: 'error',
        event,
        ...context,
      }),
    );
  },
};

Metrics

MUST expose metrics for monitoring:

import { Counter, Histogram } from 'prom-client';

const toolInvocations = new Counter({
  name: 'mcp_tool_invocations_total',
  help: 'Total tool invocations',
  labelNames: ['tool', 'status', 'risk_level'],
});

const toolDuration = new Histogram({
  name: 'mcp_tool_duration_seconds',
  help: 'Tool execution duration',
  labelNames: ['tool'],
  buckets: [0.1, 0.5, 1, 2, 5, 10],
});

// Wrap tool handlers
async function instrumentedHandler(
  tool: ToolDefinition,
  input: unknown,
  context: ToolContext,
): Promise<ToolResult> {
  const timer = toolDuration.startTimer({ tool: tool.name });
  try {
    const result = await tool.handler(input, context);
    toolInvocations.inc({
      tool: tool.name,
      status: 'success',
      risk_level: tool.metadata.riskLevel,
    });
    return result;
  } catch (error) {
    toolInvocations.inc({
      tool: tool.name,
      status: 'error',
      risk_level: tool.metadata.riskLevel,
    });
    throw error;
  } finally {
    timer();
  }
}

Documentation Requirements

MUST include in README:

  1. Quickstart: How to install and run
  2. Tool Reference: Each tool with input/output schemas
  3. Resource Reference: Each resource with URI format
  4. Security Model: Auth requirements, permissions
  5. Error Codes: All possible errors with meanings

Example tool documentation:

## get-incident-details

Retrieve details of a ServiceNow incident.

**Risk Level**: read-only

### Input Schema

| Parameter       | Type    | Required | Description                         |
| --------------- | ------- | -------- | ----------------------------------- |
| incidentId      | string  | Yes      | ServiceNow incident ID (INC0012345) |
| includeComments | boolean | No       | Include comments (default: false)   |

### Output

```json
{
  "number": "INC0012345",
  "short_description": "Application not responding",
  "state": "In Progress",
  "priority": "2 - High",
  "assigned_to": "[email protected]"
}
```

Errors

CodeDescription
InvalidParamsIncident ID format invalid or not found
UnauthorizedUser lacks permission to view incident

## Terraform Integration

**NEVER** execute `terraform apply` from MCP tools:

```typescript
// ✅ Read-only Terraform operations allowed
export const terraformPlanTool: ToolDefinition = {
  name: 'terraform-plan',
  riskLevel: 'read-only',
  async handler(input, context) {
    // Generate plan output for review
    const plan = await terraform.plan(input.workingDir);
    return { content: [{ type: 'text', text: plan }] };
  },
};

// ❌ NEVER implement apply in MCP
// Applies MUST go through TFE + CI/CD

Wall-E Integration

When integrating with Wall-E:

  1. MUST register tool risk levels with Wall-E policy engine
  2. MUST pass correlation IDs for distributed tracing
  3. MUST support kill-switch for emergency tool disabling
  4. SHOULD map tools to Wall-E agent capabilities
# wall-e-config.yaml
agents:
  - name: servicenow-agent
    mcp_server: mcp-server-servicenow
    tools:
      - name: get-incident-details
        wall_e_policy: allow
      - name: add-incident-comment
        wall_e_policy: allow_with_audit
      - name: close-incident
        wall_e_policy: require_approval

Related Assets

Wall-E Workflow Designer (Optum)

experimental

Assist with designing, reviewing, and optimizing multi-agent Wall-E workflows and MCP integrations following Optum enterprise patterns.

vscode
wall-e
orchestration
multi-agent
mcp
optum

Owner: epic-platform-sre

Wall-E Agent Composition Helper

experimental

Compose multiple specialized agents into a safe Wall-E workflow with proper MCP tool assignments, guardrails, and human-in-loop gates.

claude
codex
vscode
wall-e
orchestration
multi-agent
optum

Owner: epic-platform-sre

Wall-E RAG Tuning Helper

experimental

Recommend RAG chunking, embedding, and retrieval parameters for Wall-E contexts based on corpus characteristics and performance requirements.

claude
codex
vscode
wall-e
rag
retrieval
optum

Owner: epic-platform-sre

Wall-E Orchestration Patterns (Optum)

experimental

Patterns and guardrails for composing safe multi-agent workflows in Wall-E (Wide Array Large Language Engine), Optum's enterprise AI orchestration platform.

claude
codex
vscode
wall-e
orchestration
multi-agent
safety
optum

Owner: epic-platform-sre

security-agent-cca-fix

active

Run or explain Security Agent remediation through GitHub Copilot Cloud Agent from a pip-installed setup. Use when Codex needs to use --executor cca or --executor auto, create remote Copilot/CCA remediation tasks, reason about CCA budget/status, or compare local Codex execution with remote GitHub Cloud Agent execution without cloning the controller repo.

codex
security
cca
github
copilot
remediation
+3

Owner: edi-security-agent

security-agent-discovery

active

Discover, inspect, import, refresh, and export Security Agent vulnerability data from a pip-installed setup. Use when Codex needs to list Azure Defender findings, filter by repo/severity/CVE/fixable state, refresh the local UI vulnerability cache, import Security Platform findings through explicit cookie and DPoP values, or explain discovery-only workflows without cloning the controller repo.

codex
security
azure-defender
vulnerability
discovery
cve
+2

Owner: edi-security-agent