Add OAuth2 Client ID Metadata Document support

MCP connectors such as ChatGPT and Claude register via HTTPS
client_id URLs instead of pre-provisioned GIDs. Fetch and cache
their metadata documents, upsert clients on first use, and
advertise CIMD in OIDC discovery when allowed URLs are configured.

Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
Bryan Frimin
2026-06-19 16:35:53 +02:00
parent 9e6f1b9e8f
commit 5b0d3e5052
22 changed files with 1836 additions and 26 deletions

View File

@@ -0,0 +1,152 @@
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package cachecontrol
import "time"
type (
RequestDirective struct {
maxAge *uint64
maxStale *uint64
minFresh *uint64
noCache bool
noStore bool
noTransform bool
onlyIfCached bool
extensions map[string]string
}
ResponseDirective struct {
maxAge *uint64
mustRevalidate bool
noCache []string
noStore bool
noTransform bool
public bool
private []string
proxyRevalidate bool
sMaxAge *uint64
extensions map[string]string
}
)
func (d *RequestDirective) MaxAge() (uint64, bool) {
if v := d.maxAge; v != nil {
return *v, true
}
return 0, false
}
func (d *RequestDirective) MaxStale() (uint64, bool) {
if v := d.maxStale; v != nil {
return *v, true
}
return 0, false
}
func (d *RequestDirective) MinFresh() (uint64, bool) {
if v := d.minFresh; v != nil {
return *v, true
}
return 0, false
}
func (d *RequestDirective) NoCache() bool {
return d.noCache
}
func (d *RequestDirective) NoStore() bool {
return d.noStore
}
func (d *RequestDirective) NoTransform() bool {
return d.noTransform
}
func (d *RequestDirective) OnlyIfCached() bool {
return d.onlyIfCached
}
func (d *RequestDirective) Extensions() map[string]string {
return d.extensions
}
func (d *RequestDirective) Extension(name string) string {
return d.extensions[name]
}
func (d *ResponseDirective) MaxAge() (uint64, bool) {
if v := d.maxAge; v != nil {
return *v, true
}
return 0, false
}
func (d *ResponseDirective) MaxAgeDuration() (time.Duration, bool) {
seconds, ok := d.MaxAge()
if !ok {
return 0, false
}
return secondsToDuration(seconds), true
}
func (d *ResponseDirective) MustRevalidate() bool {
return d.mustRevalidate
}
func (d *ResponseDirective) NoCache() []string {
return d.noCache
}
func (d *ResponseDirective) NoStore() bool {
return d.noStore
}
func (d *ResponseDirective) NoTransform() bool {
return d.noTransform
}
func (d *ResponseDirective) Public() bool {
return d.public
}
func (d *ResponseDirective) Private() []string {
return d.private
}
func (d *ResponseDirective) ProxyRevalidate() bool {
return d.proxyRevalidate
}
func (d *ResponseDirective) SMaxAge() (uint64, bool) {
if v := d.sMaxAge; v != nil {
return *v, true
}
return 0, false
}
func (d *ResponseDirective) Extensions() map[string]string {
return d.extensions
}
func (d *ResponseDirective) Extension(name string) string {
return d.extensions[name]
}