返回 DeepSeek-Reasonix
mcp_oauth.go
根目录 / internal / cli / mcp_oauth.go
1 package cli
2
3 import (
4 "context"
5 "fmt"
6 "os"
7 "strings"
8 "time"
9
10 "reasonix/internal/boot"
11 "reasonix/internal/config"
12 "reasonix/internal/mcpdiag"
13 "reasonix/internal/netclient"
14 "reasonix/internal/plugin"
15 )
16
17 var mcpAuthorizeForCLI = plugin.AuthorizeHTTPMCP
18
19 func mcpAuthCLI(args []string) int {
20 if len(args) == 0 {
21 fmt.Fprintln(os.Stderr, "usage: reasonix mcp auth <name>")
22 return 2
23 }
24 name, workspace := strings.TrimSpace(args[0]), mcpCLIWorkspaceRoot()
25 cfg, err := config.LoadForRoot(workspace)
26 if err != nil {
27 fmt.Fprintln(os.Stderr, err)
28 return 1
29 }
30 var entry config.PluginEntry
31 found := false
32 for _, configured := range cfg.Plugins {
33 if configured.Name == name {
34 entry, found = configured, true
35 break
36 }
37 }
38 if !found {
39 fmt.Fprintf(os.Stderr, "no MCP server named %q in config\n", name)
40 return 1
41 }
42 if !mcpdiag.CanUseHTTPMCPOAuth(entry.Type, entry.URL, mcpdiag.HasAuthConfig(entry.Headers, entry.Env, entry.URL)) {
43 fmt.Fprintln(os.Stderr, "MCP OAuth is only available for Streamable HTTP MCP servers without configured authentication")
44 return 1
45 }
46 client, err := netclient.NewHTTPClient(cfg.NetworkProxySpec(), netclient.TransportOptions{})
47 if err != nil {
48 fmt.Fprintln(os.Stderr, err)
49 return 1
50 }
51 specs := boot.PluginSpecsForRootWithOptions([]config.PluginEntry{entry}, workspace, boot.PluginSpecOptions{
52 DefaultCallTimeout: 30 * time.Second, ConfigSource: string(entry.Source),
53 StateHome: config.ReasonixHomeDir(), Network: true, OAuthHTTPClient: client,
54 })
55 if len(specs) != 1 {
56 fmt.Fprintf(os.Stderr, "could not build MCP authorization specification for %q\n", name)
57 return 1
58 }
59 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
60 defer cancel()
61 if err := mcpAuthorizeForCLI(ctx, specs[0], openMCPAuthorizationURL); err != nil {
62 fmt.Fprintln(os.Stderr, err)
63 return 1
64 }
65 fmt.Printf("authorized MCP server %q — run `reasonix mcp retry %s` or reconnect it in the current session\n", name, name)
66 return 0
67 }
68
69 func openMCPAuthorizationURL(rawURL string) error {
70 cmd, err := mcpOpenCommand(rawURL)
71 if err != nil {
72 return err
73 }
74 return cmd.Start()
75 }
76
77 var mcpProbeForInstall = probeMCPReadiness
78
79 func probeMCPReadiness(entry config.PluginEntry) (plugin.MCPInstallResult, error) {
80 entry.Source = config.MCPSourceUserConfig
81 workspace, err := os.Getwd()
82 if err != nil {
83 workspace = ""
84 }
85 cfg, err := config.LoadForRoot(workspace)
86 if err != nil {
87 return plugin.InstallResultForError(entry.Name, err), err
88 }
89 client, err := netclient.NewHTTPClient(cfg.NetworkProxySpec(), netclient.TransportOptions{})
90 if err != nil {
91 return plugin.InstallResultForError(entry.Name, err), err
92 }
93 specs := boot.PluginSpecsForRootWithOptions([]config.PluginEntry{entry}, workspace, boot.PluginSpecOptions{
94 DefaultCallTimeout: 30 * time.Second, ConfigSource: string(config.MCPSourceUserConfig),
95 StateHome: config.ReasonixHomeDir(), Network: true, OAuthHTTPClient: client,
96 })
97 if len(specs) != 1 {
98 err := fmt.Errorf("could not build MCP launch specification")
99 return plugin.InstallResultForError(entry.Name, err), err
100 }
101 host := plugin.NewHost()
102 defer host.Close()
103 ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
104 defer cancel()
105 return host.InstallAndConnect(ctx, specs[0])
106 }
107
107 lines GO