Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ui/user/src/lib/components/chat/ChatSidebarMcpServer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
showDeleteConfirm = false;
closeSidebarConfig(layout);
}

async function refreshProjectMcps() {
closeAll(layout);
projectMcps.items = await ChatService.listProjectMCPs(project.assistantID, project.id);
}
</script>

<div class="flex h-fit w-full justify-center bg-gray-50 dark:bg-black">
Expand Down Expand Up @@ -74,6 +79,7 @@
onProjectToolsUpdate={() => {
closeAll(layout);
}}
onUpdate={refreshProjectMcps}
{project}
/>
</div>
Expand Down
41 changes: 21 additions & 20 deletions ui/user/src/lib/components/chat/McpServerSetup.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -243,26 +243,27 @@
>
{#snippet connectedServerCardAction(d: ConnectedServer)}
{@const requiresUpdate = requiresUserUpdate(d)}
<button
disabled={requiresUpdate}
class={twMerge(
'icon-button hover:bg-surface1 dark:hover:bg-surface2 size-6 min-h-auto min-w-auto flex-shrink-0 p-1 hover:text-blue-500',
requiresUpdate &&
'hover:text-initial cursor-not-allowed opacity-50 hover:bg-transparent dark:hover:bg-transparent'
)}
onclick={() => {
if (requiresUpdate) return;
setupProjectMcp(d);
}}
use:tooltip={{
text: 'Add To Chat',
disablePortal: true,
placement: 'top-end',
classes: ['w-26.5']
}}
>
<Import class="size-4" />
</button>
{#if !requiresUpdate}
<button
class={twMerge(
'icon-button hover:bg-surface1 dark:hover:bg-surface2 size-6 min-h-auto min-w-auto flex-shrink-0 p-1 hover:text-blue-500',
requiresUpdate &&
'hover:text-initial cursor-not-allowed opacity-50 hover:bg-transparent dark:hover:bg-transparent'
)}
onclick={() => {
if (requiresUpdate) return;
setupProjectMcp(d);
}}
use:tooltip={{
text: 'Add To Chat',
disablePortal: true,
placement: 'top-end',
classes: ['w-26.5']
}}
>
<Import class="size-4" />
</button>
{/if}
{/snippet}
</MyMcpServers>
</div>
Expand Down
10 changes: 9 additions & 1 deletion ui/user/src/lib/components/edit/McpServers.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@
}

function shouldShowWarning(mcp: (typeof projectMCPs.items)[0]) {
if (mcp.needsURL) {
return true;
}

if (typeof mcp.configured === 'boolean' && mcp.configured === false) {
return true;
}

if (typeof mcp.authenticated === 'boolean') {
return !mcp.authenticated;
}
Expand Down Expand Up @@ -111,7 +119,7 @@
{mcpServer.alias || mcpServer.name || DEFAULT_CUSTOM_SERVER_NAME}
{#if shouldShowWarning(mcpServer)}
<span
class="ml-1"
class="ml-2"
use:tooltip={mcpServer.authenticated
? 'Configuration Required'
: 'Authentication Required'}
Expand Down
116 changes: 111 additions & 5 deletions ui/user/src/lib/components/mcp/McpServerInfoAndTools.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
<script lang="ts">
import type { MCPCatalogEntry, MCPCatalogServer, Project, ProjectMCP } from '$lib/services';
import {
ChatService,
type MCPCatalogEntry,
type MCPCatalogServer,
type Project,
type ProjectMCP
} from '$lib/services';
import { twMerge } from 'tailwind-merge';
import McpServerInfo from './McpServerInfo.svelte';
import McpServerTools from './McpServerTools.svelte';
import McpOauth from './McpOauth.svelte';
import { AlertTriangle } from 'lucide-svelte';
import CatalogConfigureForm, { type LaunchFormData } from './CatalogConfigureForm.svelte';
import { convertEnvHeadersToRecord } from '$lib/services/chat/mcp';

interface Props {
entry?: MCPCatalogEntry | MCPCatalogServer | ProjectMCP;
Expand All @@ -13,6 +22,8 @@
project?: Project;
view?: 'overview' | 'tools';
onProjectToolsUpdate?: (selected: string[]) => void;
onUpdate?: () => void;
onEditConfiguration?: () => void;
}

let {
Expand All @@ -22,10 +33,11 @@
onAuthenticate,
project,
view = 'overview',
onProjectToolsUpdate
onProjectToolsUpdate,
onUpdate,
onEditConfiguration
}: Props = $props();
let selected = $state<string>(view);

const tabs = [
{ label: 'Overview', view: 'overview' },
{ label: 'Tools', view: 'tools' }
Expand All @@ -34,6 +46,60 @@
$effect(() => {
selected = view;
});

let configDialog = $state<ReturnType<typeof CatalogConfigureForm>>();
let configureForm = $state<LaunchFormData>();
let error = $state<string>();
let saving = $state(false);
let configuringServer = $state<MCPCatalogServer>();

async function handleInitConfigureForm() {
if (!entry) return;
if ('mcpID' in entry) {
const response = await ChatService.getSingleOrRemoteMcpServer(entry.mcpID);

let values: Record<string, string>;
try {
values = await ChatService.revealSingleOrRemoteMcpServer(response.id);
} catch (error) {
if (error instanceof Error && !error.message.includes('404')) {
console.error('Failed to reveal user server values due to unexpected error', error);
}
values = {};
}
configuringServer = response;
configureForm = {
envs: response.manifest.env?.map((env) => ({
...env,
value: values[env.key] ?? ''
})),
headers: response.manifest.remoteConfig?.headers?.map((header) => ({
...header,
value: values[header.key] ?? ''
})),
url: response.manifest.remoteConfig?.url
};
configDialog?.open();
}
}

async function handleConfigureFormUpdate() {
if (!configuringServer || !configureForm) return;
try {
if (configuringServer.manifest.runtime === 'remote' && configureForm.url) {
await ChatService.updateRemoteMcpServerUrl(configuringServer.id, configureForm.url.trim());
}

const secretValues = convertEnvHeadersToRecord(configureForm.envs, configureForm.headers);
await ChatService.configureSingleOrRemoteMcpServer(configuringServer.id, secretValues);

configDialog?.close();
onUpdate?.();
} catch (error) {
console.error('Error during configuration:', error);
configDialog?.close();
}
}
</script>

<div class="flex h-full w-full flex-col gap-4">
Expand Down Expand Up @@ -65,8 +131,37 @@
descriptionPlaceholder="Add a description for this MCP server in the Configuration tab"
>
{#snippet preContent()}
{#if project}
<McpOauth {entry} {onAuthenticate} {project} />
{#if 'configured' in entry && typeof entry.configured === 'boolean' && entry.configured === false}
<div class="notification-alert mb-4 flex gap-2">
<div class="flex grow flex-col gap-2">
<div class="flex items-center gap-2">
<AlertTriangle class="size-6 flex-shrink-0 self-start text-yellow-500" />
<p class="my-0.5 flex flex-col text-sm font-semibold">Update Required</p>
</div>
<span class="text-sm font-light break-all">
Due to a recent update in the server, an update on this connector's
configuration is required to continue using this server.
</span>
</div>
<div class="flex flex-shrink-0 items-center">
<button
class="button-primary text-sm"
onclick={() => {
if (onEditConfiguration) {
onEditConfiguration();
} else {
handleInitConfigureForm();
}
}}
>
Edit Configuration
</button>
</div>
</div>
{:else if project}
<div class="mb-4 w-full">
<McpOauth {entry} {onAuthenticate} {project} />
</div>
{/if}
{/snippet}
</McpServerInfo>
Expand All @@ -76,3 +171,14 @@
{/if}
</div>
</div>

<CatalogConfigureForm
bind:this={configDialog}
bind:form={configureForm}
{error}
icon={configuringServer?.manifest.icon}
name={configuringServer?.alias || configuringServer?.manifest.name}
onSave={handleConfigureFormUpdate}
submitText="Update"
loading={saving}
/>
73 changes: 44 additions & 29 deletions ui/user/src/lib/components/mcp/MyMcpServers.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,14 @@
secretValues
);

const updatedServer = await ChatService.getSingleOrRemoteMcpServer(
selectedEntryOrServer.server.id
);
selectedEntryOrServer = {
...selectedEntryOrServer,
server: updatedServer
} as ConnectedServer;

configDialog?.close();
onUpdateConfigure?.();
} else {
Expand All @@ -442,6 +450,36 @@
document.getElementsByTagName('main')[0].scrollTo({ top: 0, behavior: 'instant' });
}

async function handleEditConfiguration(connectedServer: ConnectedServer) {
if (!connectedServer?.server) {
console.error('No user configured server for this entry found');
return;
}
let values: Record<string, string>;
try {
values = await ChatService.revealSingleOrRemoteMcpServer(connectedServer.server.id);
} catch (error) {
if (error instanceof Error && !error.message.includes('404')) {
console.error('Failed to reveal user server values due to unexpected error', error);
}
values = {};
}
selectedEntryOrServer = connectedServer;
configureForm = {
envs: connectedServer.server.manifest.env?.map((env) => ({
...env,
value: values[env.key] ?? ''
})),
headers: connectedServer.server.manifest.remoteConfig?.headers?.map((header) => ({
...header,
value: values[header.key] ?? ''
})),
url: connectedServer.server.manifest.remoteConfig?.url,
hostname: connectedServer.parent?.manifest.remoteConfig?.hostname
};
configDialog?.open();
}

const duration = PAGE_TRANSITION_DURATION;
</script>

Expand Down Expand Up @@ -733,6 +771,11 @@
parent={selectedEntryOrServer && 'parent' in selectedEntryOrServer
? selectedEntryOrServer.parent
: undefined}
onEditConfiguration={() => {
if (selectedEntryOrServer && 'parent' in selectedEntryOrServer) {
handleEditConfiguration(selectedEntryOrServer);
}
}}
/>
{/if}
</div>
Expand All @@ -747,35 +790,7 @@
'menu-button',
requiresUpdate && 'bg-yellow-500/10 text-yellow-500 hover:bg-yellow-500/30'
)}
onclick={async () => {
if (!connectedServer?.server) {
console.error('No user configured server for this entry found');
return;
}
let values: Record<string, string>;
try {
values = await ChatService.revealSingleOrRemoteMcpServer(connectedServer.server.id);
} catch (error) {
if (error instanceof Error && !error.message.includes('404')) {
console.error('Failed to reveal user server values due to unexpected error', error);
}
values = {};
}
selectedEntryOrServer = connectedServer;
configureForm = {
envs: connectedServer.server.manifest.env?.map((env) => ({
...env,
value: values[env.key] ?? ''
})),
headers: connectedServer.server.manifest.remoteConfig?.headers?.map((header) => ({
...header,
value: values[header.key] ?? ''
})),
url: connectedServer.server.manifest.remoteConfig?.url,
hostname: connectedServer.parent?.manifest.remoteConfig?.hostname
};
configDialog?.open();
}}
onclick={() => handleEditConfiguration(connectedServer)}
>
Edit Configuration
</button>
Expand Down
2 changes: 2 additions & 0 deletions ui/user/src/lib/context/projectMcps.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const Key = Symbol('mcps');
export type ProjectMcpItem = ProjectMCP & {
oauthURL?: string;
authenticated?: boolean;
configured?: boolean;
needsURL?: boolean;
};

export interface ProjectMCPContext {
Expand Down
3 changes: 3 additions & 0 deletions ui/user/src/lib/services/chat/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,9 @@ export interface ProjectMCP {
name?: string;
description?: string;
icon?: string;
configured?: boolean;
needsUpdate?: boolean;
needsURL?: boolean;
}

export interface Credential {
Expand Down