Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
d372dbf
fix(mcp-server): drop buildCommand from vercel.json
volnei Apr 16, 2026
20d02db
fix(mcp-server): move Vercel handler to src/, use JS wrapper in api/
volnei Apr 16, 2026
8443e2c
chore: trigger vercel deploy
volnei Apr 16, 2026
c272bbb
fix(mcp-server): force MCP_TRANSPORT=http in the Vercel handler
volnei Apr 16, 2026
abf6a91
chore: re-trigger vercel deploy
volnei Apr 16, 2026
35afe0f
fix(mcp-server): bind sql to pool so tagged template keeps its this
volnei Apr 16, 2026
0799481
feat(mcp-server): forward OAuth scope to Cal.com authorize URL
volnei Apr 16, 2026
29c484b
fix(mcp-server): echo request Origin in CORS for credentialed requests
volnei Apr 16, 2026
0c42853
fix(mcp-server): use JSON response mode on Vercel to avoid 60s SSE ti…
volnei Apr 16, 2026
1254193
fix(mcp-server): allow mcp-protocol-version header in CORS and block …
volnei Apr 16, 2026
43da1b9
fix(mcp-server): accept root path as MCP endpoint and fix resource me…
volnei Apr 16, 2026
a1626eb
fix(mcp-server): revert resource to base URL in Protected Resource Me…
volnei Apr 16, 2026
9ff1f43
fix(mcp-server): bypass StreamableHTTPServerTransport to eliminate 60…
volnei Apr 16, 2026
5370168
Apply suggestions from code review
volnei Apr 16, 2026
97d912c
fix(mcp-server): remove stray `},` introduced in code review commit
volnei Apr 16, 2026
c4f4c78
fix(mcp-server): bump typecheck heap to 4 GB to prevent OOM on CI
volnei Apr 16, 2026
3c41a64
fix(mcp-server): exclude src/vercel-handler.ts from typecheck
volnei Apr 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/mcp-server/api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Thin JS wrapper — imports the pre-compiled handler from dist/ so @vercel/node
// does not have to run TypeScript over the heavy imports in src/vercel-handler.ts.
// The `bun run build` step (executed by Vercel before bundling functions)
// produces dist/vercel-handler.js.
export { default } from "../dist/vercel-handler.js";
200 changes: 0 additions & 200 deletions apps/mcp-server/api/index.ts

This file was deleted.

5 changes: 5 additions & 0 deletions apps/mcp-server/src/auth/oauth-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export interface OAuthConfig {
calApiBaseUrl: string;
/** Cal.com app base URL for authorize redirect (default: https://app.cal.com) */
calAppBaseUrl?: string;
/** Space-separated Cal.com OAuth scopes (e.g. "BOOKING_READ BOOKING_WRITE") */
calOAuthScopes?: string;
}

const MAX_BODY_SIZE = 1024 * 1024; // 1 MB
Expand Down Expand Up @@ -186,6 +188,9 @@ export async function handleAuthorize(
calAuthUrl.searchParams.set("code_challenge_method", "S256");
}
calAuthUrl.searchParams.set("response_type", "code");
if (config.calOAuthScopes) {
calAuthUrl.searchParams.set("scope", config.calOAuthScopes);
}

res.writeHead(302, { Location: calAuthUrl.toString() });
res.end();
Expand Down
3 changes: 3 additions & 0 deletions apps/mcp-server/src/auth/oauth-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export function buildAuthorizationServerMetadata(config: OAuthServerConfig): Rec
export function buildProtectedResourceMetadata(config: OAuthServerConfig): Record<string, unknown> {
const serverUrl = config.serverUrl.replace(/\/+$/, "");
return {
// resource is the server identifier (base URL). Per RFC 9728 §3 the client constructs
// the discovery URL as "https://host/.well-known/oauth-protected-resource" — no path
// suffix — so resource must stay as the base URL, not the /mcp endpoint path.
resource: serverUrl,
authorization_servers: [serverUrl],
bearer_methods_supported: ["header"],
Expand Down
6 changes: 6 additions & 0 deletions apps/mcp-server/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ const httpSchema = baseSchema.extend({
.regex(/^[0-9a-fA-F]+$/, "TOKEN_ENCRYPTION_KEY must be valid hex"),
serverUrl: z.string().url("MCP_SERVER_URL must be a valid URL"),
databaseUrl: z.string().min(1, "DATABASE_URL is required for HTTP mode"),
calOAuthScopes: z
.string()
.default(
"EVENT_TYPE_READ EVENT_TYPE_WRITE BOOKING_READ BOOKING_WRITE SCHEDULE_READ SCHEDULE_WRITE APPS_READ APPS_WRITE PROFILE_READ PROFILE_WRITE",
),
rateLimitWindowMs: z.coerce.number().int().positive().default(60_000),
rateLimitMax: z.coerce.number().int().positive().default(30),
maxSessions: z.coerce.number().int().positive().default(10_000),
Expand Down Expand Up @@ -84,6 +89,7 @@ function readEnv(): Record<string, unknown> {
tokenEncryptionKey: process.env.TOKEN_ENCRYPTION_KEY || undefined,
serverUrl: process.env.MCP_SERVER_URL || undefined,
databaseUrl: process.env.DATABASE_URL || undefined,
calOAuthScopes: process.env.CAL_OAUTH_SCOPES || undefined,
rateLimitWindowMs: process.env.RATE_LIMIT_WINDOW_MS || undefined,
rateLimitMax: process.env.RATE_LIMIT_MAX || undefined,
maxSessions: process.env.MAX_SESSIONS || undefined,
Expand Down
1 change: 1 addition & 0 deletions apps/mcp-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ async function main(): Promise<void> {
calOAuthClientSecret: httpConfig.calOAuthClientSecret,
calApiBaseUrl: httpConfig.calApiBaseUrl,
calAppBaseUrl: httpConfig.calAppBaseUrl,
calOAuthScopes: httpConfig.calOAuthScopes,
},
rateLimitWindowMs: httpConfig.rateLimitWindowMs,
rateLimitMax: httpConfig.rateLimitMax,
Expand Down
5 changes: 4 additions & 1 deletion apps/mcp-server/src/storage/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ export const pool = createPool({
connectionString: process.env.DATABASE_URL,
});

export const sql = pool.sql;
// Bind so the tagged template keeps its `this` — destructuring `pool.sql`
// loses the binding and @vercel/postgres then reads `connectionString` off
// `undefined` at call time.
export const sql = pool.sql.bind(pool);

let initialized = false;

Expand Down
Loading
Loading