github agentic workflows mcp configuration

πŸ”Œ GitHub Agentic Workflows MCP Configuration

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "github agentic workflows mcp configuration" with this command: npx skills add hack23/riksdagsmonitor/hack23-riksdagsmonitor-github-agentic-workflows-mcp-configuration

πŸ”Œ GitHub Agentic Workflows MCP Configuration

πŸ“‹ Overview

This skill provides comprehensive guidance for configuring Model Context Protocol (MCP) servers in GitHub Agentic Workflows. MCP enables AI agents to interact with external tools and data sources through a standardized protocol. Understanding MCP configuration is essential for building powerful, extensible agentic workflows.

What is Model Context Protocol (MCP)?

Model Context Protocol (MCP) is a standardized protocol for connecting AI models to external tools, data sources, and services:

  • Standardized Interface: Consistent API for tool registration, discovery, and invocation

  • Multiple Transports: Support for stdio, HTTP, and Server-Sent Events (SSE)

  • Tool Discovery: Dynamic tool registration and capability discovery

  • Type Safety: JSON Schema validation for tool inputs and outputs

  • Lifecycle Management: Server startup, health checks, graceful shutdown

  • Error Handling: Structured error responses and retry mechanisms

Why Use MCP Servers?

MCP servers provide several benefits for agentic workflows:

  • βœ… Extensibility: Add new tools without modifying agent code

  • βœ… Reusability: Share MCP servers across multiple agents and projects

  • βœ… Isolation: Run tools in separate processes for security and stability

  • βœ… Standardization: Use community-maintained MCP servers

  • βœ… Polyglot: Write servers in any language (Node.js, Python, Go, Rust)

  • βœ… Discoverability: Agents automatically discover available tools

πŸ—οΈ MCP Architecture

System Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ GitHub Copilot Agent β”‚ β”‚ (AI Model + Orchestration) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Tool Calls β”‚ (JSON-RPC 2.0) β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ MCP Client Runtime β”‚ β”‚ (Tool Discovery & Invocation) β”‚ β””β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ stdio β”‚ HTTP β”‚ SSE β–Ό β–Ό β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Filesystem β”‚ β”‚ GitHub API β”‚ β”‚ Database β”‚ β”‚ MCP Server β”‚ β”‚ MCP Server β”‚ β”‚ MCP Server β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Configuration File Structure

MCP servers are configured in .github/copilot-mcp.json :

{ "$schema": "https://github.com/modelcontextprotocol/schema/v1", "mcpServers": { "server-name": { "type": "local", "command": "command-to-run", "args": ["arg1", "arg2"], "env": { "ENV_VAR": "value" }, "tools": ["*"] } } }

πŸš€ MCP Server Setup Patterns

Pattern 1: Local stdio Server

Use case: File system operations, git commands, local tools.

{ "mcpServers": { "filesystem": { "type": "local", "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "/home/runner/work/myrepo/myrepo" ], "env": {}, "tools": ["*"] } } }

Implementation (Node.js):

// filesystem-mcp-server.js import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import fs from 'fs/promises'; import path from 'path';

class FileSystemMCPServer { constructor(rootPath) { this.rootPath = path.resolve(rootPath); this.server = new Server({ name: 'filesystem', version: '1.0.0', }, { capabilities: { tools: {}, }, });

this.setupTools();

}

setupTools() { // Register read_file tool this.server.setRequestHandler('tools/list', async () => ({ tools: [ { name: 'read_file', description: 'Read contents of a file', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'File path relative to root', }, }, required: ['path'], }, }, { name: 'write_file', description: 'Write contents to a file', inputSchema: { type: 'object', properties: { path: { type: 'string' }, content: { type: 'string' }, }, required: ['path', 'content'], }, }, { name: 'list_directory', description: 'List files in a directory', inputSchema: { type: 'object', properties: { path: { type: 'string' }, }, required: ['path'], }, }, ], }));

// Handle tool calls
this.server.setRequestHandler('tools/call', async (request) => {
  const { name, arguments: args } = request.params;
  
  switch (name) {
    case 'read_file':
      return this.readFile(args.path);
    case 'write_file':
      return this.writeFile(args.path, args.content);
    case 'list_directory':
      return this.listDirectory(args.path);
    default:
      throw new Error(`Unknown tool: ${name}`);
  }
});

}

validatePath(filePath) { const resolved = path.resolve(this.rootPath, filePath); if (!resolved.startsWith(this.rootPath)) { throw new Error('Path outside root directory'); } return resolved; }

async readFile(filePath) { const validated = this.validatePath(filePath); const content = await fs.readFile(validated, 'utf8'); return { content: [ { type: 'text', text: content, }, ], }; }

async writeFile(filePath, content) { const validated = this.validatePath(filePath); await fs.writeFile(validated, content, 'utf8'); return { content: [ { type: 'text', text: File written successfully: ${filePath}, }, ], }; }

async listDirectory(dirPath) { const validated = this.validatePath(dirPath); const entries = await fs.readdir(validated, { withFileTypes: true });

const files = entries.map(entry => ({
  name: entry.name,
  type: entry.isDirectory() ? 'directory' : 'file',
}));

return {
  content: [
    {
      type: 'text',
      text: JSON.stringify(files, null, 2),
    },
  ],
};

}

async start() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Filesystem MCP server started'); } }

// Start server const rootPath = process.argv[2] || process.cwd(); const server = new FileSystemMCPServer(rootPath); server.start().catch(console.error);

Usage:

Start server

npx -y @modelcontextprotocol/server-filesystem /workspace

Server communicates via stdin/stdout

Input (JSON-RPC request):

{"jsonrpc":"2.0","id":1,"method":"tools/list"}

Output (JSON-RPC response):

{"jsonrpc":"2.0","id":1,"result":{"tools":[...]}}

Pattern 2: HTTP Server

Use case: Remote services, APIs, databases.

{ "mcpServers": { "github-api": { "type": "http", "url": "https://mcp.github.com/v1", "headers": { "Authorization": "Bearer ${{ secrets.GITHUB_TOKEN }}" }, "tools": ["*"] } } }

Implementation (Node.js with Express):

// github-api-mcp-server.js import express from 'express'; import { Octokit } from '@octokit/rest';

const app = express(); app.use(express.json());

const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN, });

// MCP endpoint: List tools app.post('/mcp/tools/list', async (req, res) => { res.json({ tools: [ { name: 'github_create_issue', description: 'Create a GitHub issue', inputSchema: { type: 'object', properties: { owner: { type: 'string' }, repo: { type: 'string' }, title: { type: 'string' }, body: { type: 'string' }, }, required: ['owner', 'repo', 'title'], }, }, { name: 'github_list_issues', description: 'List GitHub issues', inputSchema: { type: 'object', properties: { owner: { type: 'string' }, repo: { type: 'string' }, state: { type: 'string', enum: ['open', 'closed', 'all'] }, }, required: ['owner', 'repo'], }, }, ], }); });

// MCP endpoint: Call tool app.post('/mcp/tools/call', async (req, res) => { const { name, arguments: args } = req.body;

try { switch (name) { case 'github_create_issue': { const { data } = await octokit.issues.create({ owner: args.owner, repo: args.repo, title: args.title, body: args.body, });

    res.json({
      content: [
        {
          type: 'text',
          text: `Issue created: ${data.html_url}`,
        },
      ],
    });
    break;
  }
  
  case 'github_list_issues': {
    const { data } = await octokit.issues.listForRepo({
      owner: args.owner,
      repo: args.repo,
      state: args.state || 'open',
    });
    
    res.json({
      content: [
        {
          type: 'text',
          text: JSON.stringify(data, null, 2),
        },
      ],
    });
    break;
  }
  
  default:
    res.status(404).json({ error: 'Tool not found' });
}

} catch (error) { res.status(500).json({ error: error.message }); } });

// Health check app.get('/health', (req, res) => { res.json({ status: 'ok' }); });

const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(GitHub API MCP server listening on port ${PORT}); });

Pattern 3: Server-Sent Events (SSE)

Use case: Real-time updates, streaming data, webhooks.

{ "mcpServers": { "realtime-monitor": { "type": "sse", "url": "https://monitor.example.com/events", "headers": { "Authorization": "Bearer ${{ secrets.API_TOKEN }}" }, "tools": ["*"] } } }

Implementation (Node.js with SSE):

// realtime-monitor-mcp-server.js import express from 'express';

const app = express();

const clients = new Set();

// SSE endpoint app.get('/events', (req, res) => { // Set headers for SSE res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive');

// Add client clients.add(res);

// Send initial connection event res.write(event: connected\ndata: {"status":"connected"}\n\n);

// Remove client on disconnect req.on('close', () => { clients.delete(res); }); });

// Function to broadcast events to all clients function broadcastEvent(eventType, data) { const message = event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n;

for (const client of clients) { client.write(message); } }

// Tool: Subscribe to repository events app.post('/mcp/tools/call', express.json(), (req, res) => { const { name, arguments: args } = req.body;

if (name === 'subscribe_repo_events') { // Simulate subscription const subscription = { repo: args.repo, events: args.events, };

// Broadcast to SSE clients
broadcastEvent('tool_result', {
  name: 'subscribe_repo_events',
  result: `Subscribed to ${args.repo}`,
});

res.json({
  content: [
    {
      type: 'text',
      text: `Subscribed to events for ${args.repo}`,
    },
  ],
});

} else { res.status(404).json({ error: 'Tool not found' }); } });

// Simulate events (for demo) setInterval(() => { broadcastEvent('repo_event', { type: 'push', repo: 'owner/repo', timestamp: new Date().toISOString(), }); }, 5000);

const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(Realtime monitor MCP server on port ${PORT}); });

πŸ”Œ Transport Protocols

stdio Transport

Characteristics:

  • Process-to-process communication via stdin/stdout

  • Lowest latency

  • Best for local tools

  • Automatic lifecycle management

Advantages:

  • βœ… Simple to implement

  • βœ… No network overhead

  • βœ… Automatic process cleanup

  • βœ… Secure (no network exposure)

Disadvantages:

  • ❌ Single client per server instance

  • ❌ No remote access

  • ❌ Requires process spawning

Configuration:

{ "mcpServers": { "local-tool": { "type": "local", "command": "node", "args": ["server.js"], "env": { "NODE_ENV": "production" }, "tools": ["*"] } } }

HTTP Transport

Characteristics:

  • RESTful JSON-RPC over HTTP/HTTPS

  • Stateless request/response

  • Can be load balanced

  • Supports authentication

Advantages:

  • βœ… Remote server support

  • βœ… Multiple concurrent clients

  • βœ… Standard HTTP infrastructure

  • βœ… Load balancing and scaling

Disadvantages:

  • ❌ Higher latency

  • ❌ Requires authentication

  • ❌ Network security considerations

Configuration:

{ "mcpServers": { "remote-api": { "type": "http", "url": "https://api.example.com/mcp/v1", "headers": { "Authorization": "Bearer ${MCP_API_TOKEN}", "X-API-Version": "1.0" }, "timeout": 30000, "retries": 3, "tools": ["*"] } } }

Server-Sent Events (SSE) Transport

Characteristics:

  • One-way server-to-client streaming

  • Real-time event notifications

  • Automatic reconnection

  • HTTP-based

Advantages:

  • βœ… Real-time updates

  • βœ… Efficient for event streams

  • βœ… Automatic reconnection

  • βœ… Works through firewalls

Disadvantages:

  • ❌ One-way only (server β†’ client)

  • ❌ Requires persistent connection

  • ❌ Browser compatibility (not relevant for agents)

Configuration:

{ "mcpServers": { "event-stream": { "type": "sse", "url": "https://events.example.com/stream", "headers": { "Authorization": "Bearer ${EVENT_TOKEN}" }, "reconnect": true, "reconnectDelay": 5000, "tools": ["*"] } } }

βœ… Configuration Validation

Schema Validation

// validate-mcp-config.js import Ajv from 'ajv'; import addFormats from 'ajv-formats';

const ajv = new Ajv({ allErrors: true }); addFormats(ajv);

const mcpConfigSchema = { $schema: 'http://json-schema.org/draft-07/schema#', type: 'object', properties: { mcpServers: { type: 'object', patternProperties: { '^[a-zA-Z0-9_-]+$': { oneOf: [ { // Local stdio server type: 'object', properties: { type: { const: 'local' }, command: { type: 'string', minLength: 1 }, args: { type: 'array', items: { type: 'string' } }, env: { type: 'object', patternProperties: { '^[A-Z_][A-Z0-9_]$': { type: 'string' }, }, }, tools: { oneOf: [ { type: 'array', items: { type: 'string' } }, { type: 'array', items: { const: '' }, maxItems: 1 }, ], }, }, required: ['type', 'command'], additionalProperties: false, }, { // HTTP server type: 'object', properties: { type: { const: 'http' }, url: { type: 'string', format: 'uri' }, headers: { type: 'object', patternProperties: { '^[A-Za-z0-9-]+$': { type: 'string' }, }, }, timeout: { type: 'integer', minimum: 1000 }, retries: { type: 'integer', minimum: 0 }, tools: { oneOf: [ { type: 'array', items: { type: 'string' } }, { type: 'array', items: { const: '' }, maxItems: 1 }, ], }, }, required: ['type', 'url'], additionalProperties: false, }, { // SSE server type: 'object', properties: { type: { const: 'sse' }, url: { type: 'string', format: 'uri' }, headers: { type: 'object', patternProperties: { '^[A-Za-z0-9-]+$': { type: 'string' }, }, }, reconnect: { type: 'boolean' }, reconnectDelay: { type: 'integer', minimum: 100 }, tools: { oneOf: [ { type: 'array', items: { type: 'string' } }, { type: 'array', items: { const: '' }, maxItems: 1 }, ], }, }, required: ['type', 'url'], additionalProperties: false, }, ], }, }, }, }, required: ['mcpServers'], additionalProperties: false, };

const validate = ajv.compile(mcpConfigSchema);

export function validateMCPConfig(config) { const valid = validate(config);

if (!valid) { const errors = validate.errors.map(err => ({ path: err.instancePath, message: err.message, params: err.params, }));

throw new Error(
  `MCP configuration validation failed:\n${JSON.stringify(errors, null, 2)}`
);

}

return true; }

// Usage import fs from 'fs';

const config = JSON.parse( fs.readFileSync('.github/copilot-mcp.json', 'utf8') );

try { validateMCPConfig(config); console.log('βœ… MCP configuration is valid'); } catch (error) { console.error('❌ Validation error:', error.message); process.exit(1); }

Runtime Validation

// runtime-validator.js class MCPConfigValidator { constructor(config) { this.config = config; }

async validateAll() { const errors = [];

for (const [name, server] of Object.entries(this.config.mcpServers)) {
  try {
    await this.validateServer(name, server);
  } catch (error) {
    errors.push({
      server: name,
      error: error.message,
    });
  }
}

if (errors.length > 0) {
  throw new Error(
    `MCP server validation failed:\n${JSON.stringify(errors, null, 2)}`
  );
}

return true;

}

async validateServer(name, server) { switch (server.type) { case 'local': await this.validateLocalServer(name, server); break; case 'http': await this.validateHTTPServer(name, server); break; case 'sse': await this.validateSSEServer(name, server); break; default: throw new Error(Unknown server type: ${server.type}); } }

async validateLocalServer(name, server) { // Check if command exists const { execSync } = require('child_process');

try {
  execSync(`command -v ${server.command}`, { stdio: 'ignore' });
} catch (error) {
  throw new Error(`Command not found: ${server.command}`);
}

// Validate environment variables
if (server.env) {
  for (const [key, value] of Object.entries(server.env)) {
    if (value.includes('${') && value.includes('}')) {
      const envVar = value.match(/\$\{([^}]+)\}/)[1];
      if (!process.env[envVar]) {
        throw new Error(`Environment variable not set: ${envVar}`);
      }
    }
  }
}

}

async validateHTTPServer(name, server) { // Health check try { const response = await fetch(${server.url}/health, { headers: server.headers || {}, signal: AbortSignal.timeout(5000), });

  if (!response.ok) {
    throw new Error(`Health check failed: ${response.status}`);
  }
} catch (error) {
  throw new Error(`Cannot connect to HTTP server: ${error.message}`);
}

}

async validateSSEServer(name, server) { // Test SSE connection return new Promise((resolve, reject) => { const eventSource = new EventSource(server.url, { headers: server.headers || {}, });

  const timeout = setTimeout(() => {
    eventSource.close();
    reject(new Error('SSE connection timeout'));
  }, 5000);
  
  eventSource.addEventListener('connected', () => {
    clearTimeout(timeout);
    eventSource.close();
    resolve();
  });
  
  eventSource.onerror = (error) => {
    clearTimeout(timeout);
    eventSource.close();
    reject(new Error(`SSE connection error: ${error.message}`));
  };
});

} }

// Usage const validator = new MCPConfigValidator(config); await validator.validateAll();

πŸ”„ Server Lifecycle Management

Startup Sequence

// mcp-lifecycle-manager.js class MCPLifecycleManager { constructor(config) { this.config = config; this.servers = new Map(); this.health = new Map(); }

async startAll() { console.log('πŸš€ Starting MCP servers...');

const promises = Object.entries(this.config.mcpServers).map(
  async ([name, server]) => {
    try {
      await this.startServer(name, server);
      console.log(`βœ… Started: ${name}`);
    } catch (error) {
      console.error(`❌ Failed to start ${name}:`, error.message);
      throw error;
    }
  }
);

await Promise.all(promises);
console.log('βœ… All MCP servers started');

}

async startServer(name, config) { switch (config.type) { case 'local': return this.startLocalServer(name, config); case 'http': return this.startHTTPClient(name, config); case 'sse': return this.startSSEClient(name, config); default: throw new Error(Unknown server type: ${config.type}); } }

async startLocalServer(name, config) { const { spawn } = require('child_process');

// Spawn process
const process = spawn(config.command, config.args || [], {
  stdio: ['pipe', 'pipe', 'pipe'],
  env: { ...process.env, ...config.env },
});

// Wait for server to be ready
await this.waitForReady(process);

this.servers.set(name, { type: 'local', process });
this.health.set(name, 'healthy');

// Monitor health
this.monitorHealth(name, process);

}

async waitForReady(process, timeout = 10000) { return new Promise((resolve, reject) => { const timer = setTimeout(() => { reject(new Error('Server startup timeout')); }, timeout);

  process.stderr.once('data', (data) => {
    const message = data.toString();
    if (message.includes('started') || message.includes('listening')) {
      clearTimeout(timer);
      resolve();
    }
  });
  
  process.once('error', (error) => {
    clearTimeout(timer);
    reject(error);
  });
  
  process.once('exit', (code) => {
    clearTimeout(timer);
    reject(new Error(`Process exited with code ${code}`));
  });
});

}

monitorHealth(name, process) { // Monitor process health process.on('exit', (code) => { console.error(❌ Server ${name} exited with code ${code}); this.health.set(name, 'unhealthy');

  // Auto-restart
  if (code !== 0) {
    console.log(`πŸ”„ Restarting ${name}...`);
    setTimeout(() => {
      this.restartServer(name);
    }, 5000);
  }
});

process.on('error', (error) => {
  console.error(`❌ Server ${name} error:`, error.message);
  this.health.set(name, 'unhealthy');
});

// Periodic health check
setInterval(async () => {
  const healthy = await this.checkHealth(name);
  this.health.set(name, healthy ? 'healthy' : 'unhealthy');
}, 30000); // Every 30 seconds

}

async checkHealth(name) { const server = this.servers.get(name); if (!server) return false;

switch (server.type) {
  case 'local':
    // Check if process is running
    return !server.process.killed;
  
  case 'http':
    // HTTP health check
    try {
      const response = await fetch(`${server.url}/health`, {
        signal: AbortSignal.timeout(5000),
      });
      return response.ok;
    } catch (error) {
      return false;
    }
  
  case 'sse':
    // Check SSE connection
    return server.connected;
  
  default:
    return false;
}

}

async restartServer(name) { const config = this.config.mcpServers[name];

// Stop existing server
await this.stopServer(name);

// Start new instance
await this.startServer(name, config);

}

async stopServer(name) { const server = this.servers.get(name); if (!server) return;

switch (server.type) {
  case 'local':
    server.process.kill('SIGTERM');
    
    // Wait for graceful shutdown
    await new Promise((resolve) => {
      const timeout = setTimeout(() => {
        server.process.kill('SIGKILL');
        resolve();
      }, 5000);
      
      server.process.once('exit', () => {
        clearTimeout(timeout);
        resolve();
      });
    });
    break;
  
  case 'http':
  case 'sse':
    // Close connections
    if (server.connection) {
      server.connection.close();
    }
    break;
}

this.servers.delete(name);
this.health.delete(name);

}

async stopAll() { console.log('πŸ›‘ Stopping MCP servers...');

const promises = Array.from(this.servers.keys()).map(
  async (name) => {
    try {
      await this.stopServer(name);
      console.log(`βœ… Stopped: ${name}`);
    } catch (error) {
      console.error(`❌ Failed to stop ${name}:`, error.message);
    }
  }
);

await Promise.all(promises);
console.log('βœ… All MCP servers stopped');

}

getHealthStatus() { const status = {}; for (const [name, health] of this.health) { status[name] = health; } return status; } }

// Usage const manager = new MCPLifecycleManager(config);

// Start all servers await manager.startAll();

// Check health console.log('Health status:', manager.getHealthStatus());

// Graceful shutdown process.on('SIGTERM', async () => { await manager.stopAll(); process.exit(0); });

πŸ” Tool Discovery and Registration

Dynamic Tool Discovery

// tool-discovery.js class MCPToolDiscovery { constructor(servers) { this.servers = servers; this.tools = new Map(); }

async discoverAll() { console.log('πŸ” Discovering MCP tools...');

for (const [serverName, server] of this.servers) {
  try {
    const tools = await this.discoverTools(serverName, server);
    
    for (const tool of tools) {
      this.registerTool(serverName, tool);
    }
    
    console.log(`βœ… Discovered ${tools.length} tools from ${serverName}`);
  } catch (error) {
    console.error(`❌ Failed to discover tools from ${serverName}:`, error.message);
  }
}

console.log(`βœ… Total tools discovered: ${this.tools.size}`);

}

async discoverTools(serverName, server) { switch (server.type) { case 'local': return this.discoverLocalTools(server); case 'http': return this.discoverHTTPTools(server); case 'sse': return this.discoverSSETools(server); default: throw new Error(Unknown server type: ${server.type}); } }

async discoverLocalTools(server) { // Send tools/list request via stdio return new Promise((resolve, reject) => { const request = { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {}, };

  server.process.stdin.write(JSON.stringify(request) + '\n');
  
  server.process.stdout.once('data', (data) => {
    const response = JSON.parse(data.toString());
    
    if (response.error) {
      reject(new Error(response.error.message));
    } else {
      resolve(response.result.tools);
    }
  });
  
  setTimeout(() => {
    reject(new Error('Tool discovery timeout'));
  }, 5000);
});

}

async discoverHTTPTools(server) { const response = await fetch(${server.url}/tools/list, { method: 'POST', headers: { 'Content-Type': 'application/json', ...server.headers, }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list', }), });

const result = await response.json();
return result.result.tools;

}

registerTool(serverName, tool) { const fullName = ${serverName}.${tool.name};

this.tools.set(fullName, {
  server: serverName,
  name: tool.name,
  description: tool.description,
  inputSchema: tool.inputSchema,
});

}

getTool(fullName) { return this.tools.get(fullName); }

listTools(filter = null) { const toolList = Array.from(this.tools.values());

if (filter) {
  return toolList.filter(tool => 
    tool.name.includes(filter) || 
    tool.description.includes(filter)
  );
}

return toolList;

}

async invokeTool(fullName, args) { const tool = this.getTool(fullName); if (!tool) { throw new Error(Tool not found: ${fullName}); }

// Validate input
this.validateInput(tool.inputSchema, args);

// Get server
const server = this.servers.get(tool.server);

// Invoke tool
return this.invokeToolOnServer(server, tool.name, args);

}

validateInput(schema, input) { const Ajv = require('ajv'); const ajv = new Ajv(); const validate = ajv.compile(schema);

if (!validate(input)) {
  throw new Error(
    `Invalid tool input: ${JSON.stringify(validate.errors)}`
  );
}

}

async invokeToolOnServer(server, toolName, args) { const request = { jsonrpc: '2.0', id: Date.now(), method: 'tools/call', params: { name: toolName, arguments: args, }, };

switch (server.type) {
  case 'local': {
    return new Promise((resolve, reject) => {
      server.process.stdin.write(JSON.stringify(request) + '\n');
      
      server.process.stdout.once('data', (data) => {
        const response = JSON.parse(data.toString());
        
        if (response.error) {
          reject(new Error(response.error.message));
        } else {
          resolve(response.result);
        }
      });
      
      setTimeout(() => {
        reject(new Error('Tool invocation timeout'));
      }, 30000);
    });
  }
  
  case 'http': {
    const response = await fetch(`${server.url}/tools/call`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        ...server.headers,
      },
      body: JSON.stringify(request),
    });
    
    const result = await response.json();
    
    if (result.error) {
      throw new Error(result.error.message);
    }
    
    return result.result;
  }
  
  default:
    throw new Error(`Unsupported server type: ${server.type}`);
}

} }

// Usage const discovery = new MCPToolDiscovery(manager.servers);

// Discover all tools await discovery.discoverAll();

// List tools console.log('Available tools:', discovery.listTools());

// Invoke tool const result = await discovery.invokeTool('filesystem.read_file', { path: 'src/index.js', });

console.log('Tool result:', result);

⚠️ Error Handling Patterns

Retry with Exponential Backoff

// retry-handler.js class RetryHandler { constructor(maxRetries = 3, baseDelay = 1000) { this.maxRetries = maxRetries; this.baseDelay = baseDelay; }

async execute(fn, context = {}) { let lastError;

for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
  try {
    return await fn();
  } catch (error) {
    lastError = error;
    
    // Don't retry on certain errors
    if (this.isNonRetryable(error)) {
      throw error;
    }
    
    if (attempt < this.maxRetries) {
      const delay = this.calculateDelay(attempt);
      console.warn(
        `Attempt ${attempt + 1} failed: ${error.message}. Retrying in ${delay}ms...`
      );
      await this.sleep(delay);
    }
  }
}

throw new Error(
  `Max retries (${this.maxRetries}) exceeded. Last error: ${lastError.message}`
);

}

isNonRetryable(error) { // Don't retry validation errors, auth errors, etc. return ( error.message.includes('validation') || error.message.includes('unauthorized') || error.message.includes('forbidden') || error.message.includes('not found') ); }

calculateDelay(attempt) { // Exponential backoff with jitter const exponentialDelay = this.baseDelay * Math.pow(2, attempt); const jitter = Math.random() * this.baseDelay; return Math.min(exponentialDelay + jitter, 30000); // Max 30s }

sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } }

// Usage const retry = new RetryHandler();

const result = await retry.execute(async () => { return await discovery.invokeTool('github.create_issue', { owner: 'user', repo: 'repo', title: 'Bug report', }); });

Circuit Breaker

// circuit-breaker.js class CircuitBreaker { constructor(threshold = 5, timeout = 60000, resetTimeout = 300000) { this.threshold = threshold; this.timeout = timeout; this.resetTimeout = resetTimeout;

this.failures = 0;
this.lastFailureTime = null;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN

}

async execute(fn) { if (this.state === 'OPEN') { if (Date.now() - this.lastFailureTime > this.resetTimeout) { // Try half-open state this.state = 'HALF_OPEN'; console.log('Circuit breaker entering HALF_OPEN state'); } else { throw new Error('Circuit breaker is OPEN'); } }

try {
  const result = await this.executeWithTimeout(fn);
  this.onSuccess();
  return result;
} catch (error) {
  this.onFailure();
  throw error;
}

}

async executeWithTimeout(fn) { return Promise.race([ fn(), new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), this.timeout) ), ]); }

onSuccess() { this.failures = 0; if (this.state === 'HALF_OPEN') { console.log('Circuit breaker entering CLOSED state'); this.state = 'CLOSED'; } }

onFailure() { this.failures++; this.lastFailureTime = Date.now();

if (this.failures >= this.threshold) {
  console.error('πŸ”΄ Circuit breaker tripped - entering OPEN state');
  this.state = 'OPEN';
}

}

getState() { return { state: this.state, failures: this.failures, lastFailure: this.lastFailureTime, }; } }

// Usage const breaker = new CircuitBreaker();

try { const result = await breaker.execute(async () => { return await fetch('https://api.example.com/data'); }); } catch (error) { console.error('Request failed:', error.message); console.log('Circuit breaker state:', breaker.getState()); }

Graceful Degradation

// graceful-degradation.js class GracefulDegradation { constructor(primaryFn, fallbackFn) { this.primaryFn = primaryFn; this.fallbackFn = fallbackFn; this.primaryFailures = 0; this.useFallback = false; }

async execute(...args) { if (this.useFallback) { return this.executeFallback(...args); }

try {
  const result = await this.primaryFn(...args);
  this.primaryFailures = 0;
  return result;
} catch (error) {
  this.primaryFailures++;
  
  console.warn(
    `Primary function failed (${this.primaryFailures} times): ${error.message}`
  );
  
  if (this.primaryFailures >= 3) {
    console.warn('Switching to fallback function');
    this.useFallback = true;
  }
  
  return this.executeFallback(...args);
}

}

async executeFallback(...args) { try { return await this.fallbackFn(...args); } catch (error) { throw new Error( Both primary and fallback functions failed: ${error.message} ); } }

reset() { this.primaryFailures = 0; this.useFallback = false; } }

// Usage const toolInvoker = new GracefulDegradation( // Primary: Use MCP server async (toolName, args) => { return await discovery.invokeTool(toolName, args); }, // Fallback: Use direct API async (toolName, args) => { console.warn('Using fallback implementation'); return await directAPICall(toolName, args); } );

const result = await toolInvoker.execute('github.create_issue', { owner: 'user', repo: 'repo', title: 'Bug', });

πŸ” Security Considerations

Authentication

{ "mcpServers": { "secure-api": { "type": "http", "url": "https://api.example.com/mcp/v1", "headers": { "Authorization": "Bearer ${MCP_API_TOKEN}", "X-API-Key": "${API_KEY}" }, "tools": ["*"] } } }

TLS/SSL

// tls-config.js import https from 'https'; import fs from 'fs';

const tlsOptions = { ca: fs.readFileSync('ca-cert.pem'), cert: fs.readFileSync('client-cert.pem'), key: fs.readFileSync('client-key.pem'), rejectUnauthorized: true, minVersion: 'TLSv1.3', };

const agent = new https.Agent(tlsOptions);

// Use with fetch const response = await fetch('https://secure-mcp.example.com', { agent, });

Input Validation

// Always validate tool inputs function validateToolInput(schema, input) { const Ajv = require('ajv'); const ajv = new Ajv({ allErrors: true });

const validate = ajv.compile(schema);

if (!validate(input)) { const errors = validate.errors.map(err => ({ path: err.instancePath, message: err.message, }));

throw new Error(
  `Invalid tool input:\n${JSON.stringify(errors, null, 2)}`
);

} }

πŸŽ“ Related Skills

  • gh-aw-security-architecture: Security for MCP servers

  • gh-aw-tools-ecosystem: Available MCP tools

  • gh-aw-safe-outputs: Output sanitization

  • github-actions-workflows: CI/CD integration

πŸ“š References

  • Model Context Protocol Specification

  • MCP TypeScript SDK

  • MCP Python SDK

  • Official MCP Servers

  • JSON-RPC 2.0 Specification

βœ… Remember

  • Validate MCP configuration with JSON Schema

  • Use appropriate transport protocol (stdio/HTTP/SSE)

  • Implement proper lifecycle management (startup, health, shutdown)

  • Discover tools dynamically at runtime

  • Validate tool inputs against schemas

  • Handle errors gracefully with retries and circuit breakers

  • Implement fallback mechanisms

  • Secure MCP servers with authentication and TLS

  • Monitor server health continuously

  • Log all MCP operations for audit

  • Test MCP servers in isolation before integration

  • Document custom MCP servers thoroughly

  • Version MCP server APIs

  • Implement graceful degradation

  • Use timeout for all MCP operations

Last Updated: 2026-02-17

Version: 1.0.0

License: Apache-2.0

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

github agentic workflows tools ecosystem

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

github-agentic-workflows

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

code-review-practices

No summary provided by upstream source.

Repository SourceNeeds Review