Skip to content

fix(mcp): resolve {{LIBRECHAT_USER_ID}} in loadTools' MCP tool-loading path (v0.8.7 port) - #33

Merged
inodb merged 3 commits into
v0.8.7-custom-v1from
fix-mcp-tool-load-user-id-v0.8.7
Aug 7, 2026
Merged

fix(mcp): resolve {{LIBRECHAT_USER_ID}} in loadTools' MCP tool-loading path (v0.8.7 port)#33
inodb merged 3 commits into
v0.8.7-custom-v1from
fix-mcp-tool-load-user-id-v0.8.7

Conversation

@inodb

@inodb inodb commented Aug 7, 2026

Copy link
Copy Markdown
Member

Port of #32 onto `v0.8.7-custom-v1`. Same fix as Foris's PR, adapted to the newer branch's test-file mock structure.

Change

One-line fix in `api/app/clients/tools/util/handleTools.js` line 454:

```diff

  • const safeUser = createSafeUser(options.req?.user);
  • const safeUser = createSafeUser(options.req?.user, user);
    ```

Same class of bug PRs #29/#30/#31 fixed elsewhere: `loadTools()` builds the MCP-tool `user` object from `options.req?.user` alone. When `req.user` is a plain object deserialized from the passport session (no `id` virtual, no `_id`), the resolver has nothing to work with — even though `loadTools` already receives the caller's resolved id via its own `user` param (see ToolService.js). Passing `user` as the fallback to `createSafeUser` closes the gap.

Test changes vs #32

Foris's version added `jest.mock('/config'...)` and `jest.mock('/server/services/MCP'...)` blocks. `v0.8.7-custom-v1` already has richer mock scaffolding at the top of the file (`mockGetServerConfig`, `mockCreateMCPTools`, etc.), so:

  • Dropped Foris's duplicate mock blocks.
  • Dropped his `require('/config')` + `require('/server/services/MCP')` imports.
  • Rewrote the two regression tests to set up via `mockGetServerConfig` / `mockCreateMCPTools` and assert against `mockCreateMCPTools`.

Test intent + coverage are identical.

Test plan

  • `npx jest app/clients/tools/util/handleTools.test.js` — 17/17 pass, including both new tests:
    • `loadTools MCP user id propagation > falls back to the loadTools user param when req.user has neither id nor _id`
    • `loadTools MCP user id propagation > prefers a resolvable req.user id over the loadTools user param`
  • After merge + rebuild + rollout: send a chat via cBioChat (prod) that triggers an MCP tool call and confirm x-user-id in cbioagent-clickhouse-mcp logs is a real ObjectID rather than the raw `{{LIBRECHAT_USER_ID}}` placeholder.

Closes #32.

…g path

PRs #29-#31 fixed user-id propagation into MCP headers across the agent
client, openai controller, and responses controller, but loadTools()
(api/app/clients/tools/util/handleTools.js) builds the MCP `user` object
from `options.req?.user` alone via createSafeUser. When req.user is a
plain object deserialized from the passport session (no `id` virtual, no
`_id`), that resolves to no usable id — even though loadTools is already
given the caller's resolved id via its own `user` param (ToolService.js
calls loadTools({ user: req.user.id, ... }), and that same `user` value is
already used directly for auth lookups a few lines up). Confirmed live in
production: mcp.tool/* spans on cbioportal-mcp still shipped
enduser.id="{{LIBRECHAT_USER_ID}}" well after PR #31 deployed.

Thread the existing `user` param through as createSafeUser's fallbackId,
matching the pattern PR #31 established elsewhere. Adds regression tests
exercising the MCP 'all'-tools branch (the one that calls createMCPTools),
verifying the fallback fires when req.user has neither id nor _id, and
that an explicit req.user.id still takes precedence.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
inodb added 2 commits August 7, 2026 21:19
Follow-up to the v0.8.7 rebase: our fork intentionally exposes
`conversation_starters` on the list-agents endpoint via commit b044fe8
("Fix agent conversation starters not returned by list endpoint") so
that the landing page can render them without a second per-agent fetch.

Upstream added a "mass assignment protection" security test that asserts
the exact safe-list of returned fields — that test doesn't know about
our extension and fails on any PR against v0.8.7-custom-v1. Rather than
undo b044fe8's projection change (which would break the landing UI),
extend the test's expected list to reflect what our fork intentionally
returns.

Distinct from the mcp user-id fix that opened this PR, but folded in
so v0.8.7-custom-v1 gets a clean CI baseline.
Second follow-up to the v0.8.7 rebase. This test asserted the pre-fork
behavior ("we only check id") — but our fork intentionally treats _id
as a fallback for id via the createSafeUser + loadTools chain from PRs
#29/#30/#31 and #32 (the fix this PR ports). The whole point of that
chain is that passport-deserialized session user objects carry _id but
not id, and MCP tool calls previously shipped the literal placeholder
{{LIBRECHAT_USER_ID}} instead of the resolved id.

Update the "_id only" assertion to reflect what our fork actually
does. The paired "id takes precedence when both are present" assertion
below is already correct and unchanged.
@inodb
inodb merged commit 5f7b757 into v0.8.7-custom-v1 Aug 7, 2026
17 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants