🔀 fix: MCP Improvements, Auto-Save Drafts, Artifact Markup (#7040)

* feat: Update MCP tool creation to use lowercase provider name

* refactor: handle MCP image output edge cases where tool outputs must contain string responses

* feat: Drop 'anyOf' and 'oneOf' fields from JSON schema conversion

* feat: Transform 'oneOf' and 'anyOf' fields to Zod union in JSON schema conversion

* fix: artifactPlugin to replace textDirective with expected text, closes #7029

* fix: auto-save functionality to handle conversation transitions from pending drafts, closes #7027

* refactor: improve async handling during user disconnection process

* fix: use correct user ID variable for MCP tool calling

* fix: improve handling of pending drafts in auto-save functionality

* fix: add support for additional model names in getValueKey function

* fix: reset form values on agent deletion when no agents remain
This commit is contained in:
Danny Avila 2025-04-23 18:56:06 -04:00 committed by GitHub
parent 150116eefe
commit 7f1d01c35a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 856 additions and 73 deletions

View file

@ -193,9 +193,11 @@ export class MCPManager {
`[MCP][User: ${userId}] User idle for too long. Disconnecting all connections.`,
);
// Disconnect all user connections
await this.disconnectUserConnections(userId).catch((err) =>
this.logger.error(`[MCP][User: ${userId}] Error disconnecting idle connections:`, err),
);
try {
await this.disconnectUserConnections(userId);
} catch (err) {
this.logger.error(`[MCP][User: ${userId}] Error disconnecting idle connections:`, err);
}
connection = undefined; // Force creation of a new connection
} else if (connection) {
if (connection.isConnected()) {
@ -302,18 +304,20 @@ export class MCPManager {
/** Disconnects and removes all connections for a specific user */
public async disconnectUserConnections(userId: string): Promise<void> {
const userMap = this.userConnections.get(userId);
const disconnectPromises: Promise<void>[] = [];
if (userMap) {
this.logger.info(`[MCP][User: ${userId}] Disconnecting all servers...`);
const disconnectPromises = Array.from(userMap.keys()).map(async (serverName) => {
try {
await this.disconnectUserConnection(userId, serverName);
} catch (error) {
this.logger.error(
`[MCP][User: ${userId}][${serverName}] Error during disconnection:`,
error,
);
}
});
const userServers = Array.from(userMap.keys());
for (const serverName of userServers) {
disconnectPromises.push(
this.disconnectUserConnection(userId, serverName).catch((error) => {
this.logger.error(
`[MCP][User: ${userId}][${serverName}] Error during disconnection:`,
error,
);
}),
);
}
await Promise.allSettled(disconnectPromises);
// Ensure user activity timestamp is removed
this.userLastActivity.delete(userId);

View file

@ -1,5 +1,6 @@
import type * as t from './types/mcp';
const RECOGNIZED_PROVIDERS = new Set(['google', 'anthropic', 'openAI']);
const RECOGNIZED_PROVIDERS = new Set(['google', 'anthropic', 'openai', 'openrouter', 'xai', 'deepseek', 'ollama']);
const CONTENT_ARRAY_PROVIDERS = new Set(['google', 'anthropic', 'openai']);
const imageFormatters: Record<string, undefined | t.ImageFormatter> = {
// google: (item) => ({
@ -76,12 +77,12 @@ function parseAsString(result: t.MCPToolCallResponse): string {
*
* @param {t.MCPToolCallResponse} result - The MCPToolCallResponse object
* @param {string} provider - The provider name (google, anthropic, openai)
* @returns {t.FormattedToolResponse} Tuple of content and image_urls
* @returns {t.FormattedContentResult} Tuple of content and image_urls
*/
export function formatToolContent(
result: t.MCPToolCallResponse,
provider: t.Provider,
): t.FormattedToolResponse {
): t.FormattedContentResult {
if (!RECOGNIZED_PROVIDERS.has(provider)) {
return [parseAsString(result), undefined];
}
@ -110,7 +111,7 @@ export function formatToolContent(
if (!isImageContent(item)) {
return;
}
if (currentTextBlock) {
if (CONTENT_ARRAY_PROVIDERS.has(provider) && currentTextBlock) {
formattedContent.push({ type: 'text', text: currentTextBlock });
currentTextBlock = '';
}
@ -149,9 +150,14 @@ export function formatToolContent(
}
}
if (currentTextBlock) {
if (CONTENT_ARRAY_PROVIDERS.has(provider) && currentTextBlock) {
formattedContent.push({ type: 'text', text: currentTextBlock });
}
return [formattedContent, imageUrls.length ? { content: imageUrls } : undefined];
const artifacts = imageUrls.length ? { content: imageUrls } : undefined;
if (CONTENT_ARRAY_PROVIDERS.has(provider)) {
return [formattedContent, artifacts];
}
return [currentTextBlock, artifacts];
}

View file

@ -84,6 +84,8 @@ export type FormattedContent =
};
};
export type FormattedContentResult = [string | FormattedContent[], undefined | { content: FormattedContent[] }];
export type ImageFormatter = (item: ImageContent) => FormattedContent;
export type FormattedToolResponse = [