Commit Graph

4 Commits

Author SHA1 Message Date
Maurice ffb6546e51 fix(mcp): prompts take the trip id as the string every client sends (#2207)
Prompt arguments cross the wire as strings, but the three trip prompts
declared tripId as z.number(), so every prompts/get failed with -32602
"expected number, received string", MCP Inspector included. The id is now
parsed from the string through a shared tripIdPromptArg (still an integer
in the safe range), PromptOptions only admits string-input schemas so the
mistake cannot come back unnoticed, and a prompt declared with an empty
argsSchema no longer demands an empty arguments object from the client.
The prompt tests run through a real in-memory MCP client instead of
calling the callbacks directly.
2026-09-03 09:15:14 +02:00
Renzo Beux a6c96f4053 feat(nest-mcp): per-session dynamic tool source on McpAttachOptions
A host can now contribute tools for one session through
McpAttachOptions.dynamicTools, consulted once per attach() with that
session's ctx.

It belongs here rather than in the host calling server.registerTool()
itself, because attach() is where the onInvoke seam fires. Anything
registered outside it is invisible to the tool-call audit trail, and
handlers that run as the calling user are the last thing that should sit
in an audit blind spot.

The source is synchronous: attach() runs inside the initialize request,
so an awaited source would put a third party's round trip in front of
every session creation.

Two properties carry the weight, both security ones:

- Names are reserved against every registered entry, not against what
  this session was granted. Reserving against the session would mean a
  token scoped to trips:read has no create_trip attached, so a
  contributed tool could take that name for exactly the callers holding
  no scope for it.
- Nothing a contributor does can make attach() throw. Hosts call it
  outside their request try block, so an escape is a 500 on every
  initialize and one bad contributor takes MCP down instance-wide. The
  source is contained once, each entry again. Both live throw sites are
  covered: the SDK duplicate-name throw, and the policy-less declarative
  access throw that registered entries only avoid because validate()
  pre-checks them at boot.

ToolOptions.inputSchema widens to ZodRawShape | McpZodSchema. The SDK has
accepted either since it normalises both to one object schema, and a
whole schema is what lets a host advertise one it did not author (via
.meta()) without converting it to Zod first. The arms are disjoint, so
nothing else moves; one test cast in decorators.test.ts records which arm
that decorator declared.

DYNTOOL-001 to DYNTOOL-014.
2026-08-29 12:22:59 +02:00
Maurice 8e5fdaa877 fix(server): tighten the guard, audit and token paths
Permissions cached an empty map before it had loaded, so a DB error during
the load cached permissive defaults and every later check answered from that
cache - fail-open, and it never expired. The rate limiter grew its per-IP map
forever; the GC the legacy version had was dropped in the fold. Idempotency
did a check-then-insert, so two concurrent duplicates both ran the handler.

Audit rows recorded whatever X-Forwarded-For said, which the client writes,
and the request-body debug logger redacted by exact key match - it missed
refresh tokens, api keys and PKCE verifiers. FORCE_HTTPS built its redirect
from the client's Host header. CSP asked for unsafe-eval on top of the
wasm-unsafe-eval it actually needs.

The OAuth deny path redirected to an unvalidated URI, ws-token minting had
no rate limit at all, and PKCE was skipped when code_verifier was absent
rather than refused. An MCP session resumed with a token whose scopes had
since been reduced kept the capability set it was created with, and the
session cap was checked before the async registration that increments it.

Plugin frame containment was lexical, so a symlink out of the directory was
never noticed, and instance config accepted keys the manifest never
declared.
2026-08-23 11:59:12 +02:00
jubnl 76c8c349b1 Fold nest mcp (#2017)
* refactor(server): fold @trek/nest-mcp into server/src/nest-mcp

* docs(server): point nest-mcp references at server/src/nest-mcp

* remove tracked md file
2026-08-22 12:55:24 +02:00