- Posted on
- • Artificial Intelligence
Getting Started with MCP on Linux
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Getting Started with MCP on Linux: From zero to a working server
If you’ve ever wished your AI assistant could safely run the exact Linux tools you already trust—grep, tail, curl, your internal CLIs—without copy/paste or brittle glue, the Model Context Protocol (MCP) is how you do it. MCP is a lightweight, open protocol that lets you expose tools and resources to AI clients (like inspectors or assistants) in a secure, auditable way over simple transports such as stdio. That makes Linux a first-class environment for building reliable, scriptable, and debuggable AI integrations.
In this guide, you’ll learn why MCP is worth your time, how to set up a minimal server on Linux, and how to run it like any other service. You’ll walk away with a runnable “Hello, MCP” server and a clear path to expand it with your own tools.
Why MCP on Linux?
It’s composable by design. MCP speaks JSON-RPC and is transport-agnostic. The stdio transport works perfectly with Unix processes, systemd, logs, and tooling you already use.
It’s safer than shelling out ad hoc. You explicitly expose tools and resources with schemas, allowlists, and guardrails, rather than giving a general-purpose shell to an AI.
It’s testable and observable. Since MCP servers are just processes, you can containerize them, log them, and CI-test them like any other service.
It reduces glue pain. Instead of unique “plugin” systems for each assistant, MCP provides a uniform interface. Write once, use in any MCP-capable client.
What we’ll build
A minimal MCP server that exposes a simple “greet” tool.
Run it on Linux using Node.js (works on any distribution).
Launch and test it with a GUI inspector (or wire it into your own client later).
Optionally make it persistent with systemd.
You can translate the concepts into Python or other languages later; the Node.js SDK path below just gets you productive quickly.
1) Install prerequisites
You’ll need Git, Curl, Python (for general scripting), and Node.js (for the example server). Use the package manager for your distro:
- Ubuntu/Debian (apt):
sudo apt update
sudo apt install -y git curl python3 python3-pip
# Node.js (distro versions can be old). For Node 20 via NodeSource:
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
- Fedora/RHEL/CentOS (dnf):
sudo dnf install -y git curl python3 python3-pip nodejs npm
- openSUSE/SLE (zypper):
sudo zypper refresh
sudo zypper install -y git curl python3 python3-pip nodejs npm
Optional but recommended for Python tooling isolation:
- pipx lets you install Python CLI tools cleanly. It may be packaged as “pipx” or “python3-pipx” on your distro. If not available, install via pip:
python3 -m pip install --user pipx
python3 -m pipx ensurepath
Verify versions:
node -v
npm -v
python3 --version
You want Node 18+ (ideally 20+) and Python 3.10+.
2) Scaffold a minimal MCP server (Node.js)
Create a project and add the official MCP SDK:
mkdir mcp-hello
cd mcp-hello
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/sdk
Create server.mjs with a minimal “greet” tool. This server speaks MCP over stdio so any MCP client can run it as a subprocess.
/* server.mjs */
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server(
{
name: "hello-mcp",
version: "0.1.0",
},
{
// Advertise capabilities (we’ll expose tools)
capabilities: {
tools: {},
resources: {},
},
}
);
// Register a simple tool: greet({ name: string }) -> "Hello, name!"
server.tool(
"greet",
{
description: "Return a friendly greeting",
inputSchema: {
type: "object",
properties: {
name: { type: "string", description: "The name to greet" }
},
required: ["name"],
additionalProperties: false
}
},
async (args) => {
const who = args.name?.trim() || "world";
return {
content: [{ type: "text", text: `Hello, ${who}!` }]
};
}
);
// Start the server over stdio
const transport = new StdioServerTransport();
await server.connect(transport);
Notes:
The SDK exports a stdio transport; the server process reads JSON-RPC requests from stdin and writes responses to stdout.
We explicitly declare the tool’s input schema so clients can validate/assist with the right structure.
Keep tools small and composable—just like CLI commands.
Run it:
node server.mjs
The process will wait for an MCP client to connect via stdio (you won’t see output until a client initializes the session).
3) Test it with an MCP Inspector
To interactively explore your server’s tools and responses, use an MCP-capable inspector. On Linux, grab the latest “MCP Inspector” build for your architecture from its official releases, mark it executable, and run it. For example:
# Example flow; adjust filename/version to match the current release you downloaded
chmod +x mcp-inspector-*.AppImage
./mcp-inspector-*.AppImage
In the inspector:
Add a new server that launches the command:
node /path/to/mcp-hello/server.mjsConnect to it and you should see the “greet” tool available.
Invoke greet with input like:
{ "name": "Linux" }
- You should get back:
Hello, Linux!
Tip: If your client supports environment variables or working directories, you can point your server at a specific project path to make future tools (e.g., file browsing) contextual.
4) Make it a proper Linux service (systemd)
Once you’re happy with your server, run it persistently so any MCP client can attach to it by command.
- Move the project to a stable path:
sudo mkdir -p /opt/mcp-hello
sudo cp -r . /opt/mcp-hello/
cd /opt/mcp-hello
sudo npm ci --omit=dev
- Create a dedicated user (optional but recommended):
sudo useradd --system --home /opt/mcp-hello --shell /usr/sbin/nologin mcp
sudo chown -R mcp:mcp /opt/mcp-hello
- Create a systemd unit:
sudo tee /etc/systemd/system/mcp-hello.service >/dev/null <<'EOF'
[Unit]
Description=MCP Hello Server
After=network.target
[Service]
Type=simple
User=mcp
WorkingDirectory=/opt/mcp-hello
ExecStart=/usr/bin/node /opt/mcp-hello/server.mjs
Restart=on-failure
Environment=NODE_ENV=production
# Tighten security where possible
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
[Install]
WantedBy=multi-user.target
EOF
- Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable --now mcp-hello
sudo systemctl status mcp-hello --no-pager
journalctl -u mcp-hello -f
Now any MCP client that launches the command /usr/bin/node /opt/mcp-hello/server.mjs will connect to the same, persistent server.
5) Patterns you can implement next (real-world ideas)
A few practical tools to expose as MCP tools. These snippets show input schemas and handler sketches you can adapt:
- List files from a safe root (allowlist a base directory):
server.tool(
"list_files",
{
description: "List files under a base directory (non-recursive by default)",
inputSchema: {
type: "object",
properties: {
base: { type: "string", description: "Absolute base directory" },
recursive: { type: "boolean", default: false }
},
required: ["base"]
}
},
async ({ base, recursive }) => {
// Validate base is in allowed roots
// Use fs.readdir / fs.opendir and avoid following symlinks for safety
// Return a structured list
return {
content: [{ type: "text", text: JSON.stringify({ files: [/* ... */] }, null, 2) }]
};
}
);
- Fetch a URL with a timeout and size cap:
server.tool(
"http_get",
{
description: "Fetch a URL with limits",
inputSchema: {
type: "object",
properties: {
url: { type: "string" },
timeoutMs: { type: "number", default: 8000 },
maxBytes: { type: "number", default: 200000 }
},
required: ["url"]
}
},
async ({ url, timeoutMs, maxBytes }) => {
// Use fetch with AbortController; enforce maxBytes when reading body
return { content: [{ type: "text", text: "<response body or summary>" }] };
}
);
- Run a specific internal CLI subcommand (never a full shell):
server.tool(
"run_status",
{
description: "Runs a read-only status command from internal CLI",
inputSchema: { type: "object", properties: {}, additionalProperties: false }
},
async () => {
// Use child_process.spawnFile with a fixed binary and args
return { content: [{ type: "text", text: "<stdout>" }] };
}
);
Security tips:
Prefer fixed binaries and allowlists over arbitrary arguments.
Validate and sanitize all paths; avoid following symlinks where it matters.
Impose timeouts, output caps, and concurrency limits.
Run under a dedicated user with minimal permissions (as shown in the systemd unit).
Troubleshooting
Node not found or old? Install Node 20 via your distro or NodeSource (shown in apt section). On Fedora, consider
sudo dnf module list nodejsto pick a newer stream.Inspector can’t connect? Ensure the command path is correct, the server starts, and no firewall/policy blocks stdio usage (it shouldn’t, as it’s local).
Nothing happens when running
node server.mjs? That’s expected until a client initializes MCP over stdio. Use the inspector to connect.
Conclusion and next steps
You’ve stood up a working MCP server on Linux, made it discoverable to an inspector, and learned how to run it as a proper service. From here:
Wrap one real command you use daily as a safe, parameterized MCP tool.
Add input schemas and guardrails so your AI client can call it confidently.
Iterate: combine a few tools (file ops + HTTP fetch + internal status) to create an effective “ops cockpit” the AI can drive—securely and reproducibly.
When you’re ready, package your server in a container, add CI tests, and point your MCP-capable assistant at it. Your Linux toolbox just became AI-native—on your terms.