Skip to content

Commit 9755a46

Browse files
authored
chore: Removed mcp server url settings from admin settings (#42208)
## Description - Removed peripheral settings from MCP settings - Now mcp toggle will require restart on single org instances Fixes https://linear.app/appsmith/issue/APP-15928/mcp-settings-toggle-should-require-a-restart-for-mcp-server-to-source ## Automation /ok-to-test tags="@tag.All" ## Testing > [!NOTE] > **How CI runs on fork PRs — no action needed from you.** > 1. **Workflow approval.** GitHub holds the first run on fork PRs until a maintainer approves it, so a pause before any check appears is expected. > 2. **Credential-free checks.** Once approved, format, lint, typecheck, unit tests, cyclic-dependency and compile-only build checks run without repository secrets. Only the checks relevant to what you changed (client / server / RTS) will run, and their logs are safe to debug against. > 3. **Maintainer-triggered checks.** Cypress, Playwright, Docker builds and deploy previews need secrets, so a maintainer starts them with `/approve-ci`, and `/build-deploy-preview` when hands-on testing is needed. Approval is pinned to one commit — pushing again requires fresh approval. > > The `awaiting-maintainer` / `awaiting-contributor` labels show whose turn it is. You do **not** need `ok-to-test` or any slash command. Full detail: [Pull request check states](https://github.com/appsmithorg/appsmith/blob/release/contributions/CodeContributionsGuidelines.md#pull-request-check-states). Select the validation relevant to this change: - [ ] Client unit tests - [ ] Server unit tests - [ ] Cypress - [ ] Playwright - [ ] Deploy preview - [ ] Not applicable Suggested Cypress tags or specs: ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/34195617186> > Commit: 584a55c > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=34195617186&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.All` > Spec: > <hr>Tue, 08 Sep 2026 07:42:19 UTC <!-- end of auto-generated comment: Cypress test results --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - MCP administration now exposes a single setting to enable or disable MCP. - Changes requiring a server restart are clearly identified, and the Save and Restart action remains available. - Organization configuration updates can automatically start restart-status polling. - **Bug Fixes** - Improved handling of MCP configuration updates that require a restart. - MCP internal secret changes no longer trigger an immediate server restart; they are applied through the Save and Restart flow. - **Tests** - Updated coverage for simplified MCP settings and restart behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 81da26b commit 9755a46

17 files changed

Lines changed: 78 additions & 259 deletions

File tree

app/client/src/ce/api/OrganizationApi.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export type UpdateOrganizationConfigResponse = ApiResponse<{
1616
export interface UpdateOrganizationConfigRequest {
1717
organizationConfiguration: Record<string, string>;
1818
needsRefresh?: boolean;
19+
needsRestart?: boolean;
1920
isOnlyOrganizationSettings?: boolean;
2021
apiConfig?: AxiosRequestConfig;
2122
}

app/client/src/ce/pages/AdminSettings/config/mcpServer.ts

Lines changed: 3 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,9 @@ import {
66
CategoryType,
77
SettingCategories,
88
SettingTypes,
9-
SettingSubtype,
109
} from "ee/pages/AdminSettings/config/types";
11-
import { getDefaultMcpServerUrl } from "ee/constants/mcp";
1210
import McpKeysPage from "pages/AdminSettings/Profile/McpKeysPage";
1311

14-
const isMcpServerOff = (
15-
// TODO: Fix this the next time the file is edited
16-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
17-
values?: Record<string, any>,
18-
) =>
19-
values?.mcpConfig?.enabled !== true && values?.["mcpConfig.enabled"] !== true;
20-
2112
export const MCP_ENABLED_SETTING: Setting = {
2213
id: "mcpConfig.enabled",
2314
name: "mcpConfig.enabled",
@@ -30,55 +21,6 @@ export const MCP_ENABLED_SETTING: Setting = {
3021
defaultValue: false,
3122
};
3223

33-
export const MCP_DATA_ENABLED_SETTING: Setting = {
34-
id: "mcpConfig.dataEnabled",
35-
name: "mcpConfig.dataEnabled",
36-
category: SettingCategories.MCP_SERVER,
37-
controlType: SettingTypes.TOGGLE,
38-
label: "MCP data tools",
39-
text: "Let agents work with datasources and queries (create datasources/queries, run read-only actions)",
40-
helpText:
41-
"* Disabled by default. Requires the MCP server above. All operations run under the connecting user's existing permissions; credentials are never exposed to agents.",
42-
defaultValue: false,
43-
isDisabled: isMcpServerOff,
44-
};
45-
46-
export const MCP_SERVER_URL_SETTING: Setting = {
47-
id: "mcpConfig.serverUrl",
48-
name: "mcpConfig.serverUrl",
49-
category: SettingCategories.MCP_SERVER,
50-
controlType: SettingTypes.TEXTINPUT,
51-
controlSubType: SettingSubtype.TEXT,
52-
label: "MCP server URL",
53-
subText: "MCP server URL which MCP clients should use to reach this instance",
54-
placeholder: getDefaultMcpServerUrl(),
55-
helpText:
56-
"* URL MCP clients should use to reach this instance. Leave blank to use the default (this origin + /mcp). Set a custom value if Appsmith is behind a reverse proxy or a different public hostname.",
57-
isDisabled: isMcpServerOff,
58-
59-
validate: (value: string) => {
60-
if (value === undefined || value === "") {
61-
return;
62-
}
63-
64-
try {
65-
const url = new URL(value);
66-
67-
if (url.protocol !== "http:" && url.protocol !== "https:") {
68-
return "Enter an http(s) URL.";
69-
}
70-
} catch {
71-
return "Enter a valid URL.";
72-
}
73-
},
74-
};
75-
76-
const instanceSettings = [
77-
MCP_ENABLED_SETTING,
78-
MCP_DATA_ENABLED_SETTING,
79-
MCP_SERVER_URL_SETTING,
80-
];
81-
8224
export const mcpKeys: AdminConfigType = {
8325
icon: "robot-2",
8426
type: SettingCategories.MCP_KEYS,
@@ -96,17 +38,17 @@ export const config: AdminConfigType = {
9638
controlType: SettingTypes.GROUP,
9739
title: "MCP Server (BETA)",
9840
canSave: true,
99-
settings: [MCP_ENABLED_SETTING, MCP_DATA_ENABLED_SETTING],
41+
settings: [MCP_ENABLED_SETTING],
10042
};
10143

10244
export const getMcpServerConfig = (
10345
isMultiOrgEnabled: boolean,
10446
): AdminConfigType => {
10547
return isMultiOrgEnabled
106-
? { ...config, settings: [MCP_ENABLED_SETTING] }
48+
? config
10749
: {
10850
...config,
10951
categoryType: CategoryType.INSTANCE,
110-
settings: instanceSettings,
52+
needsRestart: true,
11153
};
11254
};

app/client/src/ce/pages/AdminSettings/config/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export interface Category {
120120
subText?: string;
121121
isConnected?: boolean;
122122
needsRefresh?: boolean;
123+
needsRestart?: boolean;
123124
children?: Category[];
124125
icon?: string;
125126
categoryType: string;
@@ -168,6 +169,7 @@ export interface AdminConfigType {
168169
canSave: boolean;
169170
isConnected?: boolean;
170171
needsRefresh?: boolean;
172+
needsRestart?: boolean;
171173
icon?: string;
172174
categoryType: CategoryType;
173175
isEnterprise?: boolean;

app/client/src/ce/sagas/organizationSagas.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@ export function* updateOrganizationConfigSaga(
143143
if (action.payload.needsRefresh) {
144144
location.reload();
145145
}
146+
147+
if (action.payload.needsRestart) {
148+
yield put({
149+
type: ReduxActionTypes.RESTART_SERVER_POLL,
150+
});
151+
}
146152
}
147153
} catch (error) {
148154
const errorObj = error as APIResponseError;

app/client/src/ce/utils/adminSettingsHelpers.test.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,35 @@ import {
77
describe("MCP org config form mapping", () => {
88
it("treats nested mcpConfig form fields as organization settings", () => {
99
expect(isOrganizationConfig("mcpConfig.enabled")).toBe(true);
10-
expect(isOrganizationConfig("mcpConfig.dataEnabled")).toBe(true);
11-
expect(isOrganizationConfig("mcpConfig.serverUrl")).toBe(true);
1210
expect(isOrganizationConfig("APPSMITH_MCP_ENABLED")).toBe(false);
1311
});
1412

15-
it("flattens mcpConfig onto dotted form keys and fail-closes missing flags", () => {
13+
it("flattens mcpConfig.enabled onto dotted form keys and fail-closes missing flags", () => {
1614
expect(
1715
flattenOrganizationConfigForSettingsForm({
1816
instanceName: "Appsmith",
1917
mcpConfig: {
2018
enabled: true,
21-
dataEnabled: false,
22-
serverUrl: "https://appsmith.example/mcp",
2319
},
2420
}),
2521
).toEqual({
2622
instanceName: "Appsmith",
2723
"mcpConfig.enabled": true,
28-
"mcpConfig.dataEnabled": false,
29-
"mcpConfig.serverUrl": "https://appsmith.example/mcp",
3024
mcpConfig: {
3125
enabled: true,
32-
dataEnabled: false,
33-
serverUrl: "https://appsmith.example/mcp",
3426
},
3527
});
3628
});
3729

3830
it("nests only changed mcpConfig fields on save", () => {
3931
expect(
4032
nestOrganizationConfigFromSettingsForm({
41-
"mcpConfig.dataEnabled": true,
33+
"mcpConfig.enabled": true,
4234
hideWatermark: true,
4335
}),
4436
).toEqual({
4537
hideWatermark: true,
46-
mcpConfig: { dataEnabled: true },
38+
mcpConfig: { enabled: true },
4739
});
4840
});
4941
});

app/client/src/ce/utils/adminSettingsHelpers.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,20 +88,12 @@ export const flattenOrganizationConfigForSettingsForm = (
8888
if (mcpConfig && typeof mcpConfig === "object") {
8989
const mcp = mcpConfig as Record<string, unknown>;
9090
const enabled = mcp.enabled === true;
91-
const dataEnabled = mcp.dataEnabled === true;
9291

9392
// Nested paths first: redux-form Field names like "mcpConfig.enabled" use lodash path get.
9493
// Dotted keys second: settingsMap / settingsConfig[id] lookups. lodash.set skips nesting
9594
// when the dotted own-property already exists.
9695
set(configs, "mcpConfig.enabled", enabled);
97-
set(configs, "mcpConfig.dataEnabled", dataEnabled);
9896
configs["mcpConfig.enabled"] = enabled;
99-
configs["mcpConfig.dataEnabled"] = dataEnabled;
100-
101-
if (typeof mcp.serverUrl === "string") {
102-
set(configs, "mcpConfig.serverUrl", mcp.serverUrl);
103-
configs["mcpConfig.serverUrl"] = mcp.serverUrl;
104-
}
10597
}
10698

10799
return;

app/client/src/pages/AdminSettings/SaveSettings.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ interface SaveAdminSettingsProps {
3434
isOnlyOrganizationConfig?: boolean;
3535
isSaving?: boolean;
3636
needsRefresh?: boolean;
37+
needsRestart?: boolean;
3738
onSave?: () => void;
3839
onClear?: () => void;
3940
settings: Record<string, string>;
@@ -46,6 +47,7 @@ const saveAdminSettings = (props: SaveAdminSettingsProps) => {
4647
isOnlyOrganizationConfig = false,
4748
isSaving,
4849
needsRefresh = false,
50+
needsRestart = false,
4951
onClear,
5052
onSave,
5153
settings,
@@ -58,9 +60,10 @@ const saveAdminSettings = (props: SaveAdminSettingsProps) => {
5860
if (needsRefresh) {
5961
saveButtonText = SAVE_AND_REFRESH_BUTTON;
6062
} else if (
61-
isOnlyOrganizationConfig ||
62-
(updatedOrganizationSettings?.length === Object.keys(settings).length &&
63-
updatedOrganizationSettings?.length !== 0)
63+
!needsRestart &&
64+
(isOnlyOrganizationConfig ||
65+
(updatedOrganizationSettings?.length === Object.keys(settings).length &&
66+
updatedOrganizationSettings?.length !== 0))
6467
) {
6568
saveButtonText = SAVE_BUTTON;
6669
}

app/client/src/pages/AdminSettings/SettingsForm.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export function SettingsForm(
129129
organizationConfiguration: config,
130130
isOnlyOrganizationSettings: !isEnvAndOrganizationSettings,
131131
needsRefresh: details?.needsRefresh,
132+
needsRestart: details?.needsRestart,
132133
}),
133134
);
134135

@@ -313,6 +314,7 @@ export function SettingsForm(
313314
isOnlyOrganizationConfig={isOnlyOrganizationConfig}
314315
isSaving={props.isSaving}
315316
needsRefresh={details?.needsRefresh}
317+
needsRestart={details?.needsRestart}
316318
onClear={onClear}
317319
onSave={onSave}
318320
settings={props.settings}

app/client/src/pages/AdminSettings/config/ConfigFactory.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export class ConfigFactory {
4747
categoryType: config.categoryType,
4848
isEnterprise: config.isEnterprise,
4949
needsRefresh: config.needsRefresh,
50+
needsRestart: config.needsRestart,
5051
isFeatureEnabled: config.isFeatureEnabled,
5152
children: config?.children?.map((child) =>
5253
ConfigFactory.getCategory(child),

app/server/appsmith-server/src/main/java/com/appsmith/server/domains/McpConfig.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,21 @@
66
import java.io.Serializable;
77

88
/**
9-
* Organization-owned MCP policy. All admin-facing MCP switches live here; the only remaining env var is
9+
* Organization-owned MCP policy. The only admin-facing switch is {@code enabled}; the only remaining env var is
1010
* {@code APPSMITH_MCP_INTERNAL_SECRET} (install-time, never persisted on the organization).
1111
*
12-
* <p>Every flag is boxed. {@code null} and {@code false} both mean off — only {@link Boolean#TRUE} enables a
13-
* capability. Token lifetime is per-key ({@code keySpanDays}), not an org setting.
12+
* <p>{@code enabled} is boxed. {@code null} and {@code false} both mean off — only {@link Boolean#TRUE} enables MCP.
13+
* Token lifetime is per-key ({@code keySpanDays}), not an org setting.
1414
*/
1515
@Data
1616
public class McpConfig implements Serializable {
1717

1818
private Boolean enabled;
1919

20-
private Boolean dataEnabled;
21-
22-
private String serverUrl;
23-
2420
public void copyNonSensitiveValues(McpConfig source) {
2521
if (source == null) {
2622
return;
2723
}
2824
enabled = ObjectUtils.defaultIfNull(source.getEnabled(), enabled);
29-
dataEnabled = ObjectUtils.defaultIfNull(source.getDataEnabled(), dataEnabled);
30-
serverUrl = ObjectUtils.defaultIfNull(source.getServerUrl(), serverUrl);
3125
}
3226
}

0 commit comments

Comments
 (0)