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