build-custom-mcp-server
Build a custom MCP (Model Context Protocol) server that exposes domain-specific tools to AI assistants. Covers server implementation in Node.js or R, tool definitions, transport configuration, and testing with Claude Code. Use when you need to expose custom functionality beyond what mcptools provides, when building specialized domain-specific AI integrations, or when wrapping existing APIs or services as MCP tools.
Best use case
build-custom-mcp-server is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Build a custom MCP (Model Context Protocol) server that exposes domain-specific tools to AI assistants. Covers server implementation in Node.js or R, tool definitions, transport configuration, and testing with Claude Code. Use when you need to expose custom functionality beyond what mcptools provides, when building specialized domain-specific AI integrations, or when wrapping existing APIs or services as MCP tools.
Teams using build-custom-mcp-server should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/build-custom-mcp-server/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How build-custom-mcp-server Compares
| Feature / Agent | build-custom-mcp-server | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Build a custom MCP (Model Context Protocol) server that exposes domain-specific tools to AI assistants. Covers server implementation in Node.js or R, tool definitions, transport configuration, and testing with Claude Code. Use when you need to expose custom functionality beyond what mcptools provides, when building specialized domain-specific AI integrations, or when wrapping existing APIs or services as MCP tools.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Build Custom MCP Server
Create a custom MCP server that exposes domain-specific tools to AI assistants.
## When to Use
- Need to expose custom functionality to Claude Code or Claude Desktop
- Building specialized tools beyond what mcptools provides
- Creating a domain-specific AI assistant integration
- Wrapping existing APIs or services as MCP tools
## Inputs
- **Required**: List of tools to expose (name, description, parameters, behavior)
- **Required**: Implementation language (Node.js or R)
- **Required**: Transport type (stdio or HTTP)
- **Optional**: Authentication requirements
- **Optional**: Docker packaging needs
## Procedure
### Step 1: Define Tool Specifications
Before writing code, define each tool:
```yaml
tools:
- name: query_database
description: Execute a read-only SQL query against the analysis database
parameters:
query:
type: string
description: SQL SELECT query to execute
required: true
limit:
type: integer
description: Maximum rows to return
default: 100
returns: JSON array of result rows
- name: run_analysis
description: Execute a predefined statistical analysis by name
parameters:
analysis_name:
type: string
description: Name of the analysis to run
enum: [descriptive, regression, survival]
dataset:
type: string
description: Dataset identifier
required: true
```
**Got:** A YAML or markdown specification for each tool with name, description, parameters (including types, defaults, and required flags), and return type documented before writing any code.
**If fail:** If tool specifications are unclear, interview the domain expert or review the existing API documentation to determine parameter types and return formats.
### Step 2: Implement in Node.js (Using MCP SDK)
```javascript
// server.js
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "my-analysis-server",
version: "1.0.0",
});
// Define tools
server.tool(
"query_database",
"Execute a read-only SQL query against the analysis database",
{
query: z.string().describe("SQL SELECT query"),
limit: z.number().default(100).describe("Max rows to return"),
},
async ({ query, limit }) => {
// Validate read-only
if (!/^\s*SELECT/i.test(query)) {
return {
content: [{ type: "text", text: "Error: Only SELECT queries allowed" }],
isError: true,
};
}
const results = await executeQuery(query, limit);
return {
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
};
}
);
server.tool(
"run_analysis",
"Execute a predefined statistical analysis",
{
analysis_name: z.enum(["descriptive", "regression", "survival"]),
dataset: z.string().describe("Dataset identifier"),
},
async ({ analysis_name, dataset }) => {
const result = await runAnalysis(analysis_name, dataset);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
);
// Start server with stdio transport
const transport = new StdioServerTransport();
await server.connect(transport);
```
**Got:** A working `server.js` file that imports the MCP SDK, defines tools with Zod schemas, and connects via stdio transport. Running `node server.js` starts the server without errors.
**If fail:** Verify that `@modelcontextprotocol/sdk` and `zod` are installed (`npm install`). Check that the import paths match the SDK version (the SDK reorganized exports between versions).
### Step 3: Implement in R (Using mcptools)
```r
# server.R
library(mcptools)
# Register custom tools
mcp_tool(
name = "query_database",
description = "Execute a read-only SQL query",
parameters = list(
query = list(type = "string", description = "SQL SELECT query"),
limit = list(type = "integer", description = "Max rows", default = 100)
),
handler = function(query, limit = 100) {
if (!grepl("^\\s*SELECT", query, ignore.case = TRUE)) {
stop("Only SELECT queries allowed")
}
result <- DBI::dbGetQuery(con, paste(query, "LIMIT", limit))
jsonlite::toJSON(result, auto_unbox = TRUE)
}
)
# Start server
mcptools::mcp_server()
```
**Got:** A working `server.R` file that registers custom tools with `mcp_tool()` and starts the server with `mcp_server()`. Running `Rscript server.R` starts the MCP server.
**If fail:** Ensure `mcptools` is installed from GitHub (`remotes::install_github("posit-dev/mcptools")`). Check that the handler function signatures match the parameter definitions.
### Step 4: Set Up Project Structure
```
my-mcp-server/
├── package.json # Node.js dependencies
├── server.js # Server implementation
├── tools/ # Tool implementations
│ ├── database.js
│ └── analysis.js
├── test/ # Tests
│ └── tools.test.js
├── Dockerfile # Container packaging
└── README.md # Setup instructions
```
**Got:** Project directory created with `server.js` (or `server.R`), `package.json`, `tools/` directory for modular tool implementations, and `test/` directory for tests.
**If fail:** If the directory structure doesn't match your implementation language, adjust accordingly. R servers may use `R/` instead of `tools/` and `tests/testthat/` instead of `test/`.
### Step 5: Test the Server
**Manual testing with stdio**:
```bash
echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | node server.js
```
**Register with Claude Code**:
```bash
claude mcp add my-server stdio "node" "/path/to/server.js"
```
**Verify tools appear**:
Start a Claude Code session and check that custom tools are listed and functional.
**Got:** The `tools/list` JSON-RPC call returns all defined tools with correct names and schemas. `claude mcp list` shows the server registered. Tools are callable from a Claude Code session.
**If fail:** If `tools/list` returns an empty array, the tools were not registered before `server.connect()`. If Claude Code cannot find the server, verify the command path in `claude mcp add` is absolute and the binary is executable.
### Step 6: Add Error Handling
```javascript
server.tool("risky_operation", "...", schema, async (params) => {
try {
const result = await performOperation(params);
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
});
```
**Got:** Each tool handler is wrapped in try/catch. Invalid inputs return `isError: true` with a descriptive message instead of crashing the server process.
**If fail:** If the server still crashes on bad input, check that the try/catch wraps the entire handler body including any async operations. Ensure promises are awaited within the try block.
### Step 7: Package for Distribution
Create a `package.json` with a bin entry:
```json
{
"name": "my-mcp-server",
"version": "1.0.0",
"bin": {
"my-mcp-server": "./server.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"zod": "^3.22.0"
}
}
```
Users can then install and configure:
```bash
npm install -g my-mcp-server
claude mcp add my-server stdio "my-mcp-server"
```
**Got:** A `package.json` with a `bin` entry pointing to the server entry point. Users can install globally with `npm install -g` and register with `claude mcp add`.
**If fail:** If the bin entry doesn't work after global install, ensure `server.js` has a shebang line (`#!/usr/bin/env node`) and is marked executable. Verify the package name doesn't conflict with existing npm packages.
## Validation
- [ ] Server starts without errors
- [ ] `tools/list` returns all defined tools with correct schemas
- [ ] Each tool executes correctly with valid input
- [ ] Tools return appropriate errors for invalid input
- [ ] Server works with Claude Code via stdio transport
- [ ] Tools are discoverable and usable in Claude sessions
## Pitfalls
- **Blocking operations**: MCP servers should handle requests asynchronously. Long-running operations block other tool calls.
- **Missing error handling**: Unhandled exceptions crash the server. Always wrap tool handlers in try/catch.
- **Schema mismatches**: Tool parameter schemas must exactly match what the handler expects
- **stdio buffering**: When using stdio transport, ensure output is flushed. Node.js buffers stdout by default.
- **Security**: MCP servers have the same access as the process. Validate inputs carefully, especially for shell commands or database queries.
## Related Skills
- `configure-mcp-server` - connect the built server to clients
- `troubleshoot-mcp-connection` - debug connectivity issues
- `containerize-mcp-server` - package the server in DockerRelated Skills
scaffold-mcp-server
Scaffold a new MCP server from tool specifications using the official SDK (TypeScript or Python), including transport configuration, tool handlers, and test harness. Use when you have a tool spec and need a working server, starting a new MCP project with correct structure, migrating an existing tool integration to the MCP protocol, or prototyping a tool surface to test with Claude Code before full implementation.
optimize-docker-build-cache
Optimize Docker build times using layer caching, multi-stage builds, BuildKit features, and dependency-first copy patterns. Applicable to R, Node.js, and Python projects. Use when Docker builds are slow due to repeated package installations, when rebuilds reinstall all dependencies on every code change, when image sizes are unnecessarily large, or when CI/CD pipeline builds are a bottleneck.
implement-a2a-server
Implement a JSON-RPC 2.0 A2A server with full task lifecycle management (submitted/working/completed/failed/canceled/input-required), SSE streaming, and push notifications. Use when implementing an agent that participates in multi-agent A2A workflows, building a backend for an Agent Card, adding A2A protocol support to an existing agent or service, or deploying an agent that must interoperate with other A2A-compliant agents.
containerize-mcp-server
Containerize an R-based MCP (Model Context Protocol) server using Docker. Covers mcptools integration, port exposure, stdio vs HTTP transport, and connecting Claude Code to the containerized server. Use when deploying an R MCP server without requiring a local R installation, creating a reproducible MCP server environment, running MCP servers alongside other containerized services, or distributing an MCP server to other developers.
configure-mcp-server
Configure MCP (Model Context Protocol) servers for Claude Code and Claude Desktop. Covers mcptools setup, Hugging Face integration, WSL path handling, and multi-client configuration. Use when setting up Claude Code to connect to R via mcptools, configuring Claude Desktop with MCP servers, adding Hugging Face or other remote MCP servers, or troubleshooting MCP connectivity between clients and servers.
build-tcg-deck
Build a competitive or casual trading card game deck. Covers archetype selection, mana/energy curve analysis, win condition identification, meta-game positioning, and sideboard construction for Pokemon TCG, Magic: The Gathering, Flesh and Blood, and other TCGs. Use when building a new deck for a tournament format or casual play, adapting an existing deck to a changed meta-game, evaluating whether a new set warrants a deck change, or converting a deck concept into a tournament-ready list.
build-shiny-module
Build reusable Shiny modules with proper namespace isolation using NS(). Covers module UI/server pairs, reactive return values, inter-module communication, and nested module composition. Use when extracting a reusable component from a growing Shiny app, building a UI widget used in multiple places, encapsulating complex reactive logic behind a clean interface, or composing larger applications from smaller, testable units.
build-sequential-circuit
Build sequential (stateful) logic circuits including latches, flip-flops, registers, counters, and finite state machines. Covers SR latch, D and JK flip-flops, binary/BCD/ring counters, and Mealy/Moore FSM design with clock signal and timing analysis. Use when a circuit must remember past inputs, count events, or implement a state-dependent control sequence.
build-pkgdown-site
Build and deploy a pkgdown documentation site for an R package to GitHub Pages. Covers _pkgdown.yml configuration, theming, article organization, reference index customization, and deployment methods. Use when creating a documentation site for a new or existing package, customizing layout or navigation, fixing 404 errors on a deployed site, or migrating between branch-based and GitHub Actions deployment methods.
build-parameterized-report
Create parameterized Quarto or R Markdown reports that can be rendered with different inputs to generate multiple variations. Covers parameter definitions, programmatic rendering, and batch generation. Use when generating the same report for different departments, regions, or time periods; creating client-specific reports from a single template; building dashboards that filter to specific subsets; or automating recurring reports with varying inputs.
build-grafana-dashboards
Create production-ready Grafana dashboards with reusable panels, template variables, annotations, and provisioning for version-controlled dashboard deployment. Use when creating visual representations of Prometheus, Loki, or other data source metrics, building operational dashboards for SRE teams, migrating from manual dashboard creation to version-controlled provisioning, or establishing executive-level SLO compliance reporting.
build-feature-store
Build a feature store using Feast for centralized feature management, configure offline and online stores for batch and real-time serving, define feature views with transformations, and implement point-in-time correct joins for ML pipelines. Use when managing features for multiple ML models, ensuring training-serving consistency, serving low-latency features for real-time inference, reusing feature definitions across projects, or building a feature catalog for discovery and governance.