This is an automated email from the ASF dual-hosted git repository.

hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git


The following commit(s) were added to refs/heads/master by this push:
     new c9fd199  feat(connections): grow the catalog to 17 apps and add them 
to import links
c9fd199 is described below

commit c9fd199b2b3659ac450f0763b0720f1d5e0b51c6
Author: Yang Luo <[email protected]>
AuthorDate: Sat Sep 5 20:14:22 2026 +0800

    feat(connections): grow the catalog to 17 apps and add them to import links
---
 README.md                                     |  1 +
 README_zh.md                                  |  1 +
 connector/connectors/antv-chart.json          | 20 ++++++++++++
 connector/connectors/baidu-map.json           | 33 ++++++++++++++++++++
 connector/connectors/figma.json               | 33 ++++++++++++++++++++
 connector/connectors/firecrawl.json           | 34 ++++++++++++++++++++
 connector/connectors/memory.json              | 20 ++++++++++++
 connector/connectors/sequential-thinking.json | 20 ++++++++++++
 connector/connectors/stripe.json              | 32 +++++++++++++++++++
 connector/connectors/tavily.json              | 34 ++++++++++++++++++++
 object/import_link.go                         | 45 +++++++++++++++++++++++++++
 web/src/lib/connectors.ts                     | 32 +++++++++++++++++++
 web/src/locales/en/data.json                  |  3 ++
 web/src/locales/zh/data.json                  |  3 ++
 web/src/pages/ConnectionsPage.tsx             | 39 +++++++++++++----------
 web/src/pages/ImportLinkPage.tsx              | 38 ++++++++++++++++++++++
 web/src/types.ts                              | 18 ++++++++++-
 17 files changed, 388 insertions(+), 18 deletions(-)

diff --git a/README.md b/README.md
index 9e96961..819f635 100644
--- a/README.md
+++ b/README.md
@@ -198,6 +198,7 @@ The password behind that account is `admin` / `123`, and it 
only matters if you
 - **[Say what each agent may do](#what-each-agent-is-allowed-to-do)** — around 
forty switches per agent, in groups, over its tools, models and providers, 
enforced by Casbin on every request it relays.
 - **[Know what every agent spent, even off 
Gateway](#what-the-agents-spend-including-what-never-went-through-gateway)** — 
read straight from the agents' own transcripts.
 - **[Compare and copy skills, MCP servers and prompts across 
agents](#what-to-do-next)** — every agent's install list in one table.
+- **[Connect an application once, for every 
agent](docs/user-manual/en/2-agents/2.5-connections.md)** — GitHub, Notion, 
Lark, Figma, a browser and a dozen more. The credential stays in Gateway rather 
than in each agent's config file, every call through it is checked against that 
agent's permissions, and testing one gives you a switch per tool.
 
 ## Using it
 
diff --git a/README_zh.md b/README_zh.md
index adafa9c..475d3fc 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -196,6 +196,7 @@ Gateway 会在自己的窗口里打开 —— 不用登录:它只服务本机
 - **[规定每个 Agent 能做什么](#每个-agent-能做什么)** —— 每个 Agent 
四十来个开关,分组管理,覆盖工具、模型和供应商,每个转发的请求都由 Casbin 判定。
 - **[统计每个 Agent 花了多少,包括没走 Gateway 的部分](#每个-agent-花了多少包括没走-gateway-的那部分)** —— 
直接读 Agent 自己写的会话记录。
 - **[跨 Agent 对比、复制技能 / MCP / 提示词](#接下来做什么)** —— 一张表看到所有 Agent 装了什么。
+- **[一次连接一个应用,所有 Agent 都能用](docs/user-manual/zh/2-agents/2.5-connections.md)** 
—— GitHub、Notion、飞书、Figma、浏览器等十几个。凭据留在 Gateway 而不是散在每个 Agent 
的配置文件里,经过它的每次调用都会按该 Agent 的权限校验,测试一次还能得到每个工具的独立开关。
 
 ## 使用
 
diff --git a/connector/connectors/antv-chart.json 
b/connector/connectors/antv-chart.json
new file mode 100644
index 0000000..27b0e9e
--- /dev/null
+++ b/connector/connectors/antv-chart.json
@@ -0,0 +1,20 @@
+{
+  "id": "antv-chart",
+  "displayName": "AntV Charts",
+  "category": "docs",
+  "icon": "antv.antgroup.com",
+  "description": "Turns data into a chart image: lines, bars, pies, maps and a 
couple of dozen more, without a plotting library in the project.",
+  "docsUrl": "https://github.com/antvis/mcp-server-chart";,
+  "auth": {
+    "kind": "none"
+  },
+  "server": {
+    "name": "antv-chart",
+    "transport": "stdio",
+    "command": "npx",
+    "args": [
+      "-y",
+      "@antv/mcp-server-chart"
+    ]
+  }
+}
diff --git a/connector/connectors/baidu-map.json 
b/connector/connectors/baidu-map.json
new file mode 100644
index 0000000..b729462
--- /dev/null
+++ b/connector/connectors/baidu-map.json
@@ -0,0 +1,33 @@
+{
+  "id": "baidu-map",
+  "displayName": "Baidu Maps",
+  "category": "life",
+  "icon": "map.baidu.com",
+  "description": "Search places, plan routes, check the weather and turn an 
address into coordinates, over Baidu's map data.",
+  "docsUrl": "https://lbsyun.baidu.com/faq/api?title=mcpserver/base";,
+  "auth": {
+    "kind": "token",
+    "fields": [
+      {
+        "key": "apiKey",
+        "label": "Server-side AK",
+        "help": "From the Baidu Maps open platform. Create a server-side key, 
not a browser one.",
+        "secret": true,
+        "required": true
+      }
+    ],
+    "registerUrl": "https://lbsyun.baidu.com/apiconsole/key";
+  },
+  "server": {
+    "name": "baidu-map",
+    "transport": "stdio",
+    "command": "npx",
+    "args": [
+      "-y",
+      "@baidumap/mcp-server-baidu-map"
+    ],
+    "env": {
+      "BAIDU_MAP_API_KEY": "${apiKey}"
+    }
+  }
+}
diff --git a/connector/connectors/figma.json b/connector/connectors/figma.json
new file mode 100644
index 0000000..7b372ef
--- /dev/null
+++ b/connector/connectors/figma.json
@@ -0,0 +1,33 @@
+{
+  "id": "figma",
+  "displayName": "Figma",
+  "category": "dev",
+  "icon": "figma.com",
+  "description": "Reads a Figma file's layout, styles and components, so an 
agent can build a screen from the design rather than from a screenshot.",
+  "docsUrl": "https://github.com/GLips/Figma-Context-MCP";,
+  "auth": {
+    "kind": "token",
+    "fields": [
+      {
+        "key": "apiKey",
+        "label": "Personal access token",
+        "placeholder": "figd_...",
+        "help": "Figma, Settings, Security, Personal access tokens. Read-only 
file access is enough.",
+        "secret": true,
+        "required": true
+      }
+    ],
+    "registerUrl": "https://www.figma.com/developers/api#access-tokens";
+  },
+  "server": {
+    "name": "figma",
+    "transport": "stdio",
+    "command": "npx",
+    "args": [
+      "-y",
+      "figma-developer-mcp",
+      "--figma-api-key=${apiKey}",
+      "--stdio"
+    ]
+  }
+}
diff --git a/connector/connectors/firecrawl.json 
b/connector/connectors/firecrawl.json
new file mode 100644
index 0000000..7cc3266
--- /dev/null
+++ b/connector/connectors/firecrawl.json
@@ -0,0 +1,34 @@
+{
+  "id": "firecrawl",
+  "displayName": "Firecrawl",
+  "category": "dev",
+  "icon": "firecrawl.dev",
+  "description": "Scrapes and crawls websites into clean text the model can 
read, including pages that only render once their scripts have run.",
+  "docsUrl": "https://github.com/firecrawl/firecrawl-mcp-server";,
+  "auth": {
+    "kind": "token",
+    "fields": [
+      {
+        "key": "apiKey",
+        "label": "API key",
+        "placeholder": "fc-...",
+        "help": "From the Firecrawl dashboard.",
+        "secret": true,
+        "required": true
+      }
+    ],
+    "registerUrl": "https://www.firecrawl.dev/app/api-keys";
+  },
+  "server": {
+    "name": "firecrawl",
+    "transport": "stdio",
+    "command": "npx",
+    "args": [
+      "-y",
+      "firecrawl-mcp"
+    ],
+    "env": {
+      "FIRECRAWL_API_KEY": "${apiKey}"
+    }
+  }
+}
diff --git a/connector/connectors/memory.json b/connector/connectors/memory.json
new file mode 100644
index 0000000..f5925a7
--- /dev/null
+++ b/connector/connectors/memory.json
@@ -0,0 +1,20 @@
+{
+  "id": "memory",
+  "displayName": "Memory",
+  "category": "docs",
+  "icon": "modelcontextprotocol.io",
+  "description": "A knowledge graph the agent writes to and reads back, so 
what it learned in one session is there in the next.",
+  "docsUrl": 
"https://github.com/modelcontextprotocol/servers/tree/main/src/memory";,
+  "auth": {
+    "kind": "none"
+  },
+  "server": {
+    "name": "memory",
+    "transport": "stdio",
+    "command": "npx",
+    "args": [
+      "-y",
+      "@modelcontextprotocol/server-memory"
+    ]
+  }
+}
diff --git a/connector/connectors/sequential-thinking.json 
b/connector/connectors/sequential-thinking.json
new file mode 100644
index 0000000..b115921
--- /dev/null
+++ b/connector/connectors/sequential-thinking.json
@@ -0,0 +1,20 @@
+{
+  "id": "sequential-thinking",
+  "displayName": "Sequential Thinking",
+  "category": "dev",
+  "icon": "modelcontextprotocol.io",
+  "description": "A tool for working a hard problem through in steps, revising 
earlier ones as the shape of it becomes clear.",
+  "docsUrl": 
"https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking";,
+  "auth": {
+    "kind": "none"
+  },
+  "server": {
+    "name": "sequential-thinking",
+    "transport": "stdio",
+    "command": "npx",
+    "args": [
+      "-y",
+      "@modelcontextprotocol/server-sequential-thinking"
+    ]
+  }
+}
diff --git a/connector/connectors/stripe.json b/connector/connectors/stripe.json
new file mode 100644
index 0000000..a5b20ac
--- /dev/null
+++ b/connector/connectors/stripe.json
@@ -0,0 +1,32 @@
+{
+  "id": "stripe",
+  "displayName": "Stripe",
+  "category": "dev",
+  "icon": "stripe.com",
+  "description": "Read and write customers, products, prices, invoices and 
payments, so an agent can work against your billing without a dashboard.",
+  "docsUrl": "https://docs.stripe.com/mcp";,
+  "auth": {
+    "kind": "token",
+    "fields": [
+      {
+        "key": "apiKey",
+        "label": "Secret key",
+        "placeholder": "sk_test_... or rk_...",
+        "help": "Use a restricted key in test mode first. A live secret key 
can move real money.",
+        "secret": true,
+        "required": true
+      }
+    ],
+    "registerUrl": "https://dashboard.stripe.com/apikeys";
+  },
+  "server": {
+    "name": "stripe",
+    "transport": "stdio",
+    "command": "npx",
+    "args": [
+      "-y",
+      "@stripe/mcp",
+      "--api-key=${apiKey}"
+    ]
+  }
+}
diff --git a/connector/connectors/tavily.json b/connector/connectors/tavily.json
new file mode 100644
index 0000000..7e5992d
--- /dev/null
+++ b/connector/connectors/tavily.json
@@ -0,0 +1,34 @@
+{
+  "id": "tavily",
+  "displayName": "Tavily",
+  "category": "dev",
+  "icon": "tavily.com",
+  "description": "Web search built for models rather than people: a question 
goes in, sourced answers come back instead of a page of links.",
+  "docsUrl": "https://github.com/tavily-ai/tavily-mcp";,
+  "auth": {
+    "kind": "token",
+    "fields": [
+      {
+        "key": "apiKey",
+        "label": "API key",
+        "placeholder": "tvly-...",
+        "help": "From the Tavily dashboard.",
+        "secret": true,
+        "required": true
+      }
+    ],
+    "registerUrl": "https://app.tavily.com/home";
+  },
+  "server": {
+    "name": "tavily",
+    "transport": "stdio",
+    "command": "npx",
+    "args": [
+      "-y",
+      "tavily-mcp@latest"
+    ],
+    "env": {
+      "TAVILY_API_KEY": "${apiKey}"
+    }
+  }
+}
diff --git a/object/import_link.go b/object/import_link.go
index e909ccc..525ebfb 100644
--- a/object/import_link.go
+++ b/object/import_link.go
@@ -23,6 +23,7 @@ import (
        "unicode/utf8"
 
        "github.com/apache/casbin-gateway/agent"
+       "github.com/apache/casbin-gateway/connector"
 )
 
 // The resources an "add this to Gateway" link can carry. The names are CC
@@ -33,6 +34,10 @@ const (
        ImportResourceMcp      = "mcp"
        ImportResourcePrompt   = "prompt"
        ImportResourceSkill    = "skill"
+       // ImportResourceConnection names an application in Gateway's own 
connector
+       // catalogue. It carries no credential: a link arrives from a website, 
and
+       // the person it arrives at is the one who fills those in.
+       ImportResourceConnection = "connection"
 )
 
 // importLinkAgents translates the app names CC Switch links use into Gateway's
@@ -65,6 +70,21 @@ type ImportLink struct {
        Mcp      *McpImport    `json:"mcp,omitempty"`
        Prompt   *PromptImport `json:"prompt,omitempty"`
        Skill    *SkillImport  `json:"skill,omitempty"`
+
+       Connection *ConnectionImport `json:"connection,omitempty"`
+}
+
+// ConnectionImport is one application to connect, named by its id in the
+// catalogue. What it carries is a suggestion, not a configuration: opening it
+// is what takes the reader to the dialog they fill in themselves.
+type ConnectionImport struct {
+       Connector   string `json:"connector"`
+       DisplayName string `json:"displayName"`
+       Description string `json:"description"`
+       // Targets are the agents the link asks for, as ids of this host's 
agents.
+       Targets []string `json:"targets"`
+       // Unknown are the apps it named that Gateway does not manage.
+       Unknown []string `json:"unknown"`
 }
 
 // McpImport is one or more MCP servers, kept as the JSON block the link
@@ -134,6 +154,8 @@ func ParseImportLink(owner string, raw string) 
(*ImportLink, error) {
                link.Prompt, err = promptFromLink(query)
        case ImportResourceSkill:
                link.Skill, err = skillFromLink(query)
+       case ImportResourceConnection:
+               link.Connection, err = connectionFromLink(query)
        default:
                return nil, fmt.Errorf("this link carries a %q, which Gateway 
cannot import", link.Resource)
        }
@@ -177,6 +199,29 @@ func mcpFromLink(query url.Values) (*McpImport, error) {
        }, nil
 }
 
+// connectionFromLink reads which application a link is offering. A connector
+// this Gateway's catalogue does not have is refused by name rather than shown
+// as an empty card: the reader can then say what they were sent.
+func connectionFromLink(query url.Values) (*ConnectionImport, error) {
+       name := strings.TrimSpace(query.Get("connector"))
+       if name == "" {
+               return nil, fmt.Errorf("the link does not say which application 
to connect")
+       }
+       found, ok := connector.Get(name)
+       if !ok {
+               return nil, fmt.Errorf("this Gateway has no connector called 
%q", name)
+       }
+
+       targets, unknown := linkTargets(strings.Split(query.Get("apps"), ","))
+       return &ConnectionImport{
+               Connector:   found.Id,
+               DisplayName: found.DisplayName,
+               Description: found.Description,
+               Targets:     targets,
+               Unknown:     unknown,
+       }, nil
+}
+
 func promptFromLink(query url.Values) (*PromptImport, error) {
        name := strings.TrimSpace(query.Get("name"))
        if name == "" {
diff --git a/web/src/lib/connectors.ts b/web/src/lib/connectors.ts
new file mode 100644
index 0000000..35e13ef
--- /dev/null
+++ b/web/src/lib/connectors.ts
@@ -0,0 +1,32 @@
+// Copyright 2026 The casbin Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import i18next from "i18next";
+
+/**
+ * Catalog entries are written in English and translated like every other
+ * string, keyed by what they say. An entry nobody has translated yet falls 
back
+ * to the catalog's own wording rather than rendering its key.
+ *
+ * A description is a sentence and may hold a ":", which i18next reads as the
+ * namespace separator wherever it sits in the key — so the namespace is passed
+ * as an option and that splitting is turned off for the lookup, or every
+ * sentence holding a colon silently falls back to English.
+ */
+export function connectorText(value: string | undefined) {
+  if (value === undefined || value === "") {
+    return "";
+  }
+  return i18next.t(value, {ns: "connector", nsSeparator: false, defaultValue: 
value});
+}
diff --git a/web/src/locales/en/data.json b/web/src/locales/en/data.json
index fb0c83e..ff19b4c 100644
--- a/web/src/locales/en/data.json
+++ b/web/src/locales/en/data.json
@@ -845,6 +845,7 @@
     "Manage": "Manage",
     "Never tested": "Not tested yet",
     "No agent found": "No agent on this machine takes MCP servers Gateway 
knows how to write.",
+    "No such connector": "This Gateway has no connector called {name}.",
     "Not authorized": "Not authorized yet",
     "Office": "Office",
     "Personal access token": "Personal access token",
@@ -961,10 +962,12 @@
   },
   "link": {
     "A place to install skills from": "A place to install skills from",
+    "An application to connect": "An application to connect",
     "Branch": "Branch",
     "CC Switch cannot be read": "CC Switch's settings cannot be read",
     "CC Switch is installed here": "CC Switch is installed on this machine",
     "CC Switch is installed here hint": "Everything set up there can be 
brought over in one go",
+    "Connection hint": "This link names an application, not a credential. 
Opening it takes you to the form where you fill those in.",
     "Discard": "Discard",
     "Done": "Done",
     "Folder": "Folder",
diff --git a/web/src/locales/zh/data.json b/web/src/locales/zh/data.json
index 00d08e8..d42d7eb 100644
--- a/web/src/locales/zh/data.json
+++ b/web/src/locales/zh/data.json
@@ -916,6 +916,7 @@
     "Manage": "管理",
     "Never tested": "尚未测试",
     "No agent found": "这台机器上没有Gateway能写入MCP配置的Agent。",
+    "No such connector": "这个Gateway里没有叫{name}的连接器。",
     "Not authorized": "尚未授权",
     "Office": "办公协同",
     "Personal access token": "个人访问令牌",
@@ -1031,10 +1032,12 @@
   },
   "link": {
     "A place to install skills from": "技能来源",
+    "An application to connect": "一个可以连接的应用",
     "Branch": "分支",
     "CC Switch cannot be read": "无法读取CC Switch的配置",
     "CC Switch is installed here": "本机装了CC Switch",
     "CC Switch is installed here hint": "在那边配好的东西可以一次性搬过来",
+    "Connection hint": "这个链接只指名一个应用,不含任何凭据。打开后你会到填写凭据的表单。",
     "Discard": "丢弃",
     "Done": "完成",
     "Folder": "子目录",
diff --git a/web/src/pages/ConnectionsPage.tsx 
b/web/src/pages/ConnectionsPage.tsx
index e613bfc..f39cd1b 100644
--- a/web/src/pages/ConnectionsPage.tsx
+++ b/web/src/pages/ConnectionsPage.tsx
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 import * as React from "react";
+import {useSearchParams} from "react-router-dom";
 import {ExternalLink, Plug} from "lucide-react";
 import i18next from "i18next";
 
@@ -26,6 +27,7 @@ import {Badge} from "@/components/ui/badge";
 import {Button} from "@/components/ui/button";
 import {Card, CardContent} from "@/components/ui/card";
 import {Input} from "@/components/ui/input";
+import {connectorText as text} from "@/lib/connectors";
 import {cn} from "@/lib/utils";
 import type {Account, ConnectorCatalog, ConnectorEntry, ConnectorTarget, 
ConnectorTool} from "@/types";
 
@@ -45,23 +47,6 @@ function iconUrl(icon: string) {
   return icon === "" ? "" : 
`https://www.google.com/s2/favicons?domain=${icon}&sz=64`;
 }
 
-/**
- * Catalog entries are written in English and translated like every other
- * string, keyed by what they say. An entry nobody has translated yet falls 
back
- * to the catalog's own wording rather than rendering its key.
- *
- * A description is a sentence and may hold a ":", which i18next reads as the
- * namespace separator wherever it sits in the key — so the namespace is passed
- * as an option and that splitting is turned off for the lookup, or every
- * sentence holding a colon silently falls back to English.
- */
-function text(value: string | undefined) {
-  if (value === undefined || value === "") {
-    return "";
-  }
-  return i18next.t(value, {ns: "connector", nsSeparator: false, defaultValue: 
value});
-}
-
 /**
  * The Connections page: every application an agent on this machine can be
  * connected to. A connection is stored here once, credentials included, and
@@ -74,6 +59,10 @@ export default function ConnectionsPage({account}: {account: 
Account}) {
   const [loading, setLoading] = React.useState(true);
   const [category, setCategory] = React.useState("");
   const [editing, setEditing] = React.useState<ConnectorEntry | null>(null);
+  // An "add this to Gateway" link lands here naming one application, and what
+  // it asks for is this dialog rather than anything written on its behalf.
+  const [params, setParams] = useSearchParams();
+  const asked = params.get("connect") ?? "";
 
   const reload = React.useCallback(() => {
     if (!isAdmin) {
@@ -95,6 +84,22 @@ export default function ConnectionsPage({account}: {account: 
Account}) {
 
   React.useEffect(() => reload(), [reload]);
 
+  React.useEffect(() => {
+    if (asked === "" || catalog === null) {
+      return;
+    }
+    const found = catalog.connectors.find(entry => entry.id === asked);
+    if (found) {
+      setEditing(found);
+    } else {
+      Setting.showMessage("error", i18next.t("connector:No such 
connector").replace("{name}", asked));
+    }
+    // Taken out of the address once it has been acted on, so a refresh does 
not
+    // reopen a dialog the operator already closed.
+    params.delete("connect");
+    setParams(params, {replace: true});
+  }, [asked, catalog, params, setParams]);
+
   if (!isAdmin) {
     return <UnauthorizedResult />;
   }
diff --git a/web/src/pages/ImportLinkPage.tsx b/web/src/pages/ImportLinkPage.tsx
index c1a4524..ab68b3c 100644
--- a/web/src/pages/ImportLinkPage.tsx
+++ b/web/src/pages/ImportLinkPage.tsx
@@ -32,11 +32,13 @@ import {Card, CardContent, CardDescription, CardHeader, 
CardTitle} from "@/compo
 import {Label} from "@/components/ui/label";
 import {Switch} from "@/components/ui/switch";
 import {Textarea} from "@/components/ui/textarea";
+import {connectorText} from "@/lib/connectors";
 import {parseMcpJson} from "@/lib/mcp";
 import type {
   AgentConfigInventory,
   AgentConfigPlanItem,
   ImportLink,
+  ConnectionImport,
   McpImport,
   PromptImport,
   SkillImport,
@@ -159,6 +161,8 @@ export default function ImportLinkPage() {
         <PromptImportCard value={link.prompt} onDone={discard} />
       ) : link.skill ? (
         <SkillImportCard value={link.skill} onDone={discard} />
+      ) : link.connection ? (
+        <ConnectionImportCard value={link.connection} onDone={discard} />
       ) : null}
     </PageContainer>
   );
@@ -518,3 +522,37 @@ function SkillImportCard({value, onDone}: {value: 
SkillImport; onDone: () => voi
     </Card>
   );
 }
+
+/**
+ * One application a link offers to connect. Nothing is written from here: the
+ * link carries no credential, so the only thing to do with it is open the
+ * dialog on the Connections page with this application already picked.
+ */
+function ConnectionImportCard({value, onDone}: {value: ConnectionImport; 
onDone: () => void}) {
+  const navigate = useNavigate();
+
+  return (
+    <Card>
+      <CardHeader>
+        <CardTitle className="text-[15px]">{i18next.t("link:An application to 
connect")}</CardTitle>
+        <CardDescription>{i18next.t("link:Connection hint")}</CardDescription>
+      </CardHeader>
+      <CardContent className="flex flex-col gap-4">
+        <div className="space-y-1">
+          <div className="font-medium">{connectorText(value.displayName)}</div>
+          <p className="text-muted-foreground 
text-sm">{connectorText(value.description)}</p>
+        </div>
+        <div className="flex justify-end">
+          <Button
+            onClick={() => {
+              onDone();
+              
navigate(`/connections?connect=${encodeURIComponent(value.connector)}`);
+            }}
+          >
+            {i18next.t("connector:Connect")}
+          </Button>
+        </div>
+      </CardContent>
+    </Card>
+  );
+}
diff --git a/web/src/types.ts b/web/src/types.ts
index d171fca..bf4dfee 100644
--- a/web/src/types.ts
+++ b/web/src/types.ts
@@ -1213,7 +1213,7 @@ export interface AgentConfigTrashEntry {
 }
 
 /** What a vendor's "add this to Gateway" link carries. */
-export type ImportResource = "provider" | "mcp" | "prompt" | "skill";
+export type ImportResource = "provider" | "mcp" | "prompt" | "skill" | 
"connection";
 
 export interface ImportLink {
   resource: ImportResource;
@@ -1221,6 +1221,22 @@ export interface ImportLink {
   mcp?: McpImport;
   prompt?: PromptImport;
   skill?: SkillImport;
+  connection?: ConnectionImport;
+}
+
+/**
+ * One application a link offers to connect, named by its id in the catalogue.
+ * It carries no credential: opening it takes the reader to the dialog where
+ * they fill those in themselves.
+ */
+export interface ConnectionImport {
+  connector: string;
+  displayName: string;
+  description: string;
+  /** The agents the link asks for, as ids of the agents on this host. */
+  targets: string[];
+  /** The apps it named that Gateway does not manage. */
+  unknown: string[];
 }
 
 /** One or more MCP servers, as the JSON block the link carried. */


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to