feat(mcp): OAuth so claude.ai and ChatGPT can connect as connectors
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m49s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m49s
MindSift becomes the OAuth authorization server for its own MCP endpoint: discovery (RFC 9728/8414), dynamic client registration, authorization code with PKCE (S256), rotating refresh tokens with reuse detection. The consent page lives in the app at /oauth/authorize. A grant is an api_keys entry (kind 'oauth'), so scopes, origin badges, renaming and revoking work as for keys; tokens are valid for /mcp only. Unused connections expire after OAUTH_IDLE_DAYS (default 90). Requires BASE_URL; off in demo mode. Release 0.51.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
994f6dc554
commit
75aea4a7d4
31 files changed
+1581
-49
No files matched your search
@@ -7,7 +7,9 @@ vi.mock('../../../lib/api', () => ({
|
||||
api: {
|
||||
getAPIKeys: vi.fn(async () => [
|
||||
{ id: 'k0', userId: 'u', name: 'Rauchmelder', keyPrefix: 'mindsift_sec_xy', scopes: 'webhook:triage,webhook:task', createdAt: '' },
|
||||
{ id: 'k9', userId: 'u', name: 'Claude (OAuth)', keyPrefix: 'OAuth', scopes: 'tasks:read', createdAt: '', kind: 'oauth' },
|
||||
]),
|
||||
getAuthStatus: vi.fn(async () => ({ mcpConnectorUrl: 'https://ms.example/api/v1/mcp' })),
|
||||
getAllLists: vi.fn(async () => []),
|
||||
getTaskLists: vi.fn(async () => []),
|
||||
createAPIKey: (...args: unknown[]) => createAPIKey(...(args as [])),
|
||||
@@ -49,11 +51,21 @@ describe('ApiKeysPanel', () => {
|
||||
expect(screen.getAllByText(/tasks\?due=today&status=open/).length).toBeGreaterThan(0);
|
||||
expect(screen.queryByText(/claude mcp add/)).toBeNull();
|
||||
fireEvent.click(screen.getByRole('tab', { name: /KI-Agenten/ }));
|
||||
// claude.ai / ChatGPT first: the connector address, no key
|
||||
expect(await screen.findByText('https://ms.example/api/v1/mcp')).toBeTruthy();
|
||||
fireEvent.click(screen.getByRole('radio', { name: 'Claude Code' }));
|
||||
expect(screen.getByText(/claude mcp add --transport http mindsift/)).toBeTruthy();
|
||||
fireEvent.click(screen.getByRole('radio', { name: 'OpenAI Codex' }));
|
||||
expect(screen.getByText(/\[mcp_servers\.mindsift\]/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('marks OAuth connections instead of showing a key prefix', async () => {
|
||||
render(<ApiKeysPanel />);
|
||||
expect(await screen.findByText('Claude (OAuth)')).toBeTruthy();
|
||||
expect(screen.getByTitle(/Verbunden über OAuth/)).toBeTruthy();
|
||||
expect(screen.queryByText('OAuth', { selector: '.font-mono' })).toBeNull();
|
||||
});
|
||||
|
||||
it('renames a key and changes its rights', async () => {
|
||||
render(<ApiKeysPanel />);
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'API-Key Rauchmelder bearbeiten' }));
|
||||
|
||||
Reference in new issue
Block a user