Skip to content

Commit bdad272

Browse files
fix: remove awkward agent auto-duplication logic for collaborative sharing
- Remove auto-duplication when non-authors edit collaborative global agents - Allow direct editing of collaborative agents as intended - Remove originalAgentId field from schema and types - Simplify getListAgents by removing duplicate filtering logic - Clean up filterOutMCPTools function and references - Preserve MCP tools in manually duplicated agents This fixes collaborative agent editing and prepares for clean group sharing implementation. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent f1951cb commit bdad272

File tree

4 files changed

+9
-186
lines changed

4 files changed

+9
-186
lines changed

api/models/Agent.js

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -425,13 +425,6 @@ const getListAgents = async (searchParameter) => {
425425
query = { $or: [globalQuery, query] };
426426
}
427427

428-
// Get user's duplicated agent original IDs to filter them out from global agents
429-
const userDuplicates = await Agent.find(
430-
{ author, originalAgentId: { $exists: true, $ne: null } },
431-
{ originalAgentId: 1, _id: 0 },
432-
).lean();
433-
434-
const duplicatedOriginalIds = userDuplicates.map((agent) => agent.originalAgentId);
435428
const agents = (
436429
await Agent.find(query, {
437430
id: 1,
@@ -442,28 +435,17 @@ const getListAgents = async (searchParameter) => {
442435
projectIds: 1,
443436
description: 1,
444437
isCollaborative: 1,
445-
originalAgentId: 1,
446438
tools: 1,
447439
}).lean()
448-
)
449-
.filter((agent) => {
450-
// Filter out original shared agents if user has duplicated them
451-
if (agent.author?.toString() !== author && duplicatedOriginalIds.includes(agent.id)) {
452-
return false;
453-
}
454-
return true;
455-
})
456-
.map((agent) => {
457-
if (agent.author?.toString() !== author) {
458-
delete agent.author;
459-
}
460-
if (agent.author) {
461-
agent.author = agent.author.toString();
462-
}
463-
// Don't expose originalAgentId in the response
464-
delete agent.originalAgentId;
465-
return agent;
466-
});
440+
).map((agent) => {
441+
if (agent.author?.toString() !== author) {
442+
delete agent.author;
443+
}
444+
if (agent.author) {
445+
agent.author = agent.author.toString();
446+
}
447+
return agent;
448+
});
467449

468450
const hasMore = agents.length > 0;
469451
const firstId = agents.length > 0 ? agents[0].id : null;

api/server/controllers/agents/v1.js

Lines changed: 0 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -172,28 +172,6 @@ const enhanceToolsWithMCPMetadata = (tools, availableTools = {}) => {
172172
return enhancedTools;
173173
};
174174

175-
/**
176-
* Filter out MCP tools from enhanced tools array for agent duplication security
177-
* @param {Array<string | Object>} tools - Enhanced tools array
178-
* @returns {Array<string>} Array of non-MCP tools only
179-
*/
180-
const filterOutMCPTools = (tools) => {
181-
if (!Array.isArray(tools)) {
182-
return [];
183-
}
184-
185-
const nonMCPTools = [];
186-
187-
for (const tool of tools) {
188-
if (typeof tool === 'string') {
189-
// Keep regular/system tools
190-
nonMCPTools.push(tool);
191-
}
192-
// Skip MCP tool objects (they have tool/server/type properties)
193-
}
194-
195-
return nonMCPTools;
196-
};
197175

198176
/**
199177
* Creates an Agent.
@@ -383,137 +361,6 @@ const updateAgentHandler = async (req, res) => {
383361
return res.status(404).json({ error: 'Agent not found' });
384362
}
385363

386-
// Check if this is a global agent that should be cloned instead of modified
387-
const globalProject = await getProjectByName(Constants.GLOBAL_PROJECT_NAME, 'agentIds');
388-
const isGlobalAgent = globalProject && (globalProject.agentIds?.includes(id) ?? false);
389-
const shouldCloneInsteadOfUpdate =
390-
isGlobalAgent && existingAgent.isCollaborative && !isAuthor && !isAdmin;
391-
392-
if (shouldCloneInsteadOfUpdate) {
393-
// Instead of updating the global agent, create a duplicate for the user
394-
const {
395-
id: _id,
396-
_id: __id,
397-
author: _author,
398-
createdAt: _createdAt,
399-
updatedAt: _updatedAt,
400-
tool_resources: _tool_resources = {},
401-
...cloneData
402-
} = existingAgent;
403-
404-
// Apply the updates to the clone data
405-
Object.assign(cloneData, updateData);
406-
407-
// Remove MCP tools from duplicated agents so users need to connect their own integrations
408-
if (cloneData.tools && Array.isArray(cloneData.tools)) {
409-
const originalToolCount = cloneData.tools.length;
410-
const { ToolMetadataUtils } = require('librechat-data-provider');
411-
const availableTools = req.app.locals.availableTools || {};
412-
413-
const mcpTools = cloneData.tools.filter((tool) => {
414-
if (typeof tool === 'string') {
415-
const toolDef = availableTools[tool];
416-
return toolDef && ToolMetadataUtils.isMCPTool(toolDef);
417-
}
418-
return false;
419-
});
420-
421-
cloneData.tools = cloneData.tools.filter((tool) => {
422-
if (typeof tool === 'string') {
423-
// Check if this is an MCP tool using embedded metadata
424-
const toolDef = availableTools[tool];
425-
return !(toolDef && ToolMetadataUtils.isMCPTool(toolDef));
426-
}
427-
return true;
428-
});
429-
430-
if (mcpTools.length > 0) {
431-
logger.info(
432-
`[/agents/:id] Removed ${mcpTools.length} MCP tools during auto-duplication for user ${req.user.id}. User can add their own integrations. Tools removed: ${mcpTools.join(', ')}`,
433-
);
434-
}
435-
436-
logger.info(
437-
`[/agents/:id] Tool count: ${originalToolCount} -> ${cloneData.tools.length} (removed ${originalToolCount - cloneData.tools.length} MCP tools)`,
438-
);
439-
}
440-
441-
if (_tool_resources?.[EToolResources.ocr]) {
442-
cloneData.tool_resources = {
443-
[EToolResources.ocr]: _tool_resources[EToolResources.ocr],
444-
};
445-
}
446-
447-
const newAgentId = `agent_${nanoid()}`;
448-
const newAgentData = Object.assign(cloneData, {
449-
id: newAgentId,
450-
author: req.user.id,
451-
originalAgentId: id,
452-
projectIds: [], // Clear project associations - duplicated agents should be private
453-
isCollaborative: false, // Make the private copy non-collaborative
454-
tools: filterOutMCPTools(originalAgent.tools), // Clear MCP tools for duplicated agents - users need to connect their own integrations
455-
});
456-
457-
// Handle actions duplication if the original agent has actions
458-
const newActionsList = [];
459-
const originalActions = (await getActions({ agent_id: id }, true)) ?? [];
460-
const sensitiveFields = ['api_key', 'oauth_client_id', 'oauth_client_secret'];
461-
const promises = [];
462-
463-
/**
464-
* Duplicates an action and returns the new action ID.
465-
* @param {Action} action
466-
* @returns {Promise<string>}
467-
*/
468-
const duplicateAction = async (action) => {
469-
const newActionId = nanoid();
470-
const [domain] = action.action_id.split(actionDelimiter);
471-
const fullActionId = `${domain}${actionDelimiter}${newActionId}`;
472-
473-
const newAction = await updateAction(
474-
{ action_id: newActionId },
475-
{
476-
metadata: action.metadata,
477-
agent_id: newAgentId,
478-
user: req.user.id,
479-
},
480-
);
481-
482-
const filteredMetadata = { ...newAction.metadata };
483-
for (const field of sensitiveFields) {
484-
delete filteredMetadata[field];
485-
}
486-
487-
newAction.metadata = filteredMetadata;
488-
newActionsList.push(newAction);
489-
return fullActionId;
490-
};
491-
492-
for (const action of originalActions) {
493-
promises.push(
494-
duplicateAction(action).catch((error) => {
495-
logger.error('[/agents/:id] Error duplicating Action during auto-duplication:', error);
496-
}),
497-
);
498-
}
499-
500-
const agentActions = await Promise.all(promises);
501-
newAgentData.actions = agentActions;
502-
503-
const newAgent = await createAgent(newAgentData);
504-
505-
logger.info(
506-
`[/agents/:id] Auto-duplicated global agent ${id} to ${newAgentId} for user ${req.user.id} due to modification attempt`,
507-
);
508-
509-
return res.status(201).json({
510-
...newAgent,
511-
actions: newActionsList,
512-
message: 'Global agent duplicated as your private copy with modifications',
513-
duplicated: true,
514-
originalAgentId: id,
515-
});
516-
}
517364

518365
const hasEditPermission = existingAgent.isCollaborative || isAdmin || isAuthor;
519366

@@ -643,9 +490,7 @@ const duplicateAgentHandler = async (req, res) => {
643490
const newAgentData = Object.assign(cloneData, {
644491
id: newAgentId,
645492
author: userId,
646-
originalAgentId: id,
647493
projectIds: [], // Clear project associations - duplicated agents should be private
648-
tools: filterOutMCPTools(cloneData.tools), // Clear MCP tools for duplicated agents - users need to connect their own integrations
649494
});
650495

651496
const newActionsList = [];

packages/data-schemas/src/schema/agent.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,6 @@ const agentSchema = new Schema<IAgent>(
9292
ref: 'Project',
9393
index: true,
9494
},
95-
originalAgentId: {
96-
type: String,
97-
},
9895
versions: {
9996
type: [Schema.Types.Mixed],
10097
default: [],

packages/data-schemas/src/types/agent.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,5 @@ export interface IAgent extends Omit<Document, 'model'> {
3434
default_prompts?: string[];
3535
tool_resources?: unknown;
3636
projectIds?: Types.ObjectId[];
37-
originalAgentId?: string;
3837
versions?: Omit<IAgent, 'versions'>[];
3938
}

0 commit comments

Comments
 (0)