Files
webstudio/apps/builder/app/services/api-permits.server.ts
Oleg Isonen dde30ffb54 feat: Improve MCP authoring, compact output, and agent onboarding (#5883)
## Summary

This improves how agents discover, author, inspect, and verify Webstudio
projects while reducing unnecessary MCP context usage.

## What changed

- Adds an **Agents** section to Project Settings with ready-to-run setup
commands and client configuration for Claude Code, Codex, Cursor, and VS
Code.
- Makes large list and schema responses compact and paginated by
default, with **verbose** available when complete records or diagnostics
are needed.
- Applies consistent compact output to assets, instances, styles, design
tokens, CLI schemas, and project-session metadata.
- Returns actionable expression warnings for props, resources, and
Collection bindings, including unknown variables and risky render-time
mutation resources.
- Improves Collection expression guidance so agents can bind array items
with the correct scoped variables.
- Reports rendered audit outcomes explicitly as complete, partial,
confirmation-required, or failed.
- Expands runtime output schemas so MCP clients can validate responses
reliably.
- Strengthens the packaged CLI smoke workflow with a realistic
seven-page stdio MCP scenario.

## Architecture

- Consolidates `project-build` from 65 file-oriented entrypoints to 11
architecture-owned surfaces.
- Separates the core model, contracts, runtime authoring, transfer,
state, project sessions, MCP, visual tooling, persistence codecs, and
authenticated server services.
- Isolates Node-only JSX evaluation from browser-safe transfer utilities
and authenticated server modules.
- Keeps persistence codecs independent from authenticated database
services, preventing unrelated module initialization.
- Defines shared instance-path types once and reuses them across runtime
modules.
- Preserves Immer initialization through explicit package side-effect
metadata.
- Adds package tests that prevent a return to source-file entrypoints or
generic exposure of Node-only surfaces.

## Validation

- Full repository checks, tests, typechecks, generated-file checks, and
fixtures
- All 163 Builder test files and 2,141 tests
- All 71 project-build test files and 1,794 tests
- Lint and package-boundary validation
- Full packed CLI release smoke test, including install, preview, agent
setup, seven-page stdio MCP workflow, rendered audit, and structured
commands
2026-07-15 18:11:28 +02:00

82 lines
2.2 KiB
TypeScript

import { TRPCError } from "@trpc/server";
import {
authorizeProject,
type AppContext,
type AuthPermit,
} from "@webstudio-is/trpc-interface/index.server";
import { db as authDb } from "@webstudio-is/authorization-token/index.server";
import type { BuilderApiCapability } from "@webstudio-is/project-build/contracts";
type ApiPermit = BuilderApiCapability;
type ProjectApiPermit = Extract<AuthPermit, BuilderApiCapability>;
type ApiToken = Awaited<ReturnType<typeof authDb.getTokenInfo>>;
export const loadApiToken = async (ctx: AppContext) => {
if (ctx.authorization.type !== "token") {
throw new TRPCError({
code: "FORBIDDEN",
message: "Builder API requires an API token",
});
}
return await authDb.getTokenInfo(ctx.authorization.authToken, ctx);
};
export const getTokenPermits = (token: ApiToken, ctx: AppContext) => {
const permits: ApiPermit[] = [...authDb.getTokenProjectPermits(token)];
if (
token.canUseApi === true &&
ctx.planFeatures.allowAdditionalPermissions === true
) {
permits.push("api");
}
return permits;
};
export const assertApiTokenPermit = async (ctx: AppContext) => {
const token = await loadApiToken(ctx);
const permits = getTokenPermits(token, ctx);
if (permits.includes("api") === false) {
throw new TRPCError({
code: "FORBIDDEN",
message: "Authorization token cannot use Builder API",
});
}
return { token, permits };
};
export const assertApiProjectPermit = async (
ctx: AppContext,
projectId: string,
permit: ProjectApiPermit
) => {
const { token, permits } = await assertApiTokenPermit(ctx);
if (token.projectId !== projectId) {
throw new TRPCError({
code: "FORBIDDEN",
message: "Authorization token is not valid for project",
});
}
if (permits.includes(permit) === false) {
throw new TRPCError({
code: "FORBIDDEN",
message: `Authorization token does not have ${permit} permission`,
});
}
const canUseProject = await authorizeProject.hasProjectPermit(
{ projectId, permit },
ctx
);
if (canUseProject === false) {
throw new TRPCError({
code: "FORBIDDEN",
message: "You don't have access to this project",
});
}
return { token, permits };
};