Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions apps/api/src/handlers/mcp/__tests__/integration-mcp.test.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions apps/api/src/handlers/mcp/integration-mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
getAllowedIntegrationMcpToolNames,
isMcpConnectionExaConfig,
isMcpConnectionXConfig,
isMcpConnectionStripeConfig,
type McpIntegration,
} from '@roomote/types';

Expand Down Expand Up @@ -87,6 +88,12 @@ async function resolveUpstreamCredentials(
};
}

if (isMcpConnectionStripeConfig(connection.authConfig)) {
const apiKey = decrypt(connection.authConfig.encryptedApiKey).trim();

return { authHeader: apiKey.length > 0 ? apiKey : null };
}

return {
authHeader: (await getValidAccessToken(connection.id, mcpUrl)) ?? null,
};
Expand Down
1 change: 1 addition & 0 deletions apps/docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@
"integrations/rippling",
"integrations/sentry",
"integrations/snowflake",
"integrations/stripe",
"integrations/supabase",
"integrations/supermemory",
"integrations/vercel",
Expand Down
1 change: 1 addition & 0 deletions apps/docs/integrations/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ from [Personal Settings](/personal-settings).
| <IntegrationName href="/integrations/rippling" icon="rippling" name="Rippling" /> | Authoritative employee and reporting context | Admin connection once |
| <IntegrationName href="/integrations/sentry" icon="sentry" name="Sentry" /> | Error and performance investigation | Admin connection once |
| <IntegrationName href="/integrations/snowflake" icon="snowflake" name="Snowflake" /> | Data warehouse exploration | Admin connection once |
| <IntegrationName href="/integrations/stripe" icon="stripe" name="Stripe" /> | Payments, billing, and Stripe API context | Admin connection once |
| <IntegrationName href="/integrations/supabase" icon="supabase" name="Supabase" /> | Read-only database access in Supabase | Enable first, then teammates link accounts |
| <IntegrationName href="/integrations/supermemory" icon="/logo/integrations/supermemory.svg" name="Supermemory" /> | Shared memory across tasks and sessions | Admin connection once |
| <IntegrationName href="/integrations/vercel" icon="vercel" name="Vercel" /> | Deployments, logs, and domain availability | Admin connection once |
Expand Down
33 changes: 33 additions & 0 deletions apps/docs/integrations/stripe.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
title: Stripe
description: Inspect Stripe payments, billing, and API context from Roomote tasks.
icon: 'https://api.iconify.design/simple-icons:stripe.svg?color=currentColor'
---

Connect Stripe when tasks need account, payment, customer, billing, analytics,
or Stripe API context.

## How setup works

A deployment operator creates a restricted API key in the Stripe Dashboard and
stores it through **Settings > Integrations**. The key is encrypted at rest and
forwarded only from Roomote's control-plane proxy to Stripe's hosted MCP server.

Grant the key only the read permissions Roomote needs. Stripe administrators
can also disable MCP access for the team in the Stripe Dashboard.

## Safer defaults

Roomote disables `stripe_api_write` by default. The API search, details, and
read tools remain available along with account, analytics, documentation, and
implementation-planning tools. An admin can opt in to the general write tool
from **Settings > Integrations > Stripe > Manage tools**.

When writes are enabled, Stripe may still require human confirmation for
sensitive actions such as refunds or outbound payments.

## Verify the connection

Start with a sandbox or test-mode key and ask Roomote to retrieve account
information or list a non-sensitive resource. Live account access depends on
the restricted key's permissions.
64 changes: 63 additions & 1 deletion apps/web/src/components/settings/CredentialIntegrations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
useSaveNotionConnection,
useSaveRipplingConnection,
useSaveXConnection,
useSaveStripeConnection,
useStripeConnection,
useXConnection,
type useEffectiveMcpIntegrations,
} from '@/hooks/mcp-connections';
Expand All @@ -26,6 +28,7 @@ import {
saveNotionConnectionSchema,
saveRipplingConnectionSchema,
saveXConnectionSchema,
saveStripeConnectionSchema,
} from '@/types';
import {
Button,
Expand Down Expand Up @@ -53,6 +56,7 @@ type CredentialIntegrationId =
| 'notion'
| 'rippling'
| 'granola'
| 'stripe'
| 'x';

type CredentialConnection = {
Expand Down Expand Up @@ -141,6 +145,20 @@ function useXCredentialMutation(): CredentialMutation<{
};
}

function useStripeCredentialMutation(): CredentialMutation<{
apiKey: string;
}> {
const mutation = useSaveStripeConnection();
return {
isPending: mutation.isPending,
mutate: (input, options) =>
mutation.mutate(input, {
onSuccess: options.onSuccess,
onError: options.onError,
}),
};
}

type CredentialDefinition<Input> = {
id: CredentialIntegrationId;
fieldId: string;
Expand Down Expand Up @@ -744,6 +762,44 @@ const credentialDefinitions = {
} as const);
},
},
stripe: {
id: 'stripe',
fieldId: 'stripe-restricted-api-key',
fieldLabel: 'Stripe Restricted API Key',
fieldPlaceholder: 'rk_...',
help: (
<p className="text-sm text-muted-foreground">
Create a restricted key in the{' '}
<a
href="https://dashboard.stripe.com/apikeys"
target="_blank"
rel="noreferrer"
className="text-primary underline hover:no-underline"
>
Stripe Dashboard
</a>{' '}
with only the read permissions Roomote needs. Start with a sandbox or
test-mode key before connecting live data.
</p>
),
blankHelp: 'Leave blank to keep the existing restricted key.',
dialogDescription:
'Store a deployment-wide Stripe restricted API key. The key stays encrypted server-side and the general Stripe write tool starts disabled.',
requiredMessage: 'Restricted API key is required',
connectedMessage: 'Stripe connected for this deployment.',
updatedMessage: 'Stripe connection updated for this deployment.',
canManageTools: true,
getCredential: (input) => input.apiKey ?? '',
parse: (secret: string) => {
const result = saveStripeConnectionSchema.safeParse({ apiKey: secret });
return result.success
? ({ success: true, data: result.data } as const)
: ({
success: false,
errors: result.error.flatten().fieldErrors.apiKey,
} as const);
},
},
} satisfies {
[Id in CredentialIntegrationId]: CredentialDefinition<Record<string, string>>;
};
Expand Down Expand Up @@ -836,7 +892,13 @@ export function useCredentialIntegrations({
useConnection: useXConnection,
useSave: useXCredentialMutation,
});
const runtimes = [asana, notion, rippling, granola, x];
const stripe = useCredentialIntegration({
...buildRuntimeOptions('stripe'),
definition: credentialDefinitions.stripe,
useConnection: useStripeConnection,
useSave: useStripeCredentialMutation,
});
const runtimes = [asana, notion, rippling, granola, stripe, x];

return {
itemsById: new Map(runtimes.map((runtime) => [runtime.id, runtime.item])),
Expand Down
34 changes: 34 additions & 0 deletions apps/web/src/components/settings/Integrations.test.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions apps/web/src/components/settings/Integrations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ const DEEP_LINK_ENABLE_DESCRIPTIONS: Record<string, string> = {
sentry:
'Roomote will be able to inspect Sentry issue context and run scheduled Sentry triage through MCP.',
supabase: 'Roomote will get read-only database access and platform context.',
stripe:
'Roomote will use one deployment-wide restricted Stripe key to inspect account, payment, and billing data. The general write tool starts disabled.',
supermemory:
'Roomote will be able to save shared memories and recall context from earlier tasks.',
vercel:
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/components/system/custom/logos/brand-icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
siSentry,
siSnowflake,
siSupabase,
siStripe,
siTelegram,
siVercel,
siX,
Expand Down Expand Up @@ -67,6 +68,7 @@ const SIMPLE_ICONS: Record<string, SimpleIcon> = {
resend: siResend,
snowflake: siSnowflake,
supabase: siSupabase,
stripe: siStripe,
telegram: siTelegram,
sentry: siSentry,
vercel: siVercel,
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/hooks/mcp-connections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ export { useSaveVercelConnection } from './useSaveVercelConnection';
export { useVercelConnection } from './useVercelConnection';
export { useSaveXConnection } from './useSaveXConnection';
export { useXConnection } from './useXConnection';
export { useSaveStripeConnection } from './useSaveStripeConnection';
export { useStripeConnection } from './useStripeConnection';
export { useSetDisabledMcpTools } from './useSetDisabledMcpTools';
22 changes: 22 additions & 0 deletions apps/web/src/hooks/mcp-connections/useSaveStripeConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use client';

import { useMutation, useQueryClient } from '@tanstack/react-query';

import { useTRPC } from '@/trpc/client';
import { invalidateMcpIntegrationStatusQueries } from './invalidateMcpIntegrationStatusQueries';

export function useSaveStripeConnection() {
const trpc = useTRPC();
const queryClient = useQueryClient();

return useMutation(
trpc.mcpConnections.saveStripeConnection.mutationOptions({
onSuccess: () => {
void invalidateMcpIntegrationStatusQueries(queryClient, trpc);
queryClient.invalidateQueries({
queryKey: trpc.mcpConnections.stripeConnection.queryKey(),
});
},
}),
);
}
14 changes: 14 additions & 0 deletions apps/web/src/hooks/mcp-connections/useStripeConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use client';

import { useQuery } from '@tanstack/react-query';

import { useTRPC } from '@/trpc/client';

export function useStripeConnection(enabled = true) {
const trpc = useTRPC();

return useQuery({
...trpc.mcpConnections.stripeConnection.queryOptions(),
enabled,
});
}
Loading
Loading