Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions adrs/specstream-mcp-vendor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Add SpecStream as an MCP vendor

I make SpecStream (https://specstream.ai/?utm_source=crm), so I'm biased.
You ask it for data in plain words and it returns rows from the live web, each value with its source URL.

What made me notice: the CRM agent researches one person or company at a time (`context-dev.ts`, `perplexity.ts`).
That breaks down on a whole list, like "200 seed stage fintechs in Germany that hired a head of sales this quarter".
Every row must meet the conditions and every fact needs its page, so a rep can open row 147 without trusting the agent.

What I'd do: add `packages/mcp-integrations/src/vendors/specstream.ts` and one line in `vendors/index.ts`, shaped like Linear and Notion.
The remote MCP (https://api.specstream.ai/mcp, OAuth with dynamic client registration) passes your `McpOAuthClient.discover`.
`finds:spend` is only in read-write, and tools that start paid work or send data out are in `riskyTools`, so each deployed agent needs an explicit grant.

What it breaks: nothing, it's additive.
One more vendor on Connections, and usage is billed to the user's own SpecStream account; the CRM pays nothing.
The PR has the code (two small files and a test), happy to drop it if you'd rather settle the idea first.
2 changes: 1 addition & 1 deletion docs/connections.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ Docusign is spelled with a lowercase s since its 2024 rebrand. The note-taker is

## MCP connections

Clerk, Cloudflare, Linear, Neon, PlanetScale, Prisma, Supabase, and Vercel use the shared MCP connection flow.
Clerk, Cloudflare, Linear, Neon, PlanetScale, Prisma, SpecStream, Supabase, and Vercel use the shared MCP connection flow.
An owner or admin chooses read-only or read-and-write access.
The API stores OAuth tokens in encrypted form.
The API never returns tokens to the browser.
Expand Down
2 changes: 2 additions & 0 deletions packages/mcp-integrations/src/vendors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { neon } from "./neon";
import { notion } from "./notion";
import { planetscale } from "./planetscale";
import { prisma } from "./prisma";
import { specstream } from "./specstream";
import { supabase } from "./supabase";
import { vercel } from "./vercel";

Expand All @@ -16,6 +17,7 @@ export const vendors = [
notion,
planetscale,
prisma,
specstream,
supabase,
vercel,
] as const;
56 changes: 56 additions & 0 deletions packages/mcp-integrations/src/vendors/specstream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { parseVendor } from "../vendor";

const READ_SCOPES = [
"specs:read",
"finds:read",
"projects:read",
"account:read",
] as const;

export const specstream = parseVendor({
slug: "specstream",
name: "SpecStream",
presentation: {
description:
"Let deployed agents find company data with a source on every value",
bringsIn: "Lists of companies and people, each value with its source URL",
sendsReadOnly: "Nothing",
sendsReadWrite: "Requests you approve, priced before they run",
},
mcp: {
resource: "https://api.specstream.ai/mcp",
transport: "streamable-http",
},
auth: {
kind: "mcp_oauth",
authorizationServers: ["https://specstream.ai"],
},
access: {
read_only: {
url: "https://api.specstream.ai/mcp",
scopes: [...READ_SCOPES],
},
read_write: {
url: "https://api.specstream.ai/mcp",
scopes: [...READ_SCOPES, "specs:write", "finds:write", "finds:spend"],
},
},
riskyTools: [
"authorize_capacity_work",
"quote_capacity_work",
"create_capacity_schedule",
"update_capacity_schedule",
"send_to_destination",
"create_destination_sync",
"update_destination_sync",
"share_find",
"share_collection",
"export_find",
],
docsHosts: ["specstream.ai"],
source: {
providerDocs: "https://specstream.ai/docs/mcp?utm_source=crm",
tools: "https://specstream.ai/docs/mcp?utm_source=crm",
checkedAt: "2026-11-04",
},
});
19 changes: 19 additions & 0 deletions packages/mcp-integrations/test/catalog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe("MCP catalog", () => {
"notion",
"planetscale",
"prisma",
"specstream",
"supabase",
"vercel",
]);
Expand All @@ -32,6 +33,24 @@ describe("MCP catalog", () => {
expect(vendorAccess(notion, "read_only").scopes).toEqual(["default"]);
});

test("registers SpecStream with spend gated behind its own scope", () => {
const specstream = vendorBySlug("specstream");
expect(specstream).toBeDefined();
if (!specstream) throw new Error("SpecStream is missing");

expect(specstream.mcp.resource).toBe("https://api.specstream.ai/mcp");
expect(specstream.auth.authorizationServers).toEqual([
"https://specstream.ai",
]);
expect(vendorAccess(specstream, "read_only").scopes).not.toContain(
"finds:spend",
);
expect(vendorAccess(specstream, "read_write").scopes).toContain(
"finds:spend",
);
expect(specstream.riskyTools).toContain("authorize_capacity_work");
});

test("keeps every provider on an HTTPS hosted server", () => {
for (const vendor of vendors) {
expect(new URL(vendor.mcp.resource).protocol).toBe("https:");
Expand Down