Spaces:
Sleeping
Sleeping
File size: 3,206 Bytes
7dc28be | 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 | import { AsyncLocalStorage } from 'node:async_hooks';
import { getAuthSession, requireAuth, UserError } from 'fastmcp';
import type { FastMCP } from 'fastmcp';
import { google, docs_v1, drive_v3, sheets_v4, script_v1, gmail_v1, calendar_v3 } from 'googleapis';
import { OAuth2Client } from 'google-auth-library';
import { logger } from './logger.js';
export interface RequestClients {
accessToken: string;
auth: OAuth2Client;
docs: docs_v1.Docs;
sheets: sheets_v4.Sheets;
drive: drive_v3.Drive;
script: script_v1.Script;
gmail: gmail_v1.Gmail;
calendar: calendar_v3.Calendar;
}
export const requestClients = new AsyncLocalStorage<RequestClients>();
const allowedDomains = (process.env.ALLOWED_DOMAINS || '').split(',').filter(Boolean);
function checkDomain(idToken?: string): boolean {
if (allowedDomains.length === 0) return true;
if (!idToken) return false;
const payload = idToken.split('.')[1];
if (!payload) return false;
try {
const { hd } = JSON.parse(Buffer.from(payload, 'base64url').toString());
return hd ? allowedDomains.includes(hd) : false;
} catch {
return false;
}
}
function createClients(accessToken: string, refreshToken?: string): RequestClients {
const auth = new OAuth2Client(process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET);
auth.setCredentials({
access_token: accessToken,
refresh_token: refreshToken,
});
return {
accessToken,
auth,
docs: google.docs({ version: 'v1', auth }),
sheets: google.sheets({ version: 'v4', auth }),
drive: google.drive({ version: 'v3', auth }),
script: google.script({ version: 'v1', auth }),
gmail: google.gmail({ version: 'v1', auth }),
calendar: google.calendar({ version: 'v3', auth }),
};
}
type AddToolArg = Parameters<FastMCP['addTool']>[0];
const wrappedServers = new WeakSet<FastMCP>();
/**
* Wraps server.addTool() so that in remote (httpStream) mode every tool
* automatically gets: auth enforcement, domain restriction, and per-request
* Google API clients via AsyncLocalStorage. Zero changes to tool files.
*/
export function wrapServerForRemote(server: FastMCP): void {
if (wrappedServers.has(server)) return;
wrappedServers.add(server);
const previousAddTool = server.addTool.bind(server);
(server as unknown as { addTool: (tool: AddToolArg) => void }).addTool = (
toolDef: AddToolArg
) => {
const originalExecute = toolDef.execute;
previousAddTool({
...toolDef,
canAccess: toolDef.canAccess
? (auth: any) => requireAuth(auth) && (toolDef.canAccess as Function)(auth)
: requireAuth,
execute: async (args: any, context: any) => {
const { accessToken, refreshToken, idToken } = getAuthSession(context.session);
if (!checkDomain(idToken)) {
throw new UserError('Your Google account domain is not allowed on this server.');
}
const clients = createClients(accessToken, refreshToken);
return requestClients.run(clients, () => originalExecute(args, context));
},
});
};
if (allowedDomains.length > 0) {
logger.info(`Remote mode: domain restriction active for [${allowedDomains.join(', ')}]`);
}
}
|