Jeremiah Lowin commited on
Commit
3c81b0a
·
unverified ·
1 Parent(s): c29318c

Update OAuth callback UI to match FastMCP branding (#1536)

Browse files
docs/assets/brand/blue-logo.png ADDED

Git LFS Details

  • SHA256: e5344918a16cca2da6bd884d373c5e499c3ce56bd5aebdaeeaa62af35a4a4e44
  • Pointer size: 130 Bytes
  • Size of remote file: 86.8 kB
src/fastmcp/client/oauth_callback.py CHANGED
@@ -29,17 +29,32 @@ def create_callback_html(
29
  server_url: str | None = None,
30
  ) -> str:
31
  """Create a styled HTML response for OAuth callbacks."""
32
- status_emoji = "" if is_success else "❌"
33
- status_color = "#10b981" if is_success else "#ef4444" # emerald-500 / red-500
34
-
35
- # Add server info for success cases
36
- server_info = ""
 
 
 
 
 
 
 
 
 
37
  if is_success and server_url:
38
- server_info = f"""
39
- <div class="server-info">
40
  Connected to: <strong>{server_url}</strong>
41
  </div>
42
  """
 
 
 
 
 
 
43
 
44
  return f"""
45
  <!DOCTYPE html>
@@ -49,127 +64,112 @@ def create_callback_html(
49
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
50
  <title>{title}</title>
51
  <style>
 
 
 
 
 
 
52
  body {{
53
- font-family: 'SF Mono', 'Monaco', 'Consolas', 'Roboto Mono', monospace;
54
  margin: 0;
55
  padding: 0;
56
  min-height: 100vh;
57
  display: flex;
58
  align-items: center;
59
  justify-content: center;
60
- background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 25%, #16213e 50%, #0f0f23 100%);
61
- color: #e2e8f0;
62
- overflow: hidden;
63
- }}
64
-
65
- body::before {{
66
- content: '';
67
- position: fixed;
68
- top: 0;
69
- left: 0;
70
- width: 100%;
71
- height: 100%;
72
- background:
73
- radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.1) 0%, transparent 50%),
74
- radial-gradient(circle at 80% 20%, rgba(16, 185, 129, 0.1) 0%, transparent 50%),
75
- radial-gradient(circle at 40% 40%, rgba(14, 165, 233, 0.1) 0%, transparent 50%);
76
- pointer-events: none;
77
- z-index: -1;
78
  }}
79
 
80
  .container {{
81
- background: rgba(30, 41, 59, 0.9);
82
- backdrop-filter: blur(10px);
83
- border: 1px solid rgba(71, 85, 105, 0.3);
84
  padding: 3rem 2rem;
85
- border-radius: 1rem;
86
- box-shadow:
87
- 0 25px 50px -12px rgba(0, 0, 0, 0.7),
88
- 0 0 0 1px rgba(255, 255, 255, 0.05),
89
- inset 0 1px 0 0 rgba(255, 255, 255, 0.1);
90
  text-align: center;
91
- max-width: 500px;
92
  margin: 1rem;
93
  position: relative;
94
  }}
95
 
96
- .container::before {{
97
- content: '';
98
- position: absolute;
99
- top: 0;
100
- left: 0;
101
- right: 0;
102
- height: 1px;
103
- background: linear-gradient(90deg, transparent, rgba(16, 185, 129, 0.5), transparent);
 
 
 
 
 
 
 
104
  }}
105
 
106
  .status-icon {{
107
- font-size: 4rem;
108
- margin-bottom: 1rem;
109
- display: block;
110
- filter: drop-shadow(0 0 20px currentColor);
 
 
 
 
 
 
111
  }}
112
 
113
  .message {{
114
- font-size: 1.25rem;
115
- line-height: 1.6;
116
- color: {status_color};
117
- margin-bottom: 1.5rem;
118
  font-weight: 600;
119
- text-shadow: 0 0 10px rgba({
120
- "16, 185, 129" if is_success else "239, 68, 68"
121
- }, 0.3);
122
  }}
123
 
124
- .server-info {{
125
- background: rgba(6, 182, 212, 0.1);
126
- border: 1px solid rgba(6, 182, 212, 0.3);
127
- border-radius: 0.75rem;
128
- padding: 1rem;
129
- margin: 1rem 0;
130
- font-size: 0.9rem;
131
- color: #67e8f9;
132
- font-family: 'SF Mono', 'Monaco', 'Consolas', 'Roboto Mono', monospace;
133
- text-shadow: 0 0 10px rgba(103, 232, 249, 0.3);
134
  }}
135
 
136
- .server-info strong {{
137
- color: #22d3ee;
138
- font-weight: 700;
 
139
  }}
140
 
141
- .subtitle {{
142
- font-size: 1rem;
143
- color: #94a3b8;
144
- margin-top: 1rem;
145
  }}
146
 
147
  .close-instruction {{
148
- background: rgba(51, 65, 85, 0.8);
149
- border: 1px solid rgba(71, 85, 105, 0.4);
150
- border-radius: 0.75rem;
151
- padding: 1rem;
152
  margin-top: 1.5rem;
153
- font-size: 0.9rem;
154
- color: #cbd5e1;
155
- font-family: 'SF Mono', 'Monaco', 'Consolas', 'Roboto Mono', monospace;
156
- }}
157
-
158
- @keyframes glow {{
159
- 0%, 100% {{ opacity: 1; }}
160
- 50% {{ opacity: 0.7; }}
161
- }}
162
-
163
- .status-icon {{
164
- animation: glow 2s ease-in-out infinite;
165
  }}
166
  </style>
167
  </head>
168
  <body>
169
  <div class="container">
170
- <span class="status-icon">{status_emoji}</span>
171
- <div class="message">{message}</div>
172
- {server_info}
 
 
 
173
  <div class="close-instruction">
174
  You can safely close this tab now.
175
  </div>
@@ -277,7 +277,7 @@ def create_oauth_callback_server(
277
  )
278
 
279
  return HTMLResponse(
280
- create_callback_html("FastMCP OAuth login complete!", server_url=server_url)
281
  )
282
 
283
  app = Starlette(routes=[Route(callback_path, callback_handler)])
 
29
  server_url: str | None = None,
30
  ) -> str:
31
  """Create a styled HTML response for OAuth callbacks."""
32
+ logo_url = "https://gofastmcp.com/assets/brand/blue-logo.png"
33
+
34
+ # Build the main status message
35
+ if is_success:
36
+ status_title = "Authentication successful"
37
+ status_icon = "✓"
38
+ icon_bg = "#10b98120"
39
+ else:
40
+ status_title = "Authentication failed"
41
+ status_icon = "✕"
42
+ icon_bg = "#ef444420"
43
+
44
+ # Add detail info box for both success and error cases
45
+ detail_info = ""
46
  if is_success and server_url:
47
+ detail_info = f"""
48
+ <div class="info-box">
49
  Connected to: <strong>{server_url}</strong>
50
  </div>
51
  """
52
+ elif not is_success:
53
+ detail_info = f"""
54
+ <div class="info-box error">
55
+ {message}
56
+ </div>
57
+ """
58
 
59
  return f"""
60
  <!DOCTYPE html>
 
64
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
65
  <title>{title}</title>
66
  <style>
67
+ * {{
68
+ margin: 0;
69
+ padding: 0;
70
+ box-sizing: border-box;
71
+ }}
72
+
73
  body {{
74
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
75
  margin: 0;
76
  padding: 0;
77
  min-height: 100vh;
78
  display: flex;
79
  align-items: center;
80
  justify-content: center;
81
+ background: #ffffff;
82
+ color: #0a0a0a;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  }}
84
 
85
  .container {{
86
+ background: #ffffff;
87
+ border: 1px solid #e5e5e5;
 
88
  padding: 3rem 2rem;
89
+ border-radius: 0.75rem;
90
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
 
 
 
91
  text-align: center;
92
+ max-width: 28rem;
93
  margin: 1rem;
94
  position: relative;
95
  }}
96
 
97
+ .logo {{
98
+ width: 60px;
99
+ height: auto;
100
+ margin-bottom: 2rem;
101
+ display: block;
102
+ margin-left: auto;
103
+ margin-right: auto;
104
+ }}
105
+
106
+ .status-message {{
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: center;
110
+ gap: 0.75rem;
111
+ margin-bottom: 1.5rem;
112
  }}
113
 
114
  .status-icon {{
115
+ font-size: 1.5rem;
116
+ line-height: 1;
117
+ display: inline-flex;
118
+ align-items: center;
119
+ justify-content: center;
120
+ width: 2rem;
121
+ height: 2rem;
122
+ background: {icon_bg};
123
+ border-radius: 0.5rem;
124
+ flex-shrink: 0;
125
  }}
126
 
127
  .message {{
128
+ font-size: 1.125rem;
129
+ line-height: 1.75;
130
+ color: #0a0a0a;
 
131
  font-weight: 600;
132
+ text-align: left;
 
 
133
  }}
134
 
135
+ .info-box {{
136
+ background: #f5f5f5;
137
+ border: 1px solid #e5e5e5;
138
+ border-radius: 0.5rem;
139
+ padding: 0.875rem;
140
+ margin: 1.25rem 0;
141
+ font-size: 0.875rem;
142
+ color: #525252;
143
+ font-family: 'SF Mono', 'Monaco', 'Consolas', 'Courier New', monospace;
144
+ text-align: left;
145
  }}
146
 
147
+ .info-box.error {{
148
+ background: #fef2f2;
149
+ border-color: #fecaca;
150
+ color: #991b1b;
151
  }}
152
 
153
+ .info-box strong {{
154
+ color: #0a0a0a;
155
+ font-weight: 600;
 
156
  }}
157
 
158
  .close-instruction {{
159
+ font-size: 0.875rem;
160
+ color: #737373;
 
 
161
  margin-top: 1.5rem;
 
 
 
 
 
 
 
 
 
 
 
 
162
  }}
163
  </style>
164
  </head>
165
  <body>
166
  <div class="container">
167
+ <img src="{logo_url}" alt="FastMCP" class="logo" />
168
+ <div class="status-message">
169
+ <span class="status-icon">{status_icon}</span>
170
+ <div class="message">{status_title}</div>
171
+ </div>
172
+ {detail_info}
173
  <div class="close-instruction">
174
  You can safely close this tab now.
175
  </div>
 
277
  )
278
 
279
  return HTMLResponse(
280
+ create_callback_html("", is_success=True, server_url=server_url)
281
  )
282
 
283
  app = Starlette(routes=[Route(callback_path, callback_handler)])