mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-04-22 21:41:29 +00:00
* feat: implement MCP provider registry and service
- Add provider registry to manage LLM providers (Claude, Codex, Cursor, Gemini).
- Create provider routes for MCP server operations (list, upsert, delete, run).
- Implement MCP service for handling server operations and validations.
- Introduce abstract provider class and MCP provider base for shared functionality.
- Add tests for MCP server operations across different providers and scopes.
- Define shared interfaces and types for MCP functionality.
- Implement utility functions for handling JSON config files and API responses.
* chore: remove dead code related to MCP server
* refactor: put /api/providers in index.js and remove /providers prefix from provider.routes.ts
* refactor(settings): move MCP server management into provider module
Extract MCP server settings out of the settings controller and agents tab into a
dedicated frontend MCP module. The settings UI now delegates MCP rendering and
behavior to a single module that only needs the selected provider and current
projects.
Changes:
- Add `src/components/mcp` as the single frontend MCP module
- Move MCP server list rendering into `McpServers`
- Move MCP add/edit modal into `McpServerFormModal`
- Move MCP API/state logic into `useMcpServers`
- Move MCP form state/validation logic into `useMcpServerForm`
- Add provider-specific MCP constants, types, and formatting helpers
- Use the unified `/api/providers/:provider/mcp/servers` API for all providers
- Support MCP management for Claude, Cursor, Codex, and Gemini
- Remove old settings-owned Claude/Codex MCP modal components
- Remove old provider-specific `McpServersContent` branching from settings
- Strip MCP server state, fetch, save, delete, and modal ownership from
`useSettingsController`
- Simplify agents settings props so MCP only receives `selectedProvider` and
`currentProjects`
- Keep Claude working-directory unsupported while preserving cwd support for
Cursor, Codex, and Gemini
- Add progressive MCP loading:
- render user/global scope first
- load project/local scopes in the background
- append project results as they resolve
- cache MCP lists briefly to avoid slow tab-switch refetches
- ignore stale async responses after provider switches
Verification:
- `npx eslint src/components/mcp`
- `npm run typecheck`
- `npm run build:client`
* fix(mcp): form with multiline text handling for args, env, headers, and envVars
* feat(mcp): add global MCP server creation flow
Add a separate global MCP add path in the settings MCP module so users can create
one shared MCP server configuration across Claude, Cursor, Codex, and Gemini from
the same screen.
The provider-specific add flow is still kept next to it because these two actions
have different intent. A global MCP server must be constrained to the subset of
configuration that every provider can accept, while a provider-specific server can
still use that provider's own supported scopes, transports, and fields. Naming the
buttons as "Add Global MCP Server" and "Add <Provider> MCP Server" makes that
distinction explicit without forcing users to infer it from the selected tab.
This also moves the explanatory copy to button hover text to keep the MCP toolbar
compact while still documenting the difference between global and provider-only
adds at the point of action.
Implementation details:
- Add global MCP form mode with shared user/project scopes and stdio/http transports.
- Submit global creates through `/api/providers/mcp/servers/global`.
- Reuse the existing MCP form modal with configurable scopes, transports, labels,
and descriptions instead of duplicating form logic.
- Disable provider-only fields for the global flow because those fields cannot be
safely written to every provider.
- Clear the MCP server cache globally after a global add because every provider tab
may have changed.
- Surface partial global add failures with provider-specific error messages.
Validation:
- npx eslint src/components/mcp/view/McpServers.tsx
- npm run typecheck
- npm run build:client
* feat: implement platform-specific provider visibility for cursor agent
* refactor(providers): centralize message handling in provider module
Move provider-specific normalizeMessage and fetchHistory logic out of the legacy
server/providers adapters and into the refactored provider classes so callers can
depend on the main provider contract instead of parallel adapter plumbing.
Add a providers service to resolve concrete providers through the registry and
delegate message normalization/history loading from realtime handlers and the
unified messages route. Add shared TypeScript message/history types and normalized
message helpers so provider implementations and callers use the same contract.
Remove the old adapter registry/files now that Claude, Codex, Cursor, and Gemini
implement the required behavior directly.
* refactor(providers): move auth status checks into provider runtimes
Move provider authentication status logic out of the CLI auth route so auth checks
live with the provider implementations that understand each provider's install
and credential model.
Add provider-specific auth runtime classes for Claude, Codex, Cursor, and Gemini,
and expose them through the shared provider contract as `provider.auth`. Add a
provider auth service that resolves providers through the registry and delegates
status checks via `auth.getStatus()`.
Keep the existing `/api/cli/<provider>/status` endpoints, but make them thin route
adapters over the new provider auth service. This removes duplicated route-local
credential parsing and makes auth status a first-class provider capability beside
MCP and message handling.
* refactor(providers): clarify provider auth and MCP naming
Rename provider auth/MCP contracts to remove the overloaded Runtime suffix so
the shared interfaces read as stable provider capabilities instead of execution
implementation details.
Add a consistent provider-first auth class naming convention by renaming
ClaudeAuthProvider, CodexAuthProvider, CursorAuthProvider, and GeminiAuthProvider
to ClaudeProviderAuth, CodexProviderAuth, CursorProviderAuth, and
GeminiProviderAuth.
This keeps the provider module API easier to scan and aligns auth naming with
the main provider ownership model.
* refactor(providers): move session message delegation into sessions service
Move provider-backed session history and message normalization calls out of the
generic providers service so the service name reflects the behavior it owns.
Add a dedicated sessions service for listing session-capable providers,
normalizing live provider events, and fetching persisted session history through
the provider registry. Update realtime handlers and the unified messages route to
depend on `sessionsService` instead of `providersService`.
This separates session message operations from other provider concerns such as
auth and MCP, keeping the provider services easier to navigate as the module
grows.
* refactor(providers): move auth status routes under provider API
Move provider authentication status endpoints out of the legacy `/api/cli` route
namespace so auth status is exposed through the same provider module that owns
provider auth and MCP behavior.
Add `GET /api/providers/:provider/auth/status` to the provider router and route
it through the provider auth service. Remove the old `cli-auth` route file and
`/api/cli` mount now that provider auth status is handled by the unified provider
API.
Update the frontend provider auth endpoint map to call the new provider-scoped
routes and rename the endpoint constant to reflect that it is no longer CLI
specific.
* chore(api): remove unused backend endpoints after MCP audit
Remove legacy backend routes that no longer have frontend or internal
callers, including the old Claude/Codex MCP APIs, unused Cursor and Codex
helper endpoints, stale TaskMaster detection/next/initialize routes,
and unused command/project helpers.
This reduces duplicated MCP behavior now handled by the provider-based
MCP API, shrinks the exposed backend surface, and removes probe/service
code that only existed for deleted endpoints.
Add an MCP settings API audit document to capture the route-usage
analysis and explain why the legacy MCP endpoints were considered safe
to remove.
* refactor(providers): remove debug logging from Claude authentication status checks
* refactor(cursor): lazy-load better-sqlite3 and remove unused type definitions
* refactor(cursor): remove SSE from CursorMcpProvider constructor and error message
* refactor(auth): standardize API response structure and remove unused error handling
* refactor: make providers use dedicated session handling classes
* refactor: remove legacy provider selection UI and logic
* fix(server/providers): harden and correct session history normalization/pagination
Address correctness and safety issues in provider session adapters while
preserving existing normalized message shapes.
Claude sessions:
- Ensure user text content parts generate unique normalized message ids.
- Replace duplicate `${baseId}_text` ids with index-suffixed ids to avoid
collisions when one user message contains multiple text segments.
Cursor sessions:
- Add session id sanitization before constructing SQLite paths to prevent
path traversal via crafted session ids.
- Enforce containment by resolving the computed DB path and asserting it stays
under ~/.cursor/chats/<cwdId>.
- Refactor blob parsing to a two-pass flow: first build blobMap and collect
JSON blobs, then parse binary parent refs against the fully populated map.
- Fix pagination semantics so limit=0 returns an empty page instead of full
history, with consistent total/hasMore/offset/limit metadata.
Gemini sessions:
- Honor FetchHistoryOptions pagination by reading limit/offset and slicing
normalized history accordingly.
- Return consistent hasMore/offset/limit metadata for paged responses.
Validation:
- eslint passed for touched files.
- server TypeScript check passed (tsc --noEmit -p server/tsconfig.json).
---------
249 lines
11 KiB
JavaScript
249 lines
11 KiB
JavaScript
import js from "@eslint/js";
|
|
import tseslint from "typescript-eslint";
|
|
import react from "eslint-plugin-react";
|
|
import reactHooks from "eslint-plugin-react-hooks";
|
|
import reactRefresh from "eslint-plugin-react-refresh";
|
|
import { createNodeResolver, importX } from "eslint-plugin-import-x";
|
|
import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
|
|
import boundaries from "eslint-plugin-boundaries";
|
|
import tailwindcss from "eslint-plugin-tailwindcss";
|
|
import unusedImports from "eslint-plugin-unused-imports";
|
|
import globals from "globals";
|
|
|
|
export default tseslint.config(
|
|
{
|
|
ignores: ["dist/**", "node_modules/**", "public/**"],
|
|
},
|
|
{
|
|
files: ["src/**/*.{ts,tsx,js,jsx}"],
|
|
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
plugins: {
|
|
react,
|
|
"react-hooks": reactHooks, // for following React rules such as dependencies in hooks, keys in lists, etc.
|
|
"react-refresh": reactRefresh, // for Vite HMR compatibility
|
|
"import-x": importX, // for import order/sorting. It also detercts circular dependencies and duplicate imports.
|
|
tailwindcss, // for detecting invalid Tailwind classnames and enforcing classname order
|
|
"unused-imports": unusedImports, // for detecting unused imports
|
|
},
|
|
languageOptions: {
|
|
globals: {
|
|
...globals.browser,
|
|
},
|
|
parserOptions: {
|
|
ecmaFeatures: { jsx: true },
|
|
},
|
|
},
|
|
settings: {
|
|
react: { version: "detect" },
|
|
},
|
|
rules: {
|
|
// --- Unused imports/vars ---
|
|
"unused-imports/no-unused-imports": "warn",
|
|
"unused-imports/no-unused-vars": [
|
|
"warn",
|
|
{
|
|
vars: "all",
|
|
varsIgnorePattern: "^_",
|
|
args: "after-used",
|
|
argsIgnorePattern: "^_",
|
|
},
|
|
],
|
|
"no-unused-vars": "off",
|
|
"@typescript-eslint/no-unused-vars": "off",
|
|
|
|
// --- React ---
|
|
"react/jsx-key": "warn",
|
|
"react/jsx-no-duplicate-props": "error",
|
|
"react/jsx-no-undef": "error",
|
|
"react/no-children-prop": "warn",
|
|
"react/no-danger-with-children": "error",
|
|
"react/no-direct-mutation-state": "error",
|
|
"react/no-unknown-property": "warn",
|
|
"react/react-in-jsx-scope": "off",
|
|
|
|
// --- React Hooks ---
|
|
"react-hooks/rules-of-hooks": "error",
|
|
"react-hooks/exhaustive-deps": "warn",
|
|
|
|
// --- React Refresh (Vite HMR) ---
|
|
"react-refresh/only-export-components": [
|
|
"warn",
|
|
{ allowConstantExport: true },
|
|
],
|
|
|
|
// --- Import ordering & hygiene ---
|
|
"import-x/no-duplicates": "warn",
|
|
"import-x/order": [
|
|
"warn",
|
|
{
|
|
groups: [
|
|
"builtin",
|
|
"external",
|
|
"internal",
|
|
"parent",
|
|
"sibling",
|
|
"index",
|
|
],
|
|
"newlines-between": "always",
|
|
},
|
|
],
|
|
|
|
// --- Tailwind CSS ---
|
|
"tailwindcss/classnames-order": "warn",
|
|
"tailwindcss/no-contradicting-classname": "warn",
|
|
"tailwindcss/no-unnecessary-arbitrary-value": "warn",
|
|
|
|
// --- Disabled base rules ---
|
|
"@typescript-eslint/no-explicit-any": "off",
|
|
"@typescript-eslint/no-require-imports": "off",
|
|
"no-case-declarations": "off",
|
|
"no-control-regex": "off",
|
|
"no-useless-escape": "off",
|
|
},
|
|
},
|
|
{
|
|
files: ["server/**/*.{js,ts}"], // apply this block only to backend source files
|
|
ignores: ["server/**/*.d.ts"], // skip generated declaration files in backend linting
|
|
plugins: {
|
|
boundaries, // enforce backend architecture boundaries (module-to-module contracts)
|
|
"import-x": importX, // keep import hygiene rules (duplicates, unresolved paths, etc.)
|
|
"unused-imports": unusedImports, // remove dead imports/variables from backend files
|
|
},
|
|
languageOptions: {
|
|
parser: tseslint.parser, // parse both JS and TS syntax in backend files
|
|
parserOptions: {
|
|
ecmaVersion: "latest", // support modern ECMAScript syntax in backend code
|
|
sourceType: "module", // treat backend files as ESM modules
|
|
},
|
|
globals: {
|
|
...globals.node, // expose Node.js globals such as process, Buffer, and __dirname equivalents
|
|
},
|
|
},
|
|
settings: {
|
|
"boundaries/include": ["server/**/*.{js,ts}"], // only analyze dependency boundaries inside backend files
|
|
"import/resolver": {
|
|
// boundaries resolves imports through eslint-module-utils, which reads the classic
|
|
// import/resolver setting instead of import-x/resolver-next.
|
|
typescript: {
|
|
project: ["server/tsconfig.json"], // resolve backend aliases using the canonical backend tsconfig
|
|
alwaysTryTypes: true, // keep normal TS package/type resolution working alongside aliases
|
|
},
|
|
node: {
|
|
extensions: [".mjs", ".cjs", ".js", ".json", ".node", ".ts", ".tsx"], // preserve Node-style fallback resolution for plain files
|
|
},
|
|
},
|
|
"import-x/resolver-next": [
|
|
// ESLint's import plugin does not read tsconfig path aliases on its own.
|
|
// This resolver teaches import-x how to understand the backend-only "@/*"
|
|
// mapping defined in server/tsconfig.json, which fixes false no-unresolved errors in editors.
|
|
createTypeScriptImportResolver({
|
|
project: ["server/tsconfig.json"], // point the resolver at the canonical backend tsconfig instead of the frontend one
|
|
alwaysTryTypes: true, // keep standard TypeScript package resolution working while backend aliases are enabled
|
|
}),
|
|
// Keep Node-style resolution available for normal package imports and plain relative JS files.
|
|
// The TypeScript resolver handles aliases, while the Node resolver preserves the expected fallback behavior.
|
|
createNodeResolver({
|
|
extensions: [".mjs", ".cjs", ".js", ".json", ".node", ".ts", ".tsx"],
|
|
}),
|
|
],
|
|
"boundaries/elements": [
|
|
{
|
|
type: "backend-shared-type-contract", // shared backend type/interface contracts that modules may consume without creating runtime coupling
|
|
pattern: [
|
|
"server/shared/types.{js,ts}",
|
|
"server/shared/interfaces.{js,ts}",
|
|
], // keep backend modules on explicit shared contract files for erased imports only
|
|
mode: "file", // treat each shared contract file itself as the boundary element instead of the whole folder
|
|
},
|
|
{
|
|
type: "backend-shared-utils", // shared backend runtime helpers that modules may import directly
|
|
pattern: ["server/shared/utils.{js,ts}"], // classify the shared utils file so modules can depend on it explicitly
|
|
mode: "file",
|
|
},
|
|
{
|
|
type: "backend-legacy-runtime", // legacy runtime persistence modules used while providers migrate into server/modules
|
|
pattern: [
|
|
"server/projects.js",
|
|
"server/sessionManager.js",
|
|
"server/database/*.{js,ts}",
|
|
"server/utils/runtime-paths.js",
|
|
], // provider history loading still resolves session data through these legacy runtime/database files
|
|
mode: "file",
|
|
},
|
|
{
|
|
type: "backend-module", // logical element name used by boundaries rules below
|
|
pattern: "server/modules/*", // each direct folder in server/modules is treated as one module boundary
|
|
mode: "folder", // classify dependencies at folder-module level (not per individual file)
|
|
capture: ["moduleName"], // capture the module folder name for messages/debugging/template use
|
|
},
|
|
],
|
|
},
|
|
rules: {
|
|
// --- Unused imports/vars (backend) ---
|
|
"unused-imports/no-unused-imports": "warn", // warn when imports are not used so they can be cleaned up
|
|
"unused-imports/no-unused-vars": "off", // keep backend signal focused on dead imports instead of local unused variables
|
|
|
|
// --- Import hygiene (backend) ---
|
|
"import-x/no-duplicates": "warn", // prevent duplicate import lines from the same module
|
|
"import-x/order": [
|
|
"warn", // keep backend import grouping/order consistent with the frontend config
|
|
{
|
|
groups: [
|
|
"builtin", // Node built-ins such as fs, path, and url come first
|
|
"external", // third-party packages come after built-ins
|
|
"internal", // aliased internal imports such as @/... come next
|
|
"parent", // ../ imports come after aliased internal imports
|
|
"sibling", // ./foo imports come after parent imports
|
|
"index", // bare ./ imports stay last
|
|
],
|
|
"newlines-between": "always", // require a blank line between import groups in backend files too
|
|
},
|
|
],
|
|
"import-x/no-unresolved": "error", // fail when an import path cannot be resolved
|
|
"import-x/no-useless-path-segments": "warn", // prefer cleaner paths (remove redundant ./ and ../ segments)
|
|
"import-x/no-absolute-path": "error", // disallow absolute filesystem imports in backend files
|
|
|
|
// --- General safety/style (backend) ---
|
|
eqeqeq: ["warn", "always", { null: "ignore" }], // avoid accidental coercion while still allowing x == null checks
|
|
|
|
// --- Architecture boundaries (backend modules) ---
|
|
"boundaries/dependencies": [
|
|
"error", // treat architecture violations as lint errors
|
|
{
|
|
default: "allow", // allow normal imports unless a rule below explicitly disallows them
|
|
checkInternals: false, // do not apply these cross-module rules to imports inside the same module
|
|
rules: [
|
|
{
|
|
from: { type: "backend-module" }, // modules may depend on shared type/interface contracts only as erased type-only imports
|
|
to: { type: "backend-shared-type-contract" },
|
|
disallow: {
|
|
dependency: { kind: ["value", "typeof"] },
|
|
}, // block runtime imports so shared contracts stay compile-time only instead of becoming hidden shared modules
|
|
message:
|
|
"Backend modules may only use `import type` when importing from server/shared/types.ts or server/shared/interfaces.ts.",
|
|
},
|
|
{
|
|
to: { type: "backend-module" }, // when importing anything that belongs to another backend module
|
|
disallow: { to: { internalPath: "**" } }, // block all direct/deep imports into module internals by default
|
|
message:
|
|
"Cross-module imports must go through that module's barrel file (server/modules/<module>/index.ts or index.js).", // explicit error message for architecture violations
|
|
},
|
|
{
|
|
to: { type: "backend-module" }, // same target scope as the disallow rule above
|
|
allow: {
|
|
to: {
|
|
internalPath: [
|
|
"index", // allow extensionless barrel imports resolved as module root index
|
|
"index.{js,mjs,cjs,ts,tsx}", // allow explicit index.* barrel file imports
|
|
],
|
|
},
|
|
}, // re-allow only public module entry points (barrel files)
|
|
},
|
|
],
|
|
},
|
|
],
|
|
"boundaries/no-unknown": "error", // fail fast if boundaries cannot classify a dependency, which prevents silent rule bypasses
|
|
},
|
|
}
|
|
);
|