fix: raise and configure per-user gateway session capacity
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import { readFileSync, statSync } from "node:fs";
|
||||
import { APPROVAL_BATCH_LIMIT } from "./approval-contract.js";
|
||||
|
||||
export const DEFAULT_MCP_SESSION_MAX_PER_USER = 32;
|
||||
export const MCP_SESSION_MAX_TOTAL = 1000;
|
||||
|
||||
export interface AppConfig {
|
||||
appMode?: "gateway";
|
||||
clientId: string;
|
||||
@@ -11,6 +14,7 @@ export interface AppConfig {
|
||||
publicBaseUrl: string;
|
||||
bindHost?: string;
|
||||
port: number;
|
||||
mcpSessionMaxPerUser?: number;
|
||||
requestTimeoutMs: number;
|
||||
officialLookbackDays: number;
|
||||
officialScanConcurrency: number;
|
||||
@@ -167,6 +171,10 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig {
|
||||
if (!remoteAuthEnabled || !xftOpenApiEnabled) {
|
||||
throw new Error("gateway 模式必须启用 MCP 远程 OAuth 和薪福通正式 OpenAPI");
|
||||
}
|
||||
const mcpSessionMaxPerUser = positiveInteger(env, "MCP_SESSION_MAX_PER_USER", DEFAULT_MCP_SESSION_MAX_PER_USER);
|
||||
if (mcpSessionMaxPerUser > MCP_SESSION_MAX_TOTAL) {
|
||||
throw new Error(`MCP_SESSION_MAX_PER_USER 不能超过全局会话上限 ${MCP_SESSION_MAX_TOTAL}`);
|
||||
}
|
||||
return {
|
||||
appMode,
|
||||
clientId: dingTalkCredential(env, "DINGTALK_CLIENT_ID", "clientId"),
|
||||
@@ -177,6 +185,7 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig {
|
||||
publicBaseUrl,
|
||||
bindHost,
|
||||
port,
|
||||
mcpSessionMaxPerUser,
|
||||
requestTimeoutMs: positiveInteger(env, "DINGTALK_REQUEST_TIMEOUT_MS", 15_000),
|
||||
officialLookbackDays: Math.min(120, positiveInteger(env, "OFFICIAL_OA_LOOKBACK_DAYS", 120)),
|
||||
officialScanConcurrency: positiveInteger(env, "OFFICIAL_OA_SCAN_CONCURRENCY", 2),
|
||||
|
||||
+4
-4
@@ -6,24 +6,24 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
||||
import type { AuthenticatedDingTalkUser } from "./xft.js";
|
||||
import { McpSessionLifecycle } from "./mcp-session-lifecycle.js";
|
||||
import { DEFAULT_MCP_SESSION_MAX_PER_USER, MCP_SESSION_MAX_TOTAL } from "./config.js";
|
||||
|
||||
type TransportEntry =
|
||||
| { kind: "streamable"; transport: StreamableHTTPServerTransport; userId: string; lifecycle: McpSessionLifecycle }
|
||||
| { kind: "sse"; transport: SSEServerTransport; userId: string; lifecycle: McpSessionLifecycle };
|
||||
|
||||
const MCP_SESSION_MAX_PER_USER = 8;
|
||||
const MCP_SESSION_MAX_TOTAL = 1000;
|
||||
|
||||
interface McpTransportOptions {
|
||||
authenticate?: RequestHandler;
|
||||
// Production always resolves this from verified OAuth, never request parameters.
|
||||
requestUser: (req: Request) => AuthenticatedDingTalkUser;
|
||||
createServer: (user: AuthenticatedDingTalkUser, lifecycle: McpSessionLifecycle) => McpServer;
|
||||
now?: () => number;
|
||||
maxSessionsPerUser?: number;
|
||||
}
|
||||
|
||||
export function installMcpTransports(app: Express, options: McpTransportOptions) {
|
||||
const now = options.now ?? Date.now;
|
||||
const maxSessionsPerUser = options.maxSessionsPerUser ?? DEFAULT_MCP_SESSION_MAX_PER_USER;
|
||||
const transports = new Map<string, TransportEntry>();
|
||||
let pendingTransportSessions = 0;
|
||||
const pendingTransportSessionsByUser = new Map<string, number>();
|
||||
@@ -40,7 +40,7 @@ export function installMcpTransports(app: Express, options: McpTransportOptions)
|
||||
};
|
||||
const sessionCapacityAvailable = (userId: string) => transports.size + pendingTransportSessions < MCP_SESSION_MAX_TOTAL
|
||||
&& [...transports.values()].filter((entry) => entry.userId === userId).length
|
||||
+ (pendingTransportSessionsByUser.get(userId) ?? 0) < MCP_SESSION_MAX_PER_USER;
|
||||
+ (pendingTransportSessionsByUser.get(userId) ?? 0) < maxSessionsPerUser;
|
||||
const reserveSession = (userId: string) => {
|
||||
pendingTransportSessions += 1;
|
||||
pendingTransportSessionsByUser.set(userId, (pendingTransportSessionsByUser.get(userId) ?? 0) + 1);
|
||||
|
||||
@@ -462,6 +462,7 @@ export async function startHttpServer(config: AppConfig): Promise<void> {
|
||||
|
||||
installMcpTransports(app, {
|
||||
authenticate: authMiddleware,
|
||||
maxSessionsPerUser: config.mcpSessionMaxPerUser,
|
||||
requestUser: (req) => requestUser(config, oauth, req.auth),
|
||||
createServer: (user, lifecycle) => createApprovalMcpServer(
|
||||
config, service, xft, undefined, officialOaReturns, user,
|
||||
|
||||
Reference in New Issue
Block a user