@cat-factory/app 0.228.1 → 0.229.0

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.
@@ -0,0 +1,103 @@
1
+ <script setup lang="ts">
2
+ import { onMounted, ref } from 'vue'
3
+
4
+ // Where a vendor's authorization server sends the operator's browser back to after they approve a
5
+ // remote MCP tool server (`/mcp-oauth-callback?code=…&state=…`).
6
+ //
7
+ // A page in the APP rather than a route on the backend, which is the security shape of this flow
8
+ // rather than a routing preference: a redirect is a third-party navigation carrying no bearer
9
+ // token, so a backend receiver could never tell WHO was completing the grant. This page re-presents
10
+ // the two values over the authenticated API, where the session, the "same user who started it"
11
+ // binding and the `secrets.manage` re-check all actually run.
12
+ //
13
+ // It is NOT a public route (unlike the password reset beside it): an expired session renders the
14
+ // login screen on this same URL, and once the operator signs in the query string is still here and
15
+ // the grant completes. That is the correct behaviour, not a gap.
16
+
17
+ const api = useApi()
18
+ const { t } = useI18n()
19
+
20
+ const state = ref<'working' | 'done' | 'failed'>('working')
21
+ const detail = ref<string | null>(null)
22
+ const serverId = ref<string | null>(null)
23
+
24
+ function query(name: string): string {
25
+ if (typeof window === 'undefined') return ''
26
+ return new URLSearchParams(window.location.search).get(name) ?? ''
27
+ }
28
+
29
+ onMounted(async () => {
30
+ // An authorization server that REFUSED reports it here rather than on the token endpoint, so the
31
+ // operator's own "Deny" and a misconfigured client both arrive as this. Named rather than folded
32
+ // into "no code": one is nothing to fix and the other is the client registration.
33
+ const denied = query('error')
34
+ if (denied) {
35
+ state.value = 'failed'
36
+ detail.value = query('error_description') || denied
37
+ return
38
+ }
39
+ const code = query('code')
40
+ const sealed = query('state')
41
+ if (!code || !sealed) {
42
+ state.value = 'failed'
43
+ detail.value = t('settings.toolServers.oauth.callback.missingParams')
44
+ return
45
+ }
46
+ try {
47
+ const result = await api.completeToolServerOAuth({ code, state: sealed })
48
+ serverId.value = result.serverId
49
+ state.value = 'done'
50
+ } catch (e) {
51
+ state.value = 'failed'
52
+ detail.value =
53
+ (e as { data?: { error?: { message?: string } } })?.data?.error?.message ??
54
+ t('settings.toolServers.oauth.callback.failed')
55
+ }
56
+ })
57
+
58
+ function backToApp() {
59
+ if (typeof window !== 'undefined') window.location.assign('/')
60
+ }
61
+ </script>
62
+
63
+ <template>
64
+ <div
65
+ class="flex h-screen w-screen items-center justify-center bg-slate-950 text-slate-100"
66
+ data-testid="mcp-oauth-callback"
67
+ >
68
+ <div
69
+ class="w-full max-w-sm rounded-xl border border-slate-800 bg-slate-900/80 p-8 text-center backdrop-blur"
70
+ >
71
+ <template v-if="state === 'working'">
72
+ <UIcon name="i-lucide-loader" class="mx-auto mb-3 h-10 w-10 animate-spin text-indigo-400" />
73
+ <h1 class="mb-1 text-lg font-semibold text-white">
74
+ {{ t('settings.toolServers.oauth.callback.working') }}
75
+ </h1>
76
+ </template>
77
+
78
+ <template v-else-if="state === 'done'">
79
+ <UIcon name="i-lucide-check-circle" class="mx-auto mb-3 h-10 w-10 text-emerald-400" />
80
+ <h1 class="mb-1 text-lg font-semibold text-white" data-testid="mcp-oauth-callback-done">
81
+ {{ t('settings.toolServers.oauth.callback.done', { server: serverId }) }}
82
+ </h1>
83
+ <p class="mb-6 text-sm text-slate-400">
84
+ {{ t('settings.toolServers.oauth.callback.doneHint') }}
85
+ </p>
86
+ <UButton block color="primary" @click="backToApp">
87
+ {{ t('settings.toolServers.oauth.callback.back') }}
88
+ </UButton>
89
+ </template>
90
+
91
+ <template v-else>
92
+ <UIcon name="i-lucide-alert-triangle" class="mx-auto mb-3 h-10 w-10 text-red-400" />
93
+ <h1 class="mb-1 text-lg font-semibold text-white" data-testid="mcp-oauth-callback-failed">
94
+ {{ t('settings.toolServers.oauth.callback.failedTitle') }}
95
+ </h1>
96
+ <p class="mb-6 text-sm break-words text-slate-400">{{ detail }}</p>
97
+ <UButton block color="neutral" variant="subtle" @click="backToApp">
98
+ {{ t('settings.toolServers.oauth.callback.back') }}
99
+ </UButton>
100
+ </template>
101
+ </div>
102
+ </div>
103
+ </template>
@@ -40,6 +40,8 @@ const STATUS_LABELS = computed<Record<ToolServerProbeStatus, string>>(() => ({
40
40
  ok: t('settings.toolServers.status.ok'),
41
41
  credentials_missing: t('settings.toolServers.status.credentialsMissing'),
42
42
  credential_refused: t('settings.toolServers.status.credentialRefused'),
43
+ oauth_not_connected: t('settings.toolServers.status.oauthNotConnected'),
44
+ oauth_token_failed: t('settings.toolServers.status.oauthTokenFailed'),
43
45
  unreachable: t('settings.toolServers.status.unreachable'),
44
46
  http_error: t('settings.toolServers.status.httpError'),
45
47
  protocol_error: t('settings.toolServers.status.protocolError'),
@@ -53,6 +55,36 @@ const NOT_PROBEABLE_LABELS = computed<Record<ToolServerNotProbeableReason, strin
53
55
 
54
56
  const servers = computed<ToolServerView[]>(() => store.view?.servers ?? [])
55
57
 
58
+ /**
59
+ * Whether a row offers the Connect / Disconnect pair.
60
+ *
61
+ * Only the INTERACTIVE grant does. A `client_credentials` declaration authenticates as the
62
+ * deployment's own client and mints its token on the first dispatch that needs one, so there is
63
+ * nothing for a person to authorise and a button would promise an action that does not exist.
64
+ */
65
+ function isInteractive(server: ToolServerView): boolean {
66
+ return server.oauth?.grant === 'authorization_code'
67
+ }
68
+
69
+ async function connect(id: string) {
70
+ try {
71
+ await store.connectOAuth(id)
72
+ } catch (e) {
73
+ // Everything that can refuse does so BEFORE the browser leaves the app: an unconfigured
74
+ // redirect URL, a deployment with no grant store, an authorization server that publishes no
75
+ // metadata. Each carries a `details.reason` the shared funnel maps to translated copy.
76
+ present(e, 'settings.toolServers.toast.connectFailed')
77
+ }
78
+ }
79
+
80
+ async function disconnect(id: string) {
81
+ try {
82
+ await store.disconnectOAuth(id)
83
+ } catch (e) {
84
+ present(e, 'settings.toolServers.toast.disconnectFailed')
85
+ }
86
+ }
87
+
56
88
  function resultFor(id: string) {
57
89
  return store.results[id]
58
90
  }
@@ -138,6 +170,85 @@ async function runProbe(id: string) {
138
170
  }}
139
171
  </p>
140
172
 
173
+ <!-- OAuth. Absent for a server that authenticates with a static credential, so the Connect
174
+ affordance never appears on a row it does not apply to. `connected` and `lastError` are
175
+ rendered TOGETHER rather than as alternatives: a grant that is on file and no longer
176
+ producing tokens is exactly the state that reads as working and is not. -->
177
+ <div
178
+ v-if="server.oauth"
179
+ class="space-y-1 rounded-md border border-slate-800 bg-slate-900/40 p-2"
180
+ :data-testid="`tool-server-oauth-${server.id}`"
181
+ >
182
+ <div class="flex flex-wrap items-center gap-2">
183
+ <UBadge
184
+ :color="server.oauth.connected ? 'success' : 'neutral'"
185
+ variant="soft"
186
+ size="sm"
187
+ :data-testid="`tool-server-oauth-state-${server.id}`"
188
+ >
189
+ {{
190
+ server.oauth.connected
191
+ ? t('settings.toolServers.oauth.connected')
192
+ : isInteractive(server)
193
+ ? t('settings.toolServers.oauth.notConnected')
194
+ : t('settings.toolServers.oauth.machineGrant')
195
+ }}
196
+ </UBadge>
197
+ <span v-if="server.oauth.connectedBy" class="text-[11px] text-slate-400">
198
+ {{ t('settings.toolServers.oauth.connectedBy', { user: server.oauth.connectedBy }) }}
199
+ </span>
200
+ </div>
201
+
202
+ <p v-if="server.oauth.scopes?.length" class="text-[11px] text-slate-400">
203
+ {{ t('settings.toolServers.oauth.scopes', { scopes: server.oauth.scopes.join(', ') }) }}
204
+ </p>
205
+ <!-- A grant with no refresh token works until its access token expires and then needs
206
+ granting again by hand. Said BEFORE it happens, which is the only time it is useful. -->
207
+ <p
208
+ v-if="server.oauth.connected && server.oauth.refreshable === false"
209
+ class="text-[11px] text-amber-400"
210
+ >
211
+ {{ t('settings.toolServers.oauth.notRefreshable') }}
212
+ </p>
213
+ <p
214
+ v-if="server.oauth.lastError"
215
+ class="text-[11px] text-red-400"
216
+ :data-testid="`tool-server-oauth-error-${server.id}`"
217
+ >
218
+ {{ t('settings.toolServers.oauth.lastError', { detail: server.oauth.lastError }) }}
219
+ </p>
220
+
221
+ <div v-if="isInteractive(server)" class="flex flex-wrap items-center gap-2 pt-1">
222
+ <UButton
223
+ size="xs"
224
+ variant="subtle"
225
+ icon="i-lucide-link"
226
+ :loading="store.connecting === server.id"
227
+ :disabled="store.connecting !== null"
228
+ :data-testid="`tool-server-connect-${server.id}`"
229
+ @click="connect(server.id)"
230
+ >
231
+ {{
232
+ server.oauth.connected
233
+ ? t('settings.toolServers.oauth.reconnect')
234
+ : t('settings.toolServers.oauth.connect')
235
+ }}
236
+ </UButton>
237
+ <UButton
238
+ v-if="server.oauth.connected"
239
+ size="xs"
240
+ variant="ghost"
241
+ color="error"
242
+ :loading="store.connecting === server.id"
243
+ :disabled="store.connecting !== null"
244
+ :data-testid="`tool-server-disconnect-${server.id}`"
245
+ @click="disconnect(server.id)"
246
+ >
247
+ {{ t('settings.toolServers.oauth.disconnect') }}
248
+ </UButton>
249
+ </div>
250
+ </div>
251
+
141
252
  <div class="flex flex-wrap items-center gap-2 pt-1">
142
253
  <UButton
143
254
  v-if="server.probeable"
@@ -1,4 +1,10 @@
1
- import { listToolServersContract, probeToolServerContract } from '@cat-factory/contracts'
1
+ import {
2
+ completeToolServerOAuthContract,
3
+ disconnectToolServerOAuthContract,
4
+ listToolServersContract,
5
+ probeToolServerContract,
6
+ startToolServerOAuthContract,
7
+ } from '@cat-factory/contracts'
2
8
  import type { ApiContext } from './context'
3
9
 
4
10
  /**
@@ -17,5 +23,21 @@ export function toolServersApi({ send, ws }: ApiContext) {
17
23
 
18
24
  probeToolServer: (workspaceId: string, id: string) =>
19
25
  send(probeToolServerContract, { pathPrefix: ws(workspaceId), pathParams: { id } }),
26
+
27
+ // Begin an interactive OAuth grant: answers with the VENDOR's authorization URL for the
28
+ // operator's browser to follow, rather than redirecting, since a redirect from a `fetch` lands
29
+ // in a cross-origin document this app cannot observe.
30
+ startToolServerOAuth: (workspaceId: string, id: string) =>
31
+ send(startToolServerOAuthContract, { pathPrefix: ws(workspaceId), pathParams: { id } }),
32
+
33
+ disconnectToolServerOAuth: (workspaceId: string, id: string) =>
34
+ send(disconnectToolServerOAuthContract, { pathPrefix: ws(workspaceId), pathParams: { id } }),
35
+
36
+ // Finish a grant with what the vendor's redirect carried. NOT workspace-prefixed: the board is
37
+ // sealed into the `state`, so the caller does not know it and could not be trusted with it
38
+ // anyway. This is the request that makes the flow's session, user binding and permission
39
+ // re-check enforceable, which a vendor's redirect landing on the backend never could.
40
+ completeToolServerOAuth: (body: { code: string; state: string }) =>
41
+ send(completeToolServerOAuthContract, { body }),
20
42
  }
21
43
  }
@@ -0,0 +1,7 @@
1
+ <script setup lang="ts">
2
+ import McpOAuthCallbackScreen from '~/components/settings/McpOAuthCallbackScreen.vue'
3
+ </script>
4
+
5
+ <template>
6
+ <McpOAuthCallbackScreen />
7
+ </template>
@@ -25,6 +25,9 @@ export const useToolServersStore = defineStore('toolServers', () => {
25
25
  // operator just asked for.
26
26
  const results = ref<Record<string, ToolServerProbeResult>>({})
27
27
  const probing = ref<string | null>(null)
28
+ // The server whose OAuth grant is being connected or disconnected, so one row's button spins and
29
+ // the rest stay clickable — the same shape `probing` has, and for the same reason.
30
+ const connecting = ref<string | null>(null)
28
31
  const loading = ref(false)
29
32
  // The backend's two definitive refusals: no `secrets.manage` (403), and — unlike the credential
30
33
  // store — never a 503, since the inventory needs no encryption key to project a registry. `null`
@@ -108,5 +111,58 @@ export const useToolServersStore = defineStore('toolServers', () => {
108
111
  }
109
112
  }
110
113
 
111
- return { view, results, probing, loading, available, hasSurface, load, ensureLoaded, probe }
114
+ /**
115
+ * Start an OAuth grant and hand the browser to the vendor.
116
+ *
117
+ * A full-page navigation rather than a popup: the operator has to sign in at a third party, and a
118
+ * popup is what a browser blocks and a password manager cannot fill. Nothing is stored here — the
119
+ * vendor redirects back to `/mcp-oauth-callback`, which finishes the grant over the authenticated
120
+ * API and returns here, so the connection state comes from the row rather than from anything this
121
+ * store guessed across a navigation that leaves the app entirely.
122
+ */
123
+ async function connectOAuth(id: string) {
124
+ const ws = useWorkspaceStore()
125
+ connecting.value = id
126
+ try {
127
+ const { url } = await api.startToolServerOAuth(ws.requireId(), id)
128
+ window.location.href = url
129
+ } finally {
130
+ connecting.value = null
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Drop the workspace's grant, then re-read so the row's state comes from the backend rather than
136
+ * from an optimistic edit: a disconnect is the one action whose whole point is that the server
137
+ * stops being usable, and a row that looked connected for another second would be the misreport
138
+ * this panel exists to prevent.
139
+ */
140
+ async function disconnectOAuth(id: string) {
141
+ const ws = useWorkspaceStore()
142
+ connecting.value = id
143
+ try {
144
+ await api.disconnectToolServerOAuth(ws.requireId(), id)
145
+ // The stored probe result described a server this board could still reach. It cannot now.
146
+ const { [id]: _dropped, ...rest } = results.value
147
+ results.value = rest
148
+ } finally {
149
+ connecting.value = null
150
+ }
151
+ await load()
152
+ }
153
+
154
+ return {
155
+ view,
156
+ results,
157
+ probing,
158
+ connecting,
159
+ loading,
160
+ available,
161
+ hasSurface,
162
+ load,
163
+ ensureLoaded,
164
+ probe,
165
+ connectOAuth,
166
+ disconnectOAuth,
167
+ }
112
168
  })
@@ -9,6 +9,8 @@ export type {
9
9
  ToolServerAllowedToolsCheck,
10
10
  ToolServerCredential,
11
11
  ToolServerNotProbeableReason,
12
+ ToolServerOAuthGrant,
13
+ ToolServerOAuthStatus,
12
14
  ToolServerProbeResult,
13
15
  ToolServerProbeStatus,
14
16
  ToolServerTransport,
@@ -624,6 +624,27 @@
624
624
  "servableHarnessesNone": "Keine Agenten-CLI kann diesen Transport bedienen, daher greift dieser Server in keinem Lauf.",
625
625
  "allowedTools": "Eingeschränkt auf: {tools}",
626
626
  "credentials": "Zugangsdaten: {keys}",
627
+ "oauth": {
628
+ "connected": "Verbunden",
629
+ "notConnected": "Nicht verbunden",
630
+ "machineGrant": "Meldet sich als dieses Deployment an",
631
+ "connectedBy": "Verbunden von {user}",
632
+ "scopes": "Gewährt: {scopes}",
633
+ "notRefreshable": "Der Anbieter hat kein Refresh-Token ausgestellt, daher muss diese Verbindung neu hergestellt werden, sobald das Zugriffstoken abläuft.",
634
+ "lastError": "Die letzte Token-Erneuerung ist fehlgeschlagen: {detail}",
635
+ "connect": "Verbinden",
636
+ "reconnect": "Neu verbinden",
637
+ "disconnect": "Trennen",
638
+ "callback": {
639
+ "working": "Verbindung wird abgeschlossen …",
640
+ "done": "Mit {server} verbunden",
641
+ "doneHint": "Die Läufe dieses Boards können den Werkzeugserver jetzt mit dem Konto nutzen, mit dem Sie sich angemeldet haben.",
642
+ "back": "Zurück zur App",
643
+ "failedTitle": "Die Verbindung konnte nicht abgeschlossen werden",
644
+ "failed": "Die Autorisierung konnte nicht abgeschlossen werden. Starten Sie die Verbindung im Infrastruktur-Fenster erneut.",
645
+ "missingParams": "In diesem Link fehlen die vom Anbieter zurückgesendeten Werte, daher gibt es nichts abzuschließen. Starten Sie die Verbindung erneut."
646
+ }
647
+ },
627
648
  "test": "Testen",
628
649
  "notProbeable": {
629
650
  "stdio": "Läuft im Container des Agenten und kann von hier aus nicht getestet werden.",
@@ -634,6 +655,8 @@
634
655
  "ok": "Hat geantwortet",
635
656
  "credentialsMissing": "Keine Zugangsdaten",
636
657
  "credentialRefused": "Zugangsdaten abgelehnt",
658
+ "oauthNotConnected": "Nicht verbunden",
659
+ "oauthTokenFailed": "Verbindung funktioniert nicht mehr",
637
660
  "unreachable": "Keine Antwort",
638
661
  "httpError": "Anfrage abgewiesen",
639
662
  "protocolError": "Kein MCP-Server",
@@ -650,7 +673,9 @@
650
673
  "hideDetails": "Details verbergen",
651
674
  "toast": {
652
675
  "loadFailed": "Die Werkzeugserver konnten nicht geladen werden",
653
- "probeFailed": "Der Werkzeugserver konnte nicht getestet werden"
676
+ "probeFailed": "Der Werkzeugserver konnte nicht getestet werden",
677
+ "connectFailed": "Die Verbindung konnte nicht gestartet werden",
678
+ "disconnectFailed": "Der Werkzeugserver konnte nicht getrennt werden"
654
679
  }
655
680
  },
656
681
  "capabilityCredentials": {
@@ -3125,6 +3125,27 @@
3125
3125
  "servableHarnessesNone": "No agent CLI can serve this transport, so this server never applies to any run.",
3126
3126
  "allowedTools": "Narrowed to: {tools}",
3127
3127
  "credentials": "Credentials: {keys}",
3128
+ "oauth": {
3129
+ "connected": "Connected",
3130
+ "notConnected": "Not connected",
3131
+ "machineGrant": "Signs in as this deployment",
3132
+ "connectedBy": "Connected by {user}",
3133
+ "scopes": "Granted: {scopes}",
3134
+ "notRefreshable": "The vendor issued no refresh token, so this connection has to be made again once its access token expires.",
3135
+ "lastError": "The last token renewal failed: {detail}",
3136
+ "connect": "Connect",
3137
+ "reconnect": "Reconnect",
3138
+ "disconnect": "Disconnect",
3139
+ "callback": {
3140
+ "working": "Finishing the connection…",
3141
+ "done": "Connected to {server}",
3142
+ "doneHint": "This board's runs can now use the tool server as the account you signed in with.",
3143
+ "back": "Back to the app",
3144
+ "failedTitle": "The connection could not be finished",
3145
+ "failed": "The authorization could not be completed. Start the connection again from the Infrastructure window.",
3146
+ "missingParams": "This link is missing the values the vendor sends back, so there is nothing to complete. Start the connection again."
3147
+ }
3148
+ },
3128
3149
  "test": "Test",
3129
3150
  "notProbeable": {
3130
3151
  "stdio": "Runs inside the agent's container, so it cannot be tested from here.",
@@ -3135,6 +3156,8 @@
3135
3156
  "ok": "Answered",
3136
3157
  "credentialsMissing": "No credential",
3137
3158
  "credentialRefused": "Credential refused",
3159
+ "oauthNotConnected": "Not connected",
3160
+ "oauthTokenFailed": "Connection stopped working",
3138
3161
  "unreachable": "No answer",
3139
3162
  "httpError": "Rejected the request",
3140
3163
  "protocolError": "Not an MCP server",
@@ -3151,7 +3174,9 @@
3151
3174
  "hideDetails": "Hide details",
3152
3175
  "toast": {
3153
3176
  "loadFailed": "Could not load the tool servers",
3154
- "probeFailed": "Could not test the tool server"
3177
+ "probeFailed": "Could not test the tool server",
3178
+ "connectFailed": "Could not start the connection",
3179
+ "disconnectFailed": "Could not disconnect the tool server"
3155
3180
  }
3156
3181
  },
3157
3182
  "capabilityCredentials": {
@@ -2874,6 +2874,27 @@
2874
2874
  "servableHarnessesNone": "Ninguna CLI de agente puede servir este transporte, así que este servidor nunca se aplica a una ejecución.",
2875
2875
  "allowedTools": "Limitado a: {tools}",
2876
2876
  "credentials": "Credenciales: {keys}",
2877
+ "oauth": {
2878
+ "connected": "Conectado",
2879
+ "notConnected": "Sin conexión",
2880
+ "machineGrant": "Se identifica como este despliegue",
2881
+ "connectedBy": "Conectado por {user}",
2882
+ "scopes": "Concedido: {scopes}",
2883
+ "notRefreshable": "El proveedor no emitió un token de actualización, así que habrá que volver a conectar cuando caduque el token de acceso.",
2884
+ "lastError": "La última renovación del token falló: {detail}",
2885
+ "connect": "Conectar",
2886
+ "reconnect": "Volver a conectar",
2887
+ "disconnect": "Desconectar",
2888
+ "callback": {
2889
+ "working": "Finalizando la conexión…",
2890
+ "done": "Conectado a {server}",
2891
+ "doneHint": "Las ejecuciones de este tablero ya pueden usar el servidor de herramientas con la cuenta con la que iniciaste sesión.",
2892
+ "back": "Volver a la aplicación",
2893
+ "failedTitle": "No se pudo finalizar la conexión",
2894
+ "failed": "No se pudo completar la autorización. Vuelve a iniciar la conexión desde la ventana de Infraestructura.",
2895
+ "missingParams": "A este enlace le faltan los valores que devuelve el proveedor, así que no hay nada que completar. Vuelve a iniciar la conexión."
2896
+ }
2897
+ },
2877
2898
  "test": "Probar",
2878
2899
  "notProbeable": {
2879
2900
  "stdio": "Se ejecuta dentro del contenedor del agente, así que no puede probarse desde aquí.",
@@ -2884,6 +2905,8 @@
2884
2905
  "ok": "Respondió",
2885
2906
  "credentialsMissing": "Sin credencial",
2886
2907
  "credentialRefused": "Credencial rechazada",
2908
+ "oauthNotConnected": "Sin conexión",
2909
+ "oauthTokenFailed": "La conexión dejó de funcionar",
2887
2910
  "unreachable": "Sin respuesta",
2888
2911
  "httpError": "Rechazó la solicitud",
2889
2912
  "protocolError": "No es un servidor MCP",
@@ -2900,7 +2923,9 @@
2900
2923
  "hideDetails": "Ocultar detalles",
2901
2924
  "toast": {
2902
2925
  "loadFailed": "No se pudieron cargar los servidores de herramientas",
2903
- "probeFailed": "No se pudo probar el servidor de herramientas"
2926
+ "probeFailed": "No se pudo probar el servidor de herramientas",
2927
+ "connectFailed": "No se pudo iniciar la conexión",
2928
+ "disconnectFailed": "No se pudo desconectar el servidor de herramientas"
2904
2929
  }
2905
2930
  },
2906
2931
  "capabilityCredentials": {
@@ -2874,6 +2874,27 @@
2874
2874
  "servableHarnessesNone": "Aucune CLI d’agent ne peut servir ce transport, donc ce serveur ne s’applique à aucune exécution.",
2875
2875
  "allowedTools": "Restreint à : {tools}",
2876
2876
  "credentials": "Identifiants : {keys}",
2877
+ "oauth": {
2878
+ "connected": "Connecté",
2879
+ "notConnected": "Non connecté",
2880
+ "machineGrant": "S’authentifie en tant que ce déploiement",
2881
+ "connectedBy": "Connecté par {user}",
2882
+ "scopes": "Accordé : {scopes}",
2883
+ "notRefreshable": "Le fournisseur n’a pas émis de jeton de rafraîchissement : il faudra reconnecter dès que le jeton d’accès expirera.",
2884
+ "lastError": "Le dernier renouvellement du jeton a échoué : {detail}",
2885
+ "connect": "Connecter",
2886
+ "reconnect": "Reconnecter",
2887
+ "disconnect": "Déconnecter",
2888
+ "callback": {
2889
+ "working": "Finalisation de la connexion…",
2890
+ "done": "Connecté à {server}",
2891
+ "doneHint": "Les exécutions de ce tableau peuvent désormais utiliser le serveur d’outils avec le compte auquel vous vous êtes connecté.",
2892
+ "back": "Retour à l’application",
2893
+ "failedTitle": "La connexion n’a pas pu être finalisée",
2894
+ "failed": "L’autorisation n’a pas pu être menée à bien. Relancez la connexion depuis la fenêtre Infrastructure.",
2895
+ "missingParams": "Il manque à ce lien les valeurs renvoyées par le fournisseur, il n’y a donc rien à finaliser. Relancez la connexion."
2896
+ }
2897
+ },
2877
2898
  "test": "Tester",
2878
2899
  "notProbeable": {
2879
2900
  "stdio": "Il s’exécute dans le conteneur de l’agent et ne peut donc pas être testé d’ici.",
@@ -2884,6 +2905,8 @@
2884
2905
  "ok": "A répondu",
2885
2906
  "credentialsMissing": "Aucun identifiant",
2886
2907
  "credentialRefused": "Identifiant refusé",
2908
+ "oauthNotConnected": "Non connecté",
2909
+ "oauthTokenFailed": "La connexion ne fonctionne plus",
2887
2910
  "unreachable": "Aucune réponse",
2888
2911
  "httpError": "Requête rejetée",
2889
2912
  "protocolError": "Pas un serveur MCP",
@@ -2900,7 +2923,9 @@
2900
2923
  "hideDetails": "Masquer les détails",
2901
2924
  "toast": {
2902
2925
  "loadFailed": "Impossible de charger les serveurs d’outils",
2903
- "probeFailed": "Impossible de tester le serveur d’outils"
2926
+ "probeFailed": "Impossible de tester le serveur d’outils",
2927
+ "connectFailed": "Impossible de démarrer la connexion",
2928
+ "disconnectFailed": "Impossible de déconnecter le serveur d’outils"
2904
2929
  }
2905
2930
  },
2906
2931
  "capabilityCredentials": {
@@ -3015,6 +3015,27 @@
3015
3015
  "servableHarnessesNone": "אף CLI של סוכן אינו יכול לשרת תעבורה זו, ולכן שרת זה לא חל על שום הרצה.",
3016
3016
  "allowedTools": "מוגבל אל: {tools}",
3017
3017
  "credentials": "פרטי הזדהות: {keys}",
3018
+ "oauth": {
3019
+ "connected": "מחובר",
3020
+ "notConnected": "לא מחובר",
3021
+ "machineGrant": "מזדהה בשם פריסה זו",
3022
+ "connectedBy": "חובר על ידי {user}",
3023
+ "scopes": "ניתנו ההרשאות: {scopes}",
3024
+ "notRefreshable": "הספק לא הנפיק אסימון רענון, ולכן יש לחבר מחדש כאשר אסימון הגישה יפוג.",
3025
+ "lastError": "חידוש האסימון האחרון נכשל: {detail}",
3026
+ "connect": "התחברות",
3027
+ "reconnect": "התחברות מחדש",
3028
+ "disconnect": "ניתוק",
3029
+ "callback": {
3030
+ "working": "מסיים את החיבור…",
3031
+ "done": "מחובר אל {server}",
3032
+ "doneHint": "ההרצות של לוח זה יכולות מעכשיו להשתמש בשרת הכלים בחשבון שאיתו נכנסת.",
3033
+ "back": "חזרה לאפליקציה",
3034
+ "failedTitle": "לא ניתן היה לסיים את החיבור",
3035
+ "failed": "לא ניתן היה להשלים את ההרשאה. התחל את החיבור מחדש מחלון התשתית.",
3036
+ "missingParams": "בקישור הזה חסרים הערכים שהספק מחזיר, ולכן אין מה להשלים. התחל את החיבור מחדש."
3037
+ }
3038
+ },
3018
3039
  "test": "בדיקה",
3019
3040
  "notProbeable": {
3020
3041
  "stdio": "רץ בתוך המכולה של הסוכן, ולכן לא ניתן לבדוק אותו מכאן.",
@@ -3025,6 +3046,8 @@
3025
3046
  "ok": "השיב",
3026
3047
  "credentialsMissing": "אין פרטי הזדהות",
3027
3048
  "credentialRefused": "פרטי ההזדהות נדחו",
3049
+ "oauthNotConnected": "לא מחובר",
3050
+ "oauthTokenFailed": "החיבור הפסיק לעבוד",
3028
3051
  "unreachable": "אין תשובה",
3029
3052
  "httpError": "דחה את הבקשה",
3030
3053
  "protocolError": "אינו שרת MCP",
@@ -3041,7 +3064,9 @@
3041
3064
  "hideDetails": "הסתרת פרטים",
3042
3065
  "toast": {
3043
3066
  "loadFailed": "לא ניתן לטעון את שרתי הכלים",
3044
- "probeFailed": "לא ניתן לבדוק את שרת הכלים"
3067
+ "probeFailed": "לא ניתן לבדוק את שרת הכלים",
3068
+ "connectFailed": "לא ניתן להתחיל את החיבור",
3069
+ "disconnectFailed": "לא ניתן לנתק את שרת הכלים"
3045
3070
  }
3046
3071
  },
3047
3072
  "capabilityCredentials": {
@@ -624,6 +624,27 @@
624
624
  "servableHarnessesNone": "Nessuna CLI di agente può servire questo trasporto, quindi questo server non si applica a nessuna esecuzione.",
625
625
  "allowedTools": "Limitato a: {tools}",
626
626
  "credentials": "Credenziali: {keys}",
627
+ "oauth": {
628
+ "connected": "Collegato",
629
+ "notConnected": "Non collegato",
630
+ "machineGrant": "Si autentica come questo deployment",
631
+ "connectedBy": "Collegato da {user}",
632
+ "scopes": "Concesso: {scopes}",
633
+ "notRefreshable": "Il fornitore non ha emesso un token di aggiornamento, quindi il collegamento andrà rifatto alla scadenza del token di accesso.",
634
+ "lastError": "L’ultimo rinnovo del token non è riuscito: {detail}",
635
+ "connect": "Collega",
636
+ "reconnect": "Ricollega",
637
+ "disconnect": "Scollega",
638
+ "callback": {
639
+ "working": "Completamento della connessione…",
640
+ "done": "Collegato a {server}",
641
+ "doneHint": "Le esecuzioni di questa lavagna possono ora usare il server di strumenti con l’account con cui hai effettuato l’accesso.",
642
+ "back": "Torna all’app",
643
+ "failedTitle": "Non è stato possibile completare la connessione",
644
+ "failed": "Non è stato possibile completare l’autorizzazione. Riavvia la connessione dalla finestra Infrastruttura.",
645
+ "missingParams": "A questo link mancano i valori restituiti dal fornitore, quindi non c’è nulla da completare. Riavvia la connessione."
646
+ }
647
+ },
627
648
  "test": "Prova",
628
649
  "notProbeable": {
629
650
  "stdio": "Gira dentro il container dell’agente, quindi non può essere provato da qui.",
@@ -634,6 +655,8 @@
634
655
  "ok": "Ha risposto",
635
656
  "credentialsMissing": "Nessuna credenziale",
636
657
  "credentialRefused": "Credenziale rifiutata",
658
+ "oauthNotConnected": "Non collegato",
659
+ "oauthTokenFailed": "Il collegamento ha smesso di funzionare",
637
660
  "unreachable": "Nessuna risposta",
638
661
  "httpError": "Richiesta respinta",
639
662
  "protocolError": "Non è un server MCP",
@@ -650,7 +673,9 @@
650
673
  "hideDetails": "Nascondi dettagli",
651
674
  "toast": {
652
675
  "loadFailed": "Impossibile caricare i server di strumenti",
653
- "probeFailed": "Impossibile provare il server di strumenti"
676
+ "probeFailed": "Impossibile provare il server di strumenti",
677
+ "connectFailed": "Impossibile avviare il collegamento",
678
+ "disconnectFailed": "Impossibile scollegare il server di strumenti"
654
679
  }
655
680
  },
656
681
  "capabilityCredentials": {
@@ -3015,6 +3015,27 @@
3015
3015
  "servableHarnessesNone": "このトランスポートを扱えるエージェント CLI がないため、このサーバーはどの実行にも適用されません。",
3016
3016
  "allowedTools": "許可されたツール: {tools}",
3017
3017
  "credentials": "認証情報: {keys}",
3018
+ "oauth": {
3019
+ "connected": "接続済み",
3020
+ "notConnected": "未接続",
3021
+ "machineGrant": "このデプロイとしてサインインします",
3022
+ "connectedBy": "{user} が接続しました",
3023
+ "scopes": "許可されたスコープ: {scopes}",
3024
+ "notRefreshable": "プロバイダーがリフレッシュトークンを発行しなかったため、アクセストークンの期限が切れたら接続をやり直す必要があります。",
3025
+ "lastError": "直近のトークン更新に失敗しました: {detail}",
3026
+ "connect": "接続",
3027
+ "reconnect": "再接続",
3028
+ "disconnect": "接続を解除",
3029
+ "callback": {
3030
+ "working": "接続を完了しています…",
3031
+ "done": "{server} に接続しました",
3032
+ "doneHint": "このボードの実行では、サインインしたアカウントとしてツールサーバーを利用できるようになりました。",
3033
+ "back": "アプリに戻る",
3034
+ "failedTitle": "接続を完了できませんでした",
3035
+ "failed": "認可を完了できませんでした。インフラストラクチャ ウィンドウから接続をやり直してください。",
3036
+ "missingParams": "このリンクにはプロバイダーが返す値が含まれていないため、完了できる処理がありません。接続をやり直してください。"
3037
+ }
3038
+ },
3018
3039
  "test": "テスト",
3019
3040
  "notProbeable": {
3020
3041
  "stdio": "エージェントのコンテナ内で動作するため、ここからはテストできません。",
@@ -3025,6 +3046,8 @@
3025
3046
  "ok": "応答あり",
3026
3047
  "credentialsMissing": "認証情報なし",
3027
3048
  "credentialRefused": "認証情報を拒否",
3049
+ "oauthNotConnected": "未接続",
3050
+ "oauthTokenFailed": "接続が機能しなくなりました",
3028
3051
  "unreachable": "応答なし",
3029
3052
  "httpError": "リクエストを拒否",
3030
3053
  "protocolError": "MCP サーバーではない",
@@ -3041,7 +3064,9 @@
3041
3064
  "hideDetails": "詳細を隠す",
3042
3065
  "toast": {
3043
3066
  "loadFailed": "ツールサーバーを読み込めませんでした",
3044
- "probeFailed": "ツールサーバーをテストできませんでした"
3067
+ "probeFailed": "ツールサーバーをテストできませんでした",
3068
+ "connectFailed": "接続を開始できませんでした",
3069
+ "disconnectFailed": "ツールサーバーの接続を解除できませんでした"
3045
3070
  }
3046
3071
  },
3047
3072
  "capabilityCredentials": {
@@ -2874,6 +2874,27 @@
2874
2874
  "servableHarnessesNone": "Żadne CLI agenta nie obsługuje tego transportu, więc ten serwer nie dotyczy żadnego przebiegu.",
2875
2875
  "allowedTools": "Zawężone do: {tools}",
2876
2876
  "credentials": "Dane uwierzytelniające: {keys}",
2877
+ "oauth": {
2878
+ "connected": "Połączono",
2879
+ "notConnected": "Niepołączony",
2880
+ "machineGrant": "Loguje się jako to wdrożenie",
2881
+ "connectedBy": "Połączone przez {user}",
2882
+ "scopes": "Przyznano: {scopes}",
2883
+ "notRefreshable": "Dostawca nie wydał tokenu odświeżania, więc połączenie trzeba będzie nawiązać ponownie, gdy token dostępu wygaśnie.",
2884
+ "lastError": "Ostatnie odnowienie tokenu nie powiodło się: {detail}",
2885
+ "connect": "Połącz",
2886
+ "reconnect": "Połącz ponownie",
2887
+ "disconnect": "Rozłącz",
2888
+ "callback": {
2889
+ "working": "Kończenie połączenia…",
2890
+ "done": "Połączono z {server}",
2891
+ "doneHint": "Uruchomienia tej tablicy mogą teraz korzystać z serwera narzędzi na koncie, na które się zalogowano.",
2892
+ "back": "Powrót do aplikacji",
2893
+ "failedTitle": "Nie udało się zakończyć połączenia",
2894
+ "failed": "Nie udało się dokończyć autoryzacji. Rozpocznij połączenie ponownie w oknie Infrastruktura.",
2895
+ "missingParams": "W tym odnośniku brakuje wartości zwracanych przez dostawcę, więc nie ma czego kończyć. Rozpocznij połączenie ponownie."
2896
+ }
2897
+ },
2877
2898
  "test": "Testuj",
2878
2899
  "notProbeable": {
2879
2900
  "stdio": "Działa w kontenerze agenta, więc nie można go przetestować z tego miejsca.",
@@ -2884,6 +2905,8 @@
2884
2905
  "ok": "Odpowiedział",
2885
2906
  "credentialsMissing": "Brak danych uwierzytelniających",
2886
2907
  "credentialRefused": "Dane uwierzytelniające odrzucone",
2908
+ "oauthNotConnected": "Niepołączony",
2909
+ "oauthTokenFailed": "Połączenie przestało działać",
2887
2910
  "unreachable": "Brak odpowiedzi",
2888
2911
  "httpError": "Odrzucił żądanie",
2889
2912
  "protocolError": "To nie serwer MCP",
@@ -2900,7 +2923,9 @@
2900
2923
  "hideDetails": "Ukryj szczegóły",
2901
2924
  "toast": {
2902
2925
  "loadFailed": "Nie udało się wczytać serwerów narzędzi",
2903
- "probeFailed": "Nie udało się przetestować serwera narzędzi"
2926
+ "probeFailed": "Nie udało się przetestować serwera narzędzi",
2927
+ "connectFailed": "Nie udało się rozpocząć łączenia",
2928
+ "disconnectFailed": "Nie udało się rozłączyć serwera narzędzi"
2904
2929
  }
2905
2930
  },
2906
2931
  "capabilityCredentials": {
@@ -3015,6 +3015,27 @@
3015
3015
  "servableHarnessesNone": "Hiçbir ajan CLI’si bu taşımayı sunamaz, dolayısıyla bu sunucu hiçbir çalıştırmada geçerli olmaz.",
3016
3016
  "allowedTools": "Şunlarla sınırlı: {tools}",
3017
3017
  "credentials": "Kimlik bilgileri: {keys}",
3018
+ "oauth": {
3019
+ "connected": "Bağlandı",
3020
+ "notConnected": "Bağlı değil",
3021
+ "machineGrant": "Bu dağıtım olarak oturum açar",
3022
+ "connectedBy": "{user} bağladı",
3023
+ "scopes": "Verilen izinler: {scopes}",
3024
+ "notRefreshable": "Sağlayıcı yenileme belirteci vermedi, bu yüzden erişim belirteci sona erdiğinde bağlantının yeniden kurulması gerekir.",
3025
+ "lastError": "Son belirteç yenileme başarısız oldu: {detail}",
3026
+ "connect": "Bağlan",
3027
+ "reconnect": "Yeniden bağlan",
3028
+ "disconnect": "Bağlantıyı kes",
3029
+ "callback": {
3030
+ "working": "Bağlantı tamamlanıyor…",
3031
+ "done": "{server} bağlandı",
3032
+ "doneHint": "Bu panonun çalıştırmaları artık araç sunucusunu oturum açtığınız hesapla kullanabilir.",
3033
+ "back": "Uygulamaya dön",
3034
+ "failedTitle": "Bağlantı tamamlanamadı",
3035
+ "failed": "Yetkilendirme tamamlanamadı. Bağlantıyı Altyapı penceresinden yeniden başlatın.",
3036
+ "missingParams": "Bu bağlantıda sağlayıcının geri gönderdiği değerler eksik, bu yüzden tamamlanacak bir şey yok. Bağlantıyı yeniden başlatın."
3037
+ }
3038
+ },
3018
3039
  "test": "Test et",
3019
3040
  "notProbeable": {
3020
3041
  "stdio": "Ajanın konteynerinin içinde çalışır, bu yüzden buradan test edilemez.",
@@ -3025,6 +3046,8 @@
3025
3046
  "ok": "Yanıt verdi",
3026
3047
  "credentialsMissing": "Kimlik bilgisi yok",
3027
3048
  "credentialRefused": "Kimlik bilgisi reddedildi",
3049
+ "oauthNotConnected": "Bağlı değil",
3050
+ "oauthTokenFailed": "Bağlantı çalışmayı bıraktı",
3028
3051
  "unreachable": "Yanıt yok",
3029
3052
  "httpError": "İsteği reddetti",
3030
3053
  "protocolError": "MCP sunucusu değil",
@@ -3041,7 +3064,9 @@
3041
3064
  "hideDetails": "Ayrıntıları gizle",
3042
3065
  "toast": {
3043
3066
  "loadFailed": "Araç sunucuları yüklenemedi",
3044
- "probeFailed": "Araç sunucusu test edilemedi"
3067
+ "probeFailed": "Araç sunucusu test edilemedi",
3068
+ "connectFailed": "Bağlantı başlatılamadı",
3069
+ "disconnectFailed": "Araç sunucusunun bağlantısı kesilemedi"
3045
3070
  }
3046
3071
  },
3047
3072
  "capabilityCredentials": {
@@ -2874,6 +2874,27 @@
2874
2874
  "servableHarnessesNone": "Жоден CLI агента не може обслуговувати цей транспорт, тож цей сервер не застосовується ні до якого запуску.",
2875
2875
  "allowedTools": "Звужено до: {tools}",
2876
2876
  "credentials": "Облікові дані: {keys}",
2877
+ "oauth": {
2878
+ "connected": "З’єднано",
2879
+ "notConnected": "Не під’єднано",
2880
+ "machineGrant": "Входить як це розгортання",
2881
+ "connectedBy": "З’єднав {user}",
2882
+ "scopes": "Надано: {scopes}",
2883
+ "notRefreshable": "Постачальник не видав токена оновлення, тож з’єднання доведеться створити знову, щойно мине термін дії токена доступу.",
2884
+ "lastError": "Останнє оновлення токена не вдалося: {detail}",
2885
+ "connect": "З’єднати",
2886
+ "reconnect": "З’єднати знову",
2887
+ "disconnect": "Від’єднати",
2888
+ "callback": {
2889
+ "working": "Завершення з’єднання…",
2890
+ "done": "З’єднано з {server}",
2891
+ "doneHint": "Запуски цієї дошки тепер можуть використовувати сервер інструментів під обліковим записом, у який ви увійшли.",
2892
+ "back": "Повернутися до застосунку",
2893
+ "failedTitle": "Не вдалося завершити з’єднання",
2894
+ "failed": "Не вдалося завершити авторизацію. Розпочніть з’єднання знову у вікні інфраструктури.",
2895
+ "missingParams": "У цьому посиланні бракує значень, які повертає постачальник, тож завершувати нічого. Розпочніть з’єднання знову."
2896
+ }
2897
+ },
2877
2898
  "test": "Перевірити",
2878
2899
  "notProbeable": {
2879
2900
  "stdio": "Працює всередині контейнера агента, тож звідси його перевірити неможливо.",
@@ -2884,6 +2905,8 @@
2884
2905
  "ok": "Відповів",
2885
2906
  "credentialsMissing": "Немає облікових даних",
2886
2907
  "credentialRefused": "Облікові дані відхилено",
2908
+ "oauthNotConnected": "Не під’єднано",
2909
+ "oauthTokenFailed": "З’єднання перестало працювати",
2887
2910
  "unreachable": "Немає відповіді",
2888
2911
  "httpError": "Відхилив запит",
2889
2912
  "protocolError": "Це не сервер MCP",
@@ -2900,7 +2923,9 @@
2900
2923
  "hideDetails": "Сховати деталі",
2901
2924
  "toast": {
2902
2925
  "loadFailed": "Не вдалося завантажити сервери інструментів",
2903
- "probeFailed": "Не вдалося перевірити сервер інструментів"
2926
+ "probeFailed": "Не вдалося перевірити сервер інструментів",
2927
+ "connectFailed": "Не вдалося розпочати з’єднання",
2928
+ "disconnectFailed": "Не вдалося від’єднати сервер інструментів"
2904
2929
  }
2905
2930
  },
2906
2931
  "capabilityCredentials": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.228.1",
3
+ "version": "0.229.0",
4
4
  "description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -40,7 +40,7 @@
40
40
  "valibot": "^1.4.2",
41
41
  "vue": "3.5.40",
42
42
  "wretch": "^3.0.9",
43
- "@cat-factory/contracts": "0.245.0"
43
+ "@cat-factory/contracts": "0.246.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@toad-contracts/testing": "0.3.2",