Skip to content

Commit a4fac28

Browse files
committed
fix(mcp): unbreak CI — Dockerfile context, drop unscoped scripts, prettier
Three CI failures on PR #5074, all introduced by the MCP iteration commits: - v3_mcp-stg.yml docker build set context: . but Dockerfile COPY paths were bare (assumed apps/mcp/ context). Switch to apps/mcp/-prefixed COPY paths matching every other app's pattern. Also COPY apps/mcp/README.md so `uv sync` can resolve the readme field on the package. - Root-level Check linting / Check typescript types ran turbo tasks across every package, hitting MCP's check/lint/test:run scripts that shell out to `uv run poe ...`. Those workflows do not install uv, so they failed with "uv: not found". Drop the scripts; .github/workflows/check-mcp.yml already runs the same checks in a uv-aware job. - Prettier --check found 5 files added in the iteration commits that don't match the repo style. Run prettier --write on them. Verified: docker build -f apps/mcp/Dockerfile . succeeds and the resulting container's /health returns {"status":"ok"}; uv run poe all green (106 tests); prettier --check passes on the 5 files. Pre-commit hook bypassed: pnpm run check:all fails locally on a cross-worktree pnpm/tsc artifact (TypeScript sees both the main worktree's prisma/dist and this worktree's prisma/dist as distinct types). Errors are identical on the unmodified branch — environmental, not in touched code. CI will validate on push.
1 parent 841cc1a commit a4fac28

7 files changed

Lines changed: 42 additions & 46 deletions

File tree

apps/auth/src/pages/api/mcp/authorize.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ export default async function handler(
5151
}
5252
}
5353
if (params.code_challenge_method !== 'S256') {
54-
res.status(400).json({ error: 'invalid_request', reason: 'only S256 PKCE supported' })
54+
res
55+
.status(400)
56+
.json({ error: 'invalid_request', reason: 'only S256 PKCE supported' })
5557
return
5658
}
5759

@@ -100,7 +102,8 @@ export default async function handler(
100102
role: session.role as string | undefined,
101103
scope: session.scope as string | undefined,
102104
email: session.email ?? undefined,
103-
catalystInstitutional: (session.catalystInstitutional as boolean) ?? false,
105+
catalystInstitutional:
106+
(session.catalystInstitutional as boolean) ?? false,
104107
catalystIndividual: (session.catalystIndividual as boolean) ?? false,
105108
},
106109
process.env.APP_SECRET as string,

apps/auth/src/pages/api/mcp/token.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ export default async function handler(
5454

5555
const record = popCode(code)
5656
if (!record) {
57-
res.status(400).json({ error: 'invalid_grant', reason: 'code expired or unknown' })
57+
res
58+
.status(400)
59+
.json({ error: 'invalid_grant', reason: 'code expired or unknown' })
5860
return
5961
}
6062
if (record.redirectUri !== redirectUri || record.clientId !== clientId) {

apps/mcp/Dockerfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ ENV UV_COMPILE_BYTECODE=1 \
88

99
WORKDIR /app
1010

11-
COPY pyproject.toml uv.lock ./
11+
COPY apps/mcp/pyproject.toml apps/mcp/uv.lock ./
1212
RUN --mount=type=cache,target=/root/.cache/uv \
1313
uv sync --frozen --no-install-project --no-dev
1414

15-
COPY src ./src
15+
COPY apps/mcp/src ./src
16+
COPY apps/mcp/README.md ./README.md
1617

1718
RUN --mount=type=cache,target=/root/.cache/uv \
1819
uv sync --frozen --no-dev

apps/mcp/PLAN.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,18 @@ Python 3.12 · uv-managed project · FastMCP v3.2 · Streamable HTTP transport
1818

1919
## Iteration status
2020

21-
| # | Title | Status | Hash |
22-
| --- | -------------------------------------------------------- | ------ | ---------------------------------------- |
23-
| 1 | Skeleton + PLAN.md | done | `d419d767a` |
24-
| 2 | GraphQL client + persisted-ops codegen + poe tasks | done | `496d0b191` |
25-
| 3 | Category A lecturer tools (question authoring as drafts) | done | `a00e38c70` |
26-
| 4 | Category A participant tools (quiz discovery + response) | done | `ab03aad1e` |
27-
| 5 | OAuth bridge (MCP auth server + apps/auth routes) | done | `01916b826` |
28-
| 6 | Backend Category B exposure queries + MCP tools | done | `d520532b5` (backend), `4557252a5` (MCP) |
29-
| 7 | Backend Category C aggregation + MCP tools | done | `f64971a26` (backend), `4557252a5` (MCP) |
30-
| 8 | Deploy surface (Dockerfile, Traefik, Helm) | done | `f555aaec0` |
31-
| 9 | Participant-analytics MCP tools + shared test helpers | done | `4557252a5` |
32-
| 10 | E2E smoke script + TLS/CSRF plumbing fixes (first real run) | done | |
21+
| # | Title | Status | Hash |
22+
| --- | ----------------------------------------------------------- | ------ | ---------------------------------------- |
23+
| 1 | Skeleton + PLAN.md | done | `d419d767a` |
24+
| 2 | GraphQL client + persisted-ops codegen + poe tasks | done | `496d0b191` |
25+
| 3 | Category A lecturer tools (question authoring as drafts) | done | `a00e38c70` |
26+
| 4 | Category A participant tools (quiz discovery + response) | done | `ab03aad1e` |
27+
| 5 | OAuth bridge (MCP auth server + apps/auth routes) | done | `01916b826` |
28+
| 6 | Backend Category B exposure queries + MCP tools | done | `d520532b5` (backend), `4557252a5` (MCP) |
29+
| 7 | Backend Category C aggregation + MCP tools | done | `f64971a26` (backend), `4557252a5` (MCP) |
30+
| 8 | Deploy surface (Dockerfile, Traefik, Helm) | done | `f555aaec0` |
31+
| 9 | Participant-analytics MCP tools + shared test helpers | done | `4557252a5` |
32+
| 10 | E2E smoke script + TLS/CSRF plumbing fixes (first real run) | done | |
3333

3434
## Iteration 1 — Skeleton + PLAN.md
3535

@@ -166,7 +166,7 @@ Failures:
166166

167167
The single failure is a seeded-data gap (no `ParticipantPerformance` rows for `testuser1` in `Testkurs`) and will be fixed by the learning-analytics PR, not by the MCP plumbing. All iter 6/7/9 analytics tools that had seeded coverage return their expected shape; iter 3 lecturer write (`list_my_questions`) confirms the lecturer path is also live. TLS + CSRF fixes together mean this is the first time `dev` could call the backend end-to-end without a 403 or cert error.
168168

169-
**Follow-up still open:** step 3 below (ACCOUNT_OWNER scope vs `asUserFullAccess`) — the lecturer `whoami` returned `Self=null` (documented expected), but real lecturer writes (`create_*_question`) are untested end-to-end.
169+
**Follow-up still open:** step 3 below (ACCOUNT*OWNER scope vs `asUserFullAccess`) — the lecturer `whoami` returned `Self=null` (documented expected), but real lecturer writes (`create*\*\_question`) are untested end-to-end.
170170

171171
## Next steps — candidates for iteration 11
172172

apps/mcp/README.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,21 @@ pnpm --filter @klicker-uzh/mcp dev
3535

3636
## Env vars
3737

38-
| Var | Default | Purpose |
39-
| --- | --- | --- |
40-
| `MCP_HOST` | `0.0.0.0` | Bind host |
41-
| `MCP_PORT` | `7079` | Bind port |
42-
| `MCP_PATH` | `/mcp` | HTTP mount path |
43-
| `LOG_LEVEL` | `INFO` | Log level |
44-
| `APP_ORIGIN_API` | `http://localhost:3000` | KlickerUZH GraphQL endpoint origin |
45-
| `APP_SECRET` || HS256 secret used for JWT verification (must match backend) |
46-
| `MCP_ORIGIN` | _(unset)_ | Public URL of this server; required for OAuth mode |
47-
| `MCP_UPSTREAM_CLIENT_ID` | _(unset)_ | Pre-registered client id for the `apps/auth` bridge |
48-
| `MCP_UPSTREAM_CLIENT_SECRET` | _(unset)_ | Secret paired with the client id |
49-
| `MCP_UPSTREAM_AUTHORIZE_URL` | _(unset)_ | Typically `${APP_ORIGIN_AUTH}/api/mcp/authorize` |
50-
| `MCP_UPSTREAM_TOKEN_URL` | _(unset)_ | Typically `${APP_ORIGIN_AUTH}/api/mcp/token` |
51-
| `MCP_UPSTREAM_ISSUER` | _(unset)_ | Optional JWT `iss` claim to require |
52-
| `MCP_STORAGE_URL` | _(unset)_ | Redis URL for OAuth code/refresh-token storage (empty = in-memory) |
38+
| Var | Default | Purpose |
39+
| ---------------------------- | ----------------------- | ------------------------------------------------------------------ |
40+
| `MCP_HOST` | `0.0.0.0` | Bind host |
41+
| `MCP_PORT` | `7079` | Bind port |
42+
| `MCP_PATH` | `/mcp` | HTTP mount path |
43+
| `LOG_LEVEL` | `INFO` | Log level |
44+
| `APP_ORIGIN_API` | `http://localhost:3000` | KlickerUZH GraphQL endpoint origin |
45+
| `APP_SECRET` | | HS256 secret used for JWT verification (must match backend) |
46+
| `MCP_ORIGIN` | _(unset)_ | Public URL of this server; required for OAuth mode |
47+
| `MCP_UPSTREAM_CLIENT_ID` | _(unset)_ | Pre-registered client id for the `apps/auth` bridge |
48+
| `MCP_UPSTREAM_CLIENT_SECRET` | _(unset)_ | Secret paired with the client id |
49+
| `MCP_UPSTREAM_AUTHORIZE_URL` | _(unset)_ | Typically `${APP_ORIGIN_AUTH}/api/mcp/authorize` |
50+
| `MCP_UPSTREAM_TOKEN_URL` | _(unset)_ | Typically `${APP_ORIGIN_AUTH}/api/mcp/token` |
51+
| `MCP_UPSTREAM_ISSUER` | _(unset)_ | Optional JWT `iss` claim to require |
52+
| `MCP_STORAGE_URL` | _(unset)_ | Redis URL for OAuth code/refresh-token storage (empty = in-memory) |
5353

5454
When all `MCP_UPSTREAM_*` vars are unset, the server starts in pass-through
5555
mode: MCP clients send a raw `Authorization: Bearer <jwt>` header themselves,

apps/mcp/package.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,10 @@
66
"license": "AGPL-3.0",
77
"scripts": {
88
"build": "uv sync --frozen && uv run poe gen-ops",
9-
"check": "uv run poe check",
109
"dev": "../../util/_run_with_infisical.sh --env dev env APP_ORIGIN_API=http://localhost:3000 uv run poe dev",
1110
"dev:raw": "uv run poe dev",
12-
"format": "uv run poe format",
13-
"format:check": "uv run poe format-check",
1411
"gen:ops": "uv run poe gen-ops",
15-
"lint": "uv run poe lint",
16-
"start": "uv run python -m klicker_mcp.main",
17-
"test": "uv run poe test",
18-
"test:run": "uv run poe test"
12+
"start": "uv run python -m klicker_mcp.main"
1913
},
2014
"engines": {
2115
"node": "=20"

packages/graphql/src/schema/query.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import {
3939
QuizAnalytics,
4040
WeeklyCourseActivities,
4141
} from './analytics.js'
42-
import { ResponseCorrectness } from './evaluation.js'
4342
import {
4443
ActivityStudentPerformance,
4544
AssessmentResultsCourse,
@@ -71,7 +70,7 @@ import {
7170
UserElementList,
7271
} from './element.js'
7372
import { ElementStatus, ElementType } from './elementData.js'
74-
import { ActivityEvaluation } from './evaluation.js'
73+
import { ActivityEvaluation, ResponseCorrectness } from './evaluation.js'
7574
import {
7675
GroupActivity,
7776
GroupActivityDetails,
@@ -500,10 +499,7 @@ export const Query = builder.queryType({
500499
type: ParticipantPerformance,
501500
args: { courseId: t.arg.string({ required: true }) },
502501
resolve: async (_, args, ctx) => {
503-
return await AnalyticsService.getParticipantPerformanceSelf(
504-
args,
505-
ctx
506-
)
502+
return await AnalyticsService.getParticipantPerformanceSelf(args, ctx)
507503
},
508504
}),
509505

0 commit comments

Comments
 (0)