Compare commits

..

1 Commits

Author SHA1 Message Date
Luis de Dios
628a3cb50a Improve MCP section in the dashboard 2026-02-24 22:47:46 +01:00
4 changed files with 121 additions and 39 deletions

View File

@@ -8,6 +8,7 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.schema :as sm]
[app.common.time :as ct]
[app.config :as cf]
@@ -44,18 +45,25 @@
(def notification-timeout 7000)
(def ^:private schema:form
(def ^:private schema:form-access-token
[:map
[:name [::sm/text {:max 250}]]
[:expiration-date [::sm/text {:max 250}]]])
(def form-initial-data
(def ^:private schema:form-mcp-key
[:map
[:expiration-date [::sm/text {:max 250}]]])
(def form-initial-data-access-token
{:name ""
:expiration-date "never"})
(def form-initial-data-mcp-key
{:expiration-date "never"})
(mf/defc token-created*
{::mf/private true}
[{:keys [title]}]
[{:keys [title mcp-key?]}]
(let [token-created (mf/deref token-created-ref)
on-copy-to-clipboard
@@ -97,18 +105,36 @@
(tr "integrations.token-will-expire" (ct/format-inst (:expires-at token-created) "PPP"))
(tr "integrations.token-will-not-expire"))]]
(when mcp-key?
[:div {:class (stl/css :modal-content)}
[:> text* {:as "div"
:typography t/body-small
:class (stl/css :color-primary)}
(tr "integrations.info.mcp-client-config")]
[:textarea {:class (stl/css :textarea)
:wrap "off"
:rows 7
:read-only true}
(dm/str
"{\n"
" \"mcpServers\": {\n"
" \"penpot\": {\n"
" \"url\": \"" cf/mcp-server-url "?userToken=" (:token token-created "") "\"\n"
" }\n"
" }"
"\n}")]])
[:div {:class (stl/css :modal-footer)}
[:> button* {:variant "secondary"
:on-click modal/hide!}
(tr "labels.close")]]]))
(mf/defc create-token*
{::mf/private true}
[{:keys [title info mcp-key? on-created]}]
(let [form (fm/use-form
:initial form-initial-data
:schema schema:form)
:initial (if mcp-key? form-initial-data-mcp-key form-initial-data-access-token)
:schema (if mcp-key? schema:form-mcp-key schema:form-access-token))
on-error
(mf/use-fn
@@ -131,7 +157,8 @@
params (cond-> {:name (:name cdata)
:perms (:perms cdata)}
(not= "never" expiration) (assoc :expiration expiration)
(true? mcp-key?) (assoc :type "mcp"))]
(true? mcp-key?) (assoc :type "mcp"
:name "MCP key"))]
(st/emit! (du/create-access-token (with-meta params mdata))))))]
[:> fc/form* {:form form
@@ -148,13 +175,20 @@
:type :context}
info])
[:div {:class (stl/css :modal-content)}
[:> fc/form-input* {:type "text"
:auto-focus? true
:form form
:name :name
:label (tr "integrations.name.label")
:placeholder (tr "integrations.name.placeholder")}]]
(if mcp-key?
[:div {:class (stl/css :modal-content)}
[:> text* {:as "div"
:typography t/body-medium
:class (stl/css :color-secondary)}
(tr "integrations.info.mcp-server")]]
[:div {:class (stl/css :modal-content)}
[:> fc/form-input* {:type "text"
:auto-focus? true
:form form
:name :name
:label (tr "integrations.name.label")
:placeholder (tr "integrations.name.placeholder")}]])
[:div {:class (stl/css :modal-content)}
[:> text* {:as "label"
@@ -206,9 +240,9 @@
[:> create-token* {:title (tr "integrations.create-access-token.title")
:on-created on-created}])]]))
(mf/defc create-mcp-key-modal
(mf/defc generate-mcp-key-modal
{::mf/register modal/components
::mf/register-as :create-mcp-key}
::mf/register-as :generate-mcp-key}
[]
(let [created? (mf/use-state false)
@@ -222,7 +256,7 @@
(mf/use-fn
(fn []
(st/emit! (du/update-profile-props {:mcp-status true})
(ev/event {::ev/name "create-mcp-key"
(ev/event {::ev/name "generate-mcp-key"
::ev/origin "integrations"})
(ev/event {::ev/name "enable-mcp"
::ev/origin "integrations"
@@ -238,8 +272,9 @@
:icon i/close}]]
(if @created?
[:> token-created* {:title (tr "integrations.create-mcp-key.title.created")}]
[:> create-token* {:title (tr "integrations.create-mcp-key.title")
[:> token-created* {:title (tr "integrations.generate-mcp-key.title.created")
:mcp-key? true}]
[:> create-token* {:title (tr "integrations.generate-mcp-key.title")
:mcp-key? true
:on-created on-created}])]]))
@@ -277,7 +312,8 @@
:icon i/close}]]
(if @created?
[:> token-created* {:title (tr "integrations.regenerate-mcp-key.title.created")}]
[:> token-created* {:title (tr "integrations.regenerate-mcp-key.title.created")
:mcp-key? true}]
[:> create-token* {:title (tr "integrations.regenerate-mcp-key.title")
:info (tr "integrations.regenerate-mcp-key.info")
:mcp-key? true
@@ -378,7 +414,7 @@
handle-initial-mcp-status
(mf/use-fn
#(st/emit! (modal/show {:type :create-mcp-key})))
#(st/emit! (modal/show {:type :generate-mcp-key})))
handle-regenerate-mcp-key
(mf/use-fn
@@ -484,7 +520,18 @@
:typography t/body-medium
:class (stl/css :color-secondary)}
(tr "integrations.mcp-server.mcp-keys.info")]
[:div {:class (stl/css :mcp-server-notification-line)}
[:div {:class (stl/css :modal-token)}
[:> input* {:type "text"
:default-value (dm/str cf/mcp-server-url "?userToken=")
:read-only true}]
[:div {:class (stl/css :modal-token-button)}
[:> icon-button* {:variant "secondary"
:aria-label (tr "integrations.copy-token")
:on-click on-copy-to-clipboard
:icon i/clipboard}]]]
#_[:div {:class (stl/css :mcp-server-notification-line)}
[:> text* {:as "div"
:typography t/body-medium
:class (stl/css :color-primary)}

View File

@@ -8,8 +8,9 @@
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/spacing.scss" as *;
@use "ds/mixins.scss" as *;
@use "ds/spacing.scss" as *;
@use "ds/typography.scss" as t;
.color-primary {
color: var(--color-foreground-primary);
@@ -219,3 +220,21 @@
inline-size: $sz-48;
border-radius: 0 var(--sp-s) var(--sp-s) 0;
}
.textarea {
@include t.use-typography("body-small");
border-radius: $br-8;
background-color: var(--color-background-tertiary);
color: var(--color-foreground-secondary);
padding: var(--sp-xs) var(--sp-s);
border: 0;
resize: none;
&:hover {
background-color: var(--color-background-quaternary);
}
&:focus-visible {
outline: $b-1 solid var(--color-accent-primary);
}
}

View File

@@ -2098,14 +2098,6 @@ msgstr "Create access token"
msgid "integrations.create-access-token.title.created"
msgstr "Access token created"
#: src/app/main/ui/settings/integrations.cljs:290
msgid "integrations.create-mcp-key.title"
msgstr "Create new MCP key"
#: src/app/main/ui/settings/integrations.cljs:291
msgid "integrations.create-mcp-key.title.created"
msgstr "MCP key created"
#: src/app/main/ui/settings/integrations.cljs:257
msgid "integrations.delete-token.accept"
msgstr "Delete token"
@@ -2154,6 +2146,22 @@ msgstr "No expiration date"
msgid "integrations.expiration-date.label"
msgstr "Expiration date"
#: src/app/main/ui/settings/integrations.cljs:290
msgid "integrations.generate-mcp-key.title"
msgstr "Generate MCP key"
#: src/app/main/ui/settings/integrations.cljs:291
msgid "integrations.generate-mcp-key.title.created"
msgstr "MCP key generated"
#: src/app/main/ui/settings/integrations.cljs:113
msgid "integrations.info.mcp-client-config"
msgstr "Add this configuration to your MCP client (e.g. ~/.mcp.json)."
#: src/app/main/ui/settings/integrations.cljs:183
msgid "integrations.info.mcp-server"
msgstr "The Penpot MCP Server enables MCP clients to interact directly with Penpot design files."
#: src/app/main/ui/settings/integrations.cljs:131
msgid "integrations.info.non-recuperable"
msgstr "This unique token is non-recuperable. If you lose it, you will need to create a new one."

View File

@@ -2069,14 +2069,6 @@ msgstr "Crear token de accesso"
msgid "integrations.create-access-token.title.created"
msgstr "Token de acceso creado"
#: src/app/main/ui/settings/integrations.cljs:290
msgid "integrations.create-mcp-key.title"
msgstr "Crear nueva clave MCP"
#: src/app/main/ui/settings/integrations.cljs:291
msgid "integrations.create-mcp-key.title.created"
msgstr "Clave MCP creada"
#: src/app/main/ui/settings/integrations.cljs:257
msgid "integrations.delete-token.accept"
msgstr "Borrar token"
@@ -2125,6 +2117,22 @@ msgstr "Sin fecha de expiración"
msgid "integrations.expiration-date.label"
msgstr "Fecha de expiración"
#: src/app/main/ui/settings/integrations.cljs:290
msgid "integrations.generate-mcp-key.title"
msgstr "Generar clave MCP"
#: src/app/main/ui/settings/integrations.cljs:291
msgid "integrations.generate-mcp-key.title.created"
msgstr "Clave MCP generada"
#: src/app/main/ui/settings/integrations.cljs:113
msgid "integrations.info.mcp-client-config"
msgstr "Agrega esta configuración a tu cliente MCP (por ejemplo, ~/.mcp.json)."
#: src/app/main/ui/settings/integrations.cljs:183
msgid "integrations.info.mcp-server"
msgstr "El servidor MCP de Penpot permite a los clientes MCP interactuar directamente con los archivos de diseño de Penpot."
#: src/app/main/ui/settings/integrations.cljs:131
msgid "integrations.info.non-recuperable"
msgstr "Esta clave única no es recuperable. Si la pierdes, tendrás que crear una nueva."