Skip to content

Commit 750014a

Browse files
chefgsclaudeCopilotCopilot
authored
docs(mcp): improve MCP setup for beginners across all AI tools (#57)
* docs(mcp): improve MCP setup for beginners across all AI tools - Rewrote mcp_server/README.md with step-by-step guides for Claude Code, Claude Desktop, Cursor, VS Code Copilot, Windsurf, and Zed - Added tools table entry for generate_unittest_config (was missing) - Added troubleshooting section for common setup failures - Fixed setup_devops_os_mcp.sh: replaced hardcoded ~/gsdev path with configurable INSTALL_DIR (default ~/devops_os), added --local flag for users already inside a clone, added --help and Python version check - Added .mcp.json (Claude Code project-level config) - Added .cursor/mcp.json (Cursor IDE project-level config) - Added .vscode/mcp.json (VS Code Copilot project-level config) - Updated .gitignore to track IDE MCP configs while ignoring personal settings - Removed "WIP" label from MCP section in main README.md, added curl one-liner Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add MCP test automation: wire-protocol tests and setup script smoke test (#58) * Add MCP test automation: wire-protocol tests and setup script smoke test Agent-Logs-Url: https://github.com/cloudengine-labs/devops_os/sessions/bcbbfe9c-443e-4778-b9fa-0d9a5d00fc75 Co-authored-by: chefgs <7605658+chefgs@users.noreply.github.com> * fix: use importlib.metadata to check mcp version in smoke test Agent-Logs-Url: https://github.com/cloudengine-labs/devops_os/sessions/67dfed60-b36b-48f1-83f5-84cca466abb9 Co-authored-by: chefgs <7605658+chefgs@users.noreply.github.com> * Update tests/test_mcp_protocol.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update tests/test_mcp_protocol.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update .github/workflows/mcp-setup-smoke.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update .github/workflows/mcp-setup-smoke.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: chefgs <7605658+chefgs@users.noreply.github.com> Co-authored-by: Saravanan Gnanaguru <g.gsaravanan@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update .mcp.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update mcp_server/setup_devops_os_mcp.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update mcp_server/setup_devops_os_mcp.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update mcp_server/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update mcp_server/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * docs: replace curl|bash with download-then-inspect-then-run in README.md Agent-Logs-Url: https://github.com/cloudengine-labs/devops_os/sessions/a2bcc76b-f5a7-421e-9984-542b39e55436 Co-authored-by: chefgs <7605658+chefgs@users.noreply.github.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent f21e8b0 commit 750014a

10 files changed

Lines changed: 1209 additions & 60 deletions

File tree

.cursor/mcp.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"mcpServers": {
3+
"devops-os": {
4+
"command": ".venv/bin/python",
5+
"args": ["-m", "mcp_server.server"],
6+
"cwd": ".",
7+
"env": {
8+
"PYTHONPATH": "."
9+
}
10+
}
11+
}
12+
}
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
name: MCP Setup Smoke Test
2+
3+
# What this workflow proves
4+
# ─────────────────────────
5+
# The setup_devops_os_mcp.sh script is the primary on-boarding path for users
6+
# who want to connect DevOps-OS to Claude Code. This workflow proves the
7+
# full installation flow works end-to-end on a clean Ubuntu machine:
8+
#
9+
# 1. setup_devops_os_mcp.sh --local creates a Python venv at .venv/
10+
# 2. All MCP server dependencies are installed (importable from the venv)
11+
# 3. The MCP server process starts successfully under the venv interpreter
12+
# 4. The server completes the MCP initialize handshake
13+
# 5. tools/list returns all 8 expected DevOps-OS tools
14+
# 6. The claude mcp add / claude mcp list commands are exercised via a
15+
# stub so the registration code path is validated even without a real
16+
# Claude CLI binary or API key
17+
18+
on:
19+
push:
20+
branches: [main, "copilot/**"]
21+
paths:
22+
- "mcp_server/**"
23+
- ".github/workflows/mcp-setup-smoke.yml"
24+
pull_request:
25+
branches: [main]
26+
paths:
27+
- "mcp_server/**"
28+
- ".github/workflows/mcp-setup-smoke.yml"
29+
30+
permissions:
31+
contents: read
32+
33+
jobs:
34+
mcp-setup-smoke:
35+
name: MCP Setup Smoke Test
36+
runs-on: ubuntu-latest
37+
timeout-minutes: 20
38+
39+
steps:
40+
- uses: actions/checkout@v4
41+
42+
- name: Set up Python 3.11
43+
uses: actions/setup-python@v5
44+
with:
45+
python-version: "3.11"
46+
47+
# ── Step 1: stub the 'claude' binary ────────────────────────────────────
48+
# The setup script calls `claude mcp list` and `claude mcp add`.
49+
# We provide a minimal stub that:
50+
# • Returns a non-zero exit code for `mcp list` (so the script skips
51+
# the "remove existing entry" path) — mirrors a fresh install.
52+
# • Exits 0 for `mcp add` and logs the invocation so we can assert
53+
# the correct arguments were passed.
54+
# This validates the registration code path without a real Claude binary.
55+
- name: Install claude CLI stub
56+
run: |
57+
mkdir -p "$HOME/.local/bin"
58+
cat > "$HOME/.local/bin/claude" << 'EOF'
59+
#!/usr/bin/env bash
60+
# Claude CLI stub for CI smoke testing.
61+
# Logs every invocation to $HOME/claude-stub.log.
62+
echo "claude stub called: $*" >> "$HOME/claude-stub.log"
63+
if [[ "$1 $2" == "mcp list" ]]; then
64+
# Return 1 so the 'grep -q "^devops-os"' check fails ─ simulates
65+
# a clean install with no prior registration.
66+
exit 1
67+
fi
68+
# All other sub-commands (mcp add, mcp remove) succeed silently.
69+
exit 0
70+
EOF
71+
chmod +x "$HOME/.local/bin/claude"
72+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
73+
74+
# ── Step 2: run the setup script in local mode ──────────────────────────
75+
- name: Run setup_devops_os_mcp.sh --local
76+
run: bash mcp_server/setup_devops_os_mcp.sh --local
77+
78+
# ── Step 3: verify .venv was created ────────────────────────────────────
79+
- name: Verify .venv was created
80+
run: |
81+
echo "Checking for .venv directory..."
82+
test -d .venv || { echo "ERROR: .venv was not created by setup script"; exit 1; }
83+
test -f .venv/bin/python || { echo "ERROR: .venv/bin/python not found"; exit 1; }
84+
echo "✓ .venv exists: $(.venv/bin/python --version)"
85+
86+
# ── Step 4: verify MCP server dependencies are installed ────────────────
87+
- name: Verify MCP server dependencies installed in venv
88+
run: |
89+
echo "Checking that 'mcp' package is importable from .venv..."
90+
.venv/bin/python -c "
91+
import importlib.metadata
92+
v = importlib.metadata.version('mcp')
93+
from mcp.server.fastmcp import FastMCP
94+
print('✓ mcp version:', v, '— FastMCP importable')
95+
" || { echo "ERROR: mcp package not installed in .venv"; exit 1; }
96+
97+
echo "Checking that 'yaml' package is importable from .venv..."
98+
.venv/bin/python -c "import yaml; print('✓ pyyaml imported')" \
99+
|| { echo "ERROR: pyyaml not installed in .venv"; exit 1; }
100+
101+
# ── Step 5: verify claude stub was called with the right arguments ───────
102+
- name: Verify claude mcp add was invoked with correct arguments
103+
run: |
104+
echo "Contents of claude-stub.log:"
105+
cat "$HOME/claude-stub.log"
106+
107+
# Assert 'claude mcp add' was called
108+
grep -q "mcp add" "$HOME/claude-stub.log" \
109+
|| { echo "ERROR: 'claude mcp add' was never called by the setup script"; exit 1; }
110+
111+
# Assert --transport stdio was passed
112+
grep -q "stdio" "$HOME/claude-stub.log" \
113+
|| { echo "ERROR: '--transport stdio' was not passed to 'claude mcp add'"; exit 1; }
114+
115+
# Assert the server name 'devops-os' was registered
116+
grep -q "devops-os" "$HOME/claude-stub.log" \
117+
|| { echo "ERROR: 'devops-os' server name was not passed to 'claude mcp add'"; exit 1; }
118+
119+
# Assert mcp_server.server module was referenced
120+
grep -q "mcp_server.server" "$HOME/claude-stub.log" \
121+
|| { echo "ERROR: 'mcp_server.server' module not referenced in 'claude mcp add' call"; exit 1; }
122+
123+
echo "✓ claude mcp add was called with all expected arguments"
124+
125+
# ── Step 6: verify the MCP server starts and responds via JSON-RPC ───────
126+
- name: Verify MCP server starts and responds to tools/list
127+
run: |
128+
.venv/bin/python - << 'PYEOF'
129+
import json, os, subprocess, sys
130+
131+
repo_root = os.getcwd()
132+
env = {**os.environ, "PYTHONPATH": repo_root}
133+
venv_python = os.path.join(repo_root, ".venv", "bin", "python")
134+
135+
print("Starting MCP server via venv python:", venv_python)
136+
proc = subprocess.Popen(
137+
[venv_python, "-m", "mcp_server.server"],
138+
stdin=subprocess.PIPE,
139+
stdout=subprocess.PIPE,
140+
stderr=subprocess.PIPE,
141+
text=True,
142+
env=env,
143+
cwd=repo_root,
144+
)
145+
146+
def send(method, params=None, req_id=None):
147+
msg = {"jsonrpc": "2.0", "method": method}
148+
if req_id is not None:
149+
msg["id"] = req_id
150+
if params is not None:
151+
msg["params"] = params
152+
proc.stdin.write(json.dumps(msg) + "\n")
153+
proc.stdin.flush()
154+
if req_id is not None:
155+
return json.loads(proc.stdout.readline())
156+
157+
# MCP initialize handshake
158+
init_resp = send("initialize", {
159+
"protocolVersion": "2024-11-05",
160+
"capabilities": {},
161+
"clientInfo": {"name": "smoke-test", "version": "1.0"},
162+
}, req_id=1)
163+
164+
if "error" in init_resp:
165+
proc.terminate()
166+
print("ERROR: initialize failed:", init_resp["error"])
167+
sys.exit(1)
168+
169+
proto_ver = init_resp["result"]["protocolVersion"]
170+
server_name = init_resp["result"]["serverInfo"]["name"]
171+
print(f"✓ Initialize handshake OK — server: {server_name!r}, protocol: {proto_ver}")
172+
173+
# Send notifications/initialized (required before tool calls)
174+
send("notifications/initialized")
175+
176+
# tools/list
177+
list_resp = send("tools/list", {}, req_id=2)
178+
if "error" in list_resp:
179+
proc.terminate()
180+
print("ERROR: tools/list failed:", list_resp["error"])
181+
sys.exit(1)
182+
183+
tools = list_resp["result"]["tools"]
184+
names = {t["name"] for t in tools}
185+
expected = {
186+
"generate_github_actions_workflow",
187+
"generate_gitlab_ci_pipeline",
188+
"generate_jenkins_pipeline",
189+
"generate_k8s_config",
190+
"generate_argocd_config",
191+
"generate_sre_configs",
192+
"scaffold_devcontainer",
193+
"generate_unittest_config",
194+
}
195+
missing = expected - names
196+
if missing:
197+
proc.terminate()
198+
print("ERROR: missing tools:", missing)
199+
sys.exit(1)
200+
201+
print(f"✓ tools/list returned {len(tools)} tools, all 8 DevOps-OS tools present")
202+
for t in sorted(tools, key=lambda x: x["name"]):
203+
print(f" • {t['name']}")
204+
205+
# Quick tools/call sanity check: generate a GHA workflow
206+
call_resp = send("tools/call", {
207+
"name": "generate_github_actions_workflow",
208+
"arguments": {"name": "smoke-test-app", "languages": "python"},
209+
}, req_id=3)
210+
211+
if "error" in call_resp:
212+
proc.terminate()
213+
print("ERROR: tools/call failed:", call_resp["error"])
214+
sys.exit(1)
215+
216+
content_text = call_resp["result"]["content"][0]["text"]
217+
assert "smoke-test-app" in content_text, "app name not in GHA output"
218+
assert "runs-on:" in content_text, "not a valid GHA YAML"
219+
print("✓ tools/call generate_github_actions_workflow returned valid GHA YAML")
220+
221+
proc.terminate()
222+
proc.wait(timeout=5)
223+
print("\nAll smoke checks passed ✓")
224+
PYEOF

.github/workflows/sanity.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ jobs:
7070
- name: "Scenario: MCP server — Dev container tool"
7171
run: pytest tests/test_comprehensive.py::TestMCPServerDevcontainer -v
7272

73+
# ── MCP wire-protocol tests ───────────────────────────────────────────
74+
75+
- name: "Scenario: MCP wire protocol — handshake, tool discovery, invocation"
76+
run: pytest tests/test_mcp_protocol.py -v
77+
7378
# ── AI skills definitions ──────────────────────────────────────────────
7479

7580
- name: "Scenario: AI skills definitions (OpenAI & Claude)"
@@ -88,7 +93,7 @@ jobs:
8893
- name: Generate combined HTML report
8994
if: always()
9095
run: |
91-
pytest cli/test_cli.py mcp_server/test_server.py tests/test_comprehensive.py \
96+
pytest cli/test_cli.py mcp_server/test_server.py tests/test_comprehensive.py tests/test_mcp_protocol.py \
9297
--html=sanity-report.html --self-contained-html -q
9398
9499
- name: Upload sanity test report

.gitignore

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,15 @@ pytestdebug.log
6262
*.iws
6363
.idea_modules/
6464

65-
# VS Code
66-
.vscode/
65+
# VS Code (ignore personal settings, track shared MCP config)
66+
.vscode/*
67+
!.vscode/mcp.json
6768
*.code-workspace
6869

70+
# Cursor (ignore personal settings, track shared MCP config)
71+
.cursor/*
72+
!.cursor/mcp.json
73+
6974
# Spyder
7075
.spyderproject
7176
.spyproject

.mcp.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"mcpServers": {
3+
"devops-os": {
4+
"command": ".venv/bin/python",
5+
"args": ["-m", "mcp_server.server"],
6+
"cwd": ".",
7+
"env": {
8+
"PYTHONPATH": "."
9+
}
10+
}
11+
}
12+
}

.vscode/mcp.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"servers": {
3+
"devops-os": {
4+
"type": "stdio",
5+
"command": ".venv/bin/python",
6+
"args": ["-m", "mcp_server.server"],
7+
"cwd": ".",
8+
"env": {
9+
"PYTHONPATH": "."
10+
}
11+
}
12+
}
13+
}

README.md

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,17 +209,35 @@ Single Cli Command for Platform Engineering Capabilities - Dev Container and CIC
209209

210210
---
211211

212-
### 6 — Use with AI (MCP Server) - **WIP**
212+
### 6 — Use with AI (MCP Server)
213+
214+
Connect DevOps-OS tools to any MCP-compatible AI assistant: **Claude Code, Claude Desktop, Cursor, VS Code Copilot, Windsurf, and Zed**.
215+
216+
**Fastest setup (Claude Code CLI):**
213217

214218
```bash
215-
pip install -r mcp_server/requirements.txt
216-
python mcp_server/server.py
219+
# Download the setup script
220+
curl -fsSLo setup_devops_os_mcp.sh https://raw.githubusercontent.com/cloudengine-labs/devops_os/main/mcp_server/setup_devops_os_mcp.sh
221+
222+
# (Optional but recommended) Inspect the script before running it
223+
less setup_devops_os_mcp.sh
224+
225+
# Run the setup script
226+
bash setup_devops_os_mcp.sh
217227
```
218228

219-
Add to your `claude_desktop_config.json` and ask Claude:
220-
> *"Generate a complete CI/CD GitHub Actions workflow for my Python API with Kubernetes deployment using ArgoCD."*
229+
Already cloned the repo? Run locally instead:
230+
231+
```bash
232+
bash mcp_server/setup_devops_os_mcp.sh --local
233+
```
234+
235+
Then ask your AI assistant:
236+
> *"Generate a complete GitHub Actions CI/CD workflow for my Python API with Kubernetes deployment using ArgoCD."*
237+
238+
**[Full setup guide →](mcp_server/README.md)** — covers Claude Desktop, Cursor, VS Code, Windsurf, Zed, and troubleshooting.
221239

222-
See **[mcp_server/README.md](mcp_server/README.md)** for full setup and **[skills/README.md](skills/README.md)** for Claude API & OpenAI function-calling examples.
240+
See **[skills/README.md](skills/README.md)** for Claude API & OpenAI function-calling examples.
223241

224242
---
225243

0 commit comments

Comments
 (0)