Skip to content

Commit 889702a

Browse files
authored
Merge pull request #332 from docker/oauthflow
Implement OAuth Flow
2 parents 86df964 + dffa26a commit 889702a

10 files changed

Lines changed: 772 additions & 1 deletion

File tree

agentfleet_notion-expert.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env cagent run
2+
version: "2"
3+
4+
agents:
5+
root:
6+
model: anthropic/claude-3-5-sonnet-latest
7+
description: Notion Expert
8+
instruction: You are an AI assistant with expertise in the Notion project's documentation.
9+
toolsets:
10+
- type: mcp
11+
remote:
12+
url: https://mcp.notion.com/sse
13+
transport_type: sse

cmd/root/run.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/docker/cagent/pkg/chat"
2424
"github.com/docker/cagent/pkg/content"
2525
"github.com/docker/cagent/pkg/evaluation"
26+
"github.com/docker/cagent/pkg/oauth"
2627
"github.com/docker/cagent/pkg/remote"
2728
"github.com/docker/cagent/pkg/runtime"
2829
"github.com/docker/cagent/pkg/session"
@@ -180,6 +181,31 @@ func doRunCommand(ctx context.Context, args []string, exec bool) error {
180181
agentFilename = tmpFile.Name()
181182
}
182183

184+
// Set up OAuth redirect URI for CLI/TUI mode
185+
if runConfig.RedirectURI == "" {
186+
runConfig.RedirectURI = "http://localhost:8083/oauth-callback"
187+
slog.Debug("Set default OAuth redirect URI for CLI/TUI mode", "redirectURI", runConfig.RedirectURI)
188+
189+
// Start OAuth callback server for CLI/TUI mode
190+
callbackServer := oauth.NewCallbackServer(8083)
191+
err := callbackServer.Start(ctx)
192+
if err != nil {
193+
slog.Warn("Failed to start OAuth callback server", "error", err)
194+
} else {
195+
defer func() {
196+
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
197+
defer cancel()
198+
if err := callbackServer.Stop(shutdownCtx); err != nil {
199+
slog.Error("Failed to stop OAuth callback server", "error", err)
200+
}
201+
}()
202+
slog.Debug("Started OAuth callback server", "port", 8083)
203+
204+
// Set up global callback server for OAuth manager
205+
oauth.SetGlobalCallbackServer(callbackServer)
206+
}
207+
}
208+
183209
agents, err = teamloader.Load(ctx, agentFilename, runConfig)
184210
if err != nil {
185211
return err
@@ -455,6 +481,22 @@ func runWithoutTUI(ctx context.Context, agentFilename string, rt runtime.Runtime
455481
rt.Resume(ctx, string(runtime.ResumeTypeReject))
456482
return nil
457483
}
484+
case *runtime.AuthorizationRequiredEvent:
485+
if llmIsTyping {
486+
fmt.Println()
487+
llmIsTyping = false
488+
}
489+
490+
if e.Confirmation == "pending" {
491+
result := promptOAuthAuthorization(e.ServerURL, e.ServerType)
492+
switch result {
493+
case ConfirmationApprove:
494+
rt.ResumeStartAuthorizationFlow(ctx, true)
495+
case ConfirmationReject:
496+
rt.ResumeStartAuthorizationFlow(ctx, false)
497+
return fmt.Errorf("OAuth authorization rejected by user")
498+
}
499+
}
458500
}
459501
}
460502

cmd/root/run_text_utils.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,32 @@ func promptMaxIterationsContinue(maxIterations int) ConfirmationResult {
139139
}
140140
}
141141

142+
func promptOAuthAuthorization(serverURL, serverType string) ConfirmationResult {
143+
fmt.Printf("\n%s\n", yellow("🔐 OAuth Authorization Required"))
144+
fmt.Printf("%s %s (%s)\n", white("Server:"), blue(serverURL), serverType)
145+
fmt.Printf("%s\n", white("This server requires OAuth authentication to access its tools."))
146+
fmt.Printf("%s\n", white("Your browser will open automatically to complete the authorization."))
147+
fmt.Printf("\n%s (y/n): ", blue("Do you want to authorize access?"))
148+
149+
reader := bufio.NewReader(os.Stdin)
150+
response, err := reader.ReadString('\n')
151+
if err != nil {
152+
fmt.Printf("\n%s\n", red("Failed to read input, aborting authorization..."))
153+
return ConfirmationAbort
154+
}
155+
156+
response = strings.TrimSpace(strings.ToLower(response))
157+
if response == "y" || response == "yes" {
158+
fmt.Printf("%s\n", green("✓ Starting OAuth authorization..."))
159+
fmt.Printf("%s\n", white("Please complete the authorization in your browser."))
160+
fmt.Printf("%s\n\n", white("Once completed, the agent will continue automatically."))
161+
return ConfirmationApprove
162+
} else {
163+
fmt.Printf("%s\n\n", white("Authorization declined. Exiting..."))
164+
return ConfirmationReject
165+
}
166+
}
167+
142168
func formatToolCallArguments(arguments string) string {
143169
if arguments == "" {
144170
return "()"

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ These are more advanced examples, most of them involve some sort of MCP server t
4141
| [doc_generator.yaml](doc_generator.yaml) | Documentation generation from codebases | || || | | |
4242
| [mcp_generator.yaml](mcp_generator.yaml) | Generates MCP configurations | | | | | | docker,[duckduckgo](https://hub.docker.com/mcp/server/duckduckgo/overview) | |
4343
| [couchbase_agent.yaml](couchbase_agent.yaml) | Run Database commands using MCP tools | | | | | | docker,[couchbase](https://hub.docker.com/mcp/server/couchbase/overview) | |
44+
| [notion-expert.yaml](notion-expert.yaml) | Notion documentation expert using OAuth | | | | | | [notion](https://mcp.notion.com) (uses OAuth) | |
4445

4546
## **Multi-Agent Configurations**
4647

examples/notion-expert.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env cagent run
2+
version: "2"
3+
4+
agents:
5+
root:
6+
model: anthropic/claude-3-5-sonnet-latest
7+
description: Notion Expert
8+
instruction: You are an AI assistant with expertise in the Notion project's documentation.
9+
toolsets:
10+
- type: mcp
11+
remote:
12+
url: https://mcp.notion.com/sse
13+
transport_type: sse

pkg/app/app.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,10 @@ func (a *App) Resume(confirmationType string) {
8484
a.runtime.Resume(context.Background(), confirmationType)
8585
}
8686
}
87+
88+
// ResumeStartOAuth resumes the runtime with OAuth authorization confirmation
89+
func (a *App) ResumeStartOAuth(confirmation bool) {
90+
if a.runtime != nil {
91+
a.runtime.ResumeStartAuthorizationFlow(context.Background(), confirmation)
92+
}
93+
}

0 commit comments

Comments
 (0)