Other formats agents might prefer:
markdownjsonllms.txt

Agent? You probably want markdown or json, or Pod over MCP.

Reported issues for gaggiuino-mcp

Pod holds 24 of 88 problems reported by people outside the maintainer team. Issues filed by the project's own owners, members and collaborators are excluded entirely — a maintainer's release checklist is not a warning to a prospective user.

Back to gaggiuino-mcp.

Most discussed

fix(server): make the consent token stateless so /oauth/authorize cannot be evicted

Found while writing the tests for #108/#109 (PR #118). Not a vulnerability that grants anything — it is an availability defect on the login path, and the fix removes a store rather than adding a guard.

What is wrong

handleGet in apps/server/src/oauth/authorize.ts calls codes.remember(...) to park the validated authorization request before any authentication happens — it has to, because the CSRF token in the form is the key to that parked request. createCodeStore's pending ma

Read the thread · 2026-08-05 · closed · 2 comments

feat(server): zero-click Tailscale identity at /oauth/authorize

/oauth/authorize is the only endpoint a human's browser touches. On a tailnet, Tailscale already knows who that human is — so the owner passphrase (#109) could be skipped entirely, and the consent step would become physically uncompletable from the public internet. tsidp does exactly this: server/authorize.go refuses the route over Funnel with "they are not part of the tailnet that they are trying to be authenticated for."

The mechanism is real. Verified in tailscale/tailscale, `ipn/i

Read the thread · 2026-08-05 · open · 2 comments

chore(server): drop the MCP_AUTH_TOKEN startup tombstone

#111 kept env.MCP_AUTH_TOKEN read solely so a deployment that still sets it fails loudly at startup rather than silently serving an open /mcp. One release after 2.0.0, remove it:

  • the startup check and its ConfigError;
  • the .env.example tombstone line and its explanatory comment — envExample.test.ts enforces this direction too, so the code and the template must move in the same commit;
  • any remaining mention in README, SECURITY.md, AGENTS.md, server.json, turbo.json.

No behavi

Read the thread · 2026-08-05 · closed · 2 comments

feat(server)!: remove MCP_AUTH_TOKEN in favour of OAuth

This is the breaking change. It lands only after OAuth works end to end against the owner's real connector on iOS (#113's probe is the gate). Nothing before this point removes anything.

Why the token goes rather than staying alongside

It is a control the owner cannot configure, and one nobody on a personal plan can. Keeping it means two auth mechanisms, two sets of gate ordering to reason about, and a writeToolDisabled that answers a per-request question by reading the process envir

Read the thread · 2026-08-05 · closed · 2 comments

feat(server): delegate to an external issuer with MCP_OAUTH_ISSUER

The built-in AS (#108, #109) is the default because it keeps the one-container promise. It should not be the only path — plenty of self-hosters already run Authentik, Authelia, Keycloak, Zitadel or Kanidm, and a Tailscale user may prefer tsidp. This makes that a one-variable choice against code that already exists.

Anthropic supports it with no extra ceremony: *"A cross-host authorization server doesn't need anything special on its own. The authorization_servers field in your protected reso

Read the thread · 2026-08-05 · closed · 2 comments

feat(server): delete_profile tool, gated on explicit user confirmation

upload_profile (#101) gives the server a way to create a profile and no way to remove one. A model that uploads a malformed or duplicate profile leaves the user to clean it up on the machine's own screen, which is the surface the whole server exists to avoid.

DELETE /api/profile-select/{id} is the endpoint. PR #104 deliberately left it on the not-called list (client.ts, "endpoints this client does not call") on the grounds that it was unverified and differs from the selector by HTTP metho

Read the thread · 2026-08-05 · closed · 2 comments

feat: adopt MCP spec 2026-07-28 via SDK v2 (@modelcontextprotocol/server), dual-era

Why

MCP spec 2026-07-28 shipped on July 28 (announcement). Anthropic says support is "rolling out across Claude products soon" — today's Claude clients still speak the 2025-era protocol, so there is no urgency, but once Claude's client update lands there are two concrete wins for this server:

  • **ttlMs/

Read the thread · 2026-08-12 · closed · 1 comment

epic: derived shot diagnostics

Batch from the 2026-08-08 assessment of mxkissnr/gaggiuino-local-profiler — a Home Assistant espresso profiler covering the same hardware, read in source for ideas that fit a stateless MCP server.

Why these belong together

All four are apps/server/src/analysis.ts, and they are the part of that project that transfers without a database: pure functions over datapoints this server already fetches. Nothing here needs a store, user-en

Read the thread · 2026-08-08 · closed · 1 comment

Most recent

epic: loose-boundary tolerance

Batch 3 of the 2026-08-22 codebase improvement audit.

Why these belong together

Four places where a malformed or hostile input takes down more than it should. The repo already has a posture here, stated in AGENTS.md: upstream payloads are validated at the client boundary with deliberately loose schemas so a firmware revision cannot take the server down, maintenance.ts routes uninterpretable keys to extras rather than failing, and expected failures are results rather than exceptions. The

Read the thread · 2026-08-23 · open · 0 comments

epic: the /mcp gate and the OAuth path

Batch 1 of the 2026-08-22 codebase improvement audit.

Why these belong together

The first two are not adjacent issues, they are one edit. Both restructure the same block of apps/server/src/http.ts (:203-239):

  • #176 moves authenticate to run after checkRequest, restoring the ordering mcpAuth.ts documents and this file currently defeats.
  • #185 wraps the gate's rejection in withCors so an allowlisted browser origin can read the 401 challenge.

They also interact in a way neith

Read the thread · 2026-08-23 · open · 0 comments

chore(repo): add a CONTRIBUTING pointer to the local verification gates

Why

Dev commands live accurately in README §Development, but nothing onboards a contributor to the actual working loop: check vs check:affected, the coverage-ratchet rule (commit the raised numbers; never hand-edit vitest.config.ts), story screenshots for visual review, fake-machine for machineless development, and the fact that the squash-merged PR title is the only thing release-please reads.

Fix

A thin CONTRIBUTING.md that points at the relevant AGENTS.md sections rather than

Read the thread · 2026-08-22 · open · 0 comments

ci: pin the mcp-publisher binary by release checksum

Why

publish-mcp.yml downloads mcp-publisher from releases/latest unpinned (curl | tar xz), then hands that binary a GitHub OIDC token two steps later. Latest is a documented requirement — the registry rejects stale publisher binaries — but the download is currently unverified, an acknowledged supply-chain exposure directly on the publish path.

Fix

Keep resolving latest at run time, but verify the tarball against the release's published checksum before execution (checksum fetched w

Read the thread · 2026-08-22 · open · 0 comments

refactor(server): read GAGGIUINO_URL lazily so resetClient stays a full seam

Why

MACHINE_URL is read once at module load (client.ts:979-980: process.env.GAGGIUINO_URL?.trim() || DEFAULT_MACHINE_URL) and getClient() builds from that frozen constant (client.ts:984-989). resetClient(config) — the labelled test seam — can override every knob except baseUrl, so a test (or runtime config reload) changing the env after import observes nothing. The seam exists precisely to make config-dependent client behavior exercisable.

Fix

Read the environment inside `ge

Read the thread · 2026-08-22 · open · 0 comments

refactor(server): scope cache keys by HTTP method to remove the as-T cast

Why

perform returns cache hits via hit as T (client.ts:699) against a createCache<unknown>; soundness rests entirely on a docblock invariant that GET paths and write-ack paths never collide on a key. Nothing structural enforces it — a future cached GET on a path another verb touches would silently return the wrong payload type, and zod would catch it several layers away or not at all for loose schemas.

Fix

Key cache entries by method as well as path (${method}:${path}), or sco

Read the thread · 2026-08-22 · open · 0 comments

refactor(server): extract the shared target-series pairing used by events and analysis

Why

events.ts:87-107 (toSamples) and analysis.ts:131-148 (meanDeviationFromTarget) independently encode "pair the measured and target series index-wise, treat commanded ≤ 0 as not-commanded, normalize via SCALE_BY_10" — and already drift subtly: events drops instants where a reading is missing, analysis skips them mid-loop. Any future change to that convention must be made twice and can silently disagree.

Fix

Extract pairedSamples(measured, target, fieldName) into `normalize.t

Read the thread · 2026-08-22 · open · 0 comments

refactor: give the x10 wire scaling one home shared by server and shot-graph

Why

Two ×10-scaling homes exist and have diverged:

  • apps/server/src/normalize.ts SCALE_BY_10 covers pressure, targetPressure, temperature, targetTemperature, pumpFlow, weightFlow, targetPumpFlow, shotWeight, waterPumped, timeInShot;
  • packages/shot-graph/src/normalize.ts hand-divides only the seven charted fields plus inline duration/timeInShot handling.

If a firmware-scaled field ever reaches the chart or the CSV export (targetTemperature and waterPumped are live candidates), s

Read the thread · 2026-08-22 · open · 0 comments

fix(server): send CORS headers on early rejections for allowlisted origins

Why

CORS headers decorate only responses that come back through handleMcp. The 401 refusal from the security gate is returned bare at http.ts:233, outside the withCors(...) wrap at http.ts:236-239, so a browser-based client on an allowlisted origin gets no Access-Control-Allow-Origin on the challenge and cannot read WWW-Authenticate from JavaScript. Header reflection itself is correct (mcpAuth.ts:301-324: origin echoed, never *, Vary: Origin, expose-headers covers the sessi

Read the thread · 2026-08-22 · open · 0 comments

fix(server): bind refresh-token audience to the granted resource parameter

Why

The authorization-code grant seals the refresh token's audience from the caller-supplied resource parameter via audienceFor() (oauth/token.ts:345-355), but handleRefresh verifies the presented refresh token against deps.config.resource only (oauth/tokens.ts:244-260). A grant issued with a non-default resource yields access tokens that authenticate fine while their refresh token is refused as invalid_grant — such deployments loop through re-consent every access-token lifet

Read the thread · 2026-08-22 · open · 0 comments

fix(server): negative-cache failed CIMD client-document resolutions

Why

resolveClient caches only successful CIMD document fetches (oauth/clients.ts:180-215; the cache fill sits behind the success branch), so failed or rejected resolutions re-fetch on every request. An anonymous flood of GET /oauth/authorize carrying distinct public-https client_ids therefore drives one outbound fetch each (3s timeout, 64KB cap) at arbitrary public hosts — an unmetered outbound-fetch sink on an unauthenticated path. The DNS TOCTOU residue is documented and accepted;

Read the thread · 2026-08-22 · open · 0 comments

fix(server): derive the scope gate fail-closed when readOnlyHint is absent

Why

protectedToolsIn selects tools with readOnlyHint === false (oauth/scopeGate.ts:29-33), so a future write tool whose annotations omit readOnlyHint entirely (undefined) is not scope-gated: the runtime derivation falls open where this repo otherwise fails closed. Currently mitigated — defineTool requires annotations and scopeGate.test.ts pins PROTECTED_TOOLS to exactly the three writes, so an accidental loss fails CI — but the derivation direction is wrong for any future too

Read the thread · 2026-08-22 · open · 0 comments

fix(server): answer unknown tools with JSON-RPC -32602 instead of an isError result

Why

handleToolCall throws Error("Unknown tool: …") for an unrecognized name and callTool's catch-all wraps it as { isError: true, … } over HTTP 200. The MCP spec directs servers to answer tools/call for an unknown name with Invalid params (-32602); today a host cannot distinguish a mistyped tool name from a server bug, and the caller's mistake logs at error level as though it were ours. An output-schema .parse() violation falls into the same generic channel instead of the typed I

Read the thread · 2026-08-22 · open · 0 comments

fix(mcp-app): validate tool-result JSON at readToolJson and stop NaN or raw TypeErrors reaching the UI

Why

readToolJson does JSON.parse(text) as T with no runtime validation (packages/ui/src/host/toolResult.ts), and the shot-graph app trusts the result structurally:

  • missing durationshot.duration / SCALE is NaN → ShotHeader renders "NaN g in NaN s";
  • missing profile object → shot.profile.name throws TypeError inside render, putting a raw JS message in front of the user — contradicting the shell rule that users see server-authored errors, never invented ones;
  • string-numbe

Read the thread · 2026-08-22 · open · 0 comments

fix(server): tolerate a malformed element in /api/profiles/all instead of failing the whole list

Why

MachineProfilesSchema accepts /api/profiles/all as an array of elements each requiring a string name (client.ts:273-278, element schema ~L218-224). One malformed element — a firmware revision omitting name, or a truncated body — fails the whole array parse: MalformedUpstreamError, list_profiles errors out entirely, and profileCatalog.ts degrades to documentation-only, hiding every real profile because of one bad record.

That sits badly next to this repo's own loose-bound

Read the thread · 2026-08-22 · open · 0 comments

fix(server): substitute guidance placeholders with replacer functions

Why

renderDialInGuidance substitutes placeholders with two-argument String.replace (guidance.ts:24-25):

.replace("{user_context}", prompt.userContext ?? "")
.replace("{profiles_text}", getAllProfilesText())

In JS the replacement string interpolates $&, $` and $' sequences, so a user_context in prompts.local.yaml containing e.g. a bare $& corrupts the rendered guidance; profile text flowing through getAllProfilesText() has the same power. Both surfaces inhe

Read the thread · 2026-08-22 · open · 0 comments

The remaining reports are on the project's issue tracker.