From a7bc61b69ba101cee0e1b2bb7defb04010a74f20 Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Mon, 15 Jun 2026 08:51:55 -0500 Subject: [PATCH] fix(mcp): filter private comments for portal details --- .../lib/server/mcp/__tests__/handler.test.ts | 60 +++++++++++++++++++ apps/web/src/lib/server/mcp/tools.ts | 6 +- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/apps/web/src/lib/server/mcp/__tests__/handler.test.ts b/apps/web/src/lib/server/mcp/__tests__/handler.test.ts index 27dae6b29..f521eb41b 100644 --- a/apps/web/src/lib/server/mcp/__tests__/handler.test.ts +++ b/apps/web/src/lib/server/mcp/__tests__/handler.test.ts @@ -904,6 +904,66 @@ describe('MCP HTTP Handler', () => { expect(text.comments).toEqual([]) }) + it('should exclude private comments from OAuth portal user post details', async () => { + const { getDeveloperConfig } = await import('@/lib/server/domains/settings/settings.service') + const { getCommentsWithReplies } = await import('@/lib/server/domains/posts/post.query') + vi.mocked(getDeveloperConfig) + .mockResolvedValueOnce({ + mcpEnabled: true, + mcpPortalAccessEnabled: true, + }) + .mockResolvedValueOnce({ + mcpEnabled: true, + mcpPortalAccessEnabled: true, + }) + await setupValidOAuth({ role: 'user', scopes: ['read:feedback'] }) + + const { handleMcpRequest } = await import('../handler') + await handleMcpRequest( + oauthRequest( + jsonRpcRequest('initialize', { + protocolVersion: '2025-03-26', + capabilities: {}, + clientInfo: { name: 'test', version: '1.0' }, + }) + ) + ) + await setupValidOAuth({ role: 'user', scopes: ['read:feedback'] }) + + const response = await handleMcpRequest( + oauthRequest( + jsonRpcRequest('tools/call', { + name: 'get_details', + arguments: { id: 'post_test' }, + }) + ) + ) + + expect(response.status).toBe(200) + expect(vi.mocked(getCommentsWithReplies)).toHaveBeenCalledWith('post_test', undefined, { + includePrivate: false, + }) + }) + + it('should include private comments in team member post details', async () => { + const { getCommentsWithReplies } = await import('@/lib/server/domains/posts/post.query') + const handleMcpRequest = await initializeSession() + + const response = await handleMcpRequest( + mcpRequest( + jsonRpcRequest('tools/call', { + name: 'get_details', + arguments: { id: 'post_test' }, + }) + ) + ) + + expect(response.status).toBe(200) + expect(vi.mocked(getCommentsWithReplies)).toHaveBeenCalledWith('post_test', undefined, { + includePrivate: true, + }) + }) + // ── get_details tool (changelog) ──────────────────────────────────────── it('should handle tools/call for get_details with changelog TypeID', async () => { diff --git a/apps/web/src/lib/server/mcp/tools.ts b/apps/web/src/lib/server/mcp/tools.ts index e834a7e09..e61e44c34 100644 --- a/apps/web/src/lib/server/mcp/tools.ts +++ b/apps/web/src/lib/server/mcp/tools.ts @@ -2043,14 +2043,14 @@ async function searchArticles(args: SearchArgs): Promise { // ============================================================================ async function getPostDetails(postId: PostId, auth: McpAuthContext): Promise { + const includeTeamOnlyFields = isTeamMember(auth.role) + const [post, comments, mergedPosts] = await Promise.all([ getPostWithDetails(postId), - getCommentsWithReplies(postId), + getCommentsWithReplies(postId, undefined, { includePrivate: includeTeamOnlyFields }), getMergedPosts(postId), ]) - const includeTeamOnlyFields = isTeamMember(auth.role) - return jsonResult({ id: post.id, title: post.title,