Claude-skill-registry-data mcp-server-dev
Braiins OS MCP Server Development - Building MCP tools, resources, and prompts for Bitcoin mining operations management
git clone https://github.com/majiayu000/claude-skill-registry-data
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry-data "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/mcp-server-dev" ~/.claude/skills/majiayu000-claude-skill-registry-data-mcp-server-dev && rm -rf "$T"
data/mcp-server-dev/SKILL.mdBraiins OS MCP Server Development
Description
Comprehensive skill for building high-quality MCP (Model Context Protocol) tools, resources, and prompts specifically for Braiins OS miner management. This skill extends the generic mcp-builder patterns with mining-specific workflows, gRPC integration, and fleet operations optimization.
Target Audience: Developers building the braiins-os-mcp-server
When to Use This Skill
Use this skill when:
- Designing new MCP tools for miner management (status queries, firmware updates, pool configuration)
- Creating MCP resources for fleet monitoring (aggregated metrics, miner status feeds)
- Building MCP prompts for guided mining operations (troubleshooting, batch updates)
- Implementing gRPC client patterns for miner communication
- Designing Redis caching strategies for fleet-scale operations
- Optimizing MCP responses for AI agent consumption
Don't use this skill for:
- General MCP server development (use mcp-builder skill instead)
- Braiins OS API reference (use braiins-os skill for documentation)
- Infrastructure deployment (use devops-related skills)
- Non-MCP related TypeScript development
Prerequisites
Knowledge Requirements
- MCP protocol fundamentals (tools, resources, prompts, transport)
- TypeScript/Node.js development
- gRPC concepts (clients, streams, error handling)
- Redis caching patterns
- Bitcoin mining operations basics
Project Context
- Codebase: braiins-os-mcp-server (TypeScript, Node.js 20.x)
- MCP SDK: @modelcontextprotocol/sdk
- gRPC: @grpc/grpc-js
- Cache: Redis 7.x
- Testing: Jest + Supertest
Related Skills
- braiins-os (.claude/skills/braiins-os/) - Braiins OS API documentation reference
- mcp-builder (docs/claude/skills-templates/mcp-builder/) - Generic MCP development guide
- CLAUDE.md (./CLAUDE.md) - Project-specific development patterns
Workflow
Phase 1: Tool Design (Mining Operations Focus)
Step 1.1: Identify Mining Workflow
Common Mining Operations:
- Miner Status - Get current status of one or multiple miners
- Firmware Update - Update firmware with progress tracking
- Pool Configuration - Manage mining pool settings
- Fleet Metrics - Aggregated statistics across all miners
- Troubleshooting - Guided diagnostics for offline miners
Design Principle: Consolidate workflows, not just API endpoints.
Example: Firmware Update Tool Design
/** * ❌ BAD: Granular tools forcing agent orchestration */ @tool({ name: "check_firmware_version" }) async checkFirmwareVersion(minerId: string) { /* ... */ } @tool({ name: "download_firmware" }) async downloadFirmware(url: string) { /* ... */ } @tool({ name: "flash_firmware" }) async flashFirmware(minerId: string) { /* ... */ } /** * ✅ GOOD: Consolidated workflow tool */ @tool({ name: "update_miner_firmware", description: "Update firmware on one or more miners with automatic download, flashing, and progress tracking" }) async updateMinerFirmware(params: { minerIds: string[]; // Batch operation support version: string; // Target firmware version force?: boolean; // Skip version checks progressCallback?: boolean; // Enable progress updates }): Promise<{ jobId: string; // Background job ID status: "pending" | "running" | "completed" | "failed"; progress: { total: number; completed: number; failed: number; }; }> { // Handles: version check → download → flash → verify // Returns job ID for async status polling }
Step 1.2: Design Input Schema (Zod Validation)
Mining-Specific Patterns:
import { z } from "zod"; // Common validations for mining operations const MinerIdSchema = z.string().regex(/^[a-zA-Z0-9\-_]+$/); const MinerIdsSchema = z.array(MinerIdSchema).min(1).max(100); // Batch limit const FirmwareVersionSchema = z.string().regex(/^\d+\.\d+\.\d+$/); const PoolUrlSchema = z.string().url().refine( (url) => url.startsWith("stratum+tcp://") || url.startsWith("stratum+ssl://"), { message: "Pool URL must use stratum protocol" } ); // Tool input schema const UpdateMinerFirmwareSchema = z.object({ minerIds: MinerIdsSchema, version: FirmwareVersionSchema, force: z.boolean().optional().default(false), progressCallback: z.boolean().optional().default(false) }).strict();
Step 1.3: Design Output Format (Context-Optimized)
Principle: Agents have limited context - provide concise by default, detailed on request.
// Concise output (default) - ~150 tokens { jobId: "update-20251228-abc123", status: "running", progress: { total: 10, completed: 7, failed: 1, current: "miner-008" }, estimatedCompletion: "2025-12-28T18:30:00Z" } // Detailed output (detailLevel: "verbose") - ~500 tokens { jobId: "update-20251228-abc123", status: "running", progress: { total: 10, completed: 7, failed: 1, current: "miner-008", breakdown: [ { minerId: "miner-001", status: "completed", duration: "5m32s" }, { minerId: "miner-002", status: "completed", duration: "5m28s" }, // ... all 10 miners ] }, startedAt: "2025-12-28T18:00:00Z", estimatedCompletion: "2025-12-28T18:30:00Z", errors: [ { minerId: "miner-005", error: "Connection timeout", suggestion: "Check network connectivity with ping_miner tool" } ] }
Phase 2: Implementation (TypeScript + MCP SDK)
Step 2.1: Tool Registration
// src/mcp/tools/updateMinerFirmware.ts import { tool } from "@modelcontextprotocol/sdk"; import { z } from "zod"; const UpdateMinerFirmwareSchema = z.object({ minerIds: z.array(z.string()).min(1).max(100), version: z.string().regex(/^\d+\.\d+\.\d+$/), force: z.boolean().optional().default(false), detailLevel: z.enum(["concise", "verbose"]).optional().default("concise") }).strict(); @tool({ name: "update_miner_firmware", description: "Update firmware on one or more Braiins OS miners. Handles download, flashing, and verification. Returns job ID for progress tracking.", inputSchema: UpdateMinerFirmwareSchema, annotations: { readOnlyHint: false, // Modifies miner state destructiveHint: false, // Can be rolled back idempotentHint: true, // Safe to retry openWorldHint: true // Interacts with external miners } }) export async function updateMinerFirmware( params: z.infer<typeof UpdateMinerFirmwareSchema> ): Promise<FirmwareUpdateJobStatus> { // Implementation in next step }
Step 2.2: gRPC Integration Pattern
import { GrpcConnectionPool } from "../../api/grpc/pool"; import { withRetry } from "../../api/grpc/retry"; export async function updateMinerFirmware( params: z.infer<typeof UpdateMinerFirmwareSchema> ): Promise<FirmwareUpdateJobStatus> { const { minerIds, version, force, detailLevel } = params; // 1. Create background job const jobId = await this.jobQueue.createJob({ type: "firmware_update", minerIds, version }); // 2. Execute updates asynchronously this.executeInBackground(async () => { const results = await Promise.allSettled( minerIds.map(async (minerId) => { // Get gRPC connection from pool const client = await this.grpc.pool.getConnection(minerId); // Update with retry logic return await withRetry( () => client.updateFirmware({ version, force }), { maxRetries: 3, initialDelay: 5000 } ); }) ); // 3. Update job status await this.jobQueue.updateJob(jobId, { status: "completed", results: results.map((r, i) => ({ minerId: minerIds[i], success: r.status === "fulfilled", error: r.status === "rejected" ? r.reason.message : undefined })) }); }); // 4. Return immediate response return { jobId, status: "pending", progress: { total: minerIds.length, completed: 0, failed: 0 } }; }
Step 2.3: Redis Caching Integration
Pattern: Invalidate cached data when miners update
async function updateMinerFirmware(params: /* ... */): Promise</* ... */> { // ... perform update ... // Invalidate all caches related to updated miners await Promise.all( params.minerIds.map(async (minerId) => { await this.redis.del(`cache:miner:${minerId}:status`); await this.redis.del(`cache:miner:${minerId}:config`); }) ); // Invalidate fleet-level caches await this.redis.del("cache:fleet:summary"); // Publish update event for real-time subscribers await this.redis.publish("events:firmware-update", JSON.stringify({ minerIds: params.minerIds, version: params.version, timestamp: new Date().toISOString() })); return { /* ... */ }; }
Phase 3: Resource Development (Fleet Monitoring)
Step 3.1: Design Resource URI Scheme
URI Pattern:
braiins:///<category>/<resource>[/<identifier>]
braiins:///fleet/summary # Aggregated fleet metrics braiins:///fleet/miners # List of all miners braiins:///miner/miner-123/status # Single miner status braiins:///miner/miner-123/logs # Miner logs braiins:///jobs/update-abc123 # Job status
Step 3.2: Implement Cached Resource
import { resource } from "@modelcontextprotocol/sdk"; @resource({ uri: "braiins:///fleet/summary", name: "Fleet Summary", description: "Aggregated metrics for all managed miners (cached for 30s)", mimeType: "application/json" }) export async function getFleetSummary(): Promise<FleetSummary> { const cacheKey = "cache:fleet:summary"; const ttl = 30; // 30 seconds - fleet data changes slowly // Check cache const cached = await this.redis.get(cacheKey); if (cached) { return JSON.parse(cached); } // Compute fresh data (expensive operation) const [total, online, hashrate, temp, alerts] = await Promise.all([ this.db.countMiners(), this.db.countMinersOnline(), this.aggregateHashrate(), // Sum across all miners this.averageTemperature(), // Average temp this.getActiveAlerts() // Count critical alerts ]); const summary: FleetSummary = { totalMiners: total, onlineMiners: online, offlineMiners: total - online, totalHashrate: `${hashrate} TH/s`, averageTemp: `${temp}°C`, activeAlerts: alerts.length, lastUpdated: new Date().toISOString() }; // Cache for 30 seconds await this.redis.setex(cacheKey, ttl, JSON.stringify(summary)); return summary; }
Phase 4: Prompt Development (Guided Workflows)
Step 4.1: Troubleshooting Prompt
import { prompt } from "@modelcontextprotocol/sdk"; @prompt({ name: "troubleshoot_miner_offline", description: "Step-by-step guide to diagnose and fix offline miners", arguments: [ { name: "minerId", description: "Miner ID to troubleshoot", required: true } ] }) export async function troubleshootMinerOffline( minerId: string ): Promise<PromptMessage[]> { // Get miner details const miner = await this.db.getMiner(minerId); return [ { role: "user", content: { type: "text", text: `My miner ${minerId} is offline. Can you help me fix it?` } }, { role: "assistant", content: { type: "text", text: `I'll help troubleshoot miner ${minerId}. Let me run through diagnostics: **Step 1: Check Last Known Status** Last seen: ${miner.lastSeen || "Never"} Last status: ${miner.lastStatus || "Unknown"} Last IP: ${miner.lastIp || "Unknown"} **Step 2: Test Network Connectivity** I'll ping the miner to check if it's reachable. → Using tool: ping_miner({ minerId: "${minerId}" }) Please wait while I test connectivity... **Next Steps Based on Results:** ✅ If ping succeeds: - Use tool: get_miner_logs({ minerId: "${minerId}", lines: 50 }) - Check for error patterns in logs - May need to restart miner services ❌ If ping fails: - Check physical power connection - Verify network cable connected - Check router/switch port status - Verify IP address hasn't changed Would you like me to proceed with the ping test?` } } ]; }
Phase 5: Testing (MCP-Specific)
Step 5.1: Unit Test for MCP Tool
// tests/unit/mcp/tools/updateMinerFirmware.test.ts import { MCPServer } from "../../../../src/server"; import { createMockGrpcClient } from "../../../mocks/grpc"; import { createMockRedis } from "../../../mocks/redis"; describe("update_miner_firmware tool", () => { let mcpServer: MCPServer; let mockGrpc: jest.Mocked<GrpcClient>; let mockRedis: jest.Mocked<Redis>; beforeEach(() => { mockGrpc = createMockGrpcClient(); mockRedis = createMockRedis(); mcpServer = new MCPServer({ grpc: mockGrpc, redis: mockRedis }); }); it("should accept valid firmware update request", async () => { const result = await mcpServer.callTool("update_miner_firmware", { minerIds: ["miner-1", "miner-2"], version: "2.0.1" }); expect(result.jobId).toBeDefined(); expect(result.status).toBe("pending"); expect(result.progress.total).toBe(2); }); it("should reject invalid version format", async () => { await expect( mcpServer.callTool("update_miner_firmware", { minerIds: ["miner-1"], version: "invalid-version" }) ).rejects.toThrow("version must match format"); }); it("should enforce batch size limit", async () => { const tooManyMiners = Array.from({ length: 101 }, (_, i) => `miner-${i}`); await expect( mcpServer.callTool("update_miner_firmware", { minerIds: tooManyMiners, version: "2.0.1" }) ).rejects.toThrow("maximum 100 miners"); }); });
Step 5.2: Integration Test for Resource
// tests/integration/mcp/resources/fleetSummary.test.ts describe("Fleet Summary Resource", () => { let mcpServer: MCPServer; let redis: Redis; beforeAll(async () => { redis = new Redis(process.env.REDIS_URL); mcpServer = new MCPServer({ redis }); }); it("should return cached fleet summary on second request", async () => { const result1 = await mcpServer.readResource("braiins:///fleet/summary"); const result2 = await mcpServer.readResource("braiins:///fleet/summary"); expect(result1).toEqual(result2); // Verify cache was used (mock/spy on Redis get method) expect(redis.get).toHaveBeenCalledWith("cache:fleet:summary"); }); it("should refresh cache after TTL expiry", async () => { await mcpServer.readResource("braiins:///fleet/summary"); // Wait for cache to expire (30s TTL) await sleep(31000); const result = await mcpServer.readResource("braiins:///fleet/summary"); expect(result.lastUpdated).not.toBe(/* previous timestamp */); }); });
Mining-Specific Patterns
Pattern 1: Batch Operations with Progress Tracking
Use Case: Update firmware on 50 miners simultaneously
@tool({ name: "update_multiple_miners" }) async updateMultipleMiners(params: { minerIds: string[]; operation: "firmware_update" | "pool_change" | "reboot"; config: any; }) { // Create job for background execution const jobId = uuid(); // Process in parallel with concurrency limit const concurrency = 10; // Max 10 simultaneous updates const queue = new PQueue({ concurrency }); const promises = params.minerIds.map((minerId) => queue.add(async () => { try { await this.performOperation(minerId, params.operation, params.config); await this.updateJobProgress(jobId, { completed: minerId }); } catch (error) { await this.updateJobProgress(jobId, { failed: minerId, error }); } }) ); // Don't wait - return job ID immediately Promise.all(promises).then(() => { this.updateJobStatus(jobId, "completed"); }); return { jobId, status: "running", total: params.minerIds.length, pollUrl: `braiins:///jobs/${jobId}` }; }
Pattern 2: Real-Time Status Streaming
Use Case: Subscribe to miner status updates via Redis pub/sub
@tool({ name: "subscribe_miner_status" }) async subscribeMinerStatus(params: { minerId: string }) { // Start gRPC stream const stream = await this.grpc.streamMinerStatus(params.minerId); // Publish to Redis for agent consumption for await (const status of stream) { await this.redis.publish( `miner:${params.minerId}:status`, JSON.stringify(status) ); } return { subscribed: true, channel: `miner:${params.minerId}:status`, message: "Status updates will be published to Redis pub/sub channel" }; } // Agent can then read from resource: @resource({ uri: "braiins:///miner/{minerId}/status/stream" }) async getMinerStatusStream(minerId: string) { // Subscribe to Redis channel and return latest status const status = await this.redis.get(`miner:${minerId}:status:latest`); return JSON.parse(status); }
Pattern 3: Actionable Error Guidance
Use Case: Miner unreachable - guide agent to next steps
async function pingMiner(minerId: string) { try { const client = await this.grpc.pool.getConnection(minerId); await client.ping({ timeout: 5000 }); return { reachable: true, latency: "45ms" }; } catch (error) { // Return actionable error with next steps return { reachable: false, error: { code: "MINER_UNREACHABLE", message: `Cannot connect to miner ${minerId}`, suggestions: [ "Check if miner is powered on", "Verify network connectivity with: list_miners", "Check miner IP address in configuration", "Try rebooting miner with: reboot_miner" ], possibleCauses: [ "Miner offline or powered off", "Network firewall blocking gRPC port (50051)", "Incorrect IP address in database", "Miner experiencing hardware failure" ] } }; } }
Quality Standards
MCP Tool Checklist
- Input Validation: Zod schema with proper constraints
- Batch Support: Handles multiple miners when applicable
- Concise Output: Default response < 300 tokens
- Detailed Option: Verbose mode available via
paramdetailLevel - Error Guidance: Errors include suggestions for next steps
- Caching: Reads use cached data when appropriate
- Cache Invalidation: Writes invalidate related caches
- Background Jobs: Long operations return job ID immediately
- Progress Tracking: Job progress available via separate tool/resource
- Annotations: readOnlyHint, destructiveHint, idempotentHint, openWorldHint set correctly
- Documentation: Clear description with examples
- Tests: Unit tests cover happy path + error cases
MCP Resource Checklist
- URI Format: Follows
patternbraiins:///<category>/<resource> - Caching: Appropriate TTL for data type
- Freshness: lastUpdated timestamp included
- Mime Type: Correct content type (application/json, text/plain, etc.)
- Performance: Queries optimized for large fleets
- Tests: Integration tests verify caching behavior
MCP Prompt Checklist
- Guided Workflow: Clear step-by-step instructions
- Tool References: Suggests specific tools for each step
- Resource References: Links to relevant resources
- Conditional Logic: Different paths based on outcomes
- Next Steps: Always provides clear next actions
- Context-Aware: Uses miner-specific data in guidance
Common Pitfalls
❌ Pitfall 1: Not Supporting Batch Operations
Problem: Tool only accepts single miner ID
// BAD @tool({ name: "get_miner_status" }) async getMinerStatus(minerId: string) { /* ... */ } // Agent must call 50 times for 50 miners
Solution: Accept array of IDs
// GOOD @tool({ name: "get_miner_status" }) async getMinerStatus(minerIds: string[]) { return Promise.all(minerIds.map(id => this.fetchStatus(id))); } // Agent calls once for all 50 miners
❌ Pitfall 2: Returning Too Much Data
Problem: Tool returns 5000+ token response overwhelming context
// BAD - Returns full miner details { minerId: "miner-123", status: "running", hashrate: { /* 20 fields */ }, temperature: { /* 15 fields */ }, fans: [ /* 6 fans × 10 fields each */ ], pools: [ /* 3 pools × 30 fields each */ ], // ... 4500 more tokens }
Solution: Concise by default, detailed on request
// GOOD - Concise default { minerId: "miner-123", status: "running", hashrate: "95 TH/s", temp: "65°C", issues: [] // Only show if problems exist } // Detailed available via detailLevel: "verbose"
❌ Pitfall 3: Blocking on Long Operations
Problem: Firmware update takes 10 minutes, blocking agent context
// BAD @tool({ name: "update_firmware" }) async updateFirmware(params: {/* ... */}) { // Wait for 10 minute update to complete await this.performUpdate(params); return { status: "completed" }; } // Agent stuck for 10 minutes
Solution: Return job ID immediately, poll separately
// GOOD @tool({ name: "update_firmware" }) async updateFirmware(params: {/* ... */}) { const jobId = await this.startBackgroundUpdate(params); return { jobId, status: "pending", pollWith: "check_job_status" }; } // Separate tool for polling @tool({ name: "check_job_status" }) async checkJobStatus(jobId: string) { return await this.jobQueue.getStatus(jobId); }
Integration Notes
Related Skills
- braiins-os - Braiins OS API documentation and reference
- mcp-builder - Generic MCP server development patterns
- grpc-client-dev (planned) - gRPC client implementation patterns
- redis-caching-patterns (planned) - Advanced Redis caching strategies
Related Commands
- Initialize development session with project context/start-session
- End session with documentation updates/close-session
- Run comprehensive test suite/test-all
(planned) - Test MCP tools in isolation/test-mcp-tools
Related Agents
- Architect - System design and API design review
- Builder - Feature implementation
- Validator - Testing and code review
- Scribe - Documentation updates
Version History
- 1.0.0 (2025-12-28): Initial skill creation
- Mining-specific MCP development patterns
- Tool/Resource/Prompt workflows
- gRPC integration patterns
- Redis caching strategies
- Batch operations and progress tracking
- Quality checklists and common pitfalls
Metadata
{ "name": "mcp-server-dev", "version": "1.0.0", "description": "Braiins OS MCP Server Development Skill", "author": "braiins-os-mcp-server project", "created": "2025-12-28", "status": "active", "complexity": "complex", "category": "mcp-development", "tags": ["mcp", "braiins-os", "mining", "grpc", "redis", "typescript"], "extends": "mcp-builder", "related_skills": ["braiins-os", "mcp-builder"], "target_project": "braiins-os-mcp-server" }