oclif MCP Server Plugin

by npjonath

Not rated
GitHub

About

An oclif CLI plugin that automatically discovers and serves commands via the Model Context Protocol (MCP).

Details

Author
npjonath
Categories
Developer Tools

Setup

Install oclif MCP Server Plugin in your MCP client (Claude Desktop, Cursor, Windsurf, and others).

Repository: https://github.com/npjonath/oclif-plugin-mcp-server

Follow the installation instructions in the repository README, then restart your MCP client.

An oclif CLI plugin that automatically discovers and serves commands via the Model Context Protocol (MCP).

Transform any oclif CLI into afully MCP 2025-06-18 compliantserver for seamless AI assistant integration

This plugin automatically converts your oclif CLI commands into afully MCP 2025-06-18 protocol compliant server, implementing the latestModel Context Protocol specification. It allows AI assistants like Claude, ChatGPT, and Cursor to discover and execute your CLI tools naturally through conversation.

πŸŽ‰Latest MCP Specification: Full compliance with MCP 2025-06-18 including:

- πŸ”’OAuth 2.1 Authorization: Complete OAuth 2.1 support with PKCE for secure HTTP transport
- πŸ”„Sampling Capability: Server-side LLM interaction requests for advanced AI workflows
- ❓Elicitation Support: Request additional user input and confirmations from clients
- πŸ“Structured Logging: Advanced logging capability with level management and notifications
- ⏳Progress Tracking: Enhanced progress tracking with cancellation support
- 🌐Protocol Version Headers: FullMCP-Protocol-Version: 2025-06-18header support
- πŸ›‘οΈEnhanced Security: Resource Indicators (RFC 8707) and improved authorization flows

TheModel Context Protocol (MCP)is an open standard that enables AI assistants to securely connect to external data sources and tools. With MCP, your CLI becomes a first-class citizen in AI workflows, allowing assistants to:

- πŸ”Discoveryour commands and resources automatically
- βœ…Validateinputs using type-safe schemas
- πŸš€Executecommands with proper error handling
- πŸ“ŠAccessresources with lazy loading and proper metadata
- πŸ”’Secureinteractions through standardized protocols

- πŸ” Auto-discovery: Automatically discovers and exposes oclif commands as MCP tools
- πŸ“ Schema Generation: Converts oclif arguments and flags to Zod schemas for type-safe execution
- πŸ“Š MCP-Compliant Resources: Full support for static and dynamic resources following MCP specification
- 🎯 Prompt Templates: Reusable prompt templates with argument validation and handlers
- 🌳 Workspace Roots: Automatic CLI working directory registration as MCP root
- πŸ”„ Lazy Loading: Resources are fetched on-demand through proper MCP endpoints
- πŸ›‘οΈ Error Handling: Graceful error handling with detailed feedback and proper JSON-RPC error codes
- βš™οΈ Zero Configuration: Works out-of-the-box with any oclif CLI
- πŸ“‹ Standards Compliant: Implements the official MCP 2025-06-18 specification
- βœ… Input Validation: Type-safe argument validation for all commands and prompts
- πŸ”” Smart Notifications: Debounced resource change notifications for optimal performance

- πŸ”’ OAuth 2.1 Security: Full OAuth 2.1 authorization server integration with PKCE support
- πŸ”„ Sampling Support: Server-side capability for LLM interaction requests
- ❓ Elicitation Framework: Request user input and confirmations through the client
- πŸ“ Structured Logging: Advanced logging with level management and client notifications
- ⏳ Progress Tracking: Enhanced progress tokens with cancellation support
- 🌐 Protocol Headers: ProperMCP-Protocol-Version: 2025-06-18header handling
- πŸ›‘οΈ Enhanced Authorization: Resource Indicators (RFC 8707) for secure token usage
- πŸ” Session Management: Advanced HTTP session handling with cleanup and monitoring

Embed plugin in your CLI code (Recommended)

{ "dependencies": { "oclif-plugin-mcp-server": "latest" }, "oclif": { "plugins": ["oclif-plugin-mcp-server"] } }
# Install directly from GitHub (requires oclif-plugin-plugins) your-cli plugins install npjonath/oclif-plugin-mcp-server # Verify installation your-cli mcp --help

Add your CLI to your AI assistant's MCP configuration:

{ "mcpServers": { "your-cli": { "command": "your-cli", "args": ["mcp"], "env": {} } } }
{ "mcpServers": { "your-cli": { "command": "your-cli", "args": ["mcp"] } } }

- Build your CLI:yarn build
- Generate manifest:npx oclif manifest
- Update your MCP configuration:

{ "mcpServers": { "your-cli-dev": { "command": "node <path_to_project_folder>/bin/dev.js", "args": ["mcp"] } } }
{ "mcpServers": { "your-cli-dev-http": { "command": "node <path_to_project_folder>/bin/dev.js", "args": ["mcp", "--transport", "http", "--port", "3000"] } } }

Your AI assistant can now discover and use your CLI commands and resources:

πŸ‘€ "Deploy my-app to staging and show me the deployment logs" πŸ€– "I'll deploy your application to staging and fetch the deployment logs." Executing: deploy my-app --environment staging βœ… Deploying my-app to staging Fetching resource: logs://deployment/my-app πŸ“Š Deployment completed successfully! πŸ” Logs: [deployment details...]

This plugin supports both MCP transport protocols as defined in theofficial specification:

πŸ“‘ Standard Input/Output (stdio) - Default

The default transport for local integrations and command-line tools.

# Start MCP server with stdio transport (default) your-cli mcp your-cli mcp --transport stdio

- Local integrations (Claude Desktop, Cursor)
- Command-line tools
- Simple process communication
- Shell scripts

HTTP-based transport with Server-Sent Events (SSE) for web integrations.

# Start MCP server with HTTP transport your-cli mcp --transport http --port 3000 --host 127.0.0.1

- Web-based integrations
- Client-server communication over HTTP
- Stateful sessions
- Multiple concurrent clients
- Resumable connections
- Docker containers

- JSON-RPC over HTTP: Client-to-server communication via POST requests
- Server-Sent Events (SSE): Server-to-client communication via GET requests
- Session Management: Stateful sessions withX-Session-Idheaders
- Protocol Headers:MCP-Protocol-Version: 2025-06-18on all responses
- OAuth 2.1 Integration: Secure authorization with PKCE support
- Resumability: Event IDs andLast-Event-IDheader support
- CORS Support: Configurable cross-origin resource sharing with security controls
- Health Check:/healthendpoint for monitoring with protocol version

- POST /- JSON-RPC requests (client-to-server)
- GET /events/:sessionId- SSE streams (server-to-client)
- DELETE /sessions/:sessionId- Session termination
- GET /health- Health check with protocol version
- GET /oauth/authorize- OAuth 2.1 authorization endpoint (if configured)
- GET /oauth/callback- OAuth 2.1 callback endpoint (if configured)

// Initialize HTTP MCP client with 2025-06-18 support const client = new MCPClient({ transport: 'http', endpoint: 'http://localhost:3000/', protocolVersion: '2025-06-18', // Optional OAuth configuration oauth: { authorizationUrl: 'http://localhost:3000/oauth/authorize', callbackUrl: 'http://localhost:3000/oauth/callback', }, }) await client.connect() const tools = await client.listTools()

This plugin exposes your CLI commands to AI assistants through the MCP protocol. The latest 2025-06-18 specification includes enhanced security features:

- πŸ”’ OAuth 2.1 Authorization: Complete OAuth 2.1 implementation with PKCE support
- πŸ›‘οΈ Resource Indicators: RFC 8707 compliance for secure resource access
- πŸ” Session Management: Advanced HTTP session handling with automatic cleanup
- 🌐 CORS Protection: Enhanced cross-origin controls with origin validation
- πŸ“‹ Protocol Headers: Proper version negotiation and security headers

- Local Development: When running locally, the plugin operates in your user context with your permissions
- Production Use: Only expose commands that are safe for AI assistants to execute
- HTTP Transport: Use OAuth 2.1 for secure remote access with proper authorization flows
- Sensitive Operations: Use thedisableMCPflag for commands that perform sensitive operations

export default class SensitiveCommand extends Command { static description = 'This command performs sensitive operations' static disableMCP = true // πŸ”’ Exclude from MCP exposure async run() { // Sensitive operations that shouldn't be exposed to AI } }
// Configure OAuth for secure HTTP transport const oauthConfig = { authorizationServer: 'https://your-auth-server.com', clientId: 'your-client-id', clientSecret: 'your-client-secret', // Optional for public clients tokenEndpoint: 'https://your-auth-server.com/token', scope: 'mcp:read mcp:write', }

- βœ…Review exposed commandsbefore deployment
- βœ…Use OAuth 2.1for production HTTP deployments
- βœ…Implement Resource Indicatorsfor secure token scoping
- βœ…Use tool annotationsto clearly mark destructive operations
- βœ…Implement proper validationin your command handlers
- βœ…Monitor MCP usagein production environments
- βœ…Configure CORS properlyfor web integrations
- ⚠️Avoid exposing commandsthat modify system-level configurations
- ⚠️Be cautious with file operationsthat could affect sensitive data
- ⚠️Use HTTPSfor all production HTTP transport deployments

Different AI providers have varying limits on the number of tools they can handle effectively:

To manage large CLIs with many commands, you can configure filtering to stay within these limits:

{ "oclif": { "mcp": { "toolLimits": { "maxTools": 40, "warnThreshold": 35 }, "topics": { "include": ["auth", "deploy", "config"], "exclude": ["debug", "internal", "experimental"] } } } }
{ "oclif": { "mcp": { "toolLimits": { "maxTools": 80, "strategy": "prioritize" }, "commands": { "include": ["auth:", "deploy:", "config:get", "config:set", "status", "logs:"], "exclude": [":debug", "internal:", "test:", ":experimental"], "priority": ["auth:login", "deploy:production", "status", "logs:tail"] } } } }
{ "oclif": { "mcp": { "profiles": { "development": { "maxTools": 128, "topics": { "include": [""] } }, "production": { "maxTools": 40, "topics": { "include": ["auth", "deploy", "config", "status", "logs"], "exclude": ["debug", "test", "internal"] } }, "minimal": { "maxTools": 20, "commands": { "include": ["auth:login", "auth:logout", "deploy:production", "status", "logs:tail"] } } }, "defaultProfile": "production" } } }

You can also configure filtering at runtime:

# Use a specific profile your-cli mcp --profile minimal # Override max tools your-cli mcp --max-tools 50 # Include specific topics only your-cli mcp --include-topics auth,deploy,config # Exclude specific patterns your-cli mcp --exclude-patterns ":debug,test:,internal:*"

- first- Include first N commands up to the limit
- prioritize- Include priority commands first, then others up to limit
- balanced- Try to include commands from all topics proportionally
- strict- Fail if filtered commands exceed limit

When commands are filtered out due to limits, the plugin will log suggestions:

⚠️ Filtered out 45 commands due to tool limit (40) πŸ’‘ Consider using topic filtering: --include-topics auth,deploy πŸ’‘ Or increase limit for your AI provider: --max-tools 80 πŸ” See filtered commands: your-cli mcp --show-filtered

Override the default tool ID generation:

export default class MyCommand extends Command { static toolId = 'custom-tool-name' // Custom MCP tool identifier }

Add MCP-compliant tool annotations to provide AI assistants with metadata about your command's behavior:

import {Command} from '@oclif/core' export default class DeployCommand extends Command { static description = 'Deploy your application to production' // Specify tool behavior annotations following MCP specification static mcpAnnotations = { readOnlyHint: false, // This command modifies the environment destructiveHint: true, // This operation may be destructive idempotentHint: false, // Multiple calls may have different effects openWorldHint: true, // Interacts with external systems (deployment) } async run() { // ... deployment logic } } export default class StatusCommand extends Command { static description = 'Get application status' static mcpAnnotations = { readOnlyHint: true, // This command only reads data destructiveHint: false, // Safe operation idempotentHint: true, // Multiple calls return same result openWorldHint: true, // May check external systems } async run() { // ... status logic } }

Create prompts with advanced argument validation:

import {Command} from '@oclif/core' import {z} from 'zod' export default class AnalyzeCommand extends Command { static description = 'Analyze code and provide insights' // Define prompts with custom validation schemas static mcpPrompts = [ { name: 'code-review', description: 'Review code for best practices and potential issues', arguments: [ {name: 'filePath', required: true, description: 'Path to the file to review'}, {name: 'severity', required: false, description: 'Minimum severity level'}, ], // Custom Zod schema for advanced validation argumentSchema: z.object({ filePath: z.string().min(1, 'File path is required'), severity: z.enum(['low', 'medium', 'high']).default('medium'), includePerformance: z.boolean().default(false), }), handler: 'handleCodeReview', // Method name to call }, ] async handleCodeReview(args: {filePath: string; severity: string; includePerformance: boolean}) { // Custom prompt handler with validated arguments return { description: Code review for ${args.filePath}, messages: [ { role: 'assistant' as const, content: { type: 'text' as const, text: I'll review the file "${args.filePath}" for ${args.severity} and above issues.${ args.includePerformance ? ' Including performance analysis.' : '' }, }, }, ], } } } async run() { // ... status check logic } }

Resources provide contextual data to AI assistants following theofficial MCP specification. Resources are automatically discoverable through theresources/listendpoint and fetched on-demand viaresources/read. Our implementation includes100% MCP compliancewith:

Perfect for configuration, documentation, or fixed data:

export default class ConfigCommand extends Command { static mcpResources = [ { uri: 'config://app-settings', name: 'Application Settings', description: 'Current application configuration', content: JSON.stringify( { version: '1.0.0', environment: 'production', features: ['auth', 'logging'], }, null, 2, ), mimeType: 'application/json', size: 98, // Optional: size in bytes for better resource management }, ] }

Use URI templates following RFC 6570 for dynamic resource patterns:

export default class UserCommand extends Command { static mcpResourceTemplates = [ { uriTemplate: 'users://profile/{userId}', name: 'User Profile Template', description: 'Access user profiles by ID using users://profile/123', mimeType: 'application/json', }, { uriTemplate: 'files://document/{docId}/content', name: 'Document Content Template', description: 'Access document content by ID using files://document/abc/content', mimeType: 'text/plain', }, ] // Dynamic templates via methods static async getMcpResourceTemplates() { return [ { uriTemplate: 'logs://{service}/recent', name: 'Service Logs Template', description: 'Access recent logs for any service using logs://api/recent', mimeType: 'text/plain', }, ] } }

Dynamic Resources with Function Handlers

Use function handlers for dynamic content generation:

export default class UserCommand extends Command { static mcpResources = [ { uri: 'users://profile-info', name: 'User Profile', description: 'User profile information', handler: 'getUserProfile', // Method name on class mimeType: 'application/json', }, ] // Handler method generates dynamic content async getUserProfile() { const user = await this.fetchUserData() return JSON.stringify(user, null, 2) } private async fetchUserData() { // Your logic to fetch user data return { id: '123', name: 'John Doe', email: 'john@example.com', } } }
export default class StatusCommand extends Command { // Static method for dynamic resource generation static async getMcpResources() { return [ { uri: 'status://runtime', name: 'Runtime Status', description: 'Current system status', handler: async () => { const status = await this.getSystemStatus() return JSON.stringify(status, null, 2) }, mimeType: 'application/json', }, ] } private static async getSystemStatus() { return { uptime: process.uptime(), memory: process.memoryUsage(), timestamp: new Date().toISOString(), } } }

Resources that need access to command instance:

export default class LogsCommand extends Command { // Instance method for dynamic resources async getMcpResources() { return [ { uri: 'logs://recent-entries', name: 'Recent Logs', description: 'Recent log entries', handler: () => this.getRecentLogs(), mimeType: 'text/plain', }, ] } private async getRecentLogs() { // Access to command instance and configuration return await this.fetchLogs(this.config.logLevel) } private async fetchLogs(logLevel: string) { // Your logic to fetch logs return Recent logs at ${logLevel} level:\n2024-01-01 10:00:00 INFO: Application started\n2024-01-01 10:01:00 DEBUG: Processing request } }
export default class ExampleCommand extends Command { static mcpResources = [ // String content { uri: 'example://static', name: 'Static Content', content: 'Direct string content', }, // Function handler { uri: 'example://dynamic', name: 'Dynamic Content', handler: async () => { return Generated at: ${new Date().toISOString()} }, }, // Method name reference { uri: 'example://method', name: 'Method Handler', handler: 'getMethodContent', // Calls this.getMethodContent() }, ] async getMethodContent() { return 'Content from method' } }

Advanced resource patterns with full MCP compliance:

export default class AdvancedCommand extends Command { // Resource templates for dynamic URI resolution static mcpResourceTemplates = [ { uriTemplate: 'users://profile/{userId}', name: 'User Profile Template', description: 'Access user profiles by ID (e.g., users://profile/123)', mimeType: 'application/json', }, { uriTemplate: 'files://{category}/{filename}', name: 'File Template', description: 'Access files by category (e.g., files://docs/readme.txt)', mimeType: 'text/plain', }, ] // Binary resource example static mcpResources = [ { uri: 'images://screenshot', name: 'Screenshot', handler: 'captureScreen', mimeType: 'image/png', size: 1024000, // Estimated size in bytes }, ] async captureScreen() { // Return Buffer for binary content (automatically base64 encoded) return Buffer.from('fake-image-data', 'utf8') } } // AI assistants can now access: // - users://profile/123 (resolves {userId} to "123") // - files://docs/readme.txt (resolves {category} to "docs", {filename} to "readme.txt") // - images://screenshot (returns base64 binary data)

Resource Notifications and URI Generation

Advanced MCP resource management with real-time updates:

Prompts provide reusable templates that help AI assistants interact with your CLI more effectively. They follow theofficial MCP specificationusingprompts/listandprompts/getendpoints.

The plugin automatically implements the MCP prompts protocol:
- Discovery: AI assistants callprompts/listto discover available prompts
- Execution: AI assistants callprompts/getwith prompt name and arguments
- Response: Prompts return structured messages for LLM processing

Define reusable prompt templates on your command classes:

export default class AnalyzeCommand extends Command { static mcpPrompts = [ { name: 'analyze-logs', description: 'Analyze application logs for issues', arguments: [ { name: 'logLevel', description: 'Log level to focus on (error, warn, info)', required: false, }, { name: 'timeRange', description: 'Time range to analyze (e.g., "last 1 hour")', required: true, }, ], }, ] }

Generate prompts programmatically based on current state:

export default class DeployCommand extends Command { // Static method for dynamic prompt generation static async getMcpPrompts() { const environments = await this.getAvailableEnvironments() return [ { name: 'deploy-with-confirmation', description: 'Deploy with safety confirmation prompts', arguments: [ { name: 'environment', description: Target environment: ${environments.join(', ')}, required: true, }, { name: 'skipChecks', description: 'Skip pre-deployment safety checks', required: false, }, ], }, ] } private static async getAvailableEnvironments() { return ['development', 'staging', 'production'] } }

Create prompts that generate dynamic responses:

export default class StatusCommand extends Command { // Instance method for dynamic prompts async getMcpPrompts() { return [ { name: 'troubleshoot-status', description: Troubleshoot ${this.config.name} status issues, arguments: [ { name: 'component', description: 'Specific component to troubleshoot', required: false, }, ], handler: 'generateTroubleshootingPrompt', }, ] } async generateTroubleshootingPrompt(args: any) { const status = await this.getSystemStatus() return { description: 'Troubleshooting guidance based on current system status', messages: [ { role: 'user', content: { type: 'text', text: Please help troubleshoot ${args.component || 'the system'}. Current status: ${JSON.stringify(status, null, 2)}, }, }, ], } } private async getSystemStatus() { return { status: 'running', uptime: process.uptime(), memory: process.memoryUsage(), } } }

The prompts implementation follows theofficial MCP specification:

No reviews yet β€” be the first

Sign in to leave a review

Use Google, GitHub, or an email account so ratings stay tied to real people.

Email sign in

No reviews posted yet.