Skip to content

Comments

fix(backend): stamp org-scoped organizationId on MCP group template server imports | ENG-213#306

Merged
betterclever merged 1 commit intomainfrom
fix/mcp-group-template-org-scoping
Feb 21, 2026
Merged

fix(backend): stamp org-scoped organizationId on MCP group template server imports | ENG-213#306
betterclever merged 1 commit intomainfrom
fix/mcp-group-template-org-scoping

Conversation

@betterclever
Copy link
Contributor

Summary

Fixes a multi-tenant isolation bug where importTemplate created mcp_servers rows with organization_id = NULL, causing all orgs to share the same template server instances (Org A enabling/disabling a tool affected Org B).

  • Controller (mcp-groups.controller.ts): un-discards @CurrentAuth() on importTemplate, throws UnauthorizedException early if auth or organizationId is missing, passes auth.organizationId to the service
  • Service (mcp-groups.service.ts): importTemplate now accepts organizationId: string (non-nullable) and threads it to syncTemplate
  • Seeding service (mcp-groups-seeding.service.ts): syncTemplate gains an organizationId: string | null = null param (default null for backward compat); createGroupFromTemplate, updateGroupFromTemplate, and createServer all stamp organizationId on inserted server rows
  • syncAllTemplates (admin startup bootstrap) explicitly passes null — platform-seeded servers remain globally visible via the existing OR org_id IS NULL fallback in the repo

Test plan

  • mcp-groups-seeding.service.spec.ts — create path stamps organizationId; bootstrap path stamps null; update path stamps organizationId on new server inserts; skip path inserts nothing; unknown slug throws
  • mcp-groups.controller.spec.tsnull auth throws UnauthorizedException; null organizationId throws UnauthorizedException; valid auth passes organizationId to service
  • Full test suite: 642 pass, 0 fail

Closes ENG-213

🤖 Generated with Claude Code

…erver imports | ENG-213

importTemplate now requires a valid AuthContext with organizationId and passes it
through syncTemplate → createGroupFromTemplate / updateGroupFromTemplate → createServer,
so every mcp_servers row created via template import is scoped to the caller's org.

syncAllTemplates (admin-only startup bootstrap) continues to pass null so
platform-seeded servers remain globally visible via the existing OR org_id IS NULL
fallback in the repo — backward-compatible.

- Thread organizationId: string through McpGroupsService.importTemplate
- Controller un-discards @CurrentAuth and throws UnauthorizedException early if
  auth or organizationId is missing
- McpGroupsSeedingService.syncTemplate gains optional organizationId param (default null)
- createServer and updateGroupFromTemplate inline insert both stamp organizationId
- Tests: mcp-groups-seeding.service.spec.ts (create/update/skip/unknown slug paths)
         mcp-groups.controller.spec.ts (null auth, null orgId, happy path)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
@betterclever betterclever merged commit f783a1f into main Feb 21, 2026
3 checks passed
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0407032012

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +144 to +148
const result: TemplateSyncResult = await this.seedingService.syncTemplate(
slug,
false,
organizationId,
);

Choose a reason for hiding this comment

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

P1 Badge Seed per-org servers even when template hash is unchanged

importTemplate now passes organizationId into syncTemplate, but syncTemplate still short-circuits on the global mcp_groups.templateHash and does not verify that the calling org already has server rows; once one org has imported a slug, another org importing the same version can get skipped and no org-scoped servers are created for it. Because /mcp-servers queries are organization-filtered (org_id = current OR org_id IS NULL), that org can end up with no usable servers despite a successful import response.

Useful? React with 👍 / 👎.

Comment on lines +292 to 293
organizationId,
enabled: true,

Choose a reason for hiding this comment

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

P1 Badge Preserve tenant isolation when updating existing template servers

Only the insert branch now writes organizationId; when updateGroupFromTemplate finds an existing server by name, it updates that row without setting/scoping organizationId, so shared (NULL) or other-org rows remain reused and mutable during org imports. This means a template update/import path can still operate on non-tenant-scoped server records, undermining the isolation this change is intended to enforce.

Useful? React with 👍 / 👎.

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