Skip to content

Commit ea6f3f2

Browse files
authored
fix(web): fire analytics event when a workspace MCP connector is removed (#1257)
1 parent 4eae8b0 commit ea6f3f2

3 files changed

Lines changed: 76 additions & 10 deletions

File tree

packages/web/src/ee/features/chat/mcp/actions.test.ts

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const mocks = vi.hoisted(() => ({
1717
captureEvent: vi.fn(),
1818
unsafePrisma: {
1919
mcpServer: {
20-
deleteMany: vi.fn(),
20+
delete: vi.fn(),
2121
findFirst: vi.fn(),
2222
},
2323
userMcpServer: {
@@ -334,20 +334,55 @@ describe('createStaticOAuthMcpServer', () => {
334334
});
335335

336336
describe('deleteMcpServer', () => {
337-
test('owners delete through the narrowly scoped unsafe client', async () => {
337+
test('owners delete through the narrowly scoped unsafe client and track the removal', async () => {
338338
setAuthContext(OrgRole.OWNER);
339-
mocks.unsafePrisma.mcpServer.deleteMany.mockResolvedValue({ count: 1 });
339+
mocks.unsafePrisma.mcpServer.findFirst.mockResolvedValue({
340+
id: 'server-1',
341+
serverUrl: 'https://mcp.linear.app/mcp',
342+
clientInfoSource: McpServerClientInfoSource.DYNAMIC,
343+
});
344+
mocks.unsafePrisma.mcpServer.delete.mockResolvedValue({ id: 'server-1' });
340345

341346
await expect(deleteMcpServer('server-1')).resolves.toEqual({ success: true });
342-
expect(mocks.unsafePrisma.mcpServer.deleteMany).toHaveBeenCalledWith({
347+
expect(mocks.unsafePrisma.mcpServer.findFirst).toHaveBeenCalledWith({
343348
where: {
344349
id: 'server-1',
345350
orgId: 1,
346351
},
352+
select: {
353+
id: true,
354+
serverUrl: true,
355+
clientInfoSource: true,
356+
},
357+
});
358+
expect(mocks.unsafePrisma.mcpServer.delete).toHaveBeenCalledWith({
359+
where: {
360+
id: 'server-1',
361+
},
362+
});
363+
expect(mocks.captureEvent).toHaveBeenCalledWith('ask_mcp_connector_removed', {
364+
source: 'sourcebot-web-client',
365+
entryPoint: 'workspace_settings',
366+
serverId: 'server-1',
367+
serverUrl: 'https://mcp.linear.app/mcp',
368+
authMode: 'dynamic',
347369
});
348370
expect(mocks.hasEntitlement).not.toHaveBeenCalled();
349371
});
350372

373+
test('returns not found and tracks nothing when the connector does not exist', async () => {
374+
setAuthContext(OrgRole.OWNER);
375+
mocks.unsafePrisma.mcpServer.findFirst.mockResolvedValue(null);
376+
377+
const result = await deleteMcpServer('server-1');
378+
379+
expect(result).toMatchObject({
380+
errorCode: ErrorCode.MCP_SERVER_NOT_FOUND,
381+
});
382+
expect(mocks.unsafePrisma.mcpServer.delete).not.toHaveBeenCalled();
383+
expect(mocks.captureEvent).not.toHaveBeenCalled();
384+
});
385+
351386
test('members cannot delete org MCP servers', async () => {
352387
setAuthContext(OrgRole.MEMBER);
353388

@@ -356,21 +391,26 @@ describe('deleteMcpServer', () => {
356391
expect(result).toMatchObject({
357392
errorCode: ErrorCode.INSUFFICIENT_PERMISSIONS,
358393
});
359-
expect(mocks.unsafePrisma.mcpServer.deleteMany).not.toHaveBeenCalled();
394+
expect(mocks.unsafePrisma.mcpServer.findFirst).not.toHaveBeenCalled();
395+
expect(mocks.unsafePrisma.mcpServer.delete).not.toHaveBeenCalled();
360396
});
361397

362398
test('owners can delete org MCP servers when Ask Agent is unavailable', async () => {
363399
setAuthContext(OrgRole.OWNER);
364400
mocks.hasEntitlement.mockResolvedValue(false);
365-
mocks.unsafePrisma.mcpServer.deleteMany.mockResolvedValue({ count: 1 });
401+
mocks.unsafePrisma.mcpServer.findFirst.mockResolvedValue({
402+
id: 'server-1',
403+
serverUrl: 'https://mcp.linear.app/mcp',
404+
clientInfoSource: McpServerClientInfoSource.DYNAMIC,
405+
});
406+
mocks.unsafePrisma.mcpServer.delete.mockResolvedValue({ id: 'server-1' });
366407

367408
await expect(deleteMcpServer('server-1')).resolves.toEqual({ success: true });
368409

369410
expect(mocks.hasEntitlement).not.toHaveBeenCalled();
370-
expect(mocks.unsafePrisma.mcpServer.deleteMany).toHaveBeenCalledWith({
411+
expect(mocks.unsafePrisma.mcpServer.delete).toHaveBeenCalledWith({
371412
where: {
372413
id: 'server-1',
373-
orgId: 1,
374414
},
375415
});
376416
});

packages/web/src/ee/features/chat/mcp/actions.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,21 +291,40 @@ export const createMcpServer = async (name: string, serverUrl: string) => sew(()
291291
export const deleteMcpServer = async (serverId: string) => sew(() =>
292292
withAuth(async ({ org, role }) =>
293293
withMinimumOrgRole(role, OrgRole.OWNER, async () => {
294-
const result = await __unsafePrisma.mcpServer.deleteMany({
294+
const server = await __unsafePrisma.mcpServer.findFirst({
295295
where: {
296296
id: serverId,
297297
orgId: org.id,
298298
},
299+
select: {
300+
id: true,
301+
serverUrl: true,
302+
clientInfoSource: true,
303+
},
299304
});
300305

301-
if (result.count === 0) {
306+
if (!server) {
302307
return {
303308
statusCode: StatusCodes.NOT_FOUND,
304309
errorCode: ErrorCode.MCP_SERVER_NOT_FOUND,
305310
message: 'Connector not found',
306311
} satisfies ServiceError;
307312
}
308313

314+
await __unsafePrisma.mcpServer.delete({
315+
where: {
316+
id: server.id,
317+
},
318+
});
319+
320+
void captureEvent('ask_mcp_connector_removed', {
321+
source: 'sourcebot-web-client',
322+
entryPoint: 'workspace_settings',
323+
serverId: server.id,
324+
serverUrl: server.serverUrl,
325+
authMode: getMcpAuthMode(server.clientInfoSource),
326+
});
327+
309328
return { success: true };
310329
})));
311330

packages/web/src/lib/posthogEvents.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,13 @@ export type PosthogEventMap = {
234234
serverUrl: string,
235235
authMode: McpConnectorAuthMode,
236236
},
237+
ask_mcp_connector_removed: {
238+
source: SourcebotWebClientSource,
239+
entryPoint: 'workspace_settings',
240+
serverId: string,
241+
serverUrl: string,
242+
authMode: McpConnectorAuthMode,
243+
},
237244
ask_mcp_connector_connection_started: {
238245
source: SourcebotWebClientSource,
239246
entryPoint: McpConnectorEntryPoint,

0 commit comments

Comments
 (0)