Skip to content

feat(settings): add per-account settings, native settings page and connection panel - #662

Closed
polaz wants to merge 10 commits into
mainfrom
feat/#637-account-settings
Closed

polaz wants to merge 10 commits into
mainfrom
feat/#637-account-settings

Conversation

@polaz

@polaz polaz commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Summary

Per-account and per-chat settings for the server, with a native settings page for clients that render structured settings and an MCP App panel for clients that support app panels.

Replaces #661 (same change, squashed into one commit on the current main).

Settings model

  • Three layers: the operator configuration (environment and startup presets) stays the floor; account settings (preset, read-only, default project or group, tool groups) apply to every chat of a GitLab account; chat overrides from manage_context apply to the current chat only. Settings only narrow what the operator allows.
  • The caller is derived from the authenticated context: the GitLab user for OAuth, the token's instance for a static token; the chat is the MCP session.
  • Restrictions apply to the tool list (actions the preset denies leave the listed schema, and a tool with none left is not listed) and to every call, including direct calls a client never offered: a refused call returns a clear reason and does not execute the requested operation. Tool, group, read-only and action limits are checked first; only a call they allow has a project or group named by numeric id read from GitLab and checked as its path, unless the scope lists that id itself (an id GitLab does not know is refused; any other lookup failure, such as an expired sign-in, fails the call with GitLab's error and its reauthorization challenge), URL-encoded paths, in calls and in a preset's scope, are compared and used decoded, and destinations are checked too (target_project_id, target_group_id, namespace_id, a new subgroup's parent_id, and the namespace a project is created or forked into, as the group it goes into). A scope of projects reaches no group, so group-targeted calls are refused under it. get_profile, the host's account identity, is never hidden by settings.
  • Numeric scope IDs are saved as the canonical path returned by the same GitLab lookup, so later path checks and scoped listings use the confirmed identity.
  • A scope path must be confirmed by GitLab: update_settings and manage_context set_scope refuse a typo or an unreachable GitLab instead of saving a guessed project or group; an expired sign-in (401, or 403 insufficient_scope) fails with GitLab's error and the reauthorization challenge.
  • An action the operator (GITLAB_DENIED_ACTIONS) or the preset denied stays denied under a working scope, also where the scope runs other actions in its place, and a call the scope rewrites, filters or answers by other requests is parsed by the tool's schema first, so it is validated and coerced exactly like an unscoped call. Unknown arguments are stripped by the selected action schema before target checks; another action’s arguments cannot incorrectly reject a valid call.
  • manage_todos mark_all_done in a scoped chat marks only the scope's pending todos done, a few at a time, and reports how many were marked and which failed; the others stay pending. Each project and group of the scope has its todos read with GitLab's own project or group filter, so todos outside the scope never fill the pages read. One call marks at most 100; when more remain it is marked partial, and a repeated call continues (a group without its subgroups also reads theirs, at most 1000 past each group's first page). Targets are read one after another within 50 requests, so a wide scope ends within the handler timeout; targets not reached are named in a partial result.
  • A project created or forked without a namespace goes into the scope's single group; with no single group, and for a group without a parent, the call is refused. A fork naming both namespace and namespace_path has each checked. A snippet created without a project goes into the scope's single project; under any other scope it would be a personal snippet, and the call is refused.
  • The working scope is a focus, not an access boundary: the user sets and clears it, and objects addressed only by a global id (a work item or todo id) are not resolved against it, and browse_users stays the instance's user directory, to find people not yet in the scope (a global search of users finds its members). A project or group access token is the hard boundary GitLab enforces.
  • A listing or search that names no project or group reads the working scope: a global search becomes a search of the scope's group or project, a project listing lists the scope's group (keeping a caller's include_subgroups: false, and the active-projects default of a plain listing unless active or archived is given; or, for a plain listing under a scope made of projects, those projects themselves, archived ones left out), a global project search under a project scope answers with the active scope projects whose name, path or description match, ordered as GitLab orders it (newest created first unless sort/order_by name another order) before pagination (at most 1000 projects; missing ordering data or an incomplete scan is reported as partial; project search has no projects scope), a global search of groups answers with the scope's groups that match, read from the namespace listing and ordered by creation as the search orders them (updated_at ordering is marked partial; project search has no groups scope and group search none before GitLab 19.4; refused under a scope of projects), a snippet title search (instance-wide only) answers with the scope's project snippets that GitLab's search matches (title, description or file name, as Snippet.search matches them), newest updated first, a listing of public or personal snippets (which belong to no project) and of personal access tokens (which belong to an account) are refused, and a cross-project merge request or vulnerability listing names the scope's target (a merge request listing keeps the caller-only default of the cross-project one unless scope names another set). A preset scope of one project or group, named alone, as a list of one or as a namespace (which covers its subgroups), is that single target. Project listings and searches (including projects only shared with the scope group), todos (also of a group the call names, whose filter covers its subgroups), cross-project merge requests and namespace listings are filtered to the scope (a namespace listing is refused under a scope of projects, which reaches no group), reading further GitLab pages until the requested page is full (at most ten pages of 100; a page still short then comes back marked partial with a hint to narrow the listing, and a page starting past those rows answers so without a request). Under several targets, a project listing and a cross-project merge request listing instead read each target's own listing, merged without repeats in GitLab's order and paged after the merge within ten requests (a project of the scope answers a project listing itself when the call uses only search, simple, order_by and sort; similarity and priority orders keep target order, marked partial). Instance-wide listings read the scope instead: deploy keys (with public omitted or false, GitLab's default) the deduplicated keys of the scope's projects, named or in its groups (at most ten project-enumeration pages and ten total key requests, with incomplete results marked partial), instance audit events the trail of the scope project, or under a group or several targets the trails of the scope's projects and groups merged newest first (GitLab keeps a group's trail to its own events, so a group is read with its projects' trails and those of the subgroups on the way to them; a scope including subgroups is marked partial, as a subgroup holding no project is not found that way), project snippets that name no project the scope project's, or under a group or several targets those of the scope's projects merged newest first, all runners those available to the scope's project or group, the caller's own runners the owned feed (with its cursor) kept to those the scope can use (marked partial past 1000 such runners); scopes with no such form are refused. The projects of a scope made of projects are listed in GitLab's simple representation unless simple is false, read a few at a time and ordered before the page is cut. The caller's own activity (browse_events user) shows the scope project's events under a single project scope; under groups or several targets it filters the user feed by the events' resolved project paths, with at most ten feed pages and 100 distinct project lookups, reporting a partial result when either bound prevents completing the page. A namespace argument (labels, wiki, milestones) passes when it names an allowed project or an allowed group, so the scope group itself works also without its subgroups. Under a group scope without subgroups (GitLab's group results always cover them), a search and a vulnerability listing, global or naming the group itself, read the group's results kept to the group and its direct projects, in the tools' own answer shapes (vulnerabilities now carry their project's path); a user search keeps the group's own and inherited members (one member lookup per page), and a search of a group with more than 1000 direct projects comes back marked partial. Under a scope of several projects or groups, a vulnerability listing merges the targets' listings, deduplicated and in the requested order, with a cursor bound to the scope and filters; the all-runners listing (instance-wide for administrators only) is the union of the runners available to the targets, newest first under such a cursor, and the owned-runners listing keeps its own feed and cursor, limited to runners available to one of the targets; the vulnerability and all-runners listings read each target only as far as the requested page needs. Each of these, and the composed audit trails and project snippets, shares ten requests across the scope and marks a result past that bound partial. Under any scope, an audit listing filtered by entity follows GitLab's audit finder: Project or Group keeps the scope's trails of that kind, entity_id reads that one in-scope trail, and user or instance events answer an empty page. A global search there searches each target and merges the results without repeats within the same ten requests (groups without their subgroups list their direct projects within eleven more, shared by all of them), in GitLab's order for issues, merge requests, projects and milestones and in target order (marked partial) for the other kinds; in a search of projects each group searches its projects and each project of the scope answers itself when its name, path or description match.
  • A preset's allowed_tools overrides its denied_tools_regex, as documented for profiles; list-tools counts preset tools the same way.
  • A saved preset that was later removed makes the caller read-only (reported by check_connection) instead of failing every call.

Runner project restrictions

  • GITLAB_ALLOWED_PROJECT_IDS is enforced independently of the working scope in both runner handlers. Project listing and registration validate the resolved project ID or configured full path.
  • Lists filter out runners outside the allowlist while preserving upstream cursors. Operations addressed by runner ID verify all associated projects before reading, updating, pausing/resuming, deleting or rotating a token. GitLab's total project count must match the complete visible association page; hidden or truncated associations fail closed.
  • Group/instance runners cannot be confined to a project allowlist. Their registration and ID-based operations are refused under that restriction. A runner's jobs, for any runner type, are kept to the jobs of allowed projects, and internal project authorization fields are omitted from the response.
  • Ownership checks use GitLab 16.0 fields and at most 100 associated projects per runner. A GraphQL runner listing reads them in the same request; the pre-18.3 REST owned-runners page checks its runners ten per query. With no allowlist, the listing asks for no ownership fields and makes no additional requests.

Instance routing

  • Every REST call takes its GitLab base URL from one place: the OAuth token's instance, else the instance the running tool call started with (a switch_instance from another chat does not move a call already running), else the instance the server's own token works with now (it follows switch_instance), else GITLAB_API_URL. Tools that built URLs from the startup GITLAB_API_URL (core, files, pipelines, iterations, namespace detection, user search, the generic client used by scope search) now reach the caller's instance; whoami reports and links it the same way, and get_profile reads the account of that instance. GraphQL clients, connection state, tier, version and token scopes resolve from the instance of the running call the same way.

Storage

  • With PostgreSQL session storage (new oauth_account_settings table and migration), account settings live with the sessions in the database. File session storage keeps them in its file.
  • With sessions in memory (the OAuth default) or a local static-token server, they live in ~/.config/gitlab-mcp/settings/ of the user running the server, so saved settings survive a restart. A database package older than the server, which cannot keep settings, falls back to that directory with a warning instead of failing calls.
  • A scope change that does not name the subgroup choice keeps the one saved at write time for a group scope (subgroups included after a project scope or none), so concurrent edits from two chats are both kept.
  • Writes are compare-and-set; concurrent edits of different settings, including different tool groups, are both kept. The local directory needs no lock: each write saves the next numbered generation with an exclusive link and, when another process saved it first, re-reads and checks its version again, so a process that dies or is suspended mid-write never blocks the others or overwrites newer settings. As pruning frees old numbers, a write counts only when the newest state holds it after the link, so a writer resuming after several newer writes is refused instead of reported as saved. Older generations and temporary files of crashed writers are pruned, the directory is synced after each write (the link is the commit, so a failed sync is logged rather than reported as a failed write), and entries of the wrong shape are skipped; a failed first open of the shared storage is retried by the next request.

Tools and capabilities

  • get_settings / update_settings: the account settings page, advertised through the openai/settings capability. A patch is validated in full before anything is saved; administrator read-only mode cannot be turned off.
  • check_connection: account, GitLab version and tier, restrictions in effect, the number of tools the caller can use (counted as tools/list shows them, including the OAuth token's scopes), whether GitLab answered (an outage is not reported as a refused sign-in; whoami reports how its user lookup ended as userLookup), and what to do about problems.
  • open_settings_panel: MCP App (ui:// resource, text/html;profile=mcp-app, empty CSP; the server and the channel gateway declare the io.modelcontextprotocol/ui extension) to search projects and groups, apply a scope to this chat or save it for new chats, and check the connection. Follows the host theme, keyboard accessible, shows GitLab strings as text only, reports success from the settings the server holds after saving. Its preview shows the tool groups off in the chat, including those its preset turns off (manage_context show reports them), and a superseded search finishing late cannot change it. In OAuth mode listing and reading resources needs mcp:resources (refused with an insufficient_scope challenge otherwise), and the panel tool declares both mcp:tools and mcp:resources.
  • find_scope_targets: member projects and every group the account can see (not only those it belongs to) by name, for choosing a scope (needs an api or read_api token scope).
  • browse_merge_requests list takes group_id: the merge requests of a group and its subgroups (not combined with project_id).
  • manage_context clear_scope: the chat works everywhere, also without the account's default scope; its preset and read-only mode stay. The panel's "Work everywhere in this chat" uses it.
  • The channel gateway forwards resources, so the panel opens through it as well. After a lost link it replays a call when the newest downstream catalog marks the tool read-only, falling back to the tool name for a tool not yet listed or without the annotation; a complete catalog that drops a tool drops its verdict. It forwards the downstream tools/list_changed notification and advertises listChanged, so a host refreshes its catalog after a settings change; it also tells the host to list again after it reconnects downstream.
  • Settings tools are exempt from settings restrictions and from the unreachable-instance check, so a restricted caller can still fix its settings; the OAuth token's scopes are checked for them as for any call. One answered while GitLab is marked unreachable has the health monitor probe GitLab again, so other tools recover as soon as GitLab answers.
  • The mcp.tools catalog in package.json is regenerated by the release PR (prepare-release.sh), as for every tool; this PR does not edit it.

Behaviour change

manage_context switch_preset, set_scope and reset now affect the current chat only (they used to change the whole server process); reset returns the chat to the account settings. In OAuth mode switch_profile no longer moves the chat's tool list to the profile's host: calls always go to the token's instance, and the tool list follows them. With a static token, switch_instance keeps every chat's read-only mode and preset, clears its scope, and points every session at the new instance before telling it to refresh its tool list.

Integration test maintenance

  • Integration runs wait up to 60 s per test; the API timeouts come from .env.test.
  • closing_issues test accepts GitLab's own issue link in both forms (/-/issues/N before 19.0, /-/work_items/N from 19.0).
  • removeLabelIds test sets a known label first instead of relying on earlier tests that skip without enough lifecycle labels.
  • The lifecycle project step reports GitLab's answer when creation fails.
  • The set_scope project test scopes to a project that exists under the test group.

Testing

  • Unit and integration suites (yarn test:all, integration against GitLab 19.0 EE), lint and a compile of the build configuration pass. Tests read and write settings in a temporary directory, never the user's own settings.
  • Database package tests pass; the migration applies from scratch on PostgreSQL 17 with no drift against the Prisma schema, and compare-and-set holds under concurrent writes from two connections.
  • Instance routing: each tool family's request URL checked against an OAuth caller on another instance; base URL selection checked for OAuth, switch_instance and the configured fallback.
  • Settings panel exercised in jsdom against a simulated MCP Apps host (handshake, theme, untrusted strings, save and cancel, expired sign-in, unreachable GitLab).
  • Shared-store test: two users and two chats stay apart, settings survive a restart, concurrent edits from two server processes over one store are kept.

Closes #637

@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 120 files, which is 20 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration
  • Configuration used: Repository: structured-world/gitlab-mcp/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 4e38f0ea-ef52-4747-945e-7ac5b830a36e
📥 Commits

Reviewing files that changed from the base of the PR and between 3139c4c and 384fc63.

⛔ Files ignored due to path filters: 1
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing: 120
  • packages/gitlab-mcp-db/prisma/migrations/20261010000000_account_settings/migration.sql
  • packages/gitlab-mcp-db/prisma/schema.prisma
  • packages/gitlab-mcp-db/src/postgresql.ts
  • packages/gitlab-mcp-db/tests/postgresql.test.ts
  • packages/gitlab-mcp/docs/.vitepress/config.mts
  • packages/gitlab-mcp/docs/advanced/context-switching.md
  • packages/gitlab-mcp/docs/configuration/settings.md
  • packages/gitlab-mcp/docs/guide/configuration.md
  • packages/gitlab-mcp/docs/guides/multi-gitlab-setup.md
  • packages/gitlab-mcp/eslint.config.mjs
  • packages/gitlab-mcp/jest.config.js
  • packages/gitlab-mcp/package.json
  • packages/gitlab-mcp/src/channel-gateway/gateway.ts
  • packages/gitlab-mcp/src/cli/list-tools.ts
  • packages/gitlab-mcp/src/configuration/caller.ts
  • packages/gitlab-mcp/src/configuration/composed-feeds.ts
  • packages/gitlab-mcp/src/configuration/composed-listings.ts
  • packages/gitlab-mcp/src/configuration/groups.ts
  • packages/gitlab-mcp/src/configuration/index.ts
  • packages/gitlab-mcp/src/configuration/policy.ts
  • packages/gitlab-mcp/src/configuration/scope-ids.ts
  • packages/gitlab-mcp/src/configuration/scope-projects.ts
  • packages/gitlab-mcp/src/configuration/scope-targets.ts
  • packages/gitlab-mcp/src/configuration/scoped-execution.ts
  • packages/gitlab-mcp/src/configuration/scoped-feeds.ts
  • packages/gitlab-mcp/src/configuration/service.ts
  • packages/gitlab-mcp/src/configuration/settings-store.ts
  • packages/gitlab-mcp/src/configuration/types.ts
  • packages/gitlab-mcp/src/contracts/storage.ts
  • packages/gitlab-mcp/src/entities/context/connection-check.ts
  • packages/gitlab-mcp/src/entities/context/context-manager.ts
  • packages/gitlab-mcp/src/entities/context/handlers.ts
  • packages/gitlab-mcp/src/entities/context/output-schema.ts
  • packages/gitlab-mcp/src/entities/context/profile.ts
  • packages/gitlab-mcp/src/entities/context/registry.ts
  • packages/gitlab-mcp/src/entities/context/schema.ts
  • packages/gitlab-mcp/src/entities/context/scope-search.ts
  • packages/gitlab-mcp/src/entities/context/settings-panel.ts
  • packages/gitlab-mcp/src/entities/context/settings.ts
  • packages/gitlab-mcp/src/entities/context/types.ts
  • packages/gitlab-mcp/src/entities/context/whoami.ts
  • packages/gitlab-mcp/src/entities/core/registry.ts
  • packages/gitlab-mcp/src/entities/files/registry.ts
  • packages/gitlab-mcp/src/entities/iterations/registry.ts
  • packages/gitlab-mcp/src/entities/mrs/registry.ts
  • packages/gitlab-mcp/src/entities/mrs/schema-readonly.ts
  • packages/gitlab-mcp/src/entities/pipelines/registry.ts
  • packages/gitlab-mcp/src/entities/runners/project-access.ts
  • packages/gitlab-mcp/src/entities/runners/registry.ts
  • packages/gitlab-mcp/src/graphql/runners.ts
  • packages/gitlab-mcp/src/graphql/vulnerabilities.ts
  • packages/gitlab-mcp/src/handlers.ts
  • packages/gitlab-mcp/src/mcp-apps.ts
  • packages/gitlab-mcp/src/oauth/session-store.ts
  • packages/gitlab-mcp/src/oauth/storage/file.ts
  • packages/gitlab-mcp/src/oauth/storage/memory.ts
  • packages/gitlab-mcp/src/oauth/storage/types.ts
  • packages/gitlab-mcp/src/oauth/tool-auth.ts
  • packages/gitlab-mcp/src/profiles/scope-enforcer.ts
  • packages/gitlab-mcp/src/profiles/types.ts
  • packages/gitlab-mcp/src/registry-manager.ts
  • packages/gitlab-mcp/src/services/ConnectionManager.ts
  • packages/gitlab-mcp/src/services/TokenScopeDetector.ts
  • packages/gitlab-mcp/src/session-manager.ts
  • packages/gitlab-mcp/src/utils/bounded-map.ts
  • packages/gitlab-mcp/src/utils/fetch.ts
  • packages/gitlab-mcp/src/utils/gitlab-api.ts
  • packages/gitlab-mcp/src/utils/gitlab-base-url.ts
  • packages/gitlab-mcp/src/utils/namespace.ts
  • packages/gitlab-mcp/src/utils/schema-utils.ts
  • packages/gitlab-mcp/src/utils/smart-user-search.ts
  • packages/gitlab-mcp/src/utils/sync-directory.ts
  • packages/gitlab-mcp/tests/integration/data-lifecycle.test.ts
  • packages/gitlab-mcp/tests/integration/schemas-dependent/merge-requests.test.ts
  • packages/gitlab-mcp/tests/integration/schemas/context.test.ts
  • packages/gitlab-mcp/tests/integration/schemas/scoped-composition.test.ts
  • packages/gitlab-mcp/tests/integration/schemas/workitems.test.ts
  • packages/gitlab-mcp/tests/setup/isolatedSettings.ts
  • packages/gitlab-mcp/tests/setup/setupTests.ts
  • packages/gitlab-mcp/tests/setup/setupTests.unit.ts
  • packages/gitlab-mcp/tests/unit/RegistryManager.test.ts
  • packages/gitlab-mcp/tests/unit/channel-gateway/gateway.test.ts
  • packages/gitlab-mcp/tests/unit/cli/list-tools.test.ts
  • packages/gitlab-mcp/tests/unit/configuration/composed-feeds.test.ts
  • packages/gitlab-mcp/tests/unit/configuration/index.test.ts
  • packages/gitlab-mcp/tests/unit/configuration/policy.test.ts
  • packages/gitlab-mcp/tests/unit/configuration/replicas.test.ts
  • packages/gitlab-mcp/tests/unit/configuration/scope-ids.test.ts
  • packages/gitlab-mcp/tests/unit/configuration/scope-targets.test.ts
  • packages/gitlab-mcp/tests/unit/configuration/scoped-execution.test.ts
  • packages/gitlab-mcp/tests/unit/configuration/scoped-feeds.test.ts
  • packages/gitlab-mcp/tests/unit/configuration/service.test.ts
  • packages/gitlab-mcp/tests/unit/configuration/settings-store.test.ts
  • packages/gitlab-mcp/tests/unit/entities/context/connection-check.test.ts
  • packages/gitlab-mcp/tests/unit/entities/context/context-manager.test.ts
  • packages/gitlab-mcp/tests/unit/entities/context/handlers.test.ts
  • packages/gitlab-mcp/tests/unit/entities/context/profile.test.ts
  • packages/gitlab-mcp/tests/unit/entities/context/scope-search.test.ts
  • packages/gitlab-mcp/tests/unit/entities/context/settings-panel.test.ts
  • packages/gitlab-mcp/tests/unit/entities/context/settings-tools.test.ts
  • packages/gitlab-mcp/tests/unit/entities/context/settings.test.ts
  • packages/gitlab-mcp/tests/unit/entities/context/whoami.test.ts
  • packages/gitlab-mcp/tests/unit/entities/mrs/registry.test.ts
  • packages/gitlab-mcp/tests/unit/entities/runners/registry.test.ts
  • packages/gitlab-mcp/tests/unit/handlers.test.ts
  • packages/gitlab-mcp/tests/unit/mcp-client-contract.test.ts
  • packages/gitlab-mcp/tests/unit/oauth/session-store.test.ts
  • packages/gitlab-mcp/tests/unit/oauth/storage/file.test.ts
  • packages/gitlab-mcp/tests/unit/oauth/storage/memory.test.ts
  • packages/gitlab-mcp/tests/unit/oauth/tool-auth.test.ts
  • packages/gitlab-mcp/tests/unit/profiles/scope-enforcer.test.ts
  • packages/gitlab-mcp/tests/unit/services/ConnectionManager.test.ts
  • packages/gitlab-mcp/tests/unit/services/InstanceConnectionPool.test.ts
  • packages/gitlab-mcp/tests/unit/services/TokenScopeDetector.test.ts
  • packages/gitlab-mcp/tests/unit/session-manager.test.ts
  • packages/gitlab-mcp/tests/unit/utils/bounded-map.test.ts
  • packages/gitlab-mcp/tests/unit/utils/gitlab-base-url.test.ts
  • packages/gitlab-mcp/tests/unit/utils/instance-routing.test.ts
  • packages/gitlab-mcp/tests/unit/utils/namespace.coverage.test.ts
  • packages/gitlab-mcp/tests/unit/utils/schema-utils.test.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T19:12:11.581922Z 384fc63 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

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

Copy link
Copy Markdown

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: f5e234aae2

ℹ️ 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 thread packages/gitlab-mcp/src/entities/context/connection-check.ts Outdated
Comment thread packages/gitlab-mcp/src/configuration/scope-ids.ts Outdated
@polaz
polaz force-pushed the feat/#637-account-settings branch from f5e234a to 5c8dd3f Compare October 14, 2026 21:50

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

Copy link
Copy Markdown

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: 5c8dd3f2e1

ℹ️ 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 thread packages/gitlab-mcp/src/handlers.ts Outdated
Comment thread packages/gitlab-mcp/src/configuration/composed-feeds.ts
Comment thread packages/gitlab-mcp/src/configuration/composed-feeds.ts
Comment thread packages/gitlab-mcp/src/configuration/composed-feeds.ts
Comment thread packages/gitlab-mcp/src/configuration/scope-targets.ts Outdated
Comment thread packages/gitlab-mcp/src/configuration/scope-targets.ts
polaz added 4 commits October 15, 2026 01:43
…nnection panel

- Settings in three layers: the operator configuration stays the floor,
  account settings (preset, read-only, default project or group, tool
  groups) apply to every chat of a GitLab account, and manage_context
  overrides apply to the current chat. Settings only narrow what the
  operator allows, both in the tool list and on every call.
- A working scope narrows targetless listings and searches to its
  projects and groups, filters or composes results where GitLab has no
  per-scope form, refuses instance-wide calls it cannot narrow, and
  keeps creation (projects, groups, snippets) and mark_all_done inside
  the scope. Numeric IDs are resolved and checked as their paths.
- GITLAB_ALLOWED_PROJECT_IDS is enforced in the runner tools, with
  ownership checks read within the runner listing.
- Every REST call takes its base URL from the caller's instance.
- Account settings are stored with the sessions (PostgreSQL table and
  migration, or the session file), or in the user's settings directory
  with compare-and-set writes.
- New tools: get_settings and update_settings (the openai/settings
  page), check_connection, open_settings_panel (an MCP App panel) and
  find_scope_targets; manage_context gains clear_scope. The channel
  gateway forwards resources and tool list changes.
- manage_context switch_preset, set_scope and reset now affect the
  current chat only.

Closes #637
- check_connection counted a tool whose every action the preset denies,
  which tools/list leaves out. Both now decide a tool's listed schema in
  one place, so the count matches the list.
- A numeric id in an argument that names a project or a group was read
  through the namespaces endpoint, which also returns user namespaces;
  a project id shared with a user namespace was checked as that user's
  path and refused. It is now read as a group, then as a project, as the
  handlers read it. Namespace-valued arguments still read namespaces.
- Unreachable fallbacks in the vulnerability sort and the scoped call
  path are removed, and the scope compositions gain tests for repeated
  targets, bounded listings, unreadable answers and unordered rows.

Regression tests cover a tool with every action denied in the count and
a numeric namespace that is both a project and a user namespace.
… project search

- A preset may list a project or group by numeric id. A call or an event
  naming that id was checked as its resolved path, which the scope does
  not list, and was refused. Such an id is now checked as itself, with
  no GitLab lookup.
- A preset group named URL-encoded reached tools and listed-path
  comparisons encoded, so composed snippet and audit reads of that group
  found none of its projects. Scope targets now carry decoded paths.
- A todo listing naming a group of a scope without subgroups returned
  the subgroups' todos too, as GitLab's group filter covers them; it is
  now kept to the scope like a targetless listing.
- All runners of a scope of several targets first read the
  instance-wide listing, which GitLab allows administrators only. They
  are now the union of the targets' runner feeds, newest first, under a
  cursor bound to the scope and filters.
- A global search of projects under a scope with groups was refused.
  Each group now searches its projects and each project of the scope
  answers itself when it matches, merged newest created first.

Regression tests cover each case and failed before the change.
@polaz
polaz force-pushed the feat/#637-account-settings branch from 5c8dd3f to 6de577c Compare October 15, 2026 07:05

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

Copy link
Copy Markdown

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: 6de577c09f

ℹ️ 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 thread packages/gitlab-mcp/src/oauth/storage/file.ts
Comment thread packages/gitlab-mcp/src/entities/context/settings-panel.ts
Comment thread packages/gitlab-mcp/src/channel-gateway/gateway.ts
polaz added 2 commits October 15, 2026 11:02
…e catalogs

- File OAuth storage treats a write as committed once the file is renamed
  into place: a failed directory flush is logged, not reported as a failure
  that undoes the change in memory while a restart reads it from disk.
- The settings panel tracks a running search apart from a running save, so a
  search that ends or is cleared no longer re-enables the save buttons.
- The channel gateway ignores every catalog answer older than the newest
  complete one, so a late answer cannot bring back a tool the newer catalog
  dropped.

Each fix carries a regression test that failed before it.

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

Copy link
Copy Markdown

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: 5e04da788b

ℹ️ 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 thread packages/gitlab-mcp/src/configuration/service.ts
Comment thread packages/gitlab-mcp/src/entities/context/scope-search.ts
Comment thread packages/gitlab-mcp/src/configuration/composed-feeds.ts Outdated
Comment thread packages/gitlab-mcp/src/entities/context/connection-check.ts
…utages apart

- The composed vulnerability and all-runners listings read each target only
  as far as the requested page needs: each target lists in the composed
  order, so one target with a thousand rows no longer spends the shared
  request bound and leaves the other targets unread.
- find_scope_targets asks GitLab for every group the account can see; a
  non-administrator was listed only the groups it belongs to.
- whoami reports how its user lookup ended (userLookup), and check_connection
  reports whether GitLab answered (reachable), so an outage is no longer shown
  as a refused sign-in in the settings panel.
- The comment on account change notifications states that processes share
  only the settings storage.

Each fix carries a regression test that failed before it.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

if (action !== 'switch_profile') return;

P2 Badge Re-pin the session when reset clears a profile

In OAuth mode, after switch_profile pins the session to that profile's instance, manage_context reset clears the profile and emits tools/list_changed but this guard skips re-pinning for the reset action. An immediate tools/list therefore still uses the removed profile's instance and can expose the wrong tier/version catalog until another tool call restores the OAuth token's URL; handle reset by pinning the session back to the request account's instance.

ℹ️ 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 thread packages/gitlab-mcp/src/configuration/scope-targets.ts
Comment thread packages/gitlab-mcp/src/handlers.ts
Comment thread packages/gitlab-mcp/src/configuration/composed-feeds.ts Outdated
polaz added 2 commits October 15, 2026 20:15
- List projects and merge requests under several scope targets from each
  target's own listing, merged without repeats in GitLab's order and paged
  after the merge, instead of filtering the account-wide feed whose first
  1000 rows may hold nothing of the scope
- Add group_id to browse_merge_requests list (groups/:id/merge_requests);
  a narrowed merge request listing keeps the caller-only default of the
  cross-project listing unless scope names another set
- Resolve the GraphQL client, connection state and introspection from the
  instance of the running call, so switch_instance in another session no
  longer redirects a call already in flight
- Key composed search hits by project and id, so one commit found in two
  forks is kept for each

Regression tests cover each case.
In OAuth mode every call goes to the token's instance; a chat's profile
never changes that. switch_profile still re-pinned the session to the
profile's host, so the client listed tools of an instance no call reached,
the next call moved it back, and the list_changed notification could be
answered before the re-pin. The session is now pinned only by the per-call
pin, to the instance calls use; switch_profile, switch_preset and reset
need no re-pin of their own.

- Remove the switch_profile re-pin and the profile URL lookup it used
- Regression test: switch_profile keeps the session on the token's
  instance, connected and through the unreachable-instance path

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

Copy link
Copy Markdown

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: 72041401ab

ℹ️ 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 thread packages/gitlab-mcp/src/configuration/scoped-execution.ts Outdated
Comment thread packages/gitlab-mcp/src/configuration/scoped-execution.ts Outdated
Comment thread packages/gitlab-mcp/src/entities/mrs/schema-readonly.ts
…us MR targets

- A project listing under a scope made of projects that names order_by or
  sort is answered from the scope's projects in that order, instead of the
  account's project listing filtered after GitLab paged it; similarity, a
  ranking GitLab keeps to itself, keeps scope order and is marked partial
- Scoped mark_all_done reads targets one after another within 50 requests
  instead of looking up every target first, so a wide scope ends within
  the handler timeout and names the targets it did not reach
- browse_merge_requests list refuses project_id together with group_id:
  the scope check would read a group the listing never uses

Regression tests cover each case.
@sonarqubecloud

Copy link
Copy Markdown

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.

feat(settings): add native Codex settings and an isolated setup UI

1 participant