From revskills
Model Context Protocol server development patterns. Use when building custom MCP servers, exposing tools and resources, handling JSON-RPC, implementing credential isolation, or integrating with Claude Desktop, Cursor, or AI agents. Covers @modelcontextprotocol/sdk, StdioServerTransport, tool schemas, and multi-tenant credential patterns.
How this skill is triggered — by the user, by Claude, or both
Slash command
/revskills:mcp-serverThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
```typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
type CallToolRequest,
CallToolRequestSchema,
ListToolsRequestSchema,
type Tool,
} from '@modelcontextprotocol/sdk/types.js';
const server = new Server(
{ name: 'my-server', version: '0.1.0' },
{ capabilities: { tools: {} } },
);
Tools use JSON Schema for input validation:
const TOOLS: Tool[] = [
{
name: 'my_tool',
description: 'What this tool does and when to use it.',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query' },
limit: { type: 'number', description: 'Max results (default: 10)', default: 10 },
filters: {
type: 'object',
description: 'Optional filters',
properties: {
status: { type: 'string', enum: ['active', 'archived'] },
},
},
},
required: ['query'],
},
},
];
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
const startTime = Date.now();
const toolName = request.params.name;
try {
let data: unknown;
switch (toolName) {
case 'my_tool': {
const { query, limit = 10 } = request.params.arguments as {
query: string;
limit?: number;
};
data = await myApiCall(query, limit);
break;
}
default:
return {
content: [{ type: 'text', text: `Error: Unknown tool: ${toolName}` }],
isError: true,
};
}
return {
content: [{
type: 'text',
text: JSON.stringify({
data,
_meta: {
durationMs: Date.now() - startTime,
server: 'my-server',
tool: toolName,
timestamp: new Date().toISOString(),
},
}, null, 2),
}],
};
} catch (err) {
return {
content: [{
type: 'text',
text: `Error: ${err instanceof Error ? err.message : String(err)}`,
}],
isError: true,
};
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((err) => {
console.error('MCP server error:', err);
process.exit(1);
});
For multi-tenant servers, use a credential override pattern:
let _credentialOverrides: Record<string, string> = {};
export function setCredentials(creds: Record<string, string>): void {
_credentialOverrides = creds;
}
// In tool handler:
const apiKey = _credentialOverrides.API_KEY ?? process.env.API_KEY;
if (!apiKey) {
return { content: [{ type: 'text', text: 'Error: API_KEY not set' }], isError: true };
}
The hypervisor calls setCredentials() with tenant-scoped credentials before each invocation.
function authHeaders(token: string): Record<string, string> {
return {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'User-Agent': 'MyServer-MCP/0.1.0',
};
}
async function apiPost(path: string, body: Record<string, unknown>): Promise<unknown> {
const { apiUrl, token } = getConfig();
const res = await fetch(`${apiUrl}${path}`, {
method: 'POST',
headers: authHeaders(token),
body: JSON.stringify(body),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error ?? `API error ${res.status}`);
return data;
}
hypervisor.registerServer({
name: 'my-server',
command: 'node',
args: ['dist/servers/my-server.js'],
env: { API_KEY: 'resolved-at-spawn' },
requiredTier: 'pro',
});
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["/path/to/dist/servers/my-server.js"],
"env": {
"API_KEY": "your-api-key"
}
}
}
}
Always return content array with type: 'text':
// Success
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
// Error
return {
content: [{ type: 'text', text: `Error: ${message}` }],
isError: true,
};
_meta with duration, server name, tool name, timestampstripe_, memory_)required only for truly required fields; use defaults for optionalnpx claudepluginhub revealuistudio/revskills --plugin revskillsGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.