diff --git a/doc/connections/README.md b/doc/connections/README.md
index 2b7f9b3387..35e628797f 100644
--- a/doc/connections/README.md
+++ b/doc/connections/README.md
@@ -14,7 +14,7 @@ Long-term memory: [Experimental memory connectors](./MEMORY.md).
Provider notes: [Google Workspace](./GOOGLE-WORKSPACE.md),
[Gmail](./GMAIL.md), [Asana](./ASANA.md), [PostHog](./POSTHOG.md), [Neon](./NEON.md), [Superagent](./SUPERAGENT.md),
-[AgentMail](./AGENTMAIL.md), [iMessage Photon](./IMESSAGE-PHOTON.md), and
+[AgentMail](./AGENTMAIL.md), [Telem.AI](./TELEM.md), [iMessage Photon](./IMESSAGE-PHOTON.md), and
[Enterpret](./ENTERPRET.md). Optional credential custody:
[Vercel Connect](./VERCEL-CONNECT.md).
diff --git a/doc/connections/REMOTE-MCP-BRAND-SOURCES.md b/doc/connections/REMOTE-MCP-BRAND-SOURCES.md
index a9c9aa1809..e8c12e6531 100644
--- a/doc/connections/REMOTE-MCP-BRAND-SOURCES.md
+++ b/doc/connections/REMOTE-MCP-BRAND-SOURCES.md
@@ -9,8 +9,10 @@ Zapier and Composio reuse the existing reviewed local marks.
| --- | --- | --- |
| `ui/public/brands/apps/arcade.png` | [Arcade site touch icon](https://www.arcade.dev/_astro/webclip.BYnZsC5R.png), linked from [arcade.dev](https://www.arcade.dev/) | `5b3704e210b6dc70fffab260d5416624f37db19108399a6d2c18c848d33465ad` |
| `ui/public/brands/apps/executor.png` | [Executor site icon](https://executor.sh/favicon-192.png), linked from [executor.sh](https://executor.sh/) | `e5fd761a0cd80d51d451cc06e9e6d0236dcc39317248a13ac5db5fcf4d52d0ca` |
+| `ui/public/brands/apps/telem.svg` | [Telem.AI mark](https://telem.ai/logos/telem-icon.svg), published on [telem.ai](https://telem.ai/) (retrieved 2026-10-06) | `c65ebca449f1c5ceef874ac912ea43c919315f465304a2f9e63f5d04ac0d769d` |
+| `ui/public/brands/apps/telem-dark.svg` | [Telem.AI dark mark](https://telem.ai/logos/telem-icon-dark.svg), published on [telem.ai](https://telem.ai/) (retrieved 2026-10-06) | `a2735231c47371c9247ffb9168ab0c2e684403d7c4d94a6ba9f48a71bbd14145` |
-Both files preserve provider artwork without recoloring. The shared AppLogo
+Both files preserve provider artwork without recoloring. Both Telem.AI files are byte-identical to the published files: `telem-dark.svg` is Telem.AI's own published dark variant, not a recolor. The shared AppLogo
provides the standard gray frame and contained padding. Arcade’s SVG favicon
wrapped embedded raster content; the 180px touch icon linked by the same official
page was used to satisfy the repository’s artwork safety checks.
diff --git a/doc/connections/TELEM.md b/doc/connections/TELEM.md
new file mode 100644
index 0000000000..6b3dae78bd
--- /dev/null
+++ b/doc/connections/TELEM.md
@@ -0,0 +1,104 @@
+# Telem.AI connection
+
+Updated: 2026-10-03. Status: catalog definition added. The API key method was
+verified on a local instance: setup, catalog discovery, gateway `tools/list`
+and one read-only `tools/call`. Live account qualification by a maintainer is
+outstanding.
+
+Paperclip connects to the Telem.AI hosted MCP server at
+`https://mcp.telem.ai/mcp`. Telem.AI gives agents web search and page fetch
+across many search providers with one API key.
+
+The connection has one method: a Telem API key stored as a Paperclip secret
+and sent as an `Authorization: Bearer ...` header. Telem.AI does not offer
+browser sign-in or a keyless profile.
+
+The curated connection adds branding, guidance and four optional settings.
+None of it is required to reach the server. Telem.AI can also be connected
+from **Connect your own MCP server** with the same URL and header. See
+[Connecting any remote MCP server](./GENERIC-REMOTE-MCP.md).
+
+## Service involvement
+
+Telem.AI hosts the MCP resource. Paperclip ID and Paperclip Connect do not
+take part. Cloud and self-hosted instances use the same path. There is no
+OAuth flow and no Paperclip callback.
+
+```mermaid
+sequenceDiagram
+ actor A as Administrator
+ participant P as Paperclip
+ participant T as mcp.telem.ai
+
+ A->>P: Paste a Telem API key and optional settings
+ P->>P: Store the key as a company secret
+ P->>T: tools/list with Authorization Bearer and X-Telem headers
+ T-->>P: Telem tool catalog
+ Note over P,T: Each agent tool call goes through the Paperclip gateway
+ P->>T: tools/call with the same headers
+ T-->>P: Tool result
+```
+
+| Purpose | Endpoint |
+| --- | --- |
+| MCP resource | `https://mcp.telem.ai/mcp` |
+| API keys | `https://app.telem.ai` |
+| Documentation | `https://docs.telem.ai` |
+| Authorize, token, registration | none (API key only) |
+| Paperclip callback | n/a |
+
+## Administrator setup
+
+1. Create an API key in the Telem console at `app.telem.ai`.
+2. In Paperclip, open **Apps**, choose **Telem.AI**, and paste the key.
+3. Optional: open **Advanced** and set the settings below.
+4. Choose which agents get access, then finish.
+5. To verify, open the connection's **Permissions** page and click **Test**
+ beside `telem_providers`. It is read-only and returns the list of available
+ search providers.
+
+No callback URL, client registration or instance feature flag is needed.
+
+## Settings
+
+All settings are optional and apply to every agent that uses this connection.
+Paperclip sends each one as a request header. It leaves a header out when its
+setting is empty.
+
+| Setting | Values | Header |
+| --- | --- | --- |
+| Auto routing | Off, Accuracy | `X-Telem-Auto-Routing` |
+| Tier | Minimalist, Default, Extended, Max | `X-Telem-Tier` |
+| Providers to include | comma-separated provider names | `X-Telem-Providers-Include` |
+| Providers to exclude | comma-separated provider names | `X-Telem-Providers-Exclude` |
+
+The setup form cannot clear a select after a value is chosen. To turn auto
+routing off again, select **Off**. **Default** is the server's default tier.
+
+## Resource filters
+
+None. Telem.AI tools read public web content and do not act on customer
+resources.
+
+## Actions
+
+| Tool | Risk | Purpose |
+| --- | --- | --- |
+| `telem_search` | read | Search the web across providers |
+| `telem_fetch` | read | Read the content of known URLs |
+| `telem_providers` | read | List the available search providers |
+| `telem_session_history` | read | Read earlier results of a search session |
+
+Usage is billed to the Telem account that owns the API key.
+
+## Manifest
+
+- slug `telem`, name `Telem.AI`, category `ai`, risk tier `S2`
+- method `mcp-api-key`: `mcp_remote`, `api_key`, `keyPlacement`
+ `Authorization` with prefix `Bearer `
+- `tenantFields`: `autoRouting`, `tier`, `providersInclude`,
+ `providersExclude` (all optional, all header transport)
+- branding: the official Telem mark, `ui/public/brands/apps/telem.svg` and
+ `telem-dark.svg`
+- source: `scripts/ingest-app-definitions.mjs` (`specialMethodsFor`, slug
+ `telem`) and `packages/shared/src/self-serve-mcp-research.json`
diff --git a/doc/connections/tool-method-permission-reviews.json b/doc/connections/tool-method-permission-reviews.json
index 2c96d44254..f92216c57b 100644
--- a/doc/connections/tool-method-permission-reviews.json
+++ b/doc/connections/tool-method-permission-reviews.json
@@ -2042,6 +2042,21 @@
"reviewedAt": "2026-09-30",
"liveProof": "not-run"
},
+ {
+ "app": "telem",
+ "method": "mcp-api-key",
+ "auth": "api_key",
+ "policy": "provider-key",
+ "requestedScopes": [],
+ "capability": "read",
+ "supportedActions": "None: web search and page fetch tools (usage is billed to the Telem account).",
+ "evidence": [
+ "https://docs.telem.ai"
+ ],
+ "reviewedAt": "2026-10-03",
+ "liveProof": "not-run",
+ "keyPermissions": "None: web search and page fetch tools (usage is billed to the Telem account). Any Telem API key works."
+ },
{
"app": "ticket-tailor",
"method": "mcp-oauth",
diff --git a/packages/shared/src/app-definitions.generated.ts b/packages/shared/src/app-definitions.generated.ts
index 6e729faa89..5a6482dbec 100644
--- a/packages/shared/src/app-definitions.generated.ts
+++ b/packages/shared/src/app-definitions.generated.ts
@@ -70,23 +70,24 @@ import a68 from "./app-definitions/supermemory.json" with { type: "json" };
import a69 from "./app-definitions/honcho.json" with { type: "json" };
import a70 from "./app-definitions/neon.json" with { type: "json" };
import a71 from "./app-definitions/superagent.json" with { type: "json" };
-import a72 from "./app-definitions/gmail.json" with { type: "json" };
-import a73 from "./app-definitions/google-drive.json" with { type: "json" };
-import a74 from "./app-definitions/google-docs.json" with { type: "json" };
-import a75 from "./app-definitions/google-sheets.json" with { type: "json" };
-import a76 from "./app-definitions/google-slides.json" with { type: "json" };
-import a77 from "./app-definitions/google-calendar.json" with { type: "json" };
-import a78 from "./app-definitions/google-chat.json" with { type: "json" };
-import a79 from "./app-definitions/google-people.json" with { type: "json" };
-import a80 from "./app-definitions/google-workspace-search.json" with { type: "json" };
-import a81 from "./app-definitions/openai.json" with { type: "json" };
-import a82 from "./app-definitions/openrouter.json" with { type: "json" };
-import a83 from "./app-definitions/xai.json" with { type: "json" };
-import a84 from "./app-definitions/google.json" with { type: "json" };
-import a85 from "./app-definitions/bedrock.json" with { type: "json" };
-import a86 from "./app-definitions/responses-api.json" with { type: "json" };
-import a87 from "./app-definitions/messages-api.json" with { type: "json" };
-import a88 from "./app-definitions/chat-completions-api.json" with { type: "json" };
-import a89 from "./app-definitions/local.json" with { type: "json" };
+import a72 from "./app-definitions/telem.json" with { type: "json" };
+import a73 from "./app-definitions/gmail.json" with { type: "json" };
+import a74 from "./app-definitions/google-drive.json" with { type: "json" };
+import a75 from "./app-definitions/google-docs.json" with { type: "json" };
+import a76 from "./app-definitions/google-sheets.json" with { type: "json" };
+import a77 from "./app-definitions/google-slides.json" with { type: "json" };
+import a78 from "./app-definitions/google-calendar.json" with { type: "json" };
+import a79 from "./app-definitions/google-chat.json" with { type: "json" };
+import a80 from "./app-definitions/google-people.json" with { type: "json" };
+import a81 from "./app-definitions/google-workspace-search.json" with { type: "json" };
+import a82 from "./app-definitions/openai.json" with { type: "json" };
+import a83 from "./app-definitions/openrouter.json" with { type: "json" };
+import a84 from "./app-definitions/xai.json" with { type: "json" };
+import a85 from "./app-definitions/google.json" with { type: "json" };
+import a86 from "./app-definitions/bedrock.json" with { type: "json" };
+import a87 from "./app-definitions/responses-api.json" with { type: "json" };
+import a88 from "./app-definitions/messages-api.json" with { type: "json" };
+import a89 from "./app-definitions/chat-completions-api.json" with { type: "json" };
+import a90 from "./app-definitions/local.json" with { type: "json" };
import type { AppDefinition } from "./types/app-definition.js";
-export const APP_DEFINITIONS=[a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39,a40,a41,a42,a43,a44,a45,a46,a47,a48,a49,a50,a51,a52,a53,a54,a55,a56,a57,a58,a59,a60,a61,a62,a63,a64,a65,a66,a67,a68,a69,a70,a71,a72,a73,a74,a75,a76,a77,a78,a79,a80,a81,a82,a83,a84,a85,a86,a87,a88,a89] as AppDefinition[];
+export const APP_DEFINITIONS=[a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39,a40,a41,a42,a43,a44,a45,a46,a47,a48,a49,a50,a51,a52,a53,a54,a55,a56,a57,a58,a59,a60,a61,a62,a63,a64,a65,a66,a67,a68,a69,a70,a71,a72,a73,a74,a75,a76,a77,a78,a79,a80,a81,a82,a83,a84,a85,a86,a87,a88,a89,a90] as AppDefinition[];
diff --git a/packages/shared/src/app-definitions.test.ts b/packages/shared/src/app-definitions.test.ts
index 24ac23bdb4..3167197d00 100644
--- a/packages/shared/src/app-definitions.test.ts
+++ b/packages/shared/src/app-definitions.test.ts
@@ -273,7 +273,7 @@ describe("AppDefinition catalog", () => {
"google-workspace-search",
]),
);
- expect(SELF_SERVE_MCP_CANDIDATES).toHaveLength(51);
+ expect(SELF_SERVE_MCP_CANDIDATES).toHaveLength(52);
expect(BLOCKED_MCP_PROVIDERS.map((entry) => entry.slug)).toEqual([
"g2",
"vercel",
@@ -437,15 +437,15 @@ describe("AppDefinition catalog", () => {
expect(channel("slack")?.guidanceMd).toContain("reactions");
expect(channel("slack")?.guidanceMd).toContain("direct messages");
});
- it("keeps a complete, unique, dated evidence ledger for all 54 researched MCP providers", () => {
+ it("keeps a complete, unique, dated evidence ledger for all 55 researched MCP providers", () => {
// Ledger-wide date reflects the last full re-verification (2026-08-26);
// later provider additions carry their own research evidence, but
// bumping the shared date would overstate freshness for the other providers.
expect(SELF_SERVE_MCP_RESEARCH.verifiedAt).toBe("2026-08-26");
- expect(SELF_SERVE_MCP_RESEARCH.entries).toHaveLength(54);
+ expect(SELF_SERVE_MCP_RESEARCH.entries).toHaveLength(55);
expect(
new Set(SELF_SERVE_MCP_RESEARCH.entries.map((entry) => entry.slug)),
- ).toHaveProperty("size", 54);
+ ).toHaveProperty("size", 55);
for (const entry of SELF_SERVE_MCP_RESEARCH.entries) {
expect(new URL(entry.docsUrl).protocol).toBe("https:");
expect(new URL(entry.serverUrl).protocol).toBe("https:");
@@ -688,6 +688,34 @@ describe("AppDefinition catalog", () => {
defaults: { serverUrl: "https://api.you.com/mcp?profile=free" },
});
expect(method("youcom", "mcp-free")?.credentialFields).toBeUndefined();
+ expect(
+ APP_DEFINITIONS.find((app) => app.slug === "telem")?.methods.map(
+ (candidate) => candidate.key,
+ ),
+ ).toEqual(["mcp-api-key"]);
+ expect(method("telem")).toMatchObject({
+ auth: "api_key",
+ defaults: { serverUrl: "https://mcp.telem.ai/mcp" },
+ keyPlacement: {
+ location: "header",
+ name: "Authorization",
+ prefix: "Bearer ",
+ },
+ consoleLinks: { keys: "https://app.telem.ai" },
+ });
+ expect(
+ method("telem")?.tenantFields?.map((field) => [
+ field.key,
+ field.required ?? false,
+ field.transport?.location,
+ field.transport?.name,
+ ]),
+ ).toEqual([
+ ["autoRouting", false, "header", "X-Telem-Auto-Routing"],
+ ["tier", false, "header", "X-Telem-Tier"],
+ ["providersInclude", false, "header", "X-Telem-Providers-Include"],
+ ["providersExclude", false, "header", "X-Telem-Providers-Exclude"],
+ ]);
});
it("uses discovery-first Notion MCP OAuth metadata", () => {
const notion = APP_DEFINITIONS.find((app) => app.slug === "notion");
@@ -817,7 +845,7 @@ describe("AppDefinition catalog", () => {
"ticktick",
"xero",
]);
- expect(APP_STORE_DEFINITIONS).toHaveLength(67);
+ expect(APP_STORE_DEFINITIONS).toHaveLength(68);
const connectableSlugs = new Set(
CONNECTABLE_APP_DEFINITIONS.map((entry) => entry.slug),
);
diff --git a/packages/shared/src/app-definitions/telem.json b/packages/shared/src/app-definitions/telem.json
new file mode 100644
index 0000000000..1ddd27834c
--- /dev/null
+++ b/packages/shared/src/app-definitions/telem.json
@@ -0,0 +1,144 @@
+{
+ "schemaVersion": 1,
+ "slug": "telem",
+ "name": "Telem.AI",
+ "description": "Search the web and read pages across many search providers with one API key.",
+ "categories": [
+ "ai"
+ ],
+ "featured": false,
+ "branding": {
+ "logoUrl": "/brands/apps/telem.svg",
+ "darkLogoUrl": "/brands/apps/telem-dark.svg"
+ },
+ "urlPatterns": [
+ "https://mcp.telem.ai/*"
+ ],
+ "docsUrl": "https://docs.telem.ai",
+ "methods": [
+ {
+ "key": "mcp-api-key",
+ "transport": "mcp_remote",
+ "auth": "api_key",
+ "ownershipModes": [
+ "customer"
+ ],
+ "whenToUse": "Connect with a Telem API key.",
+ "defaults": {
+ "serverUrl": "https://mcp.telem.ai/mcp"
+ },
+ "guidanceMd": "Create an API key in the Telem console at app.telem.ai. Paste the key below. Open Advanced to set auto routing, the tier, or the providers to include or exclude.",
+ "riskTier": "S2",
+ "label": "Use an API key",
+ "credentialFields": [
+ {
+ "key": "authorization",
+ "label": "Telem.AI API key",
+ "type": "password",
+ "required": true,
+ "placeholder": "tlm_...",
+ "secret": true,
+ "helperMd": "None: web search and page fetch tools (usage is billed to the Telem account). Any Telem API key works."
+ }
+ ],
+ "keyPlacement": {
+ "location": "header",
+ "name": "Authorization",
+ "prefix": "Bearer "
+ },
+ "consoleLinks": {
+ "keys": "https://app.telem.ai",
+ "docs": "https://docs.telem.ai"
+ },
+ "warnings": [
+ "A Telem API key. Create one in the Telem console at app.telem.ai."
+ ],
+ "tenantFields": [
+ {
+ "key": "autoRouting",
+ "label": "Auto routing",
+ "type": "select",
+ "advanced": true,
+ "options": [
+ {
+ "value": "off",
+ "label": "Off"
+ },
+ {
+ "value": "accuracy",
+ "label": "Accuracy"
+ }
+ ],
+ "helperMd": "Optional. Accuracy lets Telem choose the search providers for each query. Off, or no selection, keeps auto routing off.",
+ "transport": {
+ "location": "header",
+ "name": "X-Telem-Auto-Routing"
+ }
+ },
+ {
+ "key": "tier",
+ "label": "Tier",
+ "type": "select",
+ "advanced": true,
+ "options": [
+ {
+ "value": "minimalist",
+ "label": "Minimalist"
+ },
+ {
+ "value": "default",
+ "label": "Default"
+ },
+ {
+ "value": "extended",
+ "label": "Extended"
+ },
+ {
+ "value": "max",
+ "label": "Max"
+ }
+ ],
+ "helperMd": "Optional. Sets how much search work Telem does for each query. No selection uses the Default tier.",
+ "transport": {
+ "location": "header",
+ "name": "X-Telem-Tier"
+ }
+ },
+ {
+ "key": "providersInclude",
+ "label": "Providers to include",
+ "type": "textarea",
+ "advanced": true,
+ "placeholder": "Optional comma-separated provider names",
+ "helperMd": "Optional. Telem searches only these providers. Leave it empty to allow all providers.",
+ "validation": {
+ "pattern": "^[A-Za-z0-9_.-]+(,[A-Za-z0-9_.-]+)*$",
+ "maxLength": 500
+ },
+ "transport": {
+ "location": "header",
+ "name": "X-Telem-Providers-Include",
+ "format": "csv"
+ }
+ },
+ {
+ "key": "providersExclude",
+ "label": "Providers to exclude",
+ "type": "textarea",
+ "advanced": true,
+ "placeholder": "Optional comma-separated provider names",
+ "helperMd": "Optional. Telem does not search these providers.",
+ "validation": {
+ "pattern": "^[A-Za-z0-9_.-]+(,[A-Za-z0-9_.-]+)*$",
+ "maxLength": 500
+ },
+ "transport": {
+ "location": "header",
+ "name": "X-Telem-Providers-Exclude",
+ "format": "csv"
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/packages/shared/src/self-serve-mcp-research.json b/packages/shared/src/self-serve-mcp-research.json
index a60fefce38..83b4601523 100644
--- a/packages/shared/src/self-serve-mcp-research.json
+++ b/packages/shared/src/self-serve-mcp-research.json
@@ -59,6 +59,7 @@
{"slug": "supermemory", "name": "Supermemory", "wave": 3, "status": "self_serve", "docsUrl": "https://supermemory.ai/docs/supermemory-mcp/mcp", "serverUrl": "https://mcp.supermemory.ai/mcp", "authMode": "dcr", "prerequisite": "Sign in to Supermemory and select the spaces this connection may access. The hosted MCP uses OAuth, not a developer API key.", "riskTier": "S3"},
{"slug": "honcho", "name": "Honcho", "wave": 3, "status": "self_serve", "docsUrl": "https://honcho.dev/docs/v3/guides/integrations/mcp", "serverUrl": "https://mcp.honcho.dev", "authMode": "api_key", "prerequisite": "An API key from the Honcho dashboard. Memory is stored in your Honcho account; select workspace and peer identifiers when calling tools.", "riskTier": "S3"},
{ "slug": "neon", "name": "Neon", "wave": 4, "status": "self_serve", "docsUrl": "https://neon.com/docs/ai/neon-mcp-server", "serverUrl": "https://mcp.neon.tech/mcp", "authMode": "dcr_or_api_key", "prerequisite": "A Neon account. The hosted server grants broad project and database management, so use a development project and review write actions before connecting production data.", "riskTier": "S4" },
- { "slug": "superagent", "name": "Superagent", "wave": 4, "status": "self_serve", "docsUrl": "https://www.superagent.sh/docs/mcp", "serverUrl": "https://www.superagent.sh/mcp", "authMode": "api_key", "prerequisite": "Superagent report and triage tools consume organization credits, and deleting a finding is permanent. Set those actions to Ask first before agents run unattended.", "riskTier": "S4" }
+ { "slug": "superagent", "name": "Superagent", "wave": 4, "status": "self_serve", "docsUrl": "https://www.superagent.sh/docs/mcp", "serverUrl": "https://www.superagent.sh/mcp", "authMode": "api_key", "prerequisite": "Superagent report and triage tools consume organization credits, and deleting a finding is permanent. Set those actions to Ask first before agents run unattended.", "riskTier": "S4" },
+ { "slug": "telem", "name": "Telem.AI", "wave": 4, "status": "self_serve", "docsUrl": "https://docs.telem.ai", "serverUrl": "https://mcp.telem.ai/mcp", "authMode": "api_key", "prerequisite": "A Telem API key. Create one in the Telem console at app.telem.ai.", "riskTier": "S2" }
]
}
diff --git a/scripts/ingest-app-definitions.mjs b/scripts/ingest-app-definitions.mjs
index ec81b7ea61..42dbb08068 100644
--- a/scripts/ingest-app-definitions.mjs
+++ b/scripts/ingest-app-definitions.mjs
@@ -1045,6 +1045,7 @@ const categoryBySlug = {
stripe: "commerce",
superagent: "developer",
supabase: "data",
+ telem: "ai",
"ticket-tailor": "commerce",
ticktick: "productivity",
todoist: "productivity",
@@ -1145,6 +1146,11 @@ const apiKeySpec = {
placeholder: "sbp_...",
},
superagent: { name: "Authorization", prefix: "Bearer ", placeholder: "sk_live_..." },
+ telem: {
+ name: "Authorization",
+ prefix: "Bearer ",
+ placeholder: "tlm_...",
+ },
youcom: {
name: "Authorization",
prefix: "Bearer ",
@@ -1576,6 +1582,73 @@ const specialMethodsFor = (entry) => {
}),
];
}
+ if (entry.slug === "telem") {
+ // Telem's hosted server takes an API key only. The optional settings are
+ // per-connection request headers that the server reads; each one is left
+ // out of the request when it is empty, so an unset field keeps the
+ // server's default (auto routing off, default tier, all providers).
+ const providerList = (key, label, header, helperMd) => ({
+ key,
+ label,
+ type: "textarea",
+ advanced: true,
+ placeholder: "Optional comma-separated provider names",
+ helperMd,
+ validation: { pattern: "^[A-Za-z0-9_.-]+(,[A-Za-z0-9_.-]+)*$", maxLength: 500 },
+ transport: { location: "header", name: header, format: "csv" },
+ });
+ const tenantFields = [
+ {
+ key: "autoRouting",
+ label: "Auto routing",
+ type: "select",
+ advanced: true,
+ options: [
+ { value: "off", label: "Off" },
+ { value: "accuracy", label: "Accuracy" },
+ ],
+ helperMd:
+ "Optional. Accuracy lets Telem choose the search providers for each query. Off, or no selection, keeps auto routing off.",
+ transport: { location: "header", name: "X-Telem-Auto-Routing" },
+ },
+ {
+ key: "tier",
+ label: "Tier",
+ type: "select",
+ advanced: true,
+ options: [
+ { value: "minimalist", label: "Minimalist" },
+ { value: "default", label: "Default" },
+ { value: "extended", label: "Extended" },
+ { value: "max", label: "Max" },
+ ],
+ helperMd:
+ "Optional. Sets how much search work Telem does for each query. No selection uses the Default tier.",
+ transport: { location: "header", name: "X-Telem-Tier" },
+ },
+ providerList(
+ "providersInclude",
+ "Providers to include",
+ "X-Telem-Providers-Include",
+ "Optional. Telem searches only these providers. Leave it empty to allow all providers.",
+ ),
+ providerList(
+ "providersExclude",
+ "Providers to exclude",
+ "X-Telem-Providers-Exclude",
+ "Optional. Telem does not search these providers.",
+ ),
+ ];
+ return [
+ apiKeyMethodFor(entry, "mcp-api-key", entry.serverUrl, {
+ guidanceMd:
+ "Create an API key in the Telem console at app.telem.ai. Paste the key below. Open Advanced to set auto routing, the tier, or the providers to include or exclude.",
+ whenToUse: "Connect with a Telem API key.",
+ consoleLinks: { keys: "https://app.telem.ai", docs: entry.docsUrl },
+ tenantFields,
+ }),
+ ];
+ }
if (entry.slug === "youcom") {
// You.com also serves a documented keyless profile at ?profile=free with a
// reduced read-only tool set. That is a real user choice: try web search
@@ -1677,7 +1750,7 @@ for (const entry of researchManifest.entries) {
schemaVersion: 1,
slug: entry.slug,
name: entry.name,
- description: ({ neon: "Manage Postgres projects and branches, run SQL, and inspect schemas in Neon.", superagent: "Review security findings, start red-team reports, and score content and packages before agents trust them.", mem0: "Remember preferences, conversations, events, and agent state.", zep: "Retrieve temporal graph memory and authorized business context.", supermemory: "Search and save shared memories, documents, and profiles.", honcho: "Remember conversations and retrieve context about peers." })[entry.slug] ?? (entry.slug === "fireflies"
+ description: ({ neon: "Manage Postgres projects and branches, run SQL, and inspect schemas in Neon.", superagent: "Review security findings, start red-team reports, and score content and packages before agents trust them.", mem0: "Remember preferences, conversations, events, and agent state.", zep: "Retrieve temporal graph memory and authorized business context.", supermemory: "Search and save shared memories, documents, and profiles.", honcho: "Remember conversations and retrieve context about peers.", telem: "Search the web and read pages across many search providers with one API key." })[entry.slug] ?? (entry.slug === "fireflies"
? "Search meeting transcripts, read summaries and action items, and connect meeting-ready routines."
: `Connect ${entry.name}'s provider-hosted MCP server.`),
categories: [categoryBySlug[entry.slug] ?? "other"],
diff --git a/server/src/__tests__/tool-access-service.test.ts b/server/src/__tests__/tool-access-service.test.ts
index 21f60559df..27b8c6d8b4 100644
--- a/server/src/__tests__/tool-access-service.test.ts
+++ b/server/src/__tests__/tool-access-service.test.ts
@@ -5181,9 +5181,10 @@ describeEmbeddedPostgres("tool access service", () => {
"messages-api",
"chat-completions-api",
"local",
+ "telem",
]),
);
- expect(res.body.apps).toHaveLength(67);
+ expect(res.body.apps).toHaveLength(68);
for (const slug of ["openrouter", "bedrock", "responses-api", "messages-api", "chat-completions-api", "local"]) {
expect(res.body.apps.find((app: { slug: string }) => app.slug === slug).tags).toContain("model-provider");
}
@@ -6628,6 +6629,67 @@ describeEmbeddedPostgres("tool access service", () => {
).rejects.toMatchObject({ status: 400 });
});
+ it("keeps a saved Telem.AI header policy when the connection is reconnected", async () => {
+ const company = await createCompany(db);
+ const service = createTestToolAccessService(db);
+ const actor = { actorType: "user" as const, actorId: "board" };
+ mockToolsList([{ name: "telem_search" }]);
+ const first = await service.connectGalleryApp(
+ company.id,
+ {
+ galleryKey: "telem",
+ connectionMethodKey: "mcp-api-key",
+ credentialValues: { "credentials.authorization": "tlm_first-key" },
+ },
+ actor,
+ );
+ await service.updateConnection(first.connectionId, {
+ status: "active",
+ config: { ...first.connection.config, headerPolicy: { metadata: { forward: [] } } },
+ });
+
+ mockToolsList([{ name: "telem_search" }]);
+ const reconnected = await service.connectGalleryApp(
+ company.id,
+ {
+ galleryKey: "telem",
+ connectionMethodKey: "mcp-api-key",
+ credentialValues: { "credentials.authorization": "tlm_second-key" },
+ reconnectConnectionId: first.connectionId,
+ },
+ actor,
+ );
+
+ expect(reconnected.connectionId).toBe(first.connectionId);
+ expect(reconnected.connection.config.headerPolicy).toEqual({ metadata: { forward: [] } });
+ });
+
+ it("forwards Paperclip context headers by default for a Telem.AI connection", async () => {
+ const company = await createCompany(db);
+ const service = createTestToolAccessService(db);
+ mockToolsList([{ name: "telem_search" }]);
+ const result = await service.connectGalleryApp(
+ company.id,
+ {
+ galleryKey: "telem",
+ connectionMethodKey: "mcp-api-key",
+ credentialValues: { "credentials.authorization": "tlm_test-secret" },
+ configValues: { tier: "extended" },
+ },
+ { actorType: "user", actorId: "board" },
+ );
+ expect(result.connection.config).toMatchObject({
+ sourceTemplateKey: "telem",
+ methodConfig: { tier: "extended" },
+ headerPolicy: {
+ metadata: {
+ forward: ["company_id", "issue_id", "agent_id", "run_id", "project_id", "correlation_id"],
+ },
+ },
+ });
+ expect(JSON.stringify(result.connection.config)).not.toContain("tlm_test-secret");
+ });
+
it("requires an explicit PostHog method and projects optional validated project filters", async () => {
const company = await createCompany(db);
const service = createTestToolAccessService(db);
@@ -19435,6 +19497,51 @@ describe("normalizeConnectionMethodConfig", () => {
(method) => method.key === "ucp-commerce",
)!;
+ it("sends Telem settings as headers and leaves unset settings out", () => {
+ const telemMethod = getConnectableAppDefinition("telem")!.methods[0]!;
+ expect(normalizeConnectionMethodConfig(telemMethod, {})).toEqual({
+ values: {},
+ url: "https://mcp.telem.ai/mcp",
+ });
+ expect(
+ normalizeConnectionMethodConfig(telemMethod, {
+ autoRouting: "accuracy",
+ tier: "extended",
+ providersInclude: "brave, exa\nbrave",
+ providersExclude: "serpapi",
+ }),
+ ).toEqual({
+ values: {
+ autoRouting: "accuracy",
+ tier: "extended",
+ providersInclude: "brave,exa",
+ providersExclude: "serpapi",
+ },
+ url: "https://mcp.telem.ai/mcp",
+ headers: {
+ "X-Telem-Auto-Routing": "accuracy",
+ "X-Telem-Tier": "extended",
+ "X-Telem-Providers-Include": "brave,exa",
+ "X-Telem-Providers-Exclude": "serpapi",
+ },
+ });
+ expect(
+ normalizeConnectionMethodConfig(telemMethod, { autoRouting: "off" }),
+ ).toEqual({
+ values: { autoRouting: "off" },
+ url: "https://mcp.telem.ai/mcp",
+ headers: { "X-Telem-Auto-Routing": "off" },
+ });
+ expect(() =>
+ normalizeConnectionMethodConfig(telemMethod, { tier: "premium" }),
+ ).toThrow("Tier has an invalid option");
+ expect(() =>
+ normalizeConnectionMethodConfig(telemMethod, {
+ providersInclude: "brave; drop",
+ }),
+ ).toThrow("Providers to include has an invalid value");
+ });
+
it("builds a concrete Shopify endpoint from the validated store domain", () => {
expect(
normalizeConnectionMethodConfig(shopifyMethod, {
diff --git a/server/src/services/tool-access.ts b/server/src/services/tool-access.ts
index 86475ecf27..d5da95b585 100644
--- a/server/src/services/tool-access.ts
+++ b/server/src/services/tool-access.ts
@@ -12763,6 +12763,19 @@ export function toolAccessService(
mcpPreserveAccess: Boolean(retainedConnection && (retainedConnection.status === "active" || asRecord(retainedConnection.config).mcpPreserveAccess === true)),
} : {}),
...(galleryEntry.slug === "posthog" ? { safeDefault: true } : {}),
+ // Telem.AI attributes each search to the Paperclip company, agent,
+ // run and issue, so its catalog connection forwards those context
+ // headers by default (the gateway still drops empty values). A
+ // reconnect keeps the policy the operator saved on the connection.
+ ...(galleryEntry.slug === "telem"
+ ? {
+ headerPolicy: asRecord(retainedConnection?.config).headerPolicy ?? {
+ metadata: {
+ forward: ["company_id", "issue_id", "agent_id", "run_id", "project_id", "correlation_id"],
+ },
+ },
+ }
+ : {}),
}
: { ...baseConfig, quarantineNewEntries: false, unverifiedServer: true };
if (method && isPaperclipCloudConnectorStrategy(method.oauthStrategy)) {
diff --git a/ui/public/brands/apps/manifest.json b/ui/public/brands/apps/manifest.json
index 48771802c0..c981650dc9 100644
--- a/ui/public/brands/apps/manifest.json
+++ b/ui/public/brands/apps/manifest.json
@@ -415,6 +415,13 @@
"catalogVisible": true,
"localAsset": "/brands/apps/superagent.png"
},
+ {
+ "slug": "telem",
+ "provider": "Telem.AI",
+ "catalogVisible": true,
+ "localAsset": "/brands/apps/telem.svg",
+ "darkAsset": "/brands/apps/telem-dark.svg"
+ },
{
"slug": "telegram",
"provider": "Telegram",
diff --git a/ui/public/brands/apps/telem-dark.svg b/ui/public/brands/apps/telem-dark.svg
new file mode 100644
index 0000000000..f43f56ca10
--- /dev/null
+++ b/ui/public/brands/apps/telem-dark.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/ui/public/brands/apps/telem.svg b/ui/public/brands/apps/telem.svg
new file mode 100644
index 0000000000..bb6e97e35c
--- /dev/null
+++ b/ui/public/brands/apps/telem.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file