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

Remote MCP clients can now register themselves (RFC-0044 Phase 3a, ADR-0045). Clients that name an HTTPS Client ID Metadata Document as their `client_id` (Claude Code, Codex, VS Code) are accepted through `@better-auth/cimd`, fetched with the media import's SSRF-safe transport (pinned on Node, DoH-checked on Workers; https only, public addresses only, no redirects, 5 KB, 5 s). Clients without one (Cursor) use dynamic client registration at `/api/auth/oauth2/register`: public clients only, rate-limited per address and per day, capped at 5,000, and pruned by the scheduler after 30 unused days. Document clients are fetched at most hourly per client, with budgets in Cloudhal's own counters that never lock out stored clients, a cap of 5,000 that evicts unused clients (the well-known clients' hosts are exempt), pruning after three hours unused, and a failed refetch that refuses the client; migration `0032_oauth_client_kind_index` indexes them. Redirect URIs must be https, loopback http (any port) or a native app's private-use scheme (`cursor`, `vscode`, reverse-domain, `CLOUDHAL_MCP_REDIRECT_SCHEMES`), and the client host list applies at every authorization. With DCR off, the clients it registered earlier cannot start new authorizations. The consent screen shows client names plain and short, a document client's host first, and marks self-registered clients as unverified. `CLOUDHAL_MCP_CIMD=off`, `CLOUDHAL_MCP_DCR=off`, `CLOUDHAL_MCP_CLIENT_HOSTS` and `CLOUDHAL_MCP_REDIRECT_SCHEMES` control it on both runtimes, and the Docker compose file passes them through. The Phase 2 server-side test client (`OauthServer.createClient`) is removed. `set_embargo` through an OAuth connection now files the gate for a person's approval instead of leaving it unapplied, and the request is notified while it waits. `safeFetch` results carry the response headers.
3 changes: 3 additions & 0 deletions apps/studio/src/i18n/generated/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -550,8 +550,11 @@ export interface MessageParams {
"oauthConsent.scopes.notInRole": { label: string | number };
"oauthConsent.scopes.sites": { count: number };
"oauthConsent.title": { client: string | number };
"oauthConsent.titleFrom": { host: string | number; client: string | number };
"oauthConsent.who.fromHost": { host: string | number };
"oauthConsent.who.lookalike": { client: string | number };
"oauthConsent.who.sendsBack": { host: string | number };
"oauthConsent.who.thisComputerApp": { scheme: string | number };
"overview.commentsWaiting": { count: number };
"pageStatus.leftOut": { count: number };
"pageStatus.publishTitle": { title: string | number };
Expand Down
7 changes: 6 additions & 1 deletion apps/studio/src/i18n/lazy/oauthConsent.ar.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Generated by `pnpm i18n:import` (ADR-0034): ar `oauthConsent.*` messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: d6d17d53696a6da9
// hash: c7bc7dea9efce92e
import type { NamespaceMessages } from "../namespaces.ts";

export const messages: NamespaceMessages<"oauthConsent"> = {
oauthConsent: {
title: "ربط {client}",
titleFrom: "{host}: ربط {client}",
description: "يطلب وكيل العمل في هذا الـStudio نيابةً عنك. تحقّق مما يُسمح له به قبل الموافقة.",
invalid: {
title: "لا يمكن استخدام هذا الطلب",
Expand All @@ -16,8 +17,12 @@ export const messages: NamespaceMessages<"oauthConsent"> = {
title: "من يطلب",
fromHost: "يُعرَّف عبر {host}",
registered: "مسجّل في هذا الـStudio",
selfRegistered: "مسجّل ذاتيًا، غير مُتحقَّق منه",
lookalike:
"يشبه الاسم {client}، لكن أي أحد يمكنه تسجيل عميل بأي اسم. وافق فقط إذا بدأت هذا الاتصال من {client} بنفسك وكان العنوان الذي يعيدك إليه، المعروض أعلاه، هو الذي يستخدمه.",
sendsBack: "يعيدك إلى {host}",
thisComputer: "يعيدك إلى تطبيق على هذا الحاسوب",
thisComputerApp: "يعيدك إلى تطبيق على هذا الكمبيوتر ({scheme}:)",
loopbackWarning:
"كل عنوان يمكن لهذا الوكيل أن يعيدك إليه موجود على هذا الحاسوب. وافق فقط إذا بدأت الاتصال من تطبيق هنا.",
stepUp: "هذا الوكيل متصل بالفعل ويطلب المزيد. ما يُسمح له به اليوم محدَّد.",
Expand Down
7 changes: 6 additions & 1 deletion apps/studio/src/i18n/lazy/oauthConsent.de.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Generated by `pnpm i18n:import` (ADR-0034): de `oauthConsent.*` messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 07b0fd828ea3973f
// hash: d3fc2f9b7d88dce0
import type { NamespaceMessages } from "../namespaces.ts";

export const messages: NamespaceMessages<"oauthConsent"> = {
oauthConsent: {
title: "{client} verbinden",
titleFrom: "{host}: {client} verbinden",
description:
"Ein Agent möchte in diesem Studio für dich arbeiten. Prüfe, was er darf, bevor du zustimmst.",
invalid: {
Expand All @@ -17,8 +18,12 @@ export const messages: NamespaceMessages<"oauthConsent"> = {
title: "Wer fragt",
fromHost: "Identifiziert durch {host}",
registered: "In diesem Studio registriert",
selfRegistered: "Selbst registriert, nicht geprüft",
lookalike:
"Der Name sieht nach {client} aus, aber jeder kann einen Client unter beliebigem Namen registrieren. Stimme nur zu, wenn du diese Verbindung selbst aus {client} gestartet hast und die Adresse, zu der sie dich zurückschickt (oben), die ist, die es verwendet.",
sendsBack: "Leitet dich zurück zu {host}",
thisComputer: "Leitet dich zurück zu einer App auf diesem Computer",
thisComputerApp: "Schickt dich zu einer App auf diesem Computer zurück ({scheme}:)",
loopbackWarning:
"Jede Adresse, zu der dieser Agent dich zurückleiten kann, liegt auf diesem Computer. Stimme nur zu, wenn du die Verbindung aus einer App hier gestartet hast.",
stepUp:
Expand Down
5 changes: 5 additions & 0 deletions apps/studio/src/i18n/lazy/oauthConsent.en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
export const messages = {
oauthConsent: {
title: "Connect {client}",
titleFrom: "{host}: connect {client}",
description:
"An agent asks to work in this Studio for you. Check what it may do before you approve.",
invalid: {
Expand All @@ -17,8 +18,12 @@ export const messages = {
title: "Who is asking",
fromHost: "Identified by {host}",
registered: "Registered with this Studio",
selfRegistered: "Self-registered, not verified",
lookalike:
"The name looks like {client}, but anyone can register a client under any name. Approve only if you started this connection from {client} yourself and the address it sends you back to, shown above, is the one it uses.",
sendsBack: "Sends you back to {host}",
thisComputer: "Sends you back to an app on this computer",
thisComputerApp: "Sends you back to an app on this computer ({scheme}:)",
loopbackWarning:
"Every address this agent can send you back to is on this computer. Approve only if you started the connection from an app here.",
stepUp: "This agent is already connected and asks for more. What it may do today is ticked.",
Expand Down
7 changes: 6 additions & 1 deletion apps/studio/src/i18n/lazy/oauthConsent.es.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Generated by `pnpm i18n:import` (ADR-0034): es `oauthConsent.*` messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 6e539e2b036bb8c6
// hash: c0a9bf4916410522
import type { NamespaceMessages } from "../namespaces.ts";

export const messages: NamespaceMessages<"oauthConsent"> = {
oauthConsent: {
title: "Conectar {client}",
titleFrom: "{host}: conectar {client}",
description:
"Un agente pide trabajar en este Studio por ti. Revisa lo que puede hacer antes de aprobar.",
invalid: {
Expand All @@ -17,8 +18,12 @@ export const messages: NamespaceMessages<"oauthConsent"> = {
title: "Quién lo pide",
fromHost: "Identificado por {host}",
registered: "Registrado en este Studio",
selfRegistered: "Autorregistrado, sin verificar",
lookalike:
"El nombre parece {client}, pero cualquiera puede registrar un cliente con cualquier nombre. Aprueba solo si iniciaste tú esta conexión desde {client} y la dirección a la que te devuelve, indicada arriba, es la que usa.",
sendsBack: "Te devuelve a {host}",
thisComputer: "Te devuelve a una app de este equipo",
thisComputerApp: "Te devuelve a una app de este equipo ({scheme}:)",
loopbackWarning:
"Todas las direcciones a las que este agente puede devolverte están en este equipo. Aprueba solo si iniciaste la conexión desde una app de aquí.",
stepUp: "Este agente ya está conectado y pide más. Lo que puede hacer hoy está marcado.",
Expand Down
7 changes: 6 additions & 1 deletion apps/studio/src/i18n/lazy/oauthConsent.fa.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Generated by `pnpm i18n:import` (ADR-0034): fa `oauthConsent.*` messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 9378e7d2a7816050
// hash: 14b933da3ad6923c
import type { NamespaceMessages } from "../namespaces.ts";

export const messages: NamespaceMessages<"oauthConsent"> = {
oauthConsent: {
title: "اتصال {client}",
titleFrom: "{host}: اتصال {client}",
description:
"یک عامل می‌خواهد در این Studio از طرف شما کار کند. پیش از تأیید، ببینید چه کارهایی مجاز است.",
invalid: {
Expand All @@ -17,8 +18,12 @@ export const messages: NamespaceMessages<"oauthConsent"> = {
title: "چه کسی درخواست می‌دهد",
fromHost: "شناسایی‌شده با {host}",
registered: "ثبت‌شده در این Studio",
selfRegistered: "خودثبت‌شده، تأییدنشده",
lookalike:
"این نام شبیه {client} است، اما هر کسی می‌تواند کلاینتی با هر نامی ثبت کند. فقط وقتی تأیید کن که خودت این اتصال را از {client} شروع کرده‌ای و نشانی‌ای که تو را به آن برمی‌گرداند (بالا) همان نشانی‌ای است که استفاده می‌کند.",
sendsBack: "شما را به {host} برمی‌گرداند",
thisComputer: "شما را به برنامه‌ای در این رایانه برمی‌گرداند",
thisComputerApp: "تو را به برنامه‌ای روی این رایانه برمی‌گرداند ({scheme}:)",
loopbackWarning:
"همهٔ نشانی‌هایی که این عامل می‌تواند شما را به آن‌ها برگرداند روی این رایانه است. فقط اگر اتصال را از برنامه‌ای در همین‌جا شروع کرده‌اید تأیید کنید.",
stepUp:
Expand Down
7 changes: 6 additions & 1 deletion apps/studio/src/i18n/lazy/oauthConsent.fr.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Generated by `pnpm i18n:import` (ADR-0034): fr `oauthConsent.*` messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 63aa7946c1e148ca
// hash: 89c891ee68bf3306
import type { NamespaceMessages } from "../namespaces.ts";

export const messages: NamespaceMessages<"oauthConsent"> = {
oauthConsent: {
title: "Connecter {client}",
titleFrom: "{host} : connecter {client}",
description:
"Un agent demande à travailler dans ce Studio pour vous. Vérifiez ce qu'il peut faire avant d'approuver.",
invalid: {
Expand All @@ -17,8 +18,12 @@ export const messages: NamespaceMessages<"oauthConsent"> = {
title: "Qui le demande",
fromHost: "Identifié par {host}",
registered: "Enregistré dans ce Studio",
selfRegistered: "Auto-inscrit, non vérifié",
lookalike:
"Le nom ressemble à {client}, mais n’importe qui peut inscrire un client sous n’importe quel nom. N’approuvez que si vous avez lancé vous-même cette connexion depuis {client} et que l’adresse vers laquelle elle vous renvoie, indiquée ci-dessus, est celle qu’il utilise.",
sendsBack: "Vous renvoie vers {host}",
thisComputer: "Vous renvoie vers une application de cet ordinateur",
thisComputerApp: "Vous renvoie vers une application de cet ordinateur ({scheme}:)",
loopbackWarning:
"Toutes les adresses vers lesquelles cet agent peut vous renvoyer sont sur cet ordinateur. N'approuvez que si vous avez lancé la connexion depuis une application ici.",
stepUp:
Expand Down
7 changes: 6 additions & 1 deletion apps/studio/src/i18n/lazy/oauthConsent.he.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Generated by `pnpm i18n:import` (ADR-0034): he `oauthConsent.*` messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 37c0b05241b08508
// hash: 3f94b9b19b92115e
import type { NamespaceMessages } from "../namespaces.ts";

export const messages: NamespaceMessages<"oauthConsent"> = {
oauthConsent: {
title: "חיבור {client}",
titleFrom: "{host}: חיבור {client}",
description: "סוכן מבקש לעבוד ב-Studio הזה בשמך. כדאי לבדוק מה מותר לו לפני האישור.",
invalid: {
title: "אי אפשר להשתמש בבקשה הזו",
Expand All @@ -16,8 +17,12 @@ export const messages: NamespaceMessages<"oauthConsent"> = {
title: "מי מבקש",
fromHost: "מזוהה על ידי {host}",
registered: "רשום ב-Studio הזה",
selfRegistered: "נרשם בעצמו, לא מאומת",
lookalike:
"השם נראה כמו {client}, אבל כל אחד יכול לרשום לקוח בכל שם. אשר רק אם אתה התחלת את החיבור הזה מ-{client} והכתובת שאליה הוא מחזיר אותך, המוצגת למעלה, היא זו שהוא משתמש בה.",
sendsBack: "יחזיר אותך אל {host}",
thisComputer: "יחזיר אותך לאפליקציה במחשב הזה",
thisComputerApp: "מחזיר אותך לאפליקציה במחשב הזה ({scheme}:)",
loopbackWarning:
"כל כתובת שהסוכן הזה יכול להחזיר אותך אליה נמצאת במחשב הזה. יש לאשר רק אם התחלת את החיבור מאפליקציה כאן.",
stepUp: "הסוכן הזה כבר מחובר ומבקש יותר. מה שמותר לו היום מסומן.",
Expand Down
7 changes: 6 additions & 1 deletion apps/studio/src/i18n/lazy/oauthConsent.hi.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Generated by `pnpm i18n:import` (ADR-0034): hi `oauthConsent.*` messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: b558b0bef4ac3ed0
// hash: ca9d9114af0b5c08
import type { NamespaceMessages } from "../namespaces.ts";

export const messages: NamespaceMessages<"oauthConsent"> = {
oauthConsent: {
title: "{client} कनेक्ट करें",
titleFrom: "{host}: {client} कनेक्ट करें",
description:
"एक एजेंट इस Studio में आपकी ओर से काम करना चाहता है। मंज़ूरी देने से पहले देखें कि वह क्या कर सकता है।",
invalid: {
Expand All @@ -17,8 +18,12 @@ export const messages: NamespaceMessages<"oauthConsent"> = {
title: "कौन पूछ रहा है",
fromHost: "{host} से पहचाना गया",
registered: "इस Studio में रजिस्टर्ड",
selfRegistered: "स्वयं पंजीकृत, सत्यापित नहीं",
lookalike:
"यह नाम {client} जैसा दिखता है, लेकिन कोई भी किसी भी नाम से क्लाइंट पंजीकृत कर सकता है। केवल तभी स्वीकृत करें जब आपने यह कनेक्शन खुद {client} से शुरू किया हो और ऊपर दिखाया गया वापसी पता वही हो जिसका वह उपयोग करता है।",
sendsBack: "आपको {host} पर वापस भेजता है",
thisComputer: "आपको इस कंप्यूटर के किसी ऐप पर वापस भेजता है",
thisComputerApp: "आपको इस कंप्यूटर के एक ऐप पर वापस भेजता है ({scheme}:)",
loopbackWarning:
"यह एजेंट आपको जिन भी पतों पर वापस भेज सकता है, वे सब इसी कंप्यूटर पर हैं। मंज़ूरी तभी दें जब आपने कनेक्शन यहीं के किसी ऐप से शुरू किया हो।",
stepUp: "यह एजेंट पहले से कनेक्टेड है और और ज़्यादा मांग रहा है। आज वह जो कर सकता है, वह चुना हुआ है।",
Expand Down
7 changes: 6 additions & 1 deletion apps/studio/src/i18n/lazy/oauthConsent.id.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Generated by `pnpm i18n:import` (ADR-0034): id `oauthConsent.*` messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 8b80d49448b0fcea
// hash: c54a2392a96c5282
import type { NamespaceMessages } from "../namespaces.ts";

export const messages: NamespaceMessages<"oauthConsent"> = {
oauthConsent: {
title: "Hubungkan {client}",
titleFrom: "{host}: hubungkan {client}",
description:
"Sebuah agen meminta untuk bekerja di Studio ini atas nama Anda. Periksa apa yang boleh dilakukannya sebelum menyetujui.",
invalid: {
Expand All @@ -17,8 +18,12 @@ export const messages: NamespaceMessages<"oauthConsent"> = {
title: "Siapa yang meminta",
fromHost: "Diidentifikasi oleh {host}",
registered: "Terdaftar di Studio ini",
selfRegistered: "Terdaftar sendiri, belum diverifikasi",
lookalike:
"Namanya mirip {client}, tetapi siapa pun bisa mendaftarkan klien dengan nama apa pun. Setujui hanya jika kamu sendiri memulai koneksi ini dari {client} dan alamat tujuan kembali yang ditampilkan di atas adalah yang dipakainya.",
sendsBack: "Mengembalikan Anda ke {host}",
thisComputer: "Mengembalikan Anda ke aplikasi di komputer ini",
thisComputerApp: "Mengembalikanmu ke aplikasi di komputer ini ({scheme}:)",
loopbackWarning:
"Setiap alamat tujuan agen ini mengembalikan Anda ada di komputer ini. Setujui hanya jika Anda memulai koneksi dari aplikasi di sini.",
stepUp:
Expand Down
7 changes: 6 additions & 1 deletion apps/studio/src/i18n/lazy/oauthConsent.it.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Generated by `pnpm i18n:import` (ADR-0034): it `oauthConsent.*` messages.
// Do not edit: change the translation file and import it; only English is edited here.
// hash: 0b67d5b2bc3557b1
// hash: b8b6c8b1978e6cd1
import type { NamespaceMessages } from "../namespaces.ts";

export const messages: NamespaceMessages<"oauthConsent"> = {
oauthConsent: {
title: "Collega {client}",
titleFrom: "{host}: collega {client}",
description:
"Un agente chiede di lavorare in questo Studio per te. Controlla cosa può fare prima di approvare.",
invalid: {
Expand All @@ -17,8 +18,12 @@ export const messages: NamespaceMessages<"oauthConsent"> = {
title: "Chi lo chiede",
fromHost: "Identificato da {host}",
registered: "Registrato in questo Studio",
selfRegistered: "Registrato da sé, non verificato",
lookalike:
"Il nome sembra {client}, ma chiunque può registrare un client con qualsiasi nome. Approva solo se hai avviato tu questa connessione da {client} e l’indirizzo a cui ti riporta, indicato sopra, è quello che usa.",
sendsBack: "Ti rimanda a {host}",
thisComputer: "Ti rimanda a un'app su questo computer",
thisComputerApp: "Ti riporta a un’app su questo computer ({scheme}:)",
loopbackWarning:
"Ogni indirizzo a cui questo agente può rimandarti è su questo computer. Approva solo se hai avviato la connessione da un'app qui.",
stepUp: "Questo agente è già collegato e chiede di più. Ciò che può fare oggi è spuntato.",
Expand Down
Loading
Loading