@@ -2843,13 +2986,16 @@ const CloudProviderEditor = ({
setSaving(true);
setSubmitError(null);
try {
+ if (slugError) {
+ throw new Error(slugError);
+ }
await onSubmit(
{
id: initial?.id ?? '',
slug,
label: label.trim() || slug,
endpoint: endpoint.trim(),
- authStyle: initial?.authStyle ?? authStyleForSlug(slug),
+ authStyle: initial?.authStyle ?? 'bearer',
maskedKey: maskKeyLabel(hasExistingKey || apiKey.length > 0),
},
apiKey.trim()
@@ -2868,7 +3014,7 @@ const CloudProviderEditor = ({
setSaving(false);
}
}}
- disabled={saving || !endpoint.trim()}
+ disabled={saving || !endpoint.trim() || Boolean(slugError)}
className="rounded-lg bg-primary-500 px-3 py-1.5 text-xs font-medium text-white hover:bg-primary-600 disabled:opacity-50">
{saving
? t('settings.ai.saving')
diff --git a/app/src/components/settings/panels/__tests__/AIPanel.test.tsx b/app/src/components/settings/panels/__tests__/AIPanel.test.tsx
index c0122e961..7ab2a3376 100644
--- a/app/src/components/settings/panels/__tests__/AIPanel.test.tsx
+++ b/app/src/components/settings/panels/__tests__/AIPanel.test.tsx
@@ -11,6 +11,7 @@ import {
saveAISettings,
setCloudProviderKey,
setOpenAICompatEndpointKey,
+ testProviderModel,
} from '../../../../services/api/aiSettingsApi';
import { creditsApi } from '../../../../services/api/creditsApi';
import { renderWithProviders } from '../../../../test/test-utils';
@@ -41,6 +42,7 @@ vi.mock('../../../../services/api/aiSettingsApi', () => ({
saveAISettings: vi.fn(),
loadLocalProviderSnapshot: vi.fn(),
setOpenAICompatEndpointKey: vi.fn(),
+ testProviderModel: vi.fn(),
clearOpenAICompatEndpointKey: vi.fn().mockResolvedValue(undefined),
setCloudProviderKey: vi.fn(),
clearCloudProviderKey: vi.fn().mockResolvedValue(undefined),
@@ -206,6 +208,7 @@ describe('AIPanel', () => {
vi.mocked(setOpenAICompatEndpointKey).mockResolvedValue(undefined);
vi.mocked(clearOpenAICompatEndpointKey).mockResolvedValue(undefined);
vi.mocked(setCloudProviderKey).mockResolvedValue(undefined);
+ vi.mocked(testProviderModel).mockResolvedValue({ reply: 'Hello from the selected model.' });
vi.mocked(listProviderModels).mockResolvedValue([]);
vi.mocked(connectOpenRouterViaOAuth).mockResolvedValue('sk-or-oauth');
vi.mocked(openhumanHeartbeatSettingsGet).mockResolvedValue({
@@ -474,6 +477,8 @@ describe('AIPanel', () => {
// The full CloudProviderEditor should appear (has "Add cloud provider" heading).
await waitFor(() => expect(screen.getByText(/Add cloud provider/i)).toBeInTheDocument());
+ expect(screen.getByLabelText(/^Name$/i)).toBeInTheDocument();
+ expect(screen.getByLabelText(/OpenAI URL/i)).toBeInTheDocument();
// The simple ProviderKeyDialog should NOT appear.
expect(screen.queryByRole('dialog', { name: /Connect Custom/i })).not.toBeInTheDocument();
});
@@ -629,18 +634,52 @@ describe('AIPanel', () => {
fireEvent.click(screen.getByRole('switch', { name: /Connect Custom/i }));
await waitFor(() => expect(screen.getByText(/Add cloud provider/i)).toBeInTheDocument());
+ fireEvent.change(screen.getByLabelText(/^Name$/i), { target: { value: 'Team Gateway' } });
+ fireEvent.change(screen.getByLabelText(/OpenAI URL/i), {
+ target: { value: 'https://api.openai.com/v1' },
+ });
fireEvent.change(screen.getByPlaceholderText('sk-...'), { target: { value: 'sk-test-key' } });
fireEvent.click(screen.getByRole('button', { name: /Add provider/i }));
const alert = await screen.findByRole('alert');
expect(
within(alert).getByText(
- 'Could not reach OpenAI: Provider teapot says no. Try another endpoint.'
+ 'Could not reach Team Gateway: Provider teapot says no. Try another endpoint.'
)
).toBeInTheDocument();
expect(within(alert).getByText('Technical details')).toBeInTheDocument();
expect(within(alert).getByText(/provider returned 418/)).toBeInTheDocument();
- expect(screen.queryByRole('switch', { name: /Disconnect OpenAI/i })).not.toBeInTheDocument();
+ expect(
+ screen.queryByRole('switch', { name: /Disconnect Team Gateway/i })
+ ).not.toBeInTheDocument();
+ });
+
+ it('derives the custom provider slug from the entered name', async () => {
+ vi.mocked(loadAISettings).mockResolvedValue({ ...baseSettings, cloudProviders: [] });
+
+ renderWithProviders(
);
+ await waitFor(() =>
+ expect(screen.getByRole('switch', { name: /Connect Custom/i })).toBeInTheDocument()
+ );
+
+ fireEvent.click(screen.getByRole('switch', { name: /Connect Custom/i }));
+ await waitFor(() => expect(screen.getByText(/Add cloud provider/i)).toBeInTheDocument());
+
+ fireEvent.change(screen.getByLabelText(/^Name$/i), { target: { value: 'My Team Gateway' } });
+ expect(screen.getByText(/Slug:/i)).toHaveTextContent('Slug: my-team-gateway');
+
+ fireEvent.change(screen.getByLabelText(/OpenAI URL/i), {
+ target: { value: 'https://gateway.example.com/v1' },
+ });
+ fireEvent.change(screen.getByPlaceholderText('sk-...'), { target: { value: 'sk-team-key' } });
+ fireEvent.click(screen.getByRole('button', { name: /Add provider/i }));
+
+ await waitFor(() =>
+ expect(vi.mocked(setCloudProviderKey)).toHaveBeenCalledWith('my-team-gateway', 'sk-team-key')
+ );
+ await waitFor(() =>
+ expect(vi.mocked(listProviderModels)).toHaveBeenCalledWith('my-team-gateway')
+ );
});
// ─── local runtime: Ollama endpoint URL dialog ──────────────────────────────
@@ -707,6 +746,30 @@ describe('AIPanel', () => {
});
});
+ it('LM Studio save persists the local_ai provider and endpoint', async () => {
+ vi.mocked(loadAISettings).mockResolvedValue({ ...baseSettings, cloudProviders: [] });
+ renderWithProviders(
);
+ await waitFor(() =>
+ expect(screen.getByRole('switch', { name: /Connect LM Studio/i })).toBeInTheDocument()
+ );
+ fireEvent.click(screen.getByRole('switch', { name: /Connect LM Studio/i }));
+ const dialog = await screen.findByRole('dialog', { name: /Connect LM Studio/i });
+
+ fireEvent.change(within(dialog).getByLabelText(/Endpoint URL/i), {
+ target: { value: 'http://127.0.0.1:1234/v1' },
+ });
+ fireEvent.click(within(dialog).getByRole('button', { name: /^Save$/i }));
+
+ await waitFor(() => expect(openhumanUpdateLocalAiSettingsMock).toHaveBeenCalled());
+ const [arg] = vi.mocked(openhumanUpdateLocalAiSettingsMock).mock.calls[0];
+ expect(arg).toMatchObject({
+ base_url: 'http://127.0.0.1:1234/v1',
+ provider: 'lm_studio',
+ runtime_enabled: true,
+ opt_in_confirmed: true,
+ });
+ });
+
// ─── Custom routing dialog: per-workload temperature override ───────────────
it('Custom routing dialog saves the routing change immediately from the modal', async () => {
@@ -767,6 +830,125 @@ describe('AIPanel', () => {
});
});
+ it('Custom routing dialog can test the selected cloud model and show its reply', async () => {
+ const settingsWithOpenAI = {
+ cloudProviders: [
+ {
+ id: 'p_openai_1',
+ slug: 'openai',
+ label: 'OpenAI',
+ endpoint: 'https://api.openai.com/v1',
+ auth_style: 'bearer' as const,
+ has_api_key: true,
+ },
+ ],
+ routing: {
+ ...baseSettings.routing,
+ reasoning: { kind: 'cloud' as const, providerSlug: 'openai', model: 'gpt-4o' },
+ },
+ };
+ vi.mocked(loadAISettings).mockResolvedValue(settingsWithOpenAI);
+ vi.mocked(listProviderModels).mockResolvedValue([{ id: 'gpt-4o' }, { id: 'gpt-4o-mini' }]);
+ vi.mocked(testProviderModel).mockResolvedValue({ reply: 'Hello from gpt-4o.' });
+
+ renderWithProviders(
);
+
+ const reasoningRow = await screen.findByText(/Main chat agent/i);
+ const rowEl = reasoningRow.closest('div.flex.items-center.justify-between');
+ expect(rowEl).not.toBeNull();
+ fireEvent.click(within(rowEl as HTMLElement).getByRole('button', { name: /Custom/i }));
+
+ const dialog = await screen.findByRole('dialog', { name: /Custom routing/i });
+ fireEvent.click(within(dialog).getByRole('button', { name: /^Test$/i }));
+
+ await waitFor(() =>
+ expect(vi.mocked(testProviderModel)).toHaveBeenCalledWith(
+ 'reasoning',
+ 'openai:gpt-4o',
+ 'Hello world'
+ )
+ );
+ expect(await within(dialog).findByText('Model response')).toBeInTheDocument();
+ expect(within(dialog).getByText('Hello from gpt-4o.')).toBeInTheDocument();
+ });
+
+ it('Custom routing dialog shows in-flight test status immediately', async () => {
+ const settingsWithOpenAI = {
+ cloudProviders: [
+ {
+ id: 'p_openai_1',
+ slug: 'openai',
+ label: 'OpenAI',
+ endpoint: 'https://api.openai.com/v1',
+ auth_style: 'bearer' as const,
+ has_api_key: true,
+ },
+ ],
+ routing: {
+ ...baseSettings.routing,
+ reasoning: { kind: 'cloud' as const, providerSlug: 'openai', model: 'gpt-4o' },
+ },
+ };
+ vi.mocked(loadAISettings).mockResolvedValue(settingsWithOpenAI);
+ vi.mocked(listProviderModels).mockResolvedValue([{ id: 'gpt-4o' }]);
+ let resolveTest: (value: { reply: string }) => void = () => {};
+ const pendingTest = new Promise<{ reply: string }>(resolve => {
+ resolveTest = resolve;
+ });
+ vi.mocked(testProviderModel).mockReturnValue(pendingTest);
+
+ renderWithProviders(
);
+
+ const reasoningRow = await screen.findByText(/Main chat agent/i);
+ const rowEl = reasoningRow.closest('div.flex.items-center.justify-between');
+ expect(rowEl).not.toBeNull();
+ fireEvent.click(within(rowEl as HTMLElement).getByRole('button', { name: /Custom/i }));
+
+ const dialog = await screen.findByRole('dialog', { name: /Custom routing/i });
+ fireEvent.click(within(dialog).getByRole('button', { name: /^Test$/i }));
+
+ expect(await within(dialog).findByText('Testing model…')).toBeInTheDocument();
+ expect(within(dialog).getByText(/Provider: openai:gpt-4o/i)).toBeInTheDocument();
+ expect(within(dialog).getByText(/Prompt: Hello world/i)).toBeInTheDocument();
+
+ resolveTest({ reply: 'Hello from gpt-4o.' });
+ expect(await within(dialog).findByText('Model response')).toBeInTheDocument();
+ });
+
+ it('Custom routing dialog shows test errors inline', async () => {
+ const settingsWithOpenAI = {
+ cloudProviders: [
+ {
+ id: 'p_openai_1',
+ slug: 'openai',
+ label: 'OpenAI',
+ endpoint: 'https://api.openai.com/v1',
+ auth_style: 'bearer' as const,
+ has_api_key: true,
+ },
+ ],
+ routing: {
+ ...baseSettings.routing,
+ reasoning: { kind: 'cloud' as const, providerSlug: 'openai', model: 'gpt-4o' },
+ },
+ };
+ vi.mocked(loadAISettings).mockResolvedValue(settingsWithOpenAI);
+ vi.mocked(listProviderModels).mockResolvedValue([{ id: 'gpt-4o' }]);
+ vi.mocked(testProviderModel).mockRejectedValue(new Error('401 invalid api key'));
+
+ renderWithProviders(
);
+
+ const reasoningRow = await screen.findByText(/Main chat agent/i);
+ const rowEl = reasoningRow.closest('div.flex.items-center.justify-between');
+ expect(rowEl).not.toBeNull();
+ fireEvent.click(within(rowEl as HTMLElement).getByRole('button', { name: /Custom/i }));
+
+ const dialog = await screen.findByRole('dialog', { name: /Custom routing/i });
+ fireEvent.click(within(dialog).getByRole('button', { name: /^Test$/i }));
+
+ expect(await within(dialog).findByRole('alert')).toHaveTextContent('401 invalid api key');
+ });
+
it('renders background loop diagnostics with newest spend row and budget math', async () => {
// BackgroundLoopControls was moved out of AIPanel into standalone panels.
renderWithProviders(
diff --git a/app/src/lib/i18n/chunks/ar-1.ts b/app/src/lib/i18n/chunks/ar-1.ts
index 2025fa576..f61578cfd 100644
--- a/app/src/lib/i18n/chunks/ar-1.ts
+++ b/app/src/lib/i18n/chunks/ar-1.ts
@@ -50,6 +50,7 @@ const ar1: TranslationMap = {
'common.showLess': 'عرض أقل',
'common.submit': 'إرسال',
'common.continue': 'متابعة',
+ 'common.comingSoon': 'Coming Soon',
'settings.general': 'عام',
'settings.featuresAndAI': 'الميزات والذكاء الاصطناعي',
'settings.billingAndRewards': 'الفوترة والمكافآت',
@@ -428,6 +429,9 @@ const ar1: TranslationMap = {
'skills.tabs.composio': 'Composio',
'skills.tabs.channels': 'Channels',
'skills.tabs.mcp': 'MCP Servers',
+ 'skills.mcpComingSoon.title': 'MCP Servers',
+ 'skills.mcpComingSoon.description':
+ 'MCP server management is coming soon. This tab will be the home for discovering, connecting, and monitoring your MCP server integrations.',
'settings.about.connection': 'Connection',
'settings.about.connectionMode': 'Mode',
'settings.about.connectionModeLocal': 'Local',
diff --git a/app/src/lib/i18n/chunks/bn-1.ts b/app/src/lib/i18n/chunks/bn-1.ts
index 3a4821df3..6f0515733 100644
--- a/app/src/lib/i18n/chunks/bn-1.ts
+++ b/app/src/lib/i18n/chunks/bn-1.ts
@@ -50,6 +50,7 @@ const bn1: TranslationMap = {
'common.showLess': 'কম দেখুন',
'common.submit': 'জমা দিন',
'common.continue': 'চালিয়ে যান',
+ 'common.comingSoon': 'Coming Soon',
'settings.general': 'সাধারণ',
'settings.featuresAndAI': 'ফিচার ও AI',
'settings.billingAndRewards': 'বিলিং ও পুরস্কার',
@@ -437,6 +438,9 @@ const bn1: TranslationMap = {
'skills.tabs.composio': 'Composio',
'skills.tabs.channels': 'Channels',
'skills.tabs.mcp': 'MCP Servers',
+ 'skills.mcpComingSoon.title': 'MCP Servers',
+ 'skills.mcpComingSoon.description':
+ 'MCP server management is coming soon. This tab will be the home for discovering, connecting, and monitoring your MCP server integrations.',
'settings.about.connection': 'Connection',
'settings.about.connectionMode': 'Mode',
'settings.about.connectionModeLocal': 'Local',
diff --git a/app/src/lib/i18n/chunks/de-1.ts b/app/src/lib/i18n/chunks/de-1.ts
index a1e24124c..45244024a 100644
--- a/app/src/lib/i18n/chunks/de-1.ts
+++ b/app/src/lib/i18n/chunks/de-1.ts
@@ -50,6 +50,7 @@ const de1: TranslationMap = {
'common.showLess': 'Weniger anzeigen',
'common.submit': 'Senden',
'common.continue': 'Weiter',
+ 'common.comingSoon': 'Coming Soon',
'settings.general': 'Allgemein',
'settings.featuresAndAI': 'Funktionen und KI',
'settings.billingAndRewards': 'Abrechnung und Prämien',
@@ -449,6 +450,9 @@ const de1: TranslationMap = {
'skills.tabs.composio': 'Composio',
'skills.tabs.channels': 'Channels',
'skills.tabs.mcp': 'MCP Servers',
+ 'skills.mcpComingSoon.title': 'MCP Servers',
+ 'skills.mcpComingSoon.description':
+ 'MCP server management is coming soon. This tab will be the home for discovering, connecting, and monitoring your MCP server integrations.',
'settings.about.connection': 'Connection',
'settings.about.connectionMode': 'Mode',
'settings.about.connectionModeLocal': 'Local',
diff --git a/app/src/lib/i18n/chunks/en-1.ts b/app/src/lib/i18n/chunks/en-1.ts
index 440f6f08e..98914d37b 100644
--- a/app/src/lib/i18n/chunks/en-1.ts
+++ b/app/src/lib/i18n/chunks/en-1.ts
@@ -50,6 +50,7 @@ const en1: TranslationMap = {
'common.showLess': 'Show less',
'common.submit': 'Submit',
'common.continue': 'Continue',
+ 'common.comingSoon': 'Coming Soon',
'settings.general': 'General',
'settings.featuresAndAI': 'Features & AI',
'settings.billingAndRewards': 'Billing & Rewards',
@@ -210,6 +211,9 @@ const en1: TranslationMap = {
'skills.tabs.composio': 'Composio',
'skills.tabs.channels': 'Channels',
'skills.tabs.mcp': 'MCP Servers',
+ 'skills.mcpComingSoon.title': 'MCP Servers',
+ 'skills.mcpComingSoon.description':
+ 'MCP server management is coming soon. This tab will be the home for discovering, connecting, and monitoring your MCP server integrations.',
'memory.title': 'Memory',
'memory.search': 'Search memories...',
'memory.noResults': 'No memories found',
diff --git a/app/src/lib/i18n/chunks/es-1.ts b/app/src/lib/i18n/chunks/es-1.ts
index 7e0301ff1..b842d2cd5 100644
--- a/app/src/lib/i18n/chunks/es-1.ts
+++ b/app/src/lib/i18n/chunks/es-1.ts
@@ -50,6 +50,7 @@ const es1: TranslationMap = {
'common.showLess': 'Ver menos',
'common.submit': 'Enviar',
'common.continue': 'Continuar',
+ 'common.comingSoon': 'Coming Soon',
'settings.general': 'General',
'settings.featuresAndAI': 'Funciones e IA',
'settings.billingAndRewards': 'Facturación y recompensas',
@@ -449,6 +450,9 @@ const es1: TranslationMap = {
'skills.tabs.composio': 'Composio',
'skills.tabs.channels': 'Channels',
'skills.tabs.mcp': 'MCP Servers',
+ 'skills.mcpComingSoon.title': 'MCP Servers',
+ 'skills.mcpComingSoon.description':
+ 'MCP server management is coming soon. This tab will be the home for discovering, connecting, and monitoring your MCP server integrations.',
'settings.about.connection': 'Connection',
'settings.about.connectionMode': 'Mode',
'settings.about.connectionModeLocal': 'Local',
diff --git a/app/src/lib/i18n/chunks/fr-1.ts b/app/src/lib/i18n/chunks/fr-1.ts
index 4647b030d..e9dc9c659 100644
--- a/app/src/lib/i18n/chunks/fr-1.ts
+++ b/app/src/lib/i18n/chunks/fr-1.ts
@@ -50,6 +50,7 @@ const fr1: TranslationMap = {
'common.showLess': 'Afficher moins',
'common.submit': 'Envoyer',
'common.continue': 'Continuer',
+ 'common.comingSoon': 'Coming Soon',
'settings.general': 'Général',
'settings.featuresAndAI': 'Fonctionnalités & IA',
'settings.billingAndRewards': 'Facturation & Récompenses',
@@ -451,6 +452,9 @@ const fr1: TranslationMap = {
'skills.tabs.composio': 'Composio',
'skills.tabs.channels': 'Channels',
'skills.tabs.mcp': 'MCP Servers',
+ 'skills.mcpComingSoon.title': 'MCP Servers',
+ 'skills.mcpComingSoon.description':
+ 'MCP server management is coming soon. This tab will be the home for discovering, connecting, and monitoring your MCP server integrations.',
'settings.about.connection': 'Connection',
'settings.about.connectionMode': 'Mode',
'settings.about.connectionModeLocal': 'Local',
diff --git a/app/src/lib/i18n/chunks/hi-1.ts b/app/src/lib/i18n/chunks/hi-1.ts
index 70d7a5143..6b3d3bab1 100644
--- a/app/src/lib/i18n/chunks/hi-1.ts
+++ b/app/src/lib/i18n/chunks/hi-1.ts
@@ -50,6 +50,7 @@ const hi1: TranslationMap = {
'common.showLess': 'कम दिखाएं',
'common.submit': 'सबमिट करें',
'common.continue': 'जारी रखें',
+ 'common.comingSoon': 'Coming Soon',
'settings.general': 'सामान्य',
'settings.featuresAndAI': 'फीचर्स और AI',
'settings.billingAndRewards': 'बिलिंग और रिवॉर्ड',
@@ -434,6 +435,9 @@ const hi1: TranslationMap = {
'skills.tabs.composio': 'Composio',
'skills.tabs.channels': 'Channels',
'skills.tabs.mcp': 'MCP Servers',
+ 'skills.mcpComingSoon.title': 'MCP Servers',
+ 'skills.mcpComingSoon.description':
+ 'MCP server management is coming soon. This tab will be the home for discovering, connecting, and monitoring your MCP server integrations.',
'settings.about.connection': 'Connection',
'settings.about.connectionMode': 'Mode',
'settings.about.connectionModeLocal': 'Local',
diff --git a/app/src/lib/i18n/chunks/id-1.ts b/app/src/lib/i18n/chunks/id-1.ts
index b093a49d5..9f4d59919 100644
--- a/app/src/lib/i18n/chunks/id-1.ts
+++ b/app/src/lib/i18n/chunks/id-1.ts
@@ -50,6 +50,7 @@ const id1: TranslationMap = {
'common.showLess': 'Tampilkan sedikit',
'common.submit': 'Kirim',
'common.continue': 'Lanjutkan',
+ 'common.comingSoon': 'Coming Soon',
'settings.general': 'Umum',
'settings.featuresAndAI': 'Fitur & AI',
'settings.billingAndRewards': 'Tagihan & Hadiah',
@@ -440,6 +441,9 @@ const id1: TranslationMap = {
'skills.tabs.composio': 'Composio',
'skills.tabs.channels': 'Channels',
'skills.tabs.mcp': 'MCP Servers',
+ 'skills.mcpComingSoon.title': 'MCP Servers',
+ 'skills.mcpComingSoon.description':
+ 'MCP server management is coming soon. This tab will be the home for discovering, connecting, and monitoring your MCP server integrations.',
'settings.about.connection': 'Connection',
'settings.about.connectionMode': 'Mode',
'settings.about.connectionModeLocal': 'Local',
diff --git a/app/src/lib/i18n/chunks/it-1.ts b/app/src/lib/i18n/chunks/it-1.ts
index 48b0071da..8c8543719 100644
--- a/app/src/lib/i18n/chunks/it-1.ts
+++ b/app/src/lib/i18n/chunks/it-1.ts
@@ -50,6 +50,7 @@ const it1: TranslationMap = {
'common.showLess': 'Mostra meno',
'common.submit': 'Invia',
'common.continue': 'Continua',
+ 'common.comingSoon': 'Coming Soon',
'settings.general': 'Generale',
'settings.featuresAndAI': 'Funzionalità e AI',
'settings.billingAndRewards': 'Fatturazione e premi',
@@ -444,6 +445,9 @@ const it1: TranslationMap = {
'skills.tabs.composio': 'Composio',
'skills.tabs.channels': 'Channels',
'skills.tabs.mcp': 'MCP Servers',
+ 'skills.mcpComingSoon.title': 'MCP Servers',
+ 'skills.mcpComingSoon.description':
+ 'MCP server management is coming soon. This tab will be the home for discovering, connecting, and monitoring your MCP server integrations.',
'settings.about.connection': 'Connection',
'settings.about.connectionMode': 'Mode',
'settings.about.connectionModeLocal': 'Local',
diff --git a/app/src/lib/i18n/chunks/ko-1.ts b/app/src/lib/i18n/chunks/ko-1.ts
index cd35fde5e..f4c4ebf39 100644
--- a/app/src/lib/i18n/chunks/ko-1.ts
+++ b/app/src/lib/i18n/chunks/ko-1.ts
@@ -50,6 +50,7 @@ const ko1: TranslationMap = {
'common.showLess': '간단히 보기',
'common.submit': '제출',
'common.continue': '계속',
+ 'common.comingSoon': 'Coming Soon',
'settings.general': '일반',
'settings.featuresAndAI': '기능 및 AI',
'settings.billingAndRewards': '결제 및 보상',
@@ -425,6 +426,9 @@ const ko1: TranslationMap = {
'skills.tabs.composio': 'Composio',
'skills.tabs.channels': 'Channels',
'skills.tabs.mcp': 'MCP Servers',
+ 'skills.mcpComingSoon.title': 'MCP Servers',
+ 'skills.mcpComingSoon.description':
+ 'MCP server management is coming soon. This tab will be the home for discovering, connecting, and monitoring your MCP server integrations.',
'settings.about.connection': 'Connection',
'settings.about.connectionMode': 'Mode',
'settings.about.connectionModeLocal': 'Local',
diff --git a/app/src/lib/i18n/chunks/pt-1.ts b/app/src/lib/i18n/chunks/pt-1.ts
index 9de3752fa..ecb65af2a 100644
--- a/app/src/lib/i18n/chunks/pt-1.ts
+++ b/app/src/lib/i18n/chunks/pt-1.ts
@@ -50,6 +50,7 @@ const pt1: TranslationMap = {
'common.showLess': 'Mostrar menos',
'common.submit': 'Enviar',
'common.continue': 'Continuar',
+ 'common.comingSoon': 'Coming Soon',
'settings.general': 'Geral',
'settings.featuresAndAI': 'Recursos e IA',
'settings.billingAndRewards': 'Cobrança e Recompensas',
@@ -449,6 +450,9 @@ const pt1: TranslationMap = {
'skills.tabs.composio': 'Composio',
'skills.tabs.channels': 'Channels',
'skills.tabs.mcp': 'MCP Servers',
+ 'skills.mcpComingSoon.title': 'MCP Servers',
+ 'skills.mcpComingSoon.description':
+ 'MCP server management is coming soon. This tab will be the home for discovering, connecting, and monitoring your MCP server integrations.',
'settings.about.connection': 'Connection',
'settings.about.connectionMode': 'Mode',
'settings.about.connectionModeLocal': 'Local',
diff --git a/app/src/lib/i18n/chunks/ru-1.ts b/app/src/lib/i18n/chunks/ru-1.ts
index a3e3b1acc..f1da90d9b 100644
--- a/app/src/lib/i18n/chunks/ru-1.ts
+++ b/app/src/lib/i18n/chunks/ru-1.ts
@@ -50,6 +50,7 @@ const ru1: TranslationMap = {
'common.showLess': 'Показать меньше',
'common.submit': 'Отправить',
'common.continue': 'Продолжить',
+ 'common.comingSoon': 'Coming Soon',
'settings.general': 'Общие',
'settings.featuresAndAI': 'Функции и AI',
'settings.billingAndRewards': 'Оплата и награды',
@@ -439,6 +440,9 @@ const ru1: TranslationMap = {
'skills.tabs.composio': 'Composio',
'skills.tabs.channels': 'Channels',
'skills.tabs.mcp': 'MCP Servers',
+ 'skills.mcpComingSoon.title': 'MCP Servers',
+ 'skills.mcpComingSoon.description':
+ 'MCP server management is coming soon. This tab will be the home for discovering, connecting, and monitoring your MCP server integrations.',
'settings.about.connection': 'Connection',
'settings.about.connectionMode': 'Mode',
'settings.about.connectionModeLocal': 'Local',
diff --git a/app/src/lib/i18n/chunks/zh-CN-1.ts b/app/src/lib/i18n/chunks/zh-CN-1.ts
index 03469f69f..1de1c3a20 100644
--- a/app/src/lib/i18n/chunks/zh-CN-1.ts
+++ b/app/src/lib/i18n/chunks/zh-CN-1.ts
@@ -50,6 +50,7 @@ const zhCN1: TranslationMap = {
'common.showLess': '收起',
'common.submit': '提交',
'common.continue': '继续',
+ 'common.comingSoon': 'Coming Soon',
'settings.general': '通用',
'settings.featuresAndAI': '功能与 AI',
'settings.billingAndRewards': '账单与奖励',
@@ -421,6 +422,9 @@ const zhCN1: TranslationMap = {
'skills.tabs.composio': 'Composio',
'skills.tabs.channels': 'Channels',
'skills.tabs.mcp': 'MCP Servers',
+ 'skills.mcpComingSoon.title': 'MCP Servers',
+ 'skills.mcpComingSoon.description':
+ 'MCP server management is coming soon. This tab will be the home for discovering, connecting, and monitoring your MCP server integrations.',
'settings.about.connection': 'Connection',
'settings.about.connectionMode': 'Mode',
'settings.about.connectionModeLocal': 'Local',
diff --git a/app/src/lib/i18n/en.ts b/app/src/lib/i18n/en.ts
index e759f262e..5171606cc 100644
--- a/app/src/lib/i18n/en.ts
+++ b/app/src/lib/i18n/en.ts
@@ -52,6 +52,7 @@ const en: TranslationMap = {
'common.showLess': 'Show less',
'common.submit': 'Submit',
'common.continue': 'Continue',
+ 'common.comingSoon': 'Coming Soon',
// Settings Home
'settings.general': 'General',
@@ -1817,6 +1818,9 @@ const en: TranslationMap = {
'settings.ai.openAiCompat.setKey': 'Set key',
'settings.ai.openAiCompat.title': 'OpenAI-compatible endpoint',
'settings.ai.providerLabel': 'Provider',
+ 'skills.mcpComingSoon.title': 'MCP Servers',
+ 'skills.mcpComingSoon.description':
+ 'MCP server management is coming soon. This tab will be the home for discovering, connecting, and monitoring your MCP server integrations.',
'settings.ai.routing': 'Routing',
'settings.ai.routingCustom': 'Custom routing',
'settings.ai.routingDefault': 'Default',
diff --git a/app/src/pages/Skills.tsx b/app/src/pages/Skills.tsx
index 921fd9147..dfd8aa1c1 100644
--- a/app/src/pages/Skills.tsx
+++ b/app/src/pages/Skills.tsx
@@ -2,7 +2,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import ChannelSetupModal from '../components/channels/ChannelSetupModal';
-import McpServersTab from '../components/channels/mcp/McpServersTab';
import ComposioConnectModal from '../components/composio/ComposioConnectModal';
import {
composioToolkitMeta,
@@ -284,6 +283,37 @@ function ChannelTile({ def, status, icon, testId, onOpen }: ChannelTileProps) {
);
}
+function McpComingSoonPanel() {
+ const { t } = useT();
+ return (
+
+
+
+ {t('skills.mcpComingSoon.title')}
+
+
+ {t('skills.mcpComingSoon.description')}
+
+
+ {t('common.comingSoon')}
+
+
+ );
+}
+
// ─── Built-in skill definitions ────────────────────────────────────────────────
const BUILT_IN_SKILLS: Array<{
@@ -1035,7 +1065,7 @@ export default function Skills() {
{t('channels.mcp.description')}
-