Model Context Protocol (MCP) in Production: Deploying Custom Servers to Automate Server SSH Workflows

Learn how to build and run custom Model Context Protocol (MCP) servers to automate deployments and manage servers securely from your IDE.

Model Context Protocol (MCP) in Production: Deploying Custom Servers to Automate Server SSH Workflows

The Model Context Protocol (MCP), developed by Anthropic, is a protocol that enables AI assistants to interact with local and remote filesystems, APIs, and dev environments. While standard MCP servers handle simple filesystem actions, deploying custom MCP servers in production allows you to automate complex server deployments and SSH configurations directly from your AI-integrated IDE.

However, granting an AI engine access to production servers via SSH introduces critical security risks. If your custom MCP server lacks strict authorization controls, a hallucinated command could delete databases or expose keys.

This guide details how to build a custom MCP server in Node.js, run SSH tasks securely, and implement restriction layers to protect your production resources.


1. What is an MCP Server?

MCP works on a client-server architecture:

  • MCP Client: Your IDE (e.g. Cursor or VS Code) which runs the LLM context.
  • MCP Server: A lightweight background service running on your machine or server that exposes specific tools (JavaScript functions) via standard input/output (stdio).
  • AI Agent: Reads tool descriptions, decides which tool to call, and processes the output.

2. Building a Custom SSH MCP Server in Node.js

Let's build a custom MCP server that allows an AI assistant to check remote server disk usage and pull the latest Git commits on staging servers.

Step 1: Initialize the Server Project

`bash

mkdir ssh-mcp-server

cd ssh-mcp-server

npm init -y

npm install @modelcontextprotocol/sdk ssh2 dotenv

`

Step 2: Write the Server Code

Create a file named index.js and implement the MCP tool definitions:

`javascript

import { Server } from "@modelcontextprotocol/sdk/server/index.js";

import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";

import { Client } from "ssh2";

import dotenv from "dotenv";

dotenv.config();

const server = new Server(

{ name: "secure-ssh-mcp-server", version: "1.0.0" },

{ capabilities: { tools: {} } }

);

// Define tools available to the AI Assistant

server.setRequestHandler(ListToolsRequestSchema, async () => ({

tools: [

{

name: "run_git_pull",

description: "Pulls the latest Git code on the staging server.",

inputSchema: {

type: "object",

properties: {

project: { type: "string", description: "Project directory name under /var/www/" }

},

required: ["project"]

}

}

]

}));

// Handle tool executions

server.setRequestHandler(CallToolRequestSchema, async (request) => {

if (request.params.name === "run_git_pull") {

const project = request.params.arguments.project;

// Strict Input Validation (Prevent Path Traversal)

if (!/^[a-zA-Z0-9-_]+$/.test(project)) {

throw new Error("Invalid project name structure.");

}

const command = cd /var/www/${project} && git pull origin main;

const result = await executeSshCommand(command);

return {

content: [{ type: "text", text: result }]

};

}

throw new Error("Tool not found.");

});

function executeSshCommand(command) {

return new Promise((resolve, reject) => {

const conn = new Client();

conn.on("ready", () => {

conn.exec(command, (err, stream) => {

if (err) return reject(err);

let output = "";

stream.on("close", (code, signal) => {

conn.end();

resolve(output);

}).on("data", (data) => {

output += data;

}).stderr.on("data", (data) => {

output += "STDERR: " + data;

});

});

}).connect({

host: process.env.SSH_HOST,

port: 22,

username: process.env.SSH_USER,

privateKey: readFileSync(process.env.SSH_KEY_PATH)

});

});

}

const transport = new StdioServerTransport();

await server.connect(transport);

`


3. Registering the Server in Your IDE

To use your custom server in Cursor or VS Code, edit your local IDE MCP configurations:

`json

{

"mcpServers": {

"ssh-mcp-server": {

"command": "node",

"args": ["/absolute/path/to/ssh-mcp-server/index.js"],

"env": {

"SSH_HOST": "192.168.1.100",

"SSH_USER": "deploy",

"SSH_KEY_PATH": "/Users/username/.ssh/id_rsa"

}

}

}

}

`


4. Security Constraints for Production

When running custom MCP tools, enforce the following security layers:

1. Avoid Arbitrary Command Execution: Never expose a general run_command tool that accepts raw strings. Only expose specific tools like run_git_pull or check_disk_usage.

2. Use a Sandboxed SSH User: Connect via an SSH key mapped to a user account with limited privileges (deploy rather than root), restricted to specific directories via sudoers parameters.

3. Validate Arguments: Use regular expressions to clean user arguments to prevent command injection exploits.

Building specialized MCP servers allows you to safely delegate deployment workflows to your AI assistant, keeping your development pipeline fast and secure.

Related posts

WordPress Database Optimization: How to Clean wp_options and Accelerate Page Speeds
Web Development5 min read

WordPress Database Optimization: How to Clean wp_options and Accelerate Page Speeds

A sluggish WordPress admin dashboard or slow page loading times is often caused by database bloat. Learn how to audit, clean, and optimize your wp_options table.

Read article →

Custom Gutenberg Blocks vs. Page Builders: Why Astra, Elementor, and Divi are Bloating Your Corporate Site
Web Development4 min read

Custom Gutenberg Blocks vs. Page Builders: Why Astra, Elementor, and Divi are Bloating Your Corporate Site

While visual page builders offer design speed, they introduce massive CSS/JS overhead. Discover why custom Gutenberg blocks are the standard for high-performance corporate sites.

Read article →

How to Configure LiteSpeed Cache (LSCache) for Complex Dynamic WordPress Sites
Web Development4 min read

How to Configure LiteSpeed Cache (LSCache) for Complex Dynamic WordPress Sites

Caching dynamic WordPress websites requires precision to prevent display bugs. Discover how to configure LiteSpeed Cache for user portals, WooCommerce carts, and web applications.

Read article →

Author

Anushka Dahanayake

Anushka Dahanayake is the founder of ANUSHKA DAHANAYAKE (PVT) LTD, building SEO-driven content, digital services, and revenue platforms for businesses in Sri Lanka and worldwide.