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
8 changes: 8 additions & 0 deletions .changeset/mcp-oauth-authorization-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@cloudhal/storage": patch
"@cloudhal/api": patch
"@cloudhal/cloudflare": patch
"@cloudhal/cli": patch
---

Remote MCP clients can now authorize with OAuth 2.1 against the deployment itself (RFC-0044 Phase 2, ADR-0045). Better Auth's `mcp()` and `jwt()` plugins run in the Studio's auth instance when the runtime has a stable public origin: the admin Worker uses its origin, `cloudhal studio` needs `--base-url` / `CLOUDHAL_BASE_URL` (it logs why OAuth is off otherwise), and `CLOUDHAL_MCP_OAUTH=off` turns it off. Access tokens are Ed25519 JWTs bound to `/mcp` for one hour, refresh tokens rotate with a 30-second reuse window, and each connection is approved on Studio's new consent screen (`/oauth/consent`), which narrows the requested scopes by the member's role and records an `agent_grants` row (migration `0031_oauth`). A token resolves to the same principal an agent key does; revoking a connection (`DELETE /api/agent-grants/:id`) or removing the member refuses it on its next call. The Better Auth family moves to 1.7.18. Client registration (CIMD, DCR) and the "Connected agents" screen follow in Phase 3.
6 changes: 6 additions & 0 deletions apps/studio/scripts/design-lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ export const ROUTE_TEMPLATES: Record<string, Template> = {
"routes/CommenterPage.tsx": "editor",
"routes/AssetPage.tsx": "editor",
"routes/LoginPage.tsx": "auth",
// RFC-0044 §4: an MCP client's authorization request, approved or denied by the member.
"routes/OAuthConsentPage.tsx": "consent",
"routes/MembersPage.tsx": "list",
"routes/InvitePage.tsx": "token",
"routes/ResetPasswordPage.tsx": "token",
Expand All @@ -100,6 +102,7 @@ export type Template =
| "auth"
| "token"
| "recover"
| "consent"
| "shell";

/** What each template must be composed from (names must appear in the file). DESIGN.md §2a–2f. */
Expand Down Expand Up @@ -141,6 +144,9 @@ export const TEMPLATE_REQUIRES: Record<Template, string[]> = {
// `/forgot-password` (RFC-0011, DESIGN.md §2r): the auth frame, one email field, the neutral
// answer as a Banner, the way back to sign in.
recover: ["AuthLayout", "Input", "Button", "Banner", "LinkButton"],
// OAuth consent (RFC-0044 §4, DESIGN.md §2): a narrow `PageLayout` of `SectionCard`s (who
// asks, the scopes as checkboxes, the mode picker) with Approve and Deny in the last footer.
consent: ["PageLayout", "title=", "SectionCard", "Checkbox.Group", "ModePicker", "Button"],
shell: ["AppFrame", "Sidebar.Group", "Sidebar.MenuButton", "SidebarSwitcher"],
};

Expand Down
4 changes: 3 additions & 1 deletion apps/studio/scripts/screenshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,9 @@ async function shoot(opts: Options, width: number): Promise<string[]> {
result?: { data?: string };
};
const tag = `${opts.scheme ? `_${opts.scheme}` : ""}${opts.locale ? `_${opts.locale}` : ""}${opts.pseudo ? (opts.pseudo === "rtl" ? "_pseudo-rtl" : "_pseudo") : ""}`;
const name = `${p.replace(/[^a-z0-9]+/gi, "_").replace(/^_|_$/g, "") || "root"}${action ? "_action" : ""}${tag}_${width}.png`;
// The path names the file; a query string (e.g. `/oauth/consent`'s signed request) does not.
const bare = p.split("?")[0] ?? p;
const name = `${bare.replace(/[^a-z0-9]+/gi, "_").replace(/^_|_$/g, "") || "root"}${action ? "_action" : ""}${tag}_${width}.png`;
const file = path.join(opts.out, name);
writeFileSync(file, Buffer.from(shot.result?.data ?? "", "base64"));
const notes = [
Expand Down
5 changes: 4 additions & 1 deletion apps/studio/src/i18n/ar.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): ar core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: de0b53138e89cd52
// hash: 44d1f7038905a304
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -1227,6 +1227,7 @@ export const messages: Messages = {
members: "الفريق والأدوار",
agentKeys: "مفاتيح الوكلاء",
mcp: "خادم MCP",
mcpOauth: "OAuth لعملاء MCP",
activity: "سجل النشاط",
previewLinks: "روابط المعاينة",
deployTransfer: "التوزيع والنقل",
Expand All @@ -1238,6 +1239,8 @@ export const messages: Messages = {
"ينضم الأشخاص برابط دعوة ويعملون بدور: المالك أو المسؤول أو المحرر أو المؤلف أو المساهم.",
agentKeys: "يسجّل الوكلاء ومهام CI الدخول بمفاتيح تنشئها أنت.",
mcp: "يتصل الوكلاء على /mcp.",
mcpOauth:
"يسجّل عملاء MCP المسجّلون في هذا الـStudio الدخول إلى /mcp عبر OAuth؛ وأنت توافق على كل اتصال.",
activity: "يُسجَّل كل استدعاء أداة لكل موقع.",
previewLinks: "روابط موقّعة تعرض المسودات على الموقع المستضاف.",
deployTransfer: "يمكن لـcloudhal push وpull الوصول إلى هذا الخادم.",
Expand Down
5 changes: 4 additions & 1 deletion apps/studio/src/i18n/de.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): de core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 844f14a5128a34f6
// hash: 009322cbc9ddcd5d
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -1275,6 +1275,7 @@ export const messages: Messages = {
members: "Team und Rollen",
agentKeys: "Agent-Schlüssel",
mcp: "MCP-Server",
mcpOauth: "OAuth für MCP-Clients",
activity: "Aktivitätsprotokoll",
previewLinks: "Vorschau-Links",
deployTransfer: "Deployment und Übertragung",
Expand All @@ -1286,6 +1287,8 @@ export const messages: Messages = {
"Personen treten per Einladungslink bei und arbeiten in einer Rolle: Inhaber, Administrator, Redakteur, Autor oder Mitarbeiter.",
agentKeys: "Agents und CI-Jobs melden sich mit Schlüsseln an, die du erstellst.",
mcp: "Agents verbinden sich unter /mcp.",
mcpOauth:
"In diesem Studio registrierte MCP-Clients melden sich unter /mcp mit OAuth an; du bestätigst jede Verbindung.",
activity: "Jeder Tool-Aufruf wird pro Website protokolliert.",
previewLinks: "Signierte Links, die Entwürfe auf der bereitgestellten Website zeigen.",
deployTransfer: "cloudhal push und pull können diesen Server erreichen.",
Expand Down
3 changes: 3 additions & 0 deletions apps/studio/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,7 @@ export const en = {
members: "Team and roles",
agentKeys: "Agent keys",
mcp: "MCP server",
mcpOauth: "OAuth for MCP clients",
activity: "Activity log",
previewLinks: "Preview links",
deployTransfer: "Deploy and transfer",
Expand All @@ -1205,6 +1206,8 @@ export const en = {
"People join by invite link and work in a role: Owner, Administrator, Editor, Author or Contributor.",
agentKeys: "Agents and CI jobs sign in with keys you create.",
mcp: "Agents connect at /mcp.",
mcpOauth:
"MCP clients registered with this Studio sign in at /mcp with OAuth; you approve each connection.",
activity: "Every tool call is recorded per site.",
previewLinks: "Signed links that show drafts on the deployed site.",
deployTransfer: "cloudhal push and pull can reach this server.",
Expand Down
5 changes: 4 additions & 1 deletion apps/studio/src/i18n/es.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): es core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 9ea0d8cd7f137822
// hash: a07985c5a97d4088
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -1269,6 +1269,7 @@ export const messages: Messages = {
members: "Equipo y roles",
agentKeys: "Claves de agente",
mcp: "Servidor MCP",
mcpOauth: "OAuth para clientes MCP",
activity: "Registro de actividad",
previewLinks: "Enlaces de vista previa",
deployTransfer: "Implementación y transferencia",
Expand All @@ -1280,6 +1281,8 @@ export const messages: Messages = {
"Las personas se unen con un enlace de invitación y trabajan con un rol: Propietario, Administrador, Editor, Autor o Colaborador.",
agentKeys: "Los agentes y los trabajos de CI inician sesión con claves que creas tú.",
mcp: "Los agentes se conectan en /mcp.",
mcpOauth:
"Los clientes MCP registrados en este Studio inician sesión en /mcp con OAuth; tú apruebas cada conexión.",
activity: "Cada llamada a herramientas se registra por sitio.",
previewLinks: "Enlaces firmados que muestran los borradores en el sitio implementado.",
deployTransfer: "cloudhal push y pull pueden llegar a este servidor.",
Expand Down
5 changes: 4 additions & 1 deletion apps/studio/src/i18n/fa.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): fa core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: a4005ab45f17daa9
// hash: ec7b9eedf08692c6
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -1248,6 +1248,7 @@ export const messages: Messages = {
members: "تیم و نقش‌ها",
agentKeys: "کلیدهای عامل",
mcp: "سرور MCP",
mcpOauth: "OAuth برای کلاینت‌های MCP",
activity: "گزارش فعالیت",
previewLinks: "پیوندهای پیش‌نمایش",
deployTransfer: "استقرار و انتقال",
Expand All @@ -1259,6 +1260,8 @@ export const messages: Messages = {
"افراد با پیوند دعوت می‌پیوندند و در یک نقش کار می‌کنند: مالک، مدیر، ویرایشگر، نویسنده یا همکار.",
agentKeys: "عامل‌ها و کارهای CI با کلیدهایی که شما می‌سازید وارد می‌شوند.",
mcp: "عامل‌ها در /mcp متصل می‌شوند.",
mcpOauth:
"کلاینت‌های MCP ثبت‌شده در این Studio با OAuth در /mcp وارد می‌شوند؛ هر اتصال را شما تأیید می‌کنید.",
activity: "هر فراخوانی ابزار برای هر سایت ثبت می‌شود.",
previewLinks: "پیوندهای امضاشده‌ای که پیش‌نویس‌ها را در سایت مستقرشده نشان می‌دهند.",
deployTransfer: "cloudhal push و pull می‌توانند به این سرور برسند.",
Expand Down
5 changes: 4 additions & 1 deletion apps/studio/src/i18n/fr.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): fr core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 82efd0e745377b9e
// hash: 804ad3e05719e4f5
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -1288,6 +1288,7 @@ export const messages: Messages = {
members: "Équipe et rôles",
agentKeys: "Clés d’agent",
mcp: "Serveur MCP",
mcpOauth: "OAuth pour les clients MCP",
activity: "Journal d’activité",
previewLinks: "Liens d’aperçu",
deployTransfer: "Déploiement et transfert",
Expand All @@ -1299,6 +1300,8 @@ export const messages: Messages = {
"Les personnes rejoignent l’équipe par lien d’invitation et travaillent avec un rôle : Propriétaire, Administrateur, Éditeur, Auteur ou Contributeur.",
agentKeys: "Les agents et jobs de CI se connectent avec des clés que vous créez.",
mcp: "Les agents se connectent à /mcp.",
mcpOauth:
"Les clients MCP enregistrés dans ce Studio se connectent à /mcp avec OAuth ; vous approuvez chaque connexion.",
activity: "Chaque appel d’outil est consigné par site.",
previewLinks: "Des liens signés qui montrent les brouillons sur le site déployé.",
deployTransfer: "cloudhal push et pull peuvent joindre ce serveur.",
Expand Down
7 changes: 7 additions & 0 deletions apps/studio/src/i18n/generated/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,13 @@ export interface MessageParams {
"notifications.roleChanged.title": { role: string | number };
"notifications.toast.endingBody": { time: string | number; from: string | number };
"notifications.toast.endingTitle": { name: string | number; mode: string | number; left: string | number };
"oauthConsent.scopes.added": { label: string | number };
"oauthConsent.scopes.narrowed": { label: string | number; scope: string | number };
"oauthConsent.scopes.notInRole": { label: string | number };
"oauthConsent.scopes.sites": { count: number };
"oauthConsent.title": { client: string | number };
"oauthConsent.who.fromHost": { host: string | number };
"oauthConsent.who.sendsBack": { host: string | number };
"overview.commentsWaiting": { count: number };
"pageStatus.leftOut": { count: number };
"pageStatus.publishTitle": { title: string | number };
Expand Down
4 changes: 3 additions & 1 deletion apps/studio/src/i18n/he.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): he core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: fb9798d4e9369c1e
// hash: b942eaf4d59c74c4
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -1223,6 +1223,7 @@ export const messages: Messages = {
members: "צוות ותפקידים",
agentKeys: "מפתחות סוכנים",
mcp: "שרת MCP",
mcpOauth: "OAuth ללקוחות MCP",
activity: "יומן פעילות",
previewLinks: "קישורי תצוגה מקדימה",
deployTransfer: "פריסה והעברה",
Expand All @@ -1234,6 +1235,7 @@ export const messages: Messages = {
"אנשים מצטרפים בקישור הזמנה ועובדים בתפקיד: בעלים, מנהל מערכת, עורך, כותב או תורם.",
agentKeys: "סוכנים ומשימות CI מתחברים עם מפתחות שיוצרים עבורם.",
mcp: "סוכנים מתחברים בכתובת /mcp.",
mcpOauth: "לקוחות MCP הרשומים ב-Studio הזה נכנסים ל-/mcp עם OAuth; כל חיבור מאושר על ידך.",
activity: "כל קריאה לכלי נרשמת לפי אתר.",
previewLinks: "קישורים חתומים שמציגים טיוטות באתר הפרוס.",
deployTransfer: "cloudhal push ו-pull יכולים להגיע לשרת הזה.",
Expand Down
5 changes: 4 additions & 1 deletion apps/studio/src/i18n/hi.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): hi core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 882e56567d7611da
// hash: b397c1c1d1b280c9
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -1218,6 +1218,7 @@ export const messages: Messages = {
members: "टीम और भूमिकाएँ",
agentKeys: "एजेंट कुंजियाँ",
mcp: "MCP सर्वर",
mcpOauth: "MCP क्लाइंट के लिए OAuth",
activity: "गतिविधि लॉग",
previewLinks: "प्रीव्यू लिंक",
deployTransfer: "डिप्लॉय और ट्रांसफ़र",
Expand All @@ -1229,6 +1230,8 @@ export const messages: Messages = {
"लोग आमंत्रण लिंक से जुड़ते हैं और एक भूमिका में काम करते हैं: स्वामी, व्यवस्थापक, संपादक, लेखक या योगदानकर्ता।",
agentKeys: "एजेंट और CI जॉब आपके बनाए कुंजियों से साइन इन करते हैं।",
mcp: "एजेंट /mcp पर कनेक्ट होते हैं।",
mcpOauth:
"इस Studio में रजिस्टर्ड MCP क्लाइंट /mcp पर OAuth से साइन इन करते हैं; हर कनेक्शन आप मंज़ूर करते हैं।",
activity: "हर टूल कॉल प्रति साइट दर्ज होती है।",
previewLinks: "हस्ताक्षरित लिंक जो डिप्लॉय की गई साइट पर ड्राफ़्ट दिखाते हैं।",
deployTransfer: "cloudhal push और pull इस सर्वर तक पहुँच सकते हैं।",
Expand Down
5 changes: 4 additions & 1 deletion apps/studio/src/i18n/id.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): id core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: fa6d4f782fb8d938
// hash: 84c3e70911fa8af6
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -1264,6 +1264,7 @@ export const messages: Messages = {
members: "Tim dan peran",
agentKeys: "Kunci agen",
mcp: "Server MCP",
mcpOauth: "OAuth untuk klien MCP",
activity: "Log aktivitas",
previewLinks: "Tautan pratinjau",
deployTransfer: "Deploy dan transfer",
Expand All @@ -1275,6 +1276,8 @@ export const messages: Messages = {
"Orang bergabung melalui tautan undangan dan bekerja dalam satu peran: Pemilik, Administrator, Editor, Penulis, atau Kontributor.",
agentKeys: "Agen dan tugas CI masuk dengan kunci yang Anda buat.",
mcp: "Agen terhubung di /mcp.",
mcpOauth:
"Klien MCP yang terdaftar di Studio ini masuk ke /mcp dengan OAuth; Anda menyetujui setiap koneksi.",
activity: "Setiap panggilan alat dicatat per situs.",
previewLinks: "Tautan bertanda tangan yang menampilkan draf di situs yang di-deploy.",
deployTransfer: "cloudhal push dan pull dapat menjangkau server ini.",
Expand Down
5 changes: 4 additions & 1 deletion apps/studio/src/i18n/it.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated by `pnpm i18n:import` (ADR-0034): it core messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 2cfeac004b958452
// hash: 66f1cb9fa0ba33f5
import type { Messages } from "./en.ts";

export const messages: Messages = {
Expand Down Expand Up @@ -1264,6 +1264,7 @@ export const messages: Messages = {
members: "Team e ruoli",
agentKeys: "Chiavi agente",
mcp: "Server MCP",
mcpOauth: "OAuth per client MCP",
activity: "Log delle attività",
previewLinks: "Link di anteprima",
deployTransfer: "Deploy e trasferimento",
Expand All @@ -1275,6 +1276,8 @@ export const messages: Messages = {
"Le persone entrano con un link di invito e lavorano con un ruolo: Proprietario, Amministratore, Editor, Autore o Collaboratore.",
agentKeys: "Agenti e job di CI accedono con chiavi che crei tu.",
mcp: "Gli agenti si collegano a /mcp.",
mcpOauth:
"I client MCP registrati in questo Studio accedono a /mcp con OAuth; approvi tu ogni connessione.",
activity: "Ogni chiamata a uno strumento viene registrata per sito.",
previewLinks: "Link firmati che mostrano le bozze sul sito distribuito.",
deployTransfer: "cloudhal push e pull possono raggiungere questo server.",
Expand Down
Loading
Loading