Keep completed connections outside pending OAuth quotas

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-01 11:00:25 -05:00
1 parent fb7946a4b8
commit d17bdce7a9
3 files changed
+14 -2

No files matched your search

+1 -1
View File
@@ -172,6 +172,6 @@ catalog to implement them.
Registration also enforces shared source quotas (6 per minute and 30 unconsented
clients) using a resource-bound hash of the trusted request IP; raw addresses are
not stored. Configure trusted proxies correctly. Authorization starts atomically
remove expired requests and enforce 10 retained requests per client and 1,000
remove expired requests and enforce 10 pending-consent requests per client and 1,000
instance-wide, independently of further client registrations. Existing grants
remain usable when anonymous registration or authorization is throttled.
+12
View File
@@ -309,4 +309,16 @@ describe.skipIf(!support.supported)("public MCP OAuth and tool boundary", () =>
expect(await db.select().from(mcpOauthRequests).where(eq(mcpOauthRequests.clientId, client.client_id))).toHaveLength(1);
});
it("does not charge completed connections against a shared client's pending consent quota", async () => {
const f = await fixture();
const input = { client_id: f.client.client_id, redirect_uri: redirectUri, response_type: "code", resource: config.resource, code_challenge: challenge, code_challenge_method: "S256" };
for (let i = 0; i < 12; i++) {
const id = (await oauth.authorize(input)).split("/").at(-1)!;
const consent = await oauth.consent(id, f.actor, { decision: "approve", companyId: f.company.id, allowWrites: false });
const code = new URL(consent.redirectUrl).searchParams.get("code")!;
await expect(oauth.token({ grant_type: "authorization_code", client_id: f.client.client_id, redirect_uri: redirectUri, resource: config.resource, code, code_verifier: verifier })).resolves.toHaveProperty("access_token");
}
await expect(oauth.authorize(input)).resolves.toContain("/mcp-connect/");
});
});
+1 -1
View File
@@ -144,7 +144,7 @@ export function createPublicMcpOAuth(db: Db, config: PublicMcpConfig) {
await tx.delete(mcpOauthRequests).where(lt(mcpOauthRequests.expiresAt, now));
const [counts] = await tx.select({ total: sql<number>`count(*)::int`,
client: sql<number>`(count(*) FILTER (WHERE ${mcpOauthRequests.clientId} = ${client.id}))::int`,
}).from(mcpOauthRequests);
}).from(mcpOauthRequests).where(isNull(mcpOauthRequests.decidedAt));
if (!counts || counts.total >= 1000 || counts.client >= 10) {
throw new McpOAuthError("temporarily_unavailable", "Too many pending connection requests. Retry later.", 429);
}