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]