Skip to content

feat(team): per-request userId + TEAM_MODE=private enforcement#689

Open
chdlc wants to merge 5 commits into
rohitg00:mainfrom
chdlc:feat/team-user-per-request
Open

feat(team): per-request userId + TEAM_MODE=private enforcement#689
chdlc wants to merge 5 commits into
rohitg00:mainfrom
chdlc:feat/team-user-per-request

Conversation

@chdlc
Copy link
Copy Markdown

@chdlc chdlc commented May 27, 2026

What

TEAM_MODE was read but never used for filtering — private and shared behaved identically. There was no way to identify which user made a team operation from the MCP server or integrations. team-share accepted any sharedBy value in the request body, enabling impersonation.

Why

Without per-request userId, a single MCP server cannot enforce privacy boundaries between different users' team memories. All team operations appear as unscoped, and any caller can impersonate any other user by setting sharedBy in the request body.

How

  • src/config.ts: resolveUserId() reads AGENTMEMORY_USER_ID from process env, falling back to USER_ID (server-level default). This lets each integration define its own user identity.

  • src/functions/team.ts:

    • team-share: accepts optional userId? in payload. In TEAM_MODE=private, forces sharedBy to the configured userId (prevents impersonation). In shared mode, body override works.
    • team-feed: accepts optional userId?. In TEAM_MODE=private, filters items to configured user only. In shared, shows all.
    • team-profile: same filtering logic as team-feed.
  • src/mcp/server.ts: All three team handlers (team-share, team-feed, team-profile) read AGENTMEMORY_USER_ID from env via resolveUserId() and inject it automatically.

  • src/mcp/tools-registry.ts: memory_team_share and memory_team_feed tools expose userId as an optional parameter, so MCP clients can identify themselves without relying on env vars.

  • README.md: Documents the server-level vs integration-level env distinction (USER_ID vs AGENTMEMORY_USER_ID), examples with alice/bob, and impersonation protection.

Verification

  • 115 suites, 1248 tests — all passing (excluding integration test that requires live server)
  • New tests cover:
    • AGENTMEMORY_USER_ID env override vs USER_ID fallback
    • userId per-request override from MCP tool arguments
    • TEAM_MODE=private filtering in team-feed, team-profile
    • Impersonation blocking: body sharedBy ignored in private mode
    • Shared mode: all items visible regardless of userId
  • No new lifecycle hooks, no breaking changes

Related

  • Complements PR fix: pass agentId per-call for integration-scoped memory #665 (agentId per-call isolation)
  • agentId scopes regular memory operations (remember, smart-search, observe)
  • userId scopes team operations (team-share, team-feed, team-profile)
  • Together they enable multi-agent + multi-user isolation on a single server

Summary by CodeRabbit

  • New Features

    • Added optional user identity override for team sharing, feed, and profile operations
    • Enhanced team memory configuration with per-integration user ID settings and private/shared mode filtering
  • Documentation

    • Updated configuration guide with team memory setup and multi-user identity handling
  • Tests

    • Added comprehensive test coverage for team configuration and user identity resolution

Review Change Stack

chdlc added 4 commits May 27, 2026 14:06
Add resolveUserId() helper that accepts optional per-request override,
falling back to AGENTMEMORY_USER_ID env var.  Trimmed + length-capped
to match agentId conventions.

resolveTeamId() reads TEAM_ID from server env (no per-request override
since team is server-level config).

Updated team-share to accept optional userId in its payload, used for
sharedBy field and audit trail.  team-feed and team-profile now use
resolveTeamId() for consistent team ID resolution but do not accept
userId since they are team-level read operations.

MCP server memory_team_share and memory_team_feed read
AGENTMEMORY_USER_ID from their process env and pass it through
automatically.

This enables single-server multi-user deployments where each integration
(MCP client, plugin, etc.) defines AGENTMEMORY_USER_ID in its own env,
and the identity flows through to agentmemory without modifying requests.

Signed-off-by: Christian de la Cruz <chrisdlc119@outlook.com>
team-feed and team-profile now respect TEAM_MODE:
- shared (default): shows all team items, no filtering
- private: filters by configured userId, ignores body override

team-share in private mode forces sharedBy to config.userId,
preventing impersonation — users can only share as themselves.

Body userId override is ignored in private mode for all three
operations (team-share, team-feed, team-profile), ensuring true
read+write privacy.

Signed-off-by: Christian de la Cruz <chrisdlc119@outlook.com>
Add userId to memory_team_share and memory_team_feed inputSchema so
MCP clients (Cursor, Claude Desktop, Codex, Kilo, OpenCode, etc.)
can pass per-request userId without relying on env vars.

This enables multi-user deployments behind a single MCP server where
each client identifies itself via the userId parameter.

Signed-off-by: Christian de la Cruz <chrisdlc119@outlook.com>
…ER_ID

Add section explaining TEAM_MODE=private/shared behavior, per-request
userId override via MCP tool calls, and combined multi-agent + multi-user
setup. Update env vars section to show USER_ID as server-level default
with AGENTMEMORY_USER_ID as per-integration override.

Signed-off-by: Christian de la Cruz <chrisdlc119@outlook.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 27, 2026

@chdlc is attempting to deploy a commit to the rohitg00's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 63a124cf-f639-427e-9446-a477ba5d74ed

📥 Commits

Reviewing files that changed from the base of the PR and between 3c0e0f6 and 3f562b6.

📒 Files selected for processing (4)
  • src/config.ts
  • src/functions/team.ts
  • src/mcp/server.ts
  • test/config-resolve.test.ts
💤 Files with no reviewable changes (1)
  • src/config.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • test/config-resolve.test.ts
  • src/mcp/server.ts
  • src/functions/team.ts

📝 Walkthrough

Walkthrough

This PR adds multi-user team memory support with per-request user identity overrides and private-mode isolation. It introduces config resolution helpers (resolveTeamId, resolveUserId), extends MCP tool schemas to accept optional userId parameters, updates team-sharing functions to enforce mode-based filtering, and documents the configuration semantics.

Changes

Multi-user team memory with private mode enforcement

Layer / File(s) Summary
Config resolution helpers
src/config.ts, test/config-resolve.test.ts
loadTeamConfig derives userId from AGENTMEMORY_USER_ID (fallback to USER_ID). New exports resolveTeamId() and resolveUserId(override?) centralize ID resolution with override precedence, trimming, and 128-character capping. Tests verify environment isolation and fallback behavior.
MCP tool schemas and server implementation
src/mcp/tools-registry.ts, src/mcp/server.ts
memory_team_share and memory_team_feed tool schemas add optional userId field to override AGENTMEMORY_USER_ID. Server handlers resolve args.userId and conditionally include it in payloads sent to team functions. Error messages updated to reference AGENTMEMORY_USER_ID.
Team function core logic with private mode
src/functions/team.ts, test/team.test.ts
mem::team-share, mem::team-feed, and mem::team-profile now accept optional userId in request payloads and resolve teamId at runtime. Private mode forces sharedBy to always match config.userId regardless of request override; shared mode allows per-request identity. Feed and profile results are filtered by configured user in private mode. Audit events and persistence use resolved identifiers. Tests cover per-request overrides, fallback behavior, and private-mode impersonation prevention.
Configuration documentation
README.md
Adds "Multi-user team memory" section documenting server-level USER_ID vs per-integration AGENTMEMORY_USER_ID scoping, TEAM_MODE shared/private behavior, userId parameter passing in MCP calls, and multi-agent isolation. Updates environment variable examples with USER_ID, TEAM_MODE, and per-integration override guidance.

Sequence Diagram

sequenceDiagram
  participant MCP as MCP Tool Call
  participant Server as server.ts
  participant Resolve as resolveUserId
  participant TeamFn as Team Function
  participant Mode as Mode Check
  participant KV as KV/Audit
  MCP->>Server: memory_team_share<br/>(userId optional)
  Server->>Resolve: resolveUserId(args.userId)
  Resolve-->>Server: canonical userId
  Server->>TeamFn: mem::team-share payload
  TeamFn->>Mode: private or shared?
  Mode-->>TeamFn: private: use config.userId only
  Mode-->>TeamFn: shared: use resolved userId
  TeamFn->>KV: write shared item,<br/>audit with resolved IDs
  KV-->>TeamFn: success
Loading

🎯 3 (Moderate) | ⏱️ ~25 minutes

🐰 A team now shares memories secure,
With private modes to endure,
Each user's own view,
While overrides come true —
Multi-user magic, I'm sure! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main changes: adding per-request userId support and enforcing TEAM_MODE=private, which are the core objectives of the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chdlc chdlc marked this pull request as ready for review May 27, 2026 20:10
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/functions/team.ts (1)

113-181: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

mem::team-profile drops per-request identity and audits the wrong actor

data.userId is accepted but never resolved/used, and recordAudit(..., config.userId) hardcodes identity. In shared mode this can misattribute audit entries for profile operations.

Suggested fix
 sdk.registerFunction("mem::team-profile",  async (data?: { userId?: string }) => {
   const teamId = resolveTeamId() ?? config.teamId;
+  const actorUserId =
+    config.mode === "private"
+      ? config.userId
+      : (resolveUserId(data?.userId) ?? config.userId);
   let items = await kv.list<TeamSharedItem>(KV.teamShared(teamId));
@@
   await recordAudit(
@@
-    config.userId,
+    actorUserId,
   );
   return profile;
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/functions/team.ts` around lines 113 - 181, The team profile handler
("mem::team-profile") currently ignores the per-request identity (data.userId)
and always audits using config.userId; change it to respect data.userId in
non-private mode and use that same resolved actor when calling recordAudit.
Specifically: keep the existing private-mode behavior that forces the filter to
config.userId, but otherwise resolve an actorId = data?.userId ?? config.userId
(or use a resolveUserId helper if available), use that actorId for any
per-request scoping and for the members list/audit metadata, and replace the
hardcoded config.userId in the recordAudit(...) call with this resolved actorId
so the audit entry reflects the actual requester.
🧹 Nitpick comments (1)
src/config.ts (1)

272-281: ⚡ Quick win

Remove WHAT-style comments in helper section

These comments describe behavior that should be expressed by naming/tests instead of inline prose in src/**/*.ts.

As per coding guidelines, "src/**/*.ts: Avoid code comments explaining WHAT — use clear naming instead".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/config.ts` around lines 272 - 281, Remove the WHAT-style inline comments
above the helper functions: delete the prose blocks preceding resolveTeamId and
resolveUserId in src/config.ts and instead rely on clear naming and tests; if
any behavior needs clarification, encode it via more descriptive function names
or add unit tests for resolveTeamId() and resolveUserId(override?: string) that
assert env-override precedence and trimming/length rules rather than keeping the
explanatory comments.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/mcp/server.ts`:
- Line 533: Update the log/error string that currently reads "Team memory not
enabled. Set TEAM_ID and AGENTMEMORY_USER_ID" to accurately reflect that
AGENTMEMORY_USER_ID is preferred but USER_ID is accepted as a fallback; mention
both keys (e.g., "Set TEAM_ID and AGENTMEMORY_USER_ID (or USER_ID as a
fallback)") and replace the two occurrences in src/mcp/server.ts (the two
message literals at the locations around the current text and the other one at
the second occurrence).

In `@test/config-resolve.test.ts`:
- Around line 12-16: The afterEach restoration loop for process.env (in the
afterEach block referencing originalEnv and process.env) only overwrites
existing keys and leaves any new keys added by tests, leaking state; update the
afterEach to fully restore the environment by removing keys not present in
originalEnv and then restoring originalEnv entries (or replace process.env with
a shallow copy of originalEnv) so that tests cannot leak environment variables
across runs; apply the same fix to the other afterEach copy referenced around
lines 36-40.

---

Outside diff comments:
In `@src/functions/team.ts`:
- Around line 113-181: The team profile handler ("mem::team-profile") currently
ignores the per-request identity (data.userId) and always audits using
config.userId; change it to respect data.userId in non-private mode and use that
same resolved actor when calling recordAudit. Specifically: keep the existing
private-mode behavior that forces the filter to config.userId, but otherwise
resolve an actorId = data?.userId ?? config.userId (or use a resolveUserId
helper if available), use that actorId for any per-request scoping and for the
members list/audit metadata, and replace the hardcoded config.userId in the
recordAudit(...) call with this resolved actorId so the audit entry reflects the
actual requester.

---

Nitpick comments:
In `@src/config.ts`:
- Around line 272-281: Remove the WHAT-style inline comments above the helper
functions: delete the prose blocks preceding resolveTeamId and resolveUserId in
src/config.ts and instead rely on clear naming and tests; if any behavior needs
clarification, encode it via more descriptive function names or add unit tests
for resolveTeamId() and resolveUserId(override?: string) that assert
env-override precedence and trimming/length rules rather than keeping the
explanatory comments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6fb07e12-92e2-4c3c-a573-0d6cf2dd2f9c

📥 Commits

Reviewing files that changed from the base of the PR and between d626b4e and 3c0e0f6.

📒 Files selected for processing (7)
  • README.md
  • src/config.ts
  • src/functions/team.ts
  • src/mcp/server.ts
  • src/mcp/tools-registry.ts
  • test/config-resolve.test.ts
  • test/team.test.ts

Comment thread src/mcp/server.ts Outdated
Comment thread test/config-resolve.test.ts
- Update team memory error messages to mention USER_ID as fallback
- Fix test env leak in config-resolve.test.ts afterEach blocks
- Resolve per-request userId in team-profile audit entry
- Remove WHAT-style inline comments from resolve helpers

Signed-off-by: Christian <chrisdlc119@outlook.com>
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.

1 participant