Skip to main content

Building Standalone MCP Servers

Create standalone Model Context Protocol servers to add tools that don't need entity discovery.

When to Use Standalone MCP vs Molecules

Use a Molecule when integrating a service where entities matter (repositories, deployments, services). Molecules provide discovery, tools, skills, and visualizations in one package.

Use Standalone MCP for tools that don't need entity discovery—like querying a database, calling internal APIs, or adding utility functions.

What are MCP Tools?

MCP tools are functions that AI can call to interact with external services, query data, or perform actions. They extend what the AI can do beyond just answering questions.

MCP Server Basics

An MCP server exposes tools via HTTP, SSE, stdio, or Unix sockets. The server:

  1. Registers available tools with metadata
  2. Receives tool call requests from SixDegree
  3. Executes the requested tool
  4. Returns results

Quick Start (Python)

Install MCP SDK

pip install mcp-python

Create an MCP Server

from mcp import MCPServer, tool

server = MCPServer(name="my-tools")

@server.tool(
name="query_database",
description="Query the PostgreSQL database"
)
def query_database(sql: str) -> dict:
"""Execute SQL query and return results."""
import psycopg2

conn = psycopg2.connect(DATABASE_URL)
cur = conn.cursor()
cur.execute(sql)
rows = cur.fetchall()

return {"rows": rows, "count": len(rows)}

@server.tool(
name="send_email",
description="Send an email notification"
)
def send_email(to: str, subject: str, body: str) -> dict:
"""Send email via SMTP."""
import smtplib

# Send email logic here

return {"status": "sent", "to": to}

if __name__ == "__main__":
server.run(host="0.0.0.0", port=8000)

Quick Start (TypeScript)

Install MCP SDK

npm install @modelcontextprotocol/sdk

Create an MCP Server

import { MCPServer } from '@modelcontextprotocol/sdk';

const server = new MCPServer({
name: 'my-tools',
version: '1.0.0',
});

server.tool({
name: 'query_database',
description: 'Query the PostgreSQL database',
parameters: {
sql: {
type: 'string',
description: 'SQL query to execute',
required: true,
},
},
handler: async ({ sql }) => {
const { Pool } = require('pg');
const pool = new Pool();

const result = await pool.query(sql);
return {
rows: result.rows,
count: result.rowCount,
};
},
});

server.listen(8000);

Tool Definition

Tool Metadata

Every tool needs:

@server.tool(
name="tool_name", # Unique identifier
description="What it does", # Help AI understand when to use it
parameters={ # Input parameters
"param1": {
"type": "string",
"description": "What this parameter is",
"required": True
}
}
)

Parameter Types

Supported types:

  • string: Text values
  • number: Numeric values
  • boolean: True/false
  • array: Lists
  • object: Structured data

Return Values

Tools should return structured data (JSON-serializable):

return {
"status": "success",
"data": [...],
"message": "Operation completed"
}

Authentication

Bearer Token

from mcp import MCPServer
from mcp.auth import bearer_token

server = MCPServer(
name="my-tools",
auth=bearer_token("secret-token")
)

Custom Auth

def custom_auth(request):
token = request.headers.get("X-API-Key")
if token != SECRET_KEY:
raise PermissionError("Invalid API key")
return True

server = MCPServer(
name="my-tools",
auth=custom_auth
)

Error Handling

Handle errors gracefully:

@server.tool(name="risky_operation")
def risky_operation(param: str) -> dict:
try:
result = do_something(param)
return {"status": "success", "result": result}
except ValueError as e:
return {"status": "error", "message": str(e)}
except Exception as e:
return {"status": "error", "message": "Internal error"}

Best Practices

Clear Descriptions

Help the AI understand when to use your tool:

@server.tool(
name="create_user",
description="Create a new user account in the system. Use this when the user asks to add a new user or create an account."
)

Input Validation

Validate parameters before execution:

def create_user(email: str, name: str) -> dict:
if not email or "@" not in email:
return {"status": "error", "message": "Invalid email"}

if not name or len(name) < 2:
return {"status": "error", "message": "Name too short"}

# Create user...

Structured Output

Return consistent, structured responses:

{
"status": "success" | "error",
"data": {...},
"message": "Human-readable message"
}

Deployment

Docker

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY server.py .

EXPOSE 8000
CMD ["python", "server.py"]

Docker Compose

version: '3.8'

services:
mcp-server:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgres://...
- API_KEY=secret

Connecting to SixDegree

Once deployed, add your MCP server to SixDegree:

sixdegree mcp create \
--name "My Tools" \
--url "https://mcp.example.com" \
--transport http \
--token "secret-token"

Example: GitHub MCP Server

from mcp import MCPServer
import requests

server = MCPServer(name="github-tools")

@server.tool(
name="github_create_issue",
description="Create a new GitHub issue"
)
def create_issue(repo: str, title: str, body: str = "") -> dict:
url = f"https://api.github.com/repos/{repo}/issues"
headers = {
"Authorization": f"Bearer {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json"
}
data = {"title": title, "body": body}

response = requests.post(url, headers=headers, json=data)

if response.status_code == 201:
issue = response.json()
return {
"status": "success",
"issue_number": issue["number"],
"url": issue["html_url"]
}
else:
return {
"status": "error",
"message": response.json().get("message", "Unknown error")
}

server.run()

Next Steps