Skip to content
Merged
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
24 changes: 24 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,30 @@ sidecar a given run lands on) rather than reimplementing execution.
Provisioning and allocation follow Interchange's own contracts; workbench
does not maintain a parallel scheduler.

## MCP connect

Remote MCP servers connect through Plugins (curated presets and
add-by-URL). The connect-time probe and later tool calls share one
origin-pinned fetch: every first hop must be the stored origin or an
explicit extra origin for that pin — not a host-suffix match — and a 3xx
is never followed, even to an allowlisted origin. Canva is the one
shipped extra: the stored MCP origin may also first-hop the protocol
origin that is not the stored `apiBaseUrl`.

OAuth presets that list advertised scopes send those scopes on RFC 7591
dynamic client registration; presets that omit the list stay on the SDK's
protected-resource metadata fallback. When `/start` fails, the return
distinguishes `client_rejected` (the authorization server refused
Workbench as a client — including RFC 7591 `invalid_redirect_uri` and
sibling client-metadata codes, even when the SDK maps an unknown code
onto a generic server error) from `discovery_failed` (the authorization
server could not be reached). A successful callback re-probes with the
new token and may put that probe's tool count on the Plugins return so
the row can show it.

Live Canva OAuth against Canva's own servers is not verified; this is
the shipped control flow, not a proven live handshake.

## Related docs

- [docs/GLOSSARY.md](docs/GLOSSARY.md) — product-term to platform-term
Expand Down
35 changes: 35 additions & 0 deletions IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,39 @@ specialist's own 1:1 — never an invite into Myra's DM.
A create-succeeded / mint-failed split is a completed tool result that
names both halves, not a bare error.

## Canva MCP connect (shipped)

Canva is the `canva` MCP preset (`packages/connections/src/mcp-presets.ts`):
`https://mcp.canva.com/mcp`, `connectionMode: "oauth"`, with the 16
advertised PRM scopes space-joined onto RFC 7591 DCR `clientMetadata.scope`
(`createMcpOAuthProvider` in `packages/connections/src/mcp-oauth.ts`).
Other presets omit `oauthScopes` and stay on the SDK's SEP-835 PRM
fallback.

Connect-time probe and credential fetch share
`mcpOriginPinnedFetch` (`packages/credential-providers/src/mcp-origin-pinned-fetch.ts`):
pin to the stored origin, extra first hop only
`https://mcp.canva.com` → `https://canva.ai` (not a host suffix),
`redirect: "manual"` so a 302 is never followed. `/start` classifies
DCR/client refusal as `client_rejected` versus unreachable discovery as
`discovery_failed` (`packages/connections/src/mcp-oauth-routes.ts`).
RFC 7591 `invalid_redirect_uri` (and `invalid_client_metadata`,
`invalid_client`, `unauthorized_client`) count as `client_rejected`; the
route clones 4xx/5xx JSON before the MCP SDK 1.30.0 maps unknown codes
onto `ServerError`.

A successful OAuth callback probes with the new token and, on success,
appends `toolCount` to the Plugins return query. The Canva row
(`packages/plugins-ui/src/mcp-preset-cards.tsx`) shows that count when
it is a non-negative integer; otherwise the row stays "Connected".
`@corbits/mcp-tools` per-request timeout is two minutes
(`MCP_REQUEST_TIMEOUT_MS` in `packages/mcp-tools/src/mcp-client.ts`) —
above the SDK's 60s default, below a five-minute chat turn.

These are unit-tested control-flow facts. Live Canva OAuth against
Canva's own servers is **not** verified; do not document a proven live
handshake.

## Related docs

- [README.md](README.md) — quickstart, local setup, repo layout, e2e detail
Expand All @@ -223,3 +256,5 @@ names both halves, not a bare error.
- Whether Pulumi stacks/config live in this repo or a separate
infrastructure repo is not established in the docs reviewed for this
pass.
- Live Canva MCP OAuth (DCR, redirect allowlist, and post-OAuth probe
against `mcp.canva.com` / `canva.ai`) is not verified as of CL-7083.
22 changes: 22 additions & 0 deletions PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,20 @@ extend what an agent knows — both are installable, both are scoped to the
bench or workbench that installs them, and neither requires touching
platform internals.

The Plugins rail is how a person connects remote MCP servers: curated
preset cards plus an add-by-URL path. Canva is an OAuth preset — Connect
sends them through that app's sign-in, then back to Plugins. After a
successful OAuth return, the row can show how many tools the connect
probe found; a missing or non-integer count stays a bare "Connected". If
sign-in cannot start, Plugins distinguishes an unreachable authorization
server from the app rejecting Workbench as a client (redirect URL or
registration). Agent MCP tool calls are allowed two minutes so a slow
design tool can finish inside a chat turn.

Live Canva OAuth against Canva's own servers is **not** verified as of
CL-7083. This documents the shipped connect path, not a proven live
handshake.

## Workbench settings

Each workbench has its own full-stage settings surface, not a dialog —
Expand Down Expand Up @@ -217,6 +231,14 @@ user-facing surfaces use the rest of the product vocabulary above.
generic `connections/pending` still wakes the asking agent. A leftover
agent 401 after GitHub already succeeded is still a first-minute bug
— see IMPLEMENTATION.md; do not document that it cannot happen.
- Connected/settle honesty (no stale Connect after success; settle never
posting as the signed-in user; no agent 401 after GitHub already
succeeded) stays **target** until CL-6737 and CL-6738 land — see
IMPLEMENTATION.md open questions; do not document those guarantees as
shipped.
- Live Canva MCP OAuth (sign-in, DCR, and post-OAuth probe against
Canva's own servers) is not verified; do not document a proven live
Canva handshake.
- The precise boundary of what Insights surfaces to a non-admin bench
member (all tenant activity vs. only their own) is not spelled out in
`packages/insights`'s own docs as of this writing.
18 changes: 18 additions & 0 deletions apps/hub/src/launch-caches.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,24 @@ describe("createLaunchCaches: assetService reads", () => {
expect(inner.listCalls.length).toBe(1);
});

test("lists an empty catalog when the package-registry has no resolvable main", async () => {
const heads = new Map<string, string | null>();
const inner = countingAssetService(heads);
const { repoStore } = countingRepoStore(heads);
const caches = createLaunchCaches({
assetService: inner.assetService,
repoStore,
});

const listed = await caches.assetService.listAssetBlobs({
assetId: ASSET_ID,
dir: "tarballs",
});

expect(listed).toEqual([]);
expect(inner.listCalls.length).toBe(0);
});

test("delegates createAsset and populateAsset untouched", () => {
const heads = new Map([[HEAD_REF, "sha-1"]]);
const inner = countingAssetService(heads);
Expand Down
6 changes: 5 additions & 1 deletion apps/hub/src/launch-caches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,11 @@ export function createLaunchCaches(deps: {
params: ListAssetBlobsParams,
): Promise<string[]> {
const sha = await resolvePackageRegistryHeadSha(params.assetId, params.ref);
if (sha === null) return assetService.listAssetBlobs(params);
// No resolvable `main` means no tarballs yet. Match the tarball REST
// list, which returns [] on this same not_found rather than failing
// the launch. Pins against a missing tarball still fail as unknown
// package.
if (sha === null) return [];
const key = `${params.assetId}:${sha}:${params.dir}`;
const cached = listCache.get(key);
if (cached !== undefined) return cached;
Expand Down
2 changes: 1 addition & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading