Skip to content

Commit 4ccba49

Browse files
committed
feat(mcp): add header auth
1 parent 0ea26d4 commit 4ccba49

File tree

3 files changed

+105
-5
lines changed

3 files changed

+105
-5
lines changed

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ![NPM Version](https://img.shields.io/npm/v/%40doitintl%2Fdoit-mcp-server?registry_uri=https%3A%2F%2Fregistry.npmjs.com%2F%40doitintl%2Fdoit-mcp-server)
44

5-
65
DoiT MCP Server provides access to the DoiT API. This server enables LLMs like Claude to access DoiT platform data for troubleshooting and analysis.
76

87
![top-services](https://github.com/user-attachments/assets/749dd237-3021-439d-b447-64605393389d)
@@ -11,14 +10,32 @@ DoiT MCP Server provides access to the DoiT API. This server enables LLMs like C
1110

1211
- Node.js v18 or higher
1312
- DoiT API key with appropriate permissions
14-
- Customer context identifier (for customer-specific data)
1513

1614
## Installation
1715

1816
To get your DoiT API key, visit the [API key section in your DoiT profile](https://help.doit.com/docs/general/profile#api-key).
1917

2018
There are several ways to install and configure the MCP server:
2119

20+
### DoiT MCP URL
21+
22+
The DoiT MCP server is available at: https://mcp.doit.com/sse
23+
24+
### Claude Desktop App
25+
26+
```json
27+
{
28+
"mcpServers": {
29+
"doit_mcp_server": {
30+
"command": "npx",
31+
"args": ["mcp-remote", "https://mcp.doit.com/sse"]
32+
}
33+
}
34+
}
35+
```
36+
37+
## STDIO - local server
38+
2239
### Claude Desktop App
2340

2441
To manually configure the MCP server for Claude Desktop App, add the following to your `claude_desktop_config.json` file or through "Settings" as described [here](https://modelcontextprotocol.io/quickstart/user#2-add-the-filesystem-mcp-server):

doit-mcp-server/src/index.ts

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export class ContextStorage extends DurableObject {
7171
customerContext,
7272
this.ctx.id.toString().slice(-6)
7373
);
74+
7475
await this.ctx.storage.put("customerContext", customerContext);
7576
}
7677

@@ -140,7 +141,9 @@ export class DoitMCPAgent extends McpAgent {
140141
private createToolCallback(toolName: string) {
141142
return async (args: any) => {
142143
const token = this.getToken();
143-
const customerContext = await this.loadPersistedProps();
144+
const persistedCustomerContext = await this.loadPersistedProps();
145+
const customerContext =
146+
persistedCustomerContext || (this.props.customerContext as string);
144147

145148
const argsWithCustomerContext = {
146149
...args,
@@ -271,13 +274,88 @@ async function handleMcpRequest(req: Request, env: Env, ctx: ExecutionContext) {
271274
return new Response("Not found", { status: 404 });
272275
}
273276

274-
// Export the OAuth handler as the default
275-
export default new OAuthProvider({
277+
// Helper function to extract token from Authorization header
278+
function extractTokenFromAuthHeader(authHeader: string): string | null {
279+
if (!authHeader) return null;
280+
281+
// Support both "Bearer <token>" and just "<token>" formats
282+
const bearerMatch = authHeader.match(/^Bearer\s+(.+)$/i);
283+
if (bearerMatch) {
284+
return bearerMatch[1];
285+
}
286+
287+
// If no Bearer prefix, assume the whole header is the token
288+
return authHeader;
289+
}
290+
291+
// Create the OAuth provider instance
292+
const oauthProvider = new OAuthProvider({
276293
apiHandler: { fetch: handleMcpRequest as any },
277294
apiRoute: ["/sse", "/mcp"],
278295
// @ts-expect-error
279296
defaultHandler: app,
280297
authorizeEndpoint: "/authorize",
281298
tokenEndpoint: "/token",
282299
clientRegistrationEndpoint: "/register",
300+
accessTokenTTL: 1000 * 60 * 60 * 24 * 24, // 24 days (2,073,600,000 - within 32-bit range)
301+
tokenExchangeCallback: async ({ grantType, props }) => {
302+
console.log("tokenExchangeCallback", grantType, props);
303+
if (grantType === "refresh_token" || grantType === "authorization_code") {
304+
return {
305+
newProps: {
306+
...props,
307+
customerContext: props.customerContext,
308+
apiKey: props.apiKey,
309+
isDoitUser: props.isDoitUser,
310+
},
311+
};
312+
}
313+
},
283314
});
315+
316+
// Main request handler that checks for Authorization header
317+
async function handleRequest(
318+
req: Request,
319+
env: Env,
320+
ctx: ExecutionContext
321+
): Promise<Response> {
322+
const url = new URL(req.url);
323+
const authHeader = req.headers.get("Authorization");
324+
325+
// Check if this is an API route and has Authorization header
326+
if (
327+
(url.pathname === "/sse" ||
328+
url.pathname === "/sse/message" ||
329+
url.pathname === "/mcp") &&
330+
authHeader
331+
) {
332+
const token = extractTokenFromAuthHeader(authHeader);
333+
const customerContext = url.searchParams.get("customerContext") || "";
334+
335+
if (token && customerContext) {
336+
console.log("Using Authorization header for authentication");
337+
338+
ctx.props = {
339+
...ctx.props,
340+
apiKey: token,
341+
customerContext: customerContext,
342+
};
343+
344+
// Handle the request directly with the modified request
345+
if (url.pathname === "/sse" || url.pathname === "/sse/message") {
346+
return DoitMCPAgent.serveSSE("/sse").fetch(req, env, ctx);
347+
}
348+
if (url.pathname === "/mcp") {
349+
return DoitMCPAgent.serve("/mcp").fetch(req, env, ctx);
350+
}
351+
}
352+
}
353+
354+
// If no Authorization header or not an API route, use the OAuth provider
355+
return oauthProvider.fetch(req, env, ctx);
356+
}
357+
358+
// Export the main handler as the default
359+
export default {
360+
fetch: handleRequest,
361+
};

doit-mcp-server/src/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,11 +261,16 @@ export const renderApproveContent = async (
261261
<a
262262
href="${redirectUrl}"
263263
class="inline-block py-2 px-4 bg-primary text-white rounded-md font-medium hover:bg-primary/90 transition-colors"
264+
id="return-home-link"
265+
style="display: none;"
264266
>
265267
Return to Home
266268
</a>
267269
${raw(`
268270
<script>
271+
setTimeout(() => {
272+
document.getElementById('return-home-link').style.display = 'inline-block';
273+
}, 1400);
269274
setTimeout(() => {
270275
window.location.href = "${redirectUrl}";
271276
}, 2000);

0 commit comments

Comments
 (0)