Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
ad7186e
feat(infra-mcp): hosted tool surface with a shared runner
richiemcilroy Nov 13, 2026
ffb5af5
refactor(web): export the developer API apps for in-process use
richiemcilroy Nov 13, 2026
3e4a821
feat(infra): hosted MCP endpoint for Cap Infra
richiemcilroy Nov 13, 2026
40e3f4b
docs(infra): point agents at the hosted MCP endpoint
richiemcilroy Nov 13, 2026
b89c75f
feat(infra-mcp): hosted transcripts of a stored video use its videoId
richiemcilroy Nov 13, 2026
f989a4d
fix(infra-mcp): refuse a local file that is not video or audio before…
richiemcilroy Nov 13, 2026
260bdc0
refactor(infra-mcp): drop hosted hints that only the local videoId pa…
richiemcilroy Nov 13, 2026
8ffee32
fix(infra-mcp): trim the API origin without a backtracking regex
richiemcilroy Nov 13, 2026
926b2d0
fix(infra-mcp): keep a reused upload open and explain unclear complet…
richiemcilroy Nov 13, 2026
e131987
refactor(web): serve the Infra MCP route through HttpApi like /api/mcp
richiemcilroy Nov 13, 2026
eed6c83
fix(infra-mcp): stop stating a per-key request number that can go stale
richiemcilroy Nov 13, 2026
4d59bfe
fix(infra-mcp): say to upload again when completing removed the file
richiemcilroy Nov 13, 2026
1c028fd
test(web): read the per-key limit from its constant in the MCP limit …
richiemcilroy Nov 13, 2026
10d43d3
Merge remote-tracking branch 'origin/main' into feat/infra-hosted-mcp
richiemcilroy Nov 13, 2026
3411f08
fix(web): send the developer API response headers from the Infra MCP …
richiemcilroy Nov 13, 2026
7612449
Merge remote-tracking branch 'origin/main' into feat/infra-hosted-mcp
richiemcilroy Nov 13, 2026
d2678a2
test(infra): expect the REST API's missing key and public key answers…
richiemcilroy Nov 13, 2026
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
2 changes: 1 addition & 1 deletion apps/web/__tests__/unit/agent-readiness.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ describe("developer OpenAPI spec", () => {
] as const;
const implemented = new Set<string>();
for (const [dir, base] of routeDirs) {
const source = readFileSync(join(apiRoot, dir, "route.ts"), "utf8");
const source = readFileSync(join(apiRoot, dir, "app.ts"), "utf8");
const modules = new Map(
[...source.matchAll(/import \* as (\w+) from "\.\/([^"]+)"/g)].map(
([, name, file]) => [name, file],
Expand Down
6 changes: 4 additions & 2 deletions apps/web/__tests__/unit/developer-get-started-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,12 @@ describe("the Get Started guide", () => {
}
});

it("keeps real credentials, the CLI and the em dash out", () => {
it("keeps real credentials, the CLI and the em dash out, and points agents at the hosted MCP server", () => {
expect(content).not.toContain("—");
expect(content).not.toMatch(/\bcap (guide|record|upload|caps|mcp)\b/);
expect(content).not.toMatch(/\bMCP\b/);
expect(content.match(/https:\/\/[^\s"`]*\/mcp\b/g) ?? []).toEqual(
Array(4).fill("https://cap.so/api/developer/mcp"),
);
const keys = content.match(/\bc(?:sk|pk|ut)_[A-Za-z0-9._-]+/g) ?? [];
expect(keys.every((key) => key === "csk_your_secret_key")).toBe(true);
});
Expand Down
24 changes: 17 additions & 7 deletions apps/web/__tests__/unit/infra-agent-prompt.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { CAP_INFRA_AGENT_PROMPT } from "@/data/infra-agent-prompt";
import {
CAP_INFRA_AGENT_PROMPT,
CAP_INFRA_MCP_URL,
} from "@/data/infra-agent-prompt";
import { developerOpenApiSpec } from "@/lib/developer-openapi";

const METHODS = ["get", "post", "put", "patch", "delete"] as const;
Expand Down Expand Up @@ -30,7 +33,9 @@ describe("Cap Infra agent prompt", () => {
);
}
const pathMentions =
CAP_INFRA_AGENT_PROMPT.match(/\/api\/developer\//g) ?? [];
CAP_INFRA_AGENT_PROMPT.replaceAll(CAP_INFRA_MCP_URL, "").match(
/\/api\/developer\//g,
) ?? [];
expect(pathMentions).toHaveLength(calls.length);
});

Expand Down Expand Up @@ -60,17 +65,22 @@ describe("Cap Infra agent prompt", () => {
expect(CAP_INFRA_AGENT_PROMPT).toContain("60 requests a minute");
});

it("links only to the API reference", () => {
it("links only to the API reference and the hosted MCP server", () => {
const links = CAP_INFRA_AGENT_PROMPT.match(/https?:\/\/[^\s)]+/g) ?? [];
expect([...new Set(links)].sort()).toEqual([
"https://cap.so/api/developer/mcp",
"https://cap.so/docs/api/rest-api",
"https://cap.so/openapi.json",
]);
});

it("never asks for the Cap CLI, an MCP server or a pasted key", () => {
it("never asks for the Cap CLI, a local MCP server or a pasted key", () => {
expect(CAP_INFRA_AGENT_PROMPT).not.toMatch(/\bcap [a-z]/);
expect(CAP_INFRA_AGENT_PROMPT).not.toMatch(/\bCLI\b|\bMCP\b|curl|install/i);
expect(CAP_INFRA_AGENT_PROMPT).not.toMatch(/\bCLI\b|curl|install|npx/i);
expect(CAP_INFRA_AGENT_PROMPT.match(/\bMCP\b/g)).toEqual(["MCP", "MCP"]);
expect(CAP_INFRA_AGENT_PROMPT).toContain(
"hosted MCP server at https://cap.so/api/developer/mcp",
);
expect(CAP_INFRA_AGENT_PROMPT).not.toContain("cpk_");
expect(CAP_INFRA_AGENT_PROMPT).toContain(
"Never ask me to paste the key into the chat",
Expand All @@ -85,15 +95,15 @@ describe("Cap Infra agent prompt", () => {
);
});

it("is introduced in the console without the Cap CLI or an MCP server", () => {
it("is introduced in the console without the Cap CLI", () => {
const source = readFileSync(
join(
process.cwd(),
"app/(infra)/dashboard/infra/_components/agent-setup.tsx",
),
"utf8",
);
expect(source).not.toMatch(/\bCLI\b|\bMCP\b/);
expect(source).not.toMatch(/\bCLI\b/);
expect(source).toContain("secret key");
});
});
Loading
Loading