Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,10 @@ jobs:
# the default compose file (that would break the ci/docker job).
run: |
cd $PROJECT_DIR
docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d --build
# --profile mcp brings up the remote MCP server (mcp-server) in prod.
# It's profile-gated so CI's bare `compose up` (docker/e2e jobs) never
# builds/runs it; prod opts in here. nginx /mcp is synced below.
docker compose --profile mcp -f docker-compose.yml -f docker-compose.gpu.yml up -d --build

- name: Sync nginx config
run: |
Expand Down Expand Up @@ -228,6 +231,18 @@ jobs:
curl -sf http://localhost:80 || exit 1
echo "Frontend health check passed"

- name: Health check MCP server
run: |
# mcp-server is localhost-only on :8090; nginx fronts /mcp.
curl -sf http://localhost:8090/health || { echo "FAIL: mcp-server /health down"; exit 1; }
echo "mcp-server /health OK"
# MCP streamable endpoint through nginx (Host-routed, like a real client).
CODE=$(curl -s -o /dev/null -w '%{http_code}' -X POST -H "Host: textstack.app" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"deploy-check","version":"1"}}}' \
http://localhost/mcp)
[ "$CODE" = "200" ] && echo "nginx /mcp → MCP initialize 200 OK" || { echo "FAIL: /mcp returned $CODE"; exit 1; }

- name: Verify SEO setup
run: |
echo "=== SPA serves real users ==="
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Phase 8 — enable remote MCP in the prod deploy (AI-049 follow-up) (2026-06-17)

Makes the remote MCP endpoint actually go live. The `mcp-server` Docker service is profile-gated (`profiles:[mcp]`) so CI's bare `compose up` never builds/runs it — which also meant the prod deploy didn't bring it up. Fixed: `deploy.yml` now runs `docker compose --profile mcp ...` so prod opts the service in (CI still excludes it). The nginx `/mcp` block already syncs via the existing "Sync nginx config" step. Added a **Health check MCP server** deploy step (fail-loud): asserts `mcp-server` `/health` is up AND a real MCP `initialize` POST through nginx `/mcp` returns 200. After this merges + deploys, `https://textstack.app/mcp` is the remote streamable-HTTP MCP endpoint (for ChatGPT / Cursor / remote Claude), authenticated per-request via the device-flow JWT. Verified the http transport locally (`/health`→200, `POST /mcp` initialize→`text/event-stream` 200).

### Phase 8 — MCP bridge: preserve API path prefix on relative URLs (AI-049) (2026-06-16)

- **Fix.** The MCP bridge dropped the API **path prefix** (e.g. `/api`) on every request when `TEXTSTACK_API_URL` carried one (`https://textstack.app/api`). `TextStackApiClient` built relative URLs with a LEADING slash (`/search`, `/me/highlights/{id}`, `/books/{id}/ask`) and `HttpClient.BaseAddress` had NO trailing slash, so .NET resolved the leading-slash relative URI from the HOST ROOT and dropped `/api` — the bridge hit the SPA (`https://textstack.app/search`, 301 → HTML → `JsonException` → "upstream unavailable") instead of the API. Silently green in unit tests (fake handler ignores `BaseAddress`) and in prod-internal (`http://api:8080`, no prefix); confirmed live against prod. Fixed two ways: `McpBridgeCore.BaseUri(...)` now trailing-slash-normalizes the base (idempotent — no double slash), and `TextStackApiClient` + `DeviceFlowTokenProvider` build relative URIs leading-slash-stripped (`auth/device/code`, `auth/device/token`, `auth/refresh-mobile`), so the prefix segment survives instead of being treated as a replaceable file.
Expand Down
Loading