Skip to content

Commit dd040c4

Browse files
committed
fix: review findings + CI pipeline for MCP server tests
Review fixes: - config.py: PORT parsing wrapped in try/except, falls back to 8080 - server.py: pinned mcp<2.0.0, documented _mcp_server private API access - requirements.txt: mcp>=1.25.0,<2.0.0 upper bound - Dockerfile: added HEALTHCHECK with curl to /health endpoint - 004_api_keys.sql: CHECK constraint on tier column, immutable column trigger (blocks key_hash/tier/name changes for non-service-role), documented NULL user_id = system key behavior CI pipeline: - Added mcp-server/** path filter to change detection - New test-mcp job: Python 3.13, runs pytest on mcp-server/tests/ - Security scan now also triggers on MCP changes
1 parent 9e9a0e4 commit dd040c4

6 files changed

Lines changed: 79 additions & 6 deletions

File tree

.github/workflows/ci.yml

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ jobs:
1717
outputs:
1818
backend: ${{ steps.filter.outputs.backend }}
1919
frontend: ${{ steps.filter.outputs.frontend }}
20+
mcp: ${{ steps.filter.outputs.mcp }}
2021
steps:
2122
- uses: actions/checkout@v4
2223
- uses: dorny/paths-filter@v3
@@ -28,6 +29,8 @@ jobs:
2829
- 'railway.json'
2930
frontend:
3031
- 'frontend/**'
32+
mcp:
33+
- 'mcp-server/**'
3134
3235
test-backend:
3336
name: Backend Tests
@@ -104,10 +107,39 @@ jobs:
104107
working-directory: ./frontend
105108
run: bun run test
106109

110+
test-mcp:
111+
name: MCP Server Tests
112+
needs: changes
113+
if: ${{ needs.changes.outputs.mcp == 'true' }}
114+
runs-on: ubuntu-latest
115+
116+
steps:
117+
- uses: actions/checkout@v4
118+
119+
- name: Set up Python
120+
uses: actions/setup-python@v5
121+
with:
122+
python-version: '3.13'
123+
cache: 'pip'
124+
125+
- name: Install dependencies
126+
working-directory: ./mcp-server
127+
run: |
128+
python -m pip install --upgrade pip
129+
pip install -r requirements.txt
130+
131+
- name: Run tests
132+
working-directory: ./mcp-server
133+
env:
134+
API_KEY: "test-key"
135+
BACKEND_API_URL: "http://localhost:8000"
136+
run: |
137+
pytest tests/ -v
138+
107139
security-scan:
108140
name: Security Scan
109141
needs: changes
110-
if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' }}
142+
if: ${{ needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true' || needs.changes.outputs.mcp == 'true' }}
111143
runs-on: ubuntu-latest
112144

113145
steps:

mcp-server/Dockerfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,9 @@ ENV MCP_HOST=0.0.0.0
1414

1515
EXPOSE 8080
1616

17+
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
18+
19+
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
20+
CMD curl -f http://localhost:${PORT:-8080}/health || exit 1
21+
1722
CMD ["python", "server.py"]

mcp-server/config.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,8 @@
1818
# Transport: "stdio" for local, "streamable-http" for remote deployment
1919
TRANSPORT = os.getenv("MCP_TRANSPORT", "stdio")
2020
HOST = os.getenv("MCP_HOST", "0.0.0.0")
21-
PORT = int(os.getenv("PORT", "8080"))
21+
_port_raw = os.getenv("PORT", "8080")
22+
try:
23+
PORT = int(_port_raw)
24+
except ValueError:
25+
PORT = 8080

mcp-server/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# MCP Server Dependencies
2-
mcp>=1.25.0
2+
mcp>=1.25.0,<2.0.0
33
httpx>=0.27.0
44
python-dotenv>=1.0.0
55
pydantic>=2.0.0

mcp-server/server.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@
3737
# Register tools at the low-level MCP server layer.
3838
# FastMCP's @tool decorator infers schemas from function signatures,
3939
# but we have well-tested schemas in tools.py and dispatch in handlers.py.
40-
_server = mcp._mcp_server
40+
# We access the private _mcp_server to register custom inputSchema directly.
41+
# TODO: monitor mcp-python for a public API to register tools with custom schemas
42+
# (see: https://github.com/modelcontextprotocol/python-sdk/issues)
43+
_server = mcp._mcp_server # pinned to mcp>=1.25.0,<2.0.0
4144

4245

4346
@_server.list_tools()

supabase/migrations/004_api_keys.sql

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@
77

88
CREATE TABLE IF NOT EXISTS api_keys (
99
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
10+
-- NULL user_id = system/service key (e.g. dogfood-mcp).
11+
-- RLS policies using auth.uid() = user_id will NOT match these rows;
12+
-- only the service_role (backend) can access system keys.
1013
user_id uuid REFERENCES auth.users(id) ON DELETE CASCADE,
1114
name text NOT NULL,
1215
key_hash text NOT NULL UNIQUE,
13-
tier text DEFAULT 'free',
16+
tier text DEFAULT 'free' CHECK (tier IN ('free', 'pro', 'enterprise')),
1417
active boolean DEFAULT true,
1518
created_at timestamptz DEFAULT now(),
1619
last_used_at timestamptz
@@ -20,10 +23,36 @@ CREATE TABLE IF NOT EXISTS api_keys (
2023
CREATE INDEX IF NOT EXISTS idx_api_keys_hash_active
2124
ON api_keys (key_hash) WHERE active = true;
2225

26+
-- Prevent users from modifying sensitive columns via UPDATE.
27+
-- Only active and last_used_at can change; key_hash, tier, name are immutable.
28+
-- Service role (backend/admin) bypasses this check.
29+
CREATE OR REPLACE FUNCTION protect_api_key_immutable_cols()
30+
RETURNS TRIGGER AS $fn$
31+
BEGIN
32+
IF current_setting('role', true) = 'service_role' THEN
33+
RETURN NEW;
34+
END IF;
35+
IF NEW.key_hash IS DISTINCT FROM OLD.key_hash THEN
36+
RAISE EXCEPTION 'Cannot modify key_hash';
37+
END IF;
38+
IF NEW.tier IS DISTINCT FROM OLD.tier THEN
39+
RAISE EXCEPTION 'Cannot modify tier';
40+
END IF;
41+
IF NEW.name IS DISTINCT FROM OLD.name THEN
42+
RAISE EXCEPTION 'Cannot modify name';
43+
END IF;
44+
RETURN NEW;
45+
END;
46+
$fn$ LANGUAGE plpgsql;
47+
48+
CREATE TRIGGER api_keys_immutable_guard
49+
BEFORE UPDATE ON api_keys
50+
FOR EACH ROW
51+
EXECUTE FUNCTION protect_api_key_immutable_cols();
52+
2353
-- Enable RLS
2454
ALTER TABLE api_keys ENABLE ROW LEVEL SECURITY;
2555

26-
-- Users can only see their own keys
2756
CREATE POLICY "Users can view own keys"
2857
ON api_keys FOR SELECT
2958
USING (auth.uid() = user_id);

0 commit comments

Comments
 (0)