forked from clerk/mcp-tools
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.ts
More file actions
152 lines (138 loc) · 4.26 KB
/
server.ts
File metadata and controls
152 lines (138 loc) · 4.26 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
import type { MachineAuthObject } from "@clerk/backend";
import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
/**
* Generates protected resource metadata for the given auth server url and
* resource server url.
*
* @param authServerUrl - URL of the auth server
* @param resourceServerUrl - URL of the resource server
* @param properties - Additional properties to include in the metadata
* @returns Protected resource metadata, serializable to JSON
*/
export function generateProtectedResourceMetadata({
authServerUrl,
resourceUrl,
properties,
}: {
authServerUrl: string;
resourceUrl: string;
properties?: Record<string, unknown>;
}) {
return Object.assign(
{
resource: resourceUrl,
authorization_servers: [authServerUrl],
token_types_supported: ["urn:ietf:params:oauth:token-type:access_token"],
token_introspection_endpoint: `${authServerUrl}/oauth/token`,
token_introspection_endpoint_auth_methods_supported: [
"client_secret_post",
"client_secret_basic",
],
jwks_uri: `${authServerUrl}/.well-known/jwks.json`,
authorization_data_types_supported: ["oauth_scope"],
authorization_data_locations_supported: ["header", "body"],
key_challenges_supported: [
{
challenge_type: "urn:ietf:params:oauth:pkce:code_challenge",
challenge_algs: ["S256"],
},
],
},
properties
);
}
/**
* Generates protected resource metadata for the given Clerk publishable key
* and resource origin.
*
* @see https://datatracker.ietf.org/doc/html/rfc9728
* @param publishableKey - Clerk publishable key
* @param origin - Origin of the resource to which the metadata applies
* @returns Protected resource metadata, serializable to JSON
*/
export function generateClerkProtectedResourceMetadata({
publishableKey,
resourceUrl,
properties,
}: {
publishableKey: string;
resourceUrl: string;
properties?: Record<string, unknown>;
}) {
const fapiUrl = deriveFapiUrl(publishableKey);
return generateProtectedResourceMetadata({
authServerUrl: fapiUrl,
resourceUrl,
properties: {
service_documentation: "https://clerk.com/docs",
...properties,
},
});
}
function deriveFapiUrl(publishableKey: string) {
const key = publishableKey.replace(/^pk_(test|live)_/, "");
const decoded = Buffer.from(key, "base64").toString("utf8");
return `https://${decoded.replace(/\$/, "")}`;
}
export function fetchClerkAuthorizationServerMetadata({
publishableKey,
}: {
publishableKey: string;
}) {
const fapiUrl = deriveFapiUrl(publishableKey);
return fetch(`${fapiUrl}/.well-known/oauth-authorization-server`)
.then((res) => res.json())
.then((metadata) => {
return metadata;
});
}
/**
* Verifies a Clerk token and returns data in the format expected to be passed
* as `authData to the MCP SDK.
* @param auth - The auth object returned from the Clerk auth() function called with acceptsToken: 'oauth_token'
* @param token - The token to verify
* @returns AuthInfo type, see `import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";`
*/
export function verifyClerkToken(
auth: MachineAuthObject<"oauth_token">,
token: string | undefined
): AuthInfo | undefined {
if (!token) return undefined;
if (!auth.isAuthenticated) {
console.error("Invalid OAuth access token");
return undefined;
}
if (auth.tokenType !== "oauth_token") {
throw new Error(
"the auth() function must be called with acceptsToken: 'oauth_token'"
);
}
// None of these _should_ ever happen
if (!auth.clientId) {
console.error("Clerk error: No clientId returned from auth()");
return undefined;
}
if (!auth.scopes) {
console.error("Clerk error: No scopes returned from auth()");
return undefined;
}
if (!auth.userId) {
console.error("Clerk error: No userId returned from auth()");
return undefined;
}
return {
token,
scopes: auth.scopes,
clientId: auth.clientId,
extra: { userId: auth.userId },
};
}
/**
* CORS headers for OAuth metadata endpoints
*/
export const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Headers": "*",
"Access-Control-Max-Age": "86400",
};