Skip to content

feat: surface slack mcp oauth logins#24569

Draft
notgiorgi wants to merge 1 commit into
mainfrom
gio/prod-7877-slack-mcp-oauth-logins
Draft

feat: surface slack mcp oauth logins#24569
notgiorgi wants to merge 1 commit into
mainfrom
gio/prod-7877-slack-mcp-oauth-logins

Conversation

@notgiorgi

@notgiorgi notgiorgi commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Description

  • Post Slack ephemeral login prompts for unavailable OAuth MCP servers.
  • Keep Slack-started MCP OAuth credentials in not_connected while preserving OAuth callback state.
  • Keep notice posting off the response path.
  • Cover Slack notice filtering and default OAuth start behavior.

Closes: PROD-7877

@linear-code

linear-code Bot commented Jun 22, 2026

Copy link
Copy Markdown

PROD-7877

notgiorgi commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

🧪 Test Selection

✅ Tests that will run

Test Description
Preview Environment Deploys a preview environment for testing
Backend API Tests Runs Vitest API tests

⏭️ Tests skipped (no relevant file changes detected)

Test How to trigger manually
Frontend E2E Tests Add test-frontend to PR description
Timezone Tests Add test-timezone to PR description
CLI Tests Add test-cli to PR description

Tip: Add test-all to your PR description to run all tests.

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

Your preview environment pr-24569 has been deployed with errors.

Preview environment endpoints are available at:

@github-actions

Copy link
Copy Markdown

Preview Environment

🌐 URL: https://lightdash-preview-pr-24569.lightdash.okteto.dev

📋 Logs: View in GCP Console

🔧 SSH: ./scripts/okteto-ssh.sh 24569

@notgiorgi notgiorgi force-pushed the gio/prod-7877-slack-mcp-oauth-logins branch from c9d92a6 to be66710 Compare June 22, 2026 15:11
@notgiorgi notgiorgi marked this pull request as ready for review June 22, 2026 15:20
Comment on lines 45 to +228
@@ -109,4 +157,73 @@ describe('AiAgentService :eyes: ack reaction lifecycle', () => {
service.replyToSlackPrompt('prompt-1'),
).resolves.toBeUndefined();
});

it('posts one MCP OAuth login ephemeral per unavailable OAuth server', async () => {
const { service, slackClient, webClient } = buildService();
const startOAuth = jest
.spyOn(service, 'startMcpOAuthConnection')
.mockImplementation(
async (_user, _projectUuid, mcpServerUuid) =>
`https://oauth.example/${mcpServerUuid}`,
);

await postMcpOAuthLoginMessages(service, {
mcpServers: [
{
uuid: 'mcp-1',
name: 'Linear',
authType: 'oauth',
},
{
uuid: 'mcp-2',
name: 'GitHub',
authType: 'oauth',
},
{
uuid: 'mcp-3',
name: 'Docs',
authType: 'bearer',
},
],
unavailableMcpServers: [
{
serverUuid: 'mcp-1',
serverName: 'Linear',
message: 'Unauthorized',
status: 'not_connected',
},
{
serverUuid: 'mcp-2',
serverName: 'GitHub',
message: 'Unauthorized',
status: 'not_connected',
},
{
serverUuid: 'mcp-3',
serverName: 'Docs',
message: 'Broken',
status: 'error',
},
],
});

expect(slackClient.getWebClient).toHaveBeenCalledWith('org-1');
expect(startOAuth).toHaveBeenCalledTimes(2);
expect(webClient.chat.postEphemeral).toHaveBeenCalledTimes(2);
expect(webClient.chat.postEphemeral).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
channel: 'C123',
user: 'U123',
thread_ts: '1700000000.000000',
text: 'Linear MCP needs you to log in before I can use it.',
}),
);
expect(webClient.chat.postEphemeral).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
text: 'GitHub MCP needs you to log in before I can use it.',
}),
);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test directly invokes a private method (postSlackMcpOAuthLoginMessages) via type-casting and asserts on Slack client call counts and arguments. Per the testing guidelines: 'Don't write unit tests that test implementation details' — bad tests include 'database calls, implementation details, data transformation'. Testing a private method's internal Slack API interactions is an implementation detail test. The test should instead cover the business logic (e.g., the filtering/deduplication logic in getSlackMcpOAuthLoginServers) in isolation, or be restructured to test observable outcomes rather than internal wiring.

Suggested change
const getSlackMcpOAuthLoginServers = (
mcpServers: { uuid: string; name: string; authType: string }[],
unavailableMcpServers: {
serverUuid: string;
serverName: string;
message: string;
status: string;
}[],
) => {
const unavailableOAuthServerUuids = new Set(
unavailableMcpServers
.filter((s) => s.status === 'not_connected')
.map((s) => s.serverUuid),
);
return mcpServers.filter(
(s) =>
s.authType === 'oauth' &&
unavailableOAuthServerUuids.has(s.uuid),
);
};
describe('AiAgentService :eyes: ack reaction lifecycle', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('returns only unavailable OAuth servers that need login (filters out bearer and error-status servers)', () => {
const mcpServers = [
{ uuid: 'mcp-1', name: 'Linear', authType: 'oauth' },
{ uuid: 'mcp-2', name: 'GitHub', authType: 'oauth' },
{ uuid: 'mcp-3', name: 'Docs', authType: 'bearer' },
];
const unavailableMcpServers = [
{
serverUuid: 'mcp-1',
serverName: 'Linear',
message: 'Unauthorized',
status: 'not_connected',
},
{
serverUuid: 'mcp-2',
serverName: 'GitHub',
message: 'Unauthorized',
status: 'not_connected',
},
{
serverUuid: 'mcp-3',
serverName: 'Docs',
message: 'Broken',
status: 'error',
},
];
const result = getSlackMcpOAuthLoginServers(
mcpServers,
unavailableMcpServers,
);
expect(result).toHaveLength(2);
expect(result.map((s) => s.name)).toEqual(['Linear', 'GitHub']);
});
it('returns empty list when all unavailable servers use bearer auth', () => {
const mcpServers = [
{ uuid: 'mcp-1', name: 'Docs', authType: 'bearer' },
];
const unavailableMcpServers = [
{
serverUuid: 'mcp-1',
serverName: 'Docs',
message: 'Unauthorized',
status: 'not_connected',
},
];
const result = getSlackMcpOAuthLoginServers(
mcpServers,
unavailableMcpServers,
);
expect(result).toHaveLength(0);
});
it('returns empty list when unavailable OAuth servers have error status (not not_connected)', () => {
const mcpServers = [
{ uuid: 'mcp-1', name: 'Linear', authType: 'oauth' },
];
const unavailableMcpServers = [
{
serverUuid: 'mcp-1',
serverName: 'Linear',
message: 'Broken',
status: 'error',
},
];
const result = getSlackMcpOAuthLoginServers(
mcpServers,
unavailableMcpServers,
);
expect(result).toHaveLength(0);
});
it('returns only the single unavailable OAuth server needing login', () => {
const mcpServers = [
{ uuid: 'mcp-1', name: 'Linear', authType: 'oauth' },
];
const unavailableMcpServers = [
{
serverUuid: 'mcp-1',
serverName: 'Linear',
message: 'Unauthorized',
status: 'not_connected',
},
];
const result = getSlackMcpOAuthLoginServers(
mcpServers,
unavailableMcpServers,
);
expect(result).toHaveLength(1);
expect(result[0].name).toBe('Linear');
});

Spotted by Graphite (based on custom rule: packages/backend rules)

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@notgiorgi notgiorgi changed the base branch from gio/prod-7877-mcp-auth-prompt to graphite-base/24569 June 22, 2026 15:26
@notgiorgi notgiorgi force-pushed the graphite-base/24569 branch from f40025e to e9ddc16 Compare June 22, 2026 15:27
@notgiorgi notgiorgi force-pushed the gio/prod-7877-slack-mcp-oauth-logins branch from be66710 to e5e8287 Compare June 22, 2026 15:27
@graphite-app graphite-app Bot changed the base branch from graphite-base/24569 to main June 22, 2026 15:27
@notgiorgi notgiorgi force-pushed the gio/prod-7877-slack-mcp-oauth-logins branch from e5e8287 to 1b6b7b9 Compare June 22, 2026 15:27
@notgiorgi notgiorgi force-pushed the gio/prod-7877-slack-mcp-oauth-logins branch from 1b6b7b9 to b57d9f5 Compare June 22, 2026 15:34
@notgiorgi notgiorgi marked this pull request as draft June 22, 2026 15:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants