-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathindex.ts
More file actions
185 lines (163 loc) Β· 7 KB
/
index.ts
File metadata and controls
185 lines (163 loc) Β· 7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
import express from 'express';
import cors from 'cors';
import { config, isProduction } from './config.js';
import { wellKnownRouter } from './oauth/wellknown.js';
import { handleDynamicClientRegistration } from './oauth/dcr.js';
import { authorizeRouter } from './oauth/authorize.js';
import { tokenRouter } from './oauth/token.js';
import { mcpRouter } from './mcp/handlers.js';
import { initializeMCPServer } from './mcp/server.js';
import { getRepositories } from './oauth/repositories/index.js';
import path from 'path';
const app = express();
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Log requests in development
if (!isProduction) {
app.use((req, res, next) => {
console.log(`${req.method} ${req.path}`);
next();
});
}
console.log('π Starting server...\n');
await initializeMCPServer();
// Bootstrap static OAuth client (chatgpt-connector)
// This ensures the static client is always available, even after restarts with postgres storage
const repos = getRepositories();
await repos.clients.create({
id: 'chatgpt-connector',
clientName: 'ChatGPT Connector',
redirectUris: [
'https://chat.openai.com/connector_platform_oauth_redirect',
'https://chatgpt.com/connector_platform_oauth_redirect',
],
});
console.log('β Registered static OAuth client: chatgpt-connector');
// Health check
app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
environment: config.server.nodeEnv,
});
});
// Root landing page (unauthenticated) so external health checks don't see 403s
app.get('/', (req, res) => {
res.send(`
<html>
<head><title>ChatGPT OAuth Bridge</title></head>
<body style="font-family: sans-serif;">
<h1>ChatGPT OAuth Bridge</h1>
<p>The server is running. Use /authorize for OAuth and /mcp for MCP.</p>
</body>
</html>
`);
});
// Serve widget assets (JS/CSS files)
app.use('/widgets', express.static(path.join(process.cwd(), 'dist/widgets'), {
setHeaders(res) {
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
res.setHeader('Access-Control-Allow-Origin', '*');
}
}));
// Serve favicon
app.get(['/favicon.ico', '/favicon.png', '/favicon.svg'], (_req, res) => {
const faviconPath = path.join(process.cwd(), 'public/favicon.svg');
// Check if favicon exists
if (require('fs').existsSync(faviconPath)) {
res.setHeader('Content-Type', 'image/svg+xml');
res.sendFile(faviconPath);
} else {
// Return empty response if favicon doesn't exist
res.status(204).end();
}
});
// OAuth2 Discovery Endpoints (/.well-known/*)
app.use('/.well-known', wellKnownRouter);
// Mirror discovery endpoints under /mcp/.well-known for clients that scope metadata per resource path
app.use('/mcp/.well-known', wellKnownRouter);
// Mirror discovery endpoints under /token/.well-known for clients that expect token endpoint metadata
app.use('/token/.well-known', wellKnownRouter);
// OAuth2 Dynamic Client Registration
app.post('/register', handleDynamicClientRegistration);
// OAuth2 Authorization Endpoint
// GET /authorize serves the React UI (handled by vite-express)
// POST /authorize receives consent from the frontend
app.use('/authorize', authorizeRouter);
// OAuth2 Token Endpoint
app.use('/token', tokenRouter);
// MCP Endpoints (requires OAuth authentication)
app.use('/mcp', mcpRouter);
// Start server
if (isProduction) {
// Production: serve static files
const clientPath = path.join(process.cwd(), 'dist/client');
app.use(express.static(clientPath));
// Serve OAuth UI for GET /authorize after validation passes (authorizeRouter calls next())
// This catches the request after authorizeRouter validates params and logs authorize_request
app.get('/authorize', (_req, res) => {
res.sendFile(path.join(clientPath, 'index.html'));
});
// Catch-all for client-side routing (after all API routes)
app.get('*', (req, res) => {
// Don't serve index.html for API routes
if (
req.path.startsWith('/mcp') ||
req.path.startsWith('/token') ||
req.path.startsWith('/.well-known') ||
req.path.startsWith('/api')
) {
return res.status(404).json({ error: 'Not found' });
}
res.sendFile(path.join(clientPath, 'index.html'));
});
// Start server
app.listen(config.server.port, () => {
console.log(`
ββββββββββββββββββββββββββββββββββββββββββββββ
β ChatGPT App Server (Production) β
β βββββββββββββββββββββββββββββββββββββββββββββ£
β Server: ${config.server.baseUrl.padEnd(30)} β
β Port: ${config.server.port.toString().padEnd(30)} β
β β
β Endpoints: β
β β’ MCP: /mcp β
β β’ OAuth: /authorize, /token β
β β’ Health: /health β
ββββββββββββββββββββββββββββββββββββββββββββββ
`);
});
} else {
// Development: use vite-express for HMR (dynamic import to avoid requiring it in production)
const ViteExpress = (await import('vite-express')).default;
ViteExpress.config({
mode: 'development',
viteConfigFile: path.join(process.cwd(), 'src/client/vite.config.ts'),
});
ViteExpress.listen(app, config.server.port, () => {
console.log(`
ββββββββββββββββββββββββββββββββββββββββββββββ
β ChatGPT App Server (Development) β
β βββββββββββββββββββββββββββββββββββββββββββββ£
β Server: http://localhost:${config.server.port.toString().padEnd(19)} β
β β
β Endpoints: β
β β’ OAuth UI: http://localhost:${config.server.port}/authorize β
β β’ MCP: http://localhost:${config.server.port}/mcp β
β β’ Token: http://localhost:${config.server.port}/token β
β β’ Health: http://localhost:${config.server.port}/health β
β β
β OAuth Discovery: β
β β’ /.well-known/oauth-authorization-server β
β β’ /.well-known/oauth-protected-resource β
β βββββββββββββββββββββββββββββββββββββββββββββ£
β π Note: Build widgets first: β
β bun run build:widgets β
ββββββββββββββββββββββββββββββββββββββββββββββ
`);
});
}