From f77fcbf4bfc2bf1fb995abda63f2345a34652dd4 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Tue, 6 Oct 2026 16:33:18 -0700 Subject: [PATCH] feat(apps): add Telem.AI web search connection (#15379) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Research agents need current web information. > - The Apps catalog connects agents to remote MCP tools through the normal access rules. > - Telem.AI supplies web search and page reading through one API key. > - This PR adds its catalog entry, optional search settings, artwork, and setup guide. > - The connection keeps an operator's saved header policy when they reconnect. ## Linked Issues or Issue Description Refs #15302. Related catalog work: #13881. This PR continues #15302 by Yifei Ai (@aiwen324). Thank you for the connector and its review fixes. All seven original commits are preserved. GitHub denied the attempt to push to the contributor's fork, so this branch retains the repair commit and merges current master. Master now includes the same test fix. For a squash merge, keep the original author in the final commit message: ```text Co-Authored-By: Yifei Ai Co-Authored-By: Paperclip ``` A search found no separate public Telem issue or competing Telem PR. The existing Apps path matches the roadmap. **Agent or provider** Telem.AI provides web search and page reading through a hosted MCP server. **Why this adapter is useful** Agents can use multiple search providers through one governed connection. Operators can set the search tier, auto routing, and provider lists. **How the agent is invoked** The remote MCP server uses Streamable HTTP at `https://mcp.telem.ai/mcp`. The API key uses an `Authorization: Bearer` header. See the [official MCP guide](https://docs.telem.ai/integrations/mcp/). ## What Changed - Add the Telem.AI definition, research entry, permission review, and generated registry entry. - Add four optional settings. Unset settings send no request header. - Add official light and dark artwork, source records, and a setup guide. - Forward company, issue, agent, run, project, and correlation IDs by default. Preserve a saved policy, including disabled forwarding, on reconnect. - Add catalog and connection tests. ## Verification Current head: `b4164477fb1b312a504789bf17b51c963244c0fd`. Merged master: `228f0e2807c5b59d2aa129cf2d80b9777ebabf07`. - Resolved five shared catalog conflicts after the Superagent connection merged. - Keep both providers in the research ledger, generated registry, generator, branding manifest, and connection guide index. - Correct the combined catalog totals: 52 self-serve candidates, 55 research entries, and 68 Apps entries. - The published Git tree exactly matches the tested local resolution. - Catalog and Apps UI suites: **295 tests pass** after the catalog count fixes. - Connection service suite: **387 tests pass** in the full run. Its only failure was the old catalog count. That test passes on a focused rerun after the fix. This gives **388 passing service tests** across the two runs. - Total focused coverage: **683 passing tests**. The first runs exposed four fixed-count assertions that needed the combined totals. - Shared package build and plugin SDK compile pass. Token gates and whitespace checks pass. - Generation with `--definitions-only` reproduces the Telem definition and registry. The unrelated AgentMail and Linear drift remains excluded. - Local UI typecheck ended with exit 137 at the container memory limit. Full local typecheck, test, and build are not claimed. Earlier runs also recorded missing Cargo and Node development headers. - GitHub reports a clean merge state against master `228f0e280`. - All 54 checks are complete: **52 passed and two Storybook checks skipped**. No check failed or remains pending. - [CI](https://github.com/paperclipai/paperclip/actions/runs/37545412406) passes on this head. This includes typecheck, build, tests, browser shards, Runner checks, and Canary Dry Run. - [Greptile](https://github.com/paperclipai/paperclip/pull/15379#issuecomment-6024519537) is **5/5 on this head**. There are no review threads, open P2s, recommendations, or follow-ups. - [Superagent](https://github.com/paperclipai/paperclip/runs/112548142930) passes. - Final recovery checks confirm all seven original commits and current master remain in history. Token gates and whitespace checks pass. - The final recovery run makes no source change. It verifies the published repair and retains the local check limits below. - [Commitperclip](https://github.com/paperclipai/paperclip/actions/runs/37545407943) passes with no failures. Its only informational note asks the merger to keep the author trailer above. - All seven original contribution commits remain in history. The diff against master contains the same 14 Telem files. It adds no dependency, lockfile, schema, or workflow change. - The managed GitHub CLI capability was missing in this run. The installed GitHub connection applied the base files, merged master, then restored the tested combined catalog. No history was rewritten. The previous head `2d32a0094` passed all remote gates and had Greptile 5/5. Those results do not verify this new head. The original PR reports live setup, discovery, settings headers, gateway calls, and context-header forwarding. This repair does not repeat those account-bound checks. The permission record still marks maintainer live qualification as outstanding. ## Risks - Telem.AI receives the six context IDs by default. The saved header policy controls forwarding. Search use is billed to the account that owns the key. - All agents on a connection share its search settings. - The merge uses master's route-test setup unchanged. The company-boundary assertions remain intact. - No schema, dependency, or workflow change is included. - Live provider evidence is attributed to the original contributor. Maintainer live qualification remains outside this CI repair. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - Original contribution: Anthropic Claude Opus 5.5 (`claude-opus-5-5`), 1M-token context, through Claude Code with shell, editing, and test tools, as disclosed in #15302. - CI repair and review: OpenAI `gpt-6-astra`, through Codex with reasoning, shell, editing, and GitHub tools. The runtime does not expose the context-window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Yifei Ai Co-authored-by: Claude Opus 5.5 (1M context) Co-authored-by: devinfoley <139239+devinfoley@users.noreply.github.com> Co-authored-by: Paperclip --- doc/connections/README.md | 2 +- doc/connections/REMOTE-MCP-BRAND-SOURCES.md | 4 +- doc/connections/TELEM.md | 104 +++++++++++++ .../tool-method-permission-reviews.json | 15 ++ .../shared/src/app-definitions.generated.ts | 39 ++--- packages/shared/src/app-definitions.test.ts | 38 ++++- .../shared/src/app-definitions/telem.json | 144 ++++++++++++++++++ .../shared/src/self-serve-mcp-research.json | 3 +- scripts/ingest-app-definitions.mjs | 75 ++++++++- .../src/__tests__/tool-access-service.test.ts | 109 ++++++++++++- server/src/services/tool-access.ts | 13 ++ ui/public/brands/apps/manifest.json | 7 + ui/public/brands/apps/telem-dark.svg | 9 ++ ui/public/brands/apps/telem.svg | 9 ++ 14 files changed, 542 insertions(+), 29 deletions(-) create mode 100644 doc/connections/TELEM.md create mode 100644 packages/shared/src/app-definitions/telem.json create mode 100644 ui/public/brands/apps/telem-dark.svg create mode 100644 ui/public/brands/apps/telem.svg 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