@vertesia/ui 1.6.0-dev.20260918.090710Z → 1.6.0-dev.20260918.115658Z
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/features/oauth/McpConnectionsButton.d.ts +2 -2
- package/lib/features/oauth/McpConnectionsButton.d.ts.map +1 -1
- package/lib/features/oauth/McpConnectionsButton.js +9 -7
- package/lib/features/oauth/McpConnectionsButton.js.map +1 -1
- package/lib/features/oauth/McpConnectionsDialog.d.ts.map +1 -1
- package/lib/features/oauth/McpConnectionsDialog.js +7 -3
- package/lib/features/oauth/McpConnectionsDialog.js.map +1 -1
- package/lib/features/oauth/useMcpConnections.d.ts +4 -2
- package/lib/features/oauth/useMcpConnections.d.ts.map +1 -1
- package/lib/features/oauth/useMcpConnections.js +15 -9
- package/lib/features/oauth/useMcpConnections.js.map +1 -1
- package/lib/i18n/locales/ar.json +1 -0
- package/lib/i18n/locales/de.json +1 -0
- package/lib/i18n/locales/en.json +1 -0
- package/lib/i18n/locales/es.json +1 -0
- package/lib/i18n/locales/fr.json +1 -0
- package/lib/i18n/locales/it.json +1 -0
- package/lib/i18n/locales/ja.json +1 -0
- package/lib/i18n/locales/ko.json +1 -0
- package/lib/i18n/locales/pt.json +1 -0
- package/lib/i18n/locales/ru.json +1 -0
- package/lib/i18n/locales/tr.json +1 -0
- package/lib/i18n/locales/zh-TW.json +1 -0
- package/lib/i18n/locales/zh.json +1 -0
- package/lib/session/auth/oauth.d.ts.map +1 -1
- package/lib/session/auth/oauth.js +3 -0
- package/lib/session/auth/oauth.js.map +1 -1
- package/lib/shell/apps/AppInstallationProvider.d.ts +4 -4
- package/lib/vertesia-ui-features.js +13 -13
- package/lib/vertesia-ui-features.js.map +1 -1
- package/lib/vertesia-ui-i18n.4e0lpFmb.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.4e0lpFmb.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.BWrAGXBo.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.BWrAGXBo.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.BWsksQSO.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.BWsksQSO.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.BtxcuCei.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.BtxcuCei.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.Bz8LMNmG.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.Bz8LMNmG.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.CSRfYYZ3.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.CSRfYYZ3.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.D0aB839H.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.D0aB839H.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.Dn3ZhpYw.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.Dn3ZhpYw.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n._vo6dQOd.chunk.js +5 -0
- package/lib/vertesia-ui-i18n._vo6dQOd.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.fNnmGqlI.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.fNnmGqlI.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.js +2 -2
- package/lib/vertesia-ui-i18n.lcqwXwSM.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.lcqwXwSM.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.snVd8TDP.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.snVd8TDP.chunk.js.map +1 -0
- package/lib/vertesia-ui-session.js +1 -1
- package/lib/vertesia-ui-session.js.map +1 -1
- package/package.json +7 -7
- package/src/features/oauth/McpConnectionsButton.test.tsx +56 -0
- package/src/features/oauth/McpConnectionsButton.tsx +9 -6
- package/src/features/oauth/McpConnectionsDialog.tsx +14 -5
- package/src/features/oauth/useMcpConnections.test.ts +10 -0
- package/src/features/oauth/useMcpConnections.ts +18 -10
- package/src/i18n/locales/ar.json +1 -0
- package/src/i18n/locales/de.json +1 -0
- package/src/i18n/locales/en.json +1 -0
- package/src/i18n/locales/es.json +1 -0
- package/src/i18n/locales/fr.json +1 -0
- package/src/i18n/locales/it.json +1 -0
- package/src/i18n/locales/ja.json +1 -0
- package/src/i18n/locales/ko.json +1 -0
- package/src/i18n/locales/pt.json +1 -0
- package/src/i18n/locales/ru.json +1 -0
- package/src/i18n/locales/tr.json +1 -0
- package/src/i18n/locales/zh-TW.json +1 -0
- package/src/i18n/locales/zh.json +1 -0
- package/src/session/auth/oauth.test.ts +2 -0
- package/src/session/auth/oauth.ts +3 -0
- package/lib/vertesia-ui-i18n.B3NFclwl.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.B3NFclwl.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.BWSrNIxc.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BWSrNIxc.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.BWvWeB1p.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BWvWeB1p.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.BZCXvTC1.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BZCXvTC1.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.BbJMvFZW.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BbJMvFZW.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.CE5ldwpt.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.CE5ldwpt.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.CMC0IShY.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.CMC0IShY.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.CkcRqBKU.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.CkcRqBKU.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.D-Mq1sw_.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.D-Mq1sw_.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DkK68-A-.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DkK68-A-.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.XE_dcCg6.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.XE_dcCg6.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.qgNqMPvK.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.qgNqMPvK.chunk.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vertesia/ui",
|
|
3
|
-
"version": "1.6.0-dev.20260918.
|
|
3
|
+
"version": "1.6.0-dev.20260918.115658Z",
|
|
4
4
|
"description": "Vertesia UI components and and hooks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -45,11 +45,11 @@
|
|
|
45
45
|
"@radix-ui/react-switch": "^1.3.7",
|
|
46
46
|
"@radix-ui/react-tabs": "^1.1.21",
|
|
47
47
|
"@radix-ui/react-tooltip": "^1.2.16",
|
|
48
|
-
"@vertesia/client": "1.6.0-dev.20260918.
|
|
49
|
-
"@vertesia/common": "1.6.0-dev.20260918.
|
|
50
|
-
"@vertesia/fusion-ux": "1.6.0-dev.20260918.
|
|
51
|
-
"@vertesia/json": "1.6.0-dev.20260918.
|
|
52
|
-
"@vertesia/rich-text": "1.6.0-dev.20260918.
|
|
48
|
+
"@vertesia/client": "1.6.0-dev.20260918.115658Z",
|
|
49
|
+
"@vertesia/common": "1.6.0-dev.20260918.115658Z",
|
|
50
|
+
"@vertesia/fusion-ux": "1.6.0-dev.20260918.115658Z",
|
|
51
|
+
"@vertesia/json": "1.6.0-dev.20260918.115658Z",
|
|
52
|
+
"@vertesia/rich-text": "1.6.0-dev.20260918.115658Z",
|
|
53
53
|
"ajv": "^8.20.0",
|
|
54
54
|
"ajv-formats": "^3.0.1",
|
|
55
55
|
"aria-hidden": "^1.2.6",
|
|
@@ -188,7 +188,7 @@
|
|
|
188
188
|
"url": "https://github.com/vertesia/composableai.git",
|
|
189
189
|
"directory": "packages/ui"
|
|
190
190
|
},
|
|
191
|
-
"gitHead": "
|
|
191
|
+
"gitHead": "5961811f0fdaee3b4902a2230875a3325e64ad80",
|
|
192
192
|
"scripts": {
|
|
193
193
|
"clean:lib": "rimraf ./lib ./tsconfig.tsbuildinfo",
|
|
194
194
|
"clean": "rimraf ./node_modules ./lib ./tsconfig.tsbuildinfo",
|
|
@@ -183,6 +183,62 @@ describe('MCP connection controls', () => {
|
|
|
183
183
|
expect(onChange).toHaveBeenCalledWith(['linear']);
|
|
184
184
|
});
|
|
185
185
|
|
|
186
|
+
it.each([{ oauthIds: [] }, { oauthIds: ['anonymous'] }])(
|
|
187
|
+
'shows anonymous servers without status requests, including stale OAuth ids %j',
|
|
188
|
+
async ({ oauthIds }) => {
|
|
189
|
+
mocks.getInstalledApps.mockResolvedValue([
|
|
190
|
+
{
|
|
191
|
+
id: 'anonymous-app',
|
|
192
|
+
manifest: {
|
|
193
|
+
name: 'anonymous-app',
|
|
194
|
+
tool_collections: [
|
|
195
|
+
{
|
|
196
|
+
type: 'mcp',
|
|
197
|
+
id: 'anonymous',
|
|
198
|
+
name: 'Anonymous MCP',
|
|
199
|
+
namespace: 'anonymous',
|
|
200
|
+
url: 'https://anonymous.example.com/mcp',
|
|
201
|
+
auth: 'none',
|
|
202
|
+
oauth_app: 'stale-oauth-app',
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
oauth_collection_ids: oauthIds,
|
|
207
|
+
},
|
|
208
|
+
]);
|
|
209
|
+
const onChange = vi.fn();
|
|
210
|
+
const { rerender } = renderWithProviders(
|
|
211
|
+
<McpConnectionsInlineList disabledCollections={['other']} onChange={onChange} />,
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
expect(await screen.findByText('Anonymous MCP')).not.toBeNull();
|
|
215
|
+
expect(screen.getAllByText('No authentication')).toHaveLength(1);
|
|
216
|
+
expect(screen.queryByRole('button', { name: /connect/i })).toBeNull();
|
|
217
|
+
expect(mocks.getStatus).not.toHaveBeenCalled();
|
|
218
|
+
expect(mocks.getCollectionStatus).not.toHaveBeenCalled();
|
|
219
|
+
|
|
220
|
+
fireEvent.click(screen.getByRole('switch', { name: 'Deactivate Anonymous MCP for this conversation' }));
|
|
221
|
+
expect(onChange).toHaveBeenLastCalledWith(['other', 'anonymous']);
|
|
222
|
+
rerender(<McpConnectionsInlineList disabledCollections={['other', 'anonymous']} onChange={onChange} />);
|
|
223
|
+
fireEvent.click(screen.getByRole('switch', { name: 'Activate Anonymous MCP for this conversation' }));
|
|
224
|
+
expect(onChange).toHaveBeenLastCalledWith(['other']);
|
|
225
|
+
rerender(<McpConnectionsInlineList onChange={onChange} readOnly />);
|
|
226
|
+
expect(screen.getByRole('switch')).toHaveProperty('disabled', true);
|
|
227
|
+
|
|
228
|
+
rerender(<McpConnectionsActionMenu onChange={onChange} />);
|
|
229
|
+
fireEvent.pointerDown(await screen.findByRole('button', { name: /settings/i }));
|
|
230
|
+
const menuItem = await screen.findByText('MCP');
|
|
231
|
+
expect(screen.getByText('1')).not.toBeNull();
|
|
232
|
+
fireEvent.click(menuItem);
|
|
233
|
+
expect(await screen.findByText('Anonymous MCP')).not.toBeNull();
|
|
234
|
+
expect(screen.getByText('No authentication')).not.toBeNull();
|
|
235
|
+
expect(screen.queryByRole('button', { name: /connect/i })).toBeNull();
|
|
236
|
+
fireEvent.click(screen.getByRole('switch', { name: 'Deactivate Anonymous MCP for this conversation' }));
|
|
237
|
+
expect(onChange).toHaveBeenLastCalledWith(['anonymous']);
|
|
238
|
+
expect(mocks.getStatus).not.toHaveBeenCalled();
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
|
|
186
242
|
it('renders MCP rows before connection statuses finish loading', async () => {
|
|
187
243
|
const statusRequest =
|
|
188
244
|
deferred<
|
|
@@ -7,6 +7,7 @@ import { RemoteMcpConnectionButton } from './RemoteMcpConnectionButton.js';
|
|
|
7
7
|
import {
|
|
8
8
|
countConnectedActiveGroups,
|
|
9
9
|
getConnectedActiveGroupLabels,
|
|
10
|
+
isGroupConnected,
|
|
10
11
|
isGroupDisabled,
|
|
11
12
|
toggleGroupDisabled,
|
|
12
13
|
useMcpConnections,
|
|
@@ -29,7 +30,7 @@ export interface McpConnectionsButtonProps {
|
|
|
29
30
|
/**
|
|
30
31
|
* Toolbar button that opens the MCP connections dialog. The badge shows how many MCP
|
|
31
32
|
* servers are connected and active for this conversation.
|
|
32
|
-
* Renders nothing when the project has no
|
|
33
|
+
* Renders nothing when the project has no MCP servers installed.
|
|
33
34
|
*/
|
|
34
35
|
export function McpConnectionsButton({
|
|
35
36
|
disabledCollections,
|
|
@@ -93,7 +94,7 @@ export function McpConnectionsButton({
|
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
/**
|
|
96
|
-
* Inline MCP status list for forms. It shows all available
|
|
97
|
+
* Inline MCP status list for forms. It shows all available MCP groups
|
|
97
98
|
* without requiring the user to open the management dialog.
|
|
98
99
|
*/
|
|
99
100
|
export function McpConnectionsInlineList({
|
|
@@ -127,7 +128,7 @@ export function McpConnectionsInlineList({
|
|
|
127
128
|
<div className="space-y-1">
|
|
128
129
|
{groups.map((group) => {
|
|
129
130
|
const active = !isGroupDisabled(group, disabledCollections);
|
|
130
|
-
const connected = group
|
|
131
|
+
const connected = isGroupConnected(group);
|
|
131
132
|
const interactiveOAuth = group.authType === 'oauth' && group.oauthGrantType !== 'client_credentials';
|
|
132
133
|
const StatusIcon = connected ? Link2 : Link2Off;
|
|
133
134
|
return (
|
|
@@ -179,9 +180,11 @@ export function McpConnectionsInlineList({
|
|
|
179
180
|
/>
|
|
180
181
|
) : (
|
|
181
182
|
<Badge variant="outline" className="h-6 w-32 justify-center px-2 text-xs">
|
|
182
|
-
{group.authType === '
|
|
183
|
-
? t('mcpConnections.
|
|
184
|
-
:
|
|
183
|
+
{group.authType === 'none'
|
|
184
|
+
? t('mcpConnections.noAuthentication')
|
|
185
|
+
: group.authType === 'api_key'
|
|
186
|
+
? t('mcpConnections.apiKey')
|
|
187
|
+
: t('mcpConnections.managedByApp')}
|
|
185
188
|
</Badge>
|
|
186
189
|
)}
|
|
187
190
|
{onChange && (
|
|
@@ -2,7 +2,12 @@ import { useUITranslation } from '@vertesia/ui/i18n';
|
|
|
2
2
|
import { Link2, Link2Off } from 'lucide-react';
|
|
3
3
|
import { Badge, Button, Modal, ModalBody, ModalTitle, Spinner, Switch, VTooltip } from '../../core/index.js';
|
|
4
4
|
import { RemoteMcpConnectionButton } from './RemoteMcpConnectionButton.js';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
isGroupConnected,
|
|
7
|
+
isGroupDisabled,
|
|
8
|
+
type McpConnectionGroup,
|
|
9
|
+
toggleGroupDisabled,
|
|
10
|
+
} from './useMcpConnections.js';
|
|
6
11
|
|
|
7
12
|
export interface McpConnectionsDialogProps {
|
|
8
13
|
isOpen: boolean;
|
|
@@ -61,7 +66,7 @@ export function McpConnectionsDialog({
|
|
|
61
66
|
<div className="space-y-1">
|
|
62
67
|
{groups.map((group) => {
|
|
63
68
|
const active = !isGroupDisabled(group, disabledCollections);
|
|
64
|
-
const connected = group
|
|
69
|
+
const connected = isGroupConnected(group);
|
|
65
70
|
const interactiveOAuth =
|
|
66
71
|
group.authType === 'oauth' && group.oauthGrantType !== 'client_credentials';
|
|
67
72
|
const StatusIcon = connected ? Link2 : Link2Off;
|
|
@@ -115,11 +120,15 @@ export function McpConnectionsDialog({
|
|
|
115
120
|
showDisconnect
|
|
116
121
|
readOnly={readOnly}
|
|
117
122
|
/>
|
|
118
|
-
) :
|
|
123
|
+
) : (
|
|
119
124
|
<Badge variant="outline" className="h-6 w-32 justify-center px-2 text-xs">
|
|
120
|
-
{
|
|
125
|
+
{group.authType === 'none'
|
|
126
|
+
? t('mcpConnections.noAuthentication')
|
|
127
|
+
: group.authType === 'api_key'
|
|
128
|
+
? t('mcpConnections.apiKey')
|
|
129
|
+
: t('mcpConnections.managedByApp')}
|
|
121
130
|
</Badge>
|
|
122
|
-
)
|
|
131
|
+
)}
|
|
123
132
|
{onChange && (
|
|
124
133
|
<Switch
|
|
125
134
|
size="sm"
|
|
@@ -93,3 +93,13 @@ describe('toggleGroupDisabled', () => {
|
|
|
93
93
|
expect(toggleGroupDisabled(g, disabled, true)).toEqual([]);
|
|
94
94
|
});
|
|
95
95
|
});
|
|
96
|
+
|
|
97
|
+
describe('anonymous MCP groups', () => {
|
|
98
|
+
it('counts anonymous servers without credential status and respects disabled collections', () => {
|
|
99
|
+
const anonymous = { ...group(['anonymous'], false, 'Anonymous'), authType: 'none' as const };
|
|
100
|
+
expect(countConnectedActiveGroups([anonymous])).toBe(1);
|
|
101
|
+
expect(getConnectedActiveGroupLabels([anonymous])).toEqual(['Anonymous']);
|
|
102
|
+
expect(countConnectedActiveGroups([anonymous], ['anonymous'])).toBe(0);
|
|
103
|
+
expect(getConnectedActiveGroupLabels([anonymous], ['anonymous'])).toEqual([]);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -20,13 +20,13 @@ export interface McpConnectionGroup {
|
|
|
20
20
|
memberIds: string[];
|
|
21
21
|
/** Collection names represented by this row. */
|
|
22
22
|
memberNames: string[];
|
|
23
|
-
authType: 'oauth' | 'api_key';
|
|
23
|
+
authType: 'none' | 'oauth' | 'api_key';
|
|
24
24
|
oauthGrantType?: 'authorization_code' | 'client_credentials';
|
|
25
25
|
authStatus?: OAuthAuthStatus;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Loads the project's installed
|
|
29
|
+
* Loads the project's installed MCP tool collections and resolves their status.
|
|
30
30
|
*
|
|
31
31
|
* Shared by the MCP connections button (badge count) and dialog (list) so the data is
|
|
32
32
|
* fetched once and refreshed together after connect/disconnect.
|
|
@@ -57,8 +57,10 @@ export function useMcpConnections() {
|
|
|
57
57
|
const mcpCollections = inst.manifest.tool_collections
|
|
58
58
|
.map((c) => normalizeToolCollection(c))
|
|
59
59
|
.filter((c): c is MCPToolCollectionObject => c.type === 'mcp');
|
|
60
|
-
const oauthCollections = mcpCollections.filter(
|
|
61
|
-
|
|
60
|
+
const oauthCollections = mcpCollections.filter(
|
|
61
|
+
(c) => c.auth !== 'none' && c.auth !== 'api_key' && oauthIds.includes(c.id),
|
|
62
|
+
);
|
|
63
|
+
const directCollections = mcpCollections.filter((c) => c.auth === 'none' || c.auth === 'api_key');
|
|
62
64
|
|
|
63
65
|
const providerMap = new Map<string, MCPToolCollectionObject[]>();
|
|
64
66
|
const oauthAppMap = new Map<string, MCPToolCollectionObject[]>();
|
|
@@ -124,16 +126,16 @@ export function useMcpConnections() {
|
|
|
124
126
|
});
|
|
125
127
|
}
|
|
126
128
|
|
|
127
|
-
for (const col of
|
|
129
|
+
for (const col of directCollections) {
|
|
128
130
|
allGroups.push({
|
|
129
|
-
key: `${inst.id}
|
|
131
|
+
key: `${inst.id}:${col.auth}:${col.id}`,
|
|
130
132
|
appId: inst.id,
|
|
131
133
|
appName: inst.manifest.title || inst.manifest.name,
|
|
132
134
|
label: col.name,
|
|
133
135
|
representativeId: col.id,
|
|
134
136
|
memberIds: [col.id],
|
|
135
137
|
memberNames: [col.name],
|
|
136
|
-
authType: 'api_key',
|
|
138
|
+
authType: col.auth === 'none' ? 'none' : 'api_key',
|
|
137
139
|
});
|
|
138
140
|
}
|
|
139
141
|
}
|
|
@@ -155,6 +157,8 @@ export function useMcpConnections() {
|
|
|
155
157
|
const statusRequestsByApp = new Map<string, Promise<OAuthAuthStatus[]>>();
|
|
156
158
|
const groupsWithStatus = await Promise.all(
|
|
157
159
|
allGroups.map(async (group) => {
|
|
160
|
+
// Anonymous servers are available without a credential status lookup.
|
|
161
|
+
if (group.authType === 'none') return group;
|
|
158
162
|
try {
|
|
159
163
|
let statusRequest = statusRequestsByApp.get(group.appId);
|
|
160
164
|
if (!statusRequest) {
|
|
@@ -219,15 +223,19 @@ export function toggleGroupDisabled(
|
|
|
219
223
|
return Array.from(current);
|
|
220
224
|
}
|
|
221
225
|
|
|
226
|
+
/** Anonymous servers are available without authentication; other modes require valid credentials. */
|
|
227
|
+
export function isGroupConnected(group: McpConnectionGroup): boolean {
|
|
228
|
+
return group.authType === 'none' || group.authStatus?.authenticated === true;
|
|
229
|
+
}
|
|
230
|
+
|
|
222
231
|
/** Count connected MCP groups that are enabled for the current conversation. */
|
|
223
232
|
export function countConnectedActiveGroups(groups: McpConnectionGroup[], disabled?: string[]): number {
|
|
224
|
-
return groups.filter((group) => group
|
|
225
|
-
.length;
|
|
233
|
+
return groups.filter((group) => isGroupConnected(group) && !isGroupDisabled(group, disabled)).length;
|
|
226
234
|
}
|
|
227
235
|
|
|
228
236
|
/** Names of connected MCP groups that are enabled for the current conversation. */
|
|
229
237
|
export function getConnectedActiveGroupLabels(groups: McpConnectionGroup[], disabled?: string[]): string[] {
|
|
230
238
|
return groups
|
|
231
|
-
.filter((group) => group
|
|
239
|
+
.filter((group) => isGroupConnected(group) && !isGroupDisabled(group, disabled))
|
|
232
240
|
.map((group) => group.label);
|
|
233
241
|
}
|
package/src/i18n/locales/ar.json
CHANGED
|
@@ -407,6 +407,7 @@
|
|
|
407
407
|
"mcpConnections.label": "MCP",
|
|
408
408
|
"mcpConnections.manage": "إدارة اتصالات MCP",
|
|
409
409
|
"mcpConnections.managedByApp": "يُدار بواسطة التطبيق",
|
|
410
|
+
"mcpConnections.noAuthentication": "بدون مصادقة",
|
|
410
411
|
"mcpConnections.settings": "الإعدادات",
|
|
411
412
|
"mcpConnections.subtitle": "إدارة خوادم MCP المتاحة لهذه المحادثة.",
|
|
412
413
|
"mcpConnections.title": "MCP",
|
package/src/i18n/locales/de.json
CHANGED
|
@@ -395,6 +395,7 @@
|
|
|
395
395
|
"mcpConnections.label": "MCP",
|
|
396
396
|
"mcpConnections.manage": "MCP-Verbindungen verwalten",
|
|
397
397
|
"mcpConnections.managedByApp": "Von der App verwaltet",
|
|
398
|
+
"mcpConnections.noAuthentication": "Keine Authentifizierung",
|
|
398
399
|
"mcpConnections.settings": "Einstellungen",
|
|
399
400
|
"mcpConnections.subtitle": "Verwalten Sie die für diese Unterhaltung verfügbaren MCP-Server.",
|
|
400
401
|
"mcpConnections.title": "MCP",
|
package/src/i18n/locales/en.json
CHANGED
|
@@ -705,6 +705,7 @@
|
|
|
705
705
|
"mcpConnections.label": "MCP",
|
|
706
706
|
"mcpConnections.manage": "Manage MCP connections",
|
|
707
707
|
"mcpConnections.managedByApp": "Managed by app",
|
|
708
|
+
"mcpConnections.noAuthentication": "No authentication",
|
|
708
709
|
"mcpConnections.settings": "Settings",
|
|
709
710
|
"mcpConnections.subtitle": "Manage the MCP servers available to this conversation.",
|
|
710
711
|
"mcpConnections.title": "MCP",
|
package/src/i18n/locales/es.json
CHANGED
|
@@ -397,6 +397,7 @@
|
|
|
397
397
|
"mcpConnections.label": "MCP",
|
|
398
398
|
"mcpConnections.manage": "Gestionar conexiones MCP",
|
|
399
399
|
"mcpConnections.managedByApp": "Gestionado por la aplicación",
|
|
400
|
+
"mcpConnections.noAuthentication": "Sin autenticación",
|
|
400
401
|
"mcpConnections.settings": "Configuración",
|
|
401
402
|
"mcpConnections.subtitle": "Gestiona los servidores MCP disponibles para esta conversación.",
|
|
402
403
|
"mcpConnections.title": "MCP",
|
package/src/i18n/locales/fr.json
CHANGED
|
@@ -397,6 +397,7 @@
|
|
|
397
397
|
"mcpConnections.label": "MCP",
|
|
398
398
|
"mcpConnections.manage": "Gérer les connexions MCP",
|
|
399
399
|
"mcpConnections.managedByApp": "Géré par l’application",
|
|
400
|
+
"mcpConnections.noAuthentication": "Sans authentification",
|
|
400
401
|
"mcpConnections.settings": "Paramètres",
|
|
401
402
|
"mcpConnections.subtitle": "Gérez les serveurs MCP disponibles pour cette conversation.",
|
|
402
403
|
"mcpConnections.title": "MCP",
|
package/src/i18n/locales/it.json
CHANGED
|
@@ -397,6 +397,7 @@
|
|
|
397
397
|
"mcpConnections.label": "MCP",
|
|
398
398
|
"mcpConnections.manage": "Gestisci connessioni MCP",
|
|
399
399
|
"mcpConnections.managedByApp": "Gestito dall'app",
|
|
400
|
+
"mcpConnections.noAuthentication": "Nessuna autenticazione",
|
|
400
401
|
"mcpConnections.settings": "Impostazioni",
|
|
401
402
|
"mcpConnections.subtitle": "Gestisci i server MCP disponibili per questa conversazione.",
|
|
402
403
|
"mcpConnections.title": "MCP",
|
package/src/i18n/locales/ja.json
CHANGED
|
@@ -395,6 +395,7 @@
|
|
|
395
395
|
"mcpConnections.label": "MCP",
|
|
396
396
|
"mcpConnections.manage": "MCP 接続を管理",
|
|
397
397
|
"mcpConnections.managedByApp": "アプリによる管理",
|
|
398
|
+
"mcpConnections.noAuthentication": "認証なし",
|
|
398
399
|
"mcpConnections.settings": "設定",
|
|
399
400
|
"mcpConnections.subtitle": "この会話で利用できる MCP サーバーを管理します。",
|
|
400
401
|
"mcpConnections.title": "MCP",
|
package/src/i18n/locales/ko.json
CHANGED
|
@@ -395,6 +395,7 @@
|
|
|
395
395
|
"mcpConnections.label": "MCP",
|
|
396
396
|
"mcpConnections.manage": "MCP 연결 관리",
|
|
397
397
|
"mcpConnections.managedByApp": "앱에서 관리",
|
|
398
|
+
"mcpConnections.noAuthentication": "인증 없음",
|
|
398
399
|
"mcpConnections.settings": "설정",
|
|
399
400
|
"mcpConnections.subtitle": "이 대화에서 사용할 수 있는 MCP 서버를 관리합니다.",
|
|
400
401
|
"mcpConnections.title": "MCP",
|
package/src/i18n/locales/pt.json
CHANGED
|
@@ -397,6 +397,7 @@
|
|
|
397
397
|
"mcpConnections.label": "MCP",
|
|
398
398
|
"mcpConnections.manage": "Gerenciar conexões MCP",
|
|
399
399
|
"mcpConnections.managedByApp": "Gerenciado pelo aplicativo",
|
|
400
|
+
"mcpConnections.noAuthentication": "Sem autenticação",
|
|
400
401
|
"mcpConnections.settings": "Configurações",
|
|
401
402
|
"mcpConnections.subtitle": "Gerencie os servidores MCP disponíveis para esta conversa.",
|
|
402
403
|
"mcpConnections.title": "MCP",
|
package/src/i18n/locales/ru.json
CHANGED
|
@@ -401,6 +401,7 @@
|
|
|
401
401
|
"mcpConnections.label": "MCP",
|
|
402
402
|
"mcpConnections.manage": "Управление подключениями MCP",
|
|
403
403
|
"mcpConnections.managedByApp": "Управляется приложением",
|
|
404
|
+
"mcpConnections.noAuthentication": "Без аутентификации",
|
|
404
405
|
"mcpConnections.settings": "Настройки",
|
|
405
406
|
"mcpConnections.subtitle": "Управляйте серверами MCP, доступными для этого разговора.",
|
|
406
407
|
"mcpConnections.title": "MCP",
|
package/src/i18n/locales/tr.json
CHANGED
|
@@ -395,6 +395,7 @@
|
|
|
395
395
|
"mcpConnections.label": "MCP",
|
|
396
396
|
"mcpConnections.manage": "MCP bağlantılarını yönet",
|
|
397
397
|
"mcpConnections.managedByApp": "Uygulama tarafından yönetiliyor",
|
|
398
|
+
"mcpConnections.noAuthentication": "Kimlik doğrulama yok",
|
|
398
399
|
"mcpConnections.settings": "Ayarlar",
|
|
399
400
|
"mcpConnections.subtitle": "Bu konuşma için kullanılabilen MCP sunucularını yönetin.",
|
|
400
401
|
"mcpConnections.title": "MCP",
|
|
@@ -395,6 +395,7 @@
|
|
|
395
395
|
"mcpConnections.label": "MCP",
|
|
396
396
|
"mcpConnections.manage": "管理 MCP 連線",
|
|
397
397
|
"mcpConnections.managedByApp": "由應用程式管理",
|
|
398
|
+
"mcpConnections.noAuthentication": "無需驗證",
|
|
398
399
|
"mcpConnections.settings": "設定",
|
|
399
400
|
"mcpConnections.subtitle": "管理可用於此對話的 MCP 伺服器。",
|
|
400
401
|
"mcpConnections.title": "MCP",
|
package/src/i18n/locales/zh.json
CHANGED
|
@@ -395,6 +395,7 @@
|
|
|
395
395
|
"mcpConnections.label": "MCP",
|
|
396
396
|
"mcpConnections.manage": "管理 MCP 连接",
|
|
397
397
|
"mcpConnections.managedByApp": "由应用管理",
|
|
398
|
+
"mcpConnections.noAuthentication": "无需身份验证",
|
|
398
399
|
"mcpConnections.settings": "设置",
|
|
399
400
|
"mcpConnections.subtitle": "管理可用于此对话的 MCP 服务器。",
|
|
400
401
|
"mcpConnections.title": "MCP",
|
|
@@ -90,6 +90,7 @@ it('uses CIMD and PKCE, exchanges the callback once and restores the app deep li
|
|
|
90
90
|
await vi.waitFor(() => expect(replace).toHaveBeenCalledTimes(1));
|
|
91
91
|
const authorize = new URL(replace.mock.calls[0][0]);
|
|
92
92
|
expect(authorize.searchParams.get('client_id')).toBe(clientId);
|
|
93
|
+
expect(authorize.searchParams.get('resource')).toBe(`${issuer}/`);
|
|
93
94
|
expect(authorize.searchParams.get('redirect_uri')).toBe(`${origin}/app`);
|
|
94
95
|
expect(authorize.searchParams.get('scope')).toBe('openid profile content:read');
|
|
95
96
|
expect(authorize.searchParams.get('project_id')).toBe('p');
|
|
@@ -106,6 +107,7 @@ it('uses CIMD and PKCE, exchanges the callback once and restores the app deep li
|
|
|
106
107
|
const body = exchange.init?.body as URLSearchParams;
|
|
107
108
|
expect(body.get('code_verifier')).toBe(txn.verifier);
|
|
108
109
|
expect(body.get('grant_type')).toBe('authorization_code');
|
|
110
|
+
expect(body.get('resource')).toBe(authorize.searchParams.get('resource'));
|
|
109
111
|
expect(body.has('client_secret')).toBe(false);
|
|
110
112
|
expect(storage.has('vertesia.oauth.transaction')).toBe(false);
|
|
111
113
|
expect(browser.location.href).toBe(`${origin}/app/report?a=a&p=p#chart`);
|
|
@@ -104,12 +104,14 @@ async function acquireToken(): Promise<string> {
|
|
|
104
104
|
if (metadata.issuer.replace(/\/+$/, '') !== issuer) throw new Error('OAuth issuer mismatch');
|
|
105
105
|
const authorize = httpsUrl(metadata.authorization_endpoint);
|
|
106
106
|
const tokenEndpoint = httpsUrl(metadata.token_endpoint);
|
|
107
|
+
const resource = new URL(metadata.issuer).toString();
|
|
107
108
|
if (transaction && code) {
|
|
108
109
|
const response = await fetch(tokenEndpoint, {
|
|
109
110
|
method: 'POST',
|
|
110
111
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
111
112
|
body: new URLSearchParams({
|
|
112
113
|
grant_type: 'authorization_code',
|
|
114
|
+
resource,
|
|
113
115
|
code,
|
|
114
116
|
client_id: clientId,
|
|
115
117
|
redirect_uri: redirectUri,
|
|
@@ -156,6 +158,7 @@ async function acquireToken(): Promise<string> {
|
|
|
156
158
|
authorize.searchParams.set('client_id', clientId);
|
|
157
159
|
authorize.searchParams.set('redirect_uri', redirectUri);
|
|
158
160
|
authorize.searchParams.set('response_type', 'code');
|
|
161
|
+
authorize.searchParams.set('resource', resource);
|
|
159
162
|
authorize.searchParams.set('state', next.state);
|
|
160
163
|
authorize.searchParams.set('code_challenge_method', 'S256');
|
|
161
164
|
authorize.searchParams.set('code_challenge', base64url(new Uint8Array(digest)));
|