@@ -159,6 +179,30 @@ export function RegisterApiKeyClientModal({
/>
diff --git a/apps/desktop/src/lib/api/gateway.ts b/apps/desktop/src/lib/api/gateway.ts
index da96fda8..9eba1d3f 100644
--- a/apps/desktop/src/lib/api/gateway.ts
+++ b/apps/desktop/src/lib/api/gateway.ts
@@ -350,6 +350,7 @@ export async function revokeOAuthClientFeatureSet(
export interface RegisteredApiKeyClient {
clientId: string;
clientName: string;
+ lockedSpaceId: string | null;
/** The full key — shown once; afterwards only its hash is kept. */
apiKey: string;
keyPrefix: string;
@@ -366,11 +367,14 @@ export interface ApiKeyInfo {
}
/**
- * Register a pre-approved client authenticated by an API key. The returned key
- * is shown once and never retrievable again.
+ * Register a pre-approved client authenticated by an API key, optionally locked
+ * to a space. The returned key is shown once and never retrievable again.
*/
-export async function registerApiKeyClient(name: string): Promise {
- return invoke('register_api_key_client', { name });
+export async function registerApiKeyClient(
+ name: string,
+ lockedSpaceId?: string | null
+): Promise {
+ return invoke('register_api_key_client', { name, lockedSpaceId: lockedSpaceId ?? null });
}
/** Issue an additional API key for an existing client (rotation). Shown once. */
diff --git a/crates/mcpmux-storage/src/repositories/inbound_client_repository.rs b/crates/mcpmux-storage/src/repositories/inbound_client_repository.rs
index f20b353a..30d00023 100644
--- a/crates/mcpmux-storage/src/repositories/inbound_client_repository.rs
+++ b/crates/mcpmux-storage/src/repositories/inbound_client_repository.rs
@@ -707,6 +707,36 @@ impl InboundClientRepository {
Ok(())
}
+ /// Set (or clear, with `None`) the Space a client is locked to. A locked
+ /// client is confined to that Space during resolution (see the gateway
+ /// FeatureSet resolver).
+ pub async fn set_locked_space(&self, client_id: &str, space_id: Option<&str>) -> Result<()> {
+ let now = chrono::Utc::now().to_rfc3339();
+ let db = self.db.lock().await;
+ let conn = db.connection();
+ conn.execute(
+ "UPDATE inbound_clients SET locked_space_id = ?1, updated_at = ?2 WHERE client_id = ?3",
+ params![space_id, now, client_id],
+ )?;
+ Ok(())
+ }
+
+ /// The Space a client is locked to, if any.
+ pub async fn get_locked_space(&self, client_id: &str) -> Result