test: remove provider and cache mock leak paths
This commit is contained in:
@@ -42,7 +42,7 @@ export async function createHttpClient(params: SkillMcpClientConnectionParams):
|
||||
|
||||
registerProcessCleanup(state)
|
||||
|
||||
const requestInit = await buildHttpRequestInit(config, state.authProviders)
|
||||
const requestInit = await buildHttpRequestInit(config, state.authProviders, state.createOAuthProvider)
|
||||
const transport = new StreamableHTTPClientTransport(url, {
|
||||
requestInit,
|
||||
})
|
||||
|
||||
@@ -7,7 +7,6 @@ const mockHttpConnect = mock(() => Promise.reject(new Error("Mocked HTTP connect
|
||||
const mockHttpClose = mock(() => Promise.resolve())
|
||||
let lastTransportInstance: { url?: URL; options?: { requestInit?: RequestInit } } = {}
|
||||
|
||||
// Mock OAuth provider for OAuth integration tests
|
||||
const mockTokens = mock(() => null as { accessToken: string } | null)
|
||||
const mockLogin = mock(() => Promise.resolve({ accessToken: "test-token" }) as Promise<{ accessToken: string } | null>)
|
||||
|
||||
@@ -26,14 +25,6 @@ async function importFreshManagerModule(): Promise<typeof import("./manager")> {
|
||||
},
|
||||
}))
|
||||
|
||||
mock.module("../mcp-oauth/provider", () => ({
|
||||
McpOAuthProvider: class MockMcpOAuthProvider {
|
||||
tokens = mockTokens
|
||||
login = mockLogin
|
||||
constructor(_opts: unknown) {}
|
||||
},
|
||||
}))
|
||||
|
||||
const module = await import(`./manager?test=${Date.now()}-${Math.random()}`)
|
||||
mock.restore()
|
||||
return module
|
||||
@@ -46,7 +37,12 @@ describe("SkillMcpManager", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
const { SkillMcpManager } = await importFreshManagerModule()
|
||||
manager = new SkillMcpManager()
|
||||
manager = new SkillMcpManager({
|
||||
createOAuthProvider: () => ({
|
||||
tokens: () => mockTokens(),
|
||||
login: () => mockLogin(),
|
||||
}),
|
||||
})
|
||||
mockHttpConnect.mockClear()
|
||||
mockHttpClose.mockClear()
|
||||
mockTokens.mockClear()
|
||||
|
||||
@@ -1,24 +1,35 @@
|
||||
import type { Client } from "@modelcontextprotocol/sdk/client/index.js"
|
||||
import type { Prompt, Resource, Tool } from "@modelcontextprotocol/sdk/types.js"
|
||||
import type { ClaudeCodeMcpServer } from "../claude-code-mcp-loader/types"
|
||||
import { McpOAuthProvider } from "../mcp-oauth/provider"
|
||||
import { disconnectAll, disconnectSession, forceReconnect } from "./cleanup"
|
||||
import { getOrCreateClient, getOrCreateClientWithRetryImpl } from "./connection"
|
||||
import { handleStepUpIfNeeded } from "./oauth-handler"
|
||||
import type { SkillMcpClientInfo, SkillMcpManagerState, SkillMcpServerContext } from "./types"
|
||||
import type {
|
||||
OAuthProviderFactory,
|
||||
SkillMcpClientInfo,
|
||||
SkillMcpManagerState,
|
||||
SkillMcpServerContext,
|
||||
} from "./types"
|
||||
|
||||
export class SkillMcpManager {
|
||||
private readonly state: SkillMcpManagerState = {
|
||||
clients: new Map(),
|
||||
pendingConnections: new Map(),
|
||||
disconnectedSessions: new Map(),
|
||||
authProviders: new Map(),
|
||||
cleanupRegistered: false,
|
||||
cleanupInterval: null,
|
||||
cleanupHandlers: [],
|
||||
idleTimeoutMs: 5 * 60 * 1000,
|
||||
shutdownGeneration: 0,
|
||||
inFlightConnections: new Map(),
|
||||
disposed: false,
|
||||
private readonly state: SkillMcpManagerState
|
||||
|
||||
constructor(options: { createOAuthProvider?: OAuthProviderFactory } = {}) {
|
||||
this.state = {
|
||||
clients: new Map(),
|
||||
pendingConnections: new Map(),
|
||||
disconnectedSessions: new Map(),
|
||||
authProviders: new Map(),
|
||||
cleanupRegistered: false,
|
||||
cleanupInterval: null,
|
||||
cleanupHandlers: [],
|
||||
idleTimeoutMs: 5 * 60 * 1000,
|
||||
shutdownGeneration: 0,
|
||||
inFlightConnections: new Map(),
|
||||
disposed: false,
|
||||
createOAuthProvider: options.createOAuthProvider ?? ((providerOptions) => new McpOAuthProvider(providerOptions)),
|
||||
}
|
||||
}
|
||||
|
||||
private getClientKey(info: SkillMcpClientInfo): string {
|
||||
@@ -112,6 +123,7 @@ export class SkillMcpManager {
|
||||
error: lastError,
|
||||
config,
|
||||
authProviders: this.state.authProviders,
|
||||
createOAuthProvider: this.state.createOAuthProvider,
|
||||
})
|
||||
if (stepUpHandled) {
|
||||
await forceReconnect(this.state, this.getClientKey(info))
|
||||
|
||||
@@ -2,16 +2,18 @@ import type { ClaudeCodeMcpServer } from "../claude-code-mcp-loader/types"
|
||||
import { McpOAuthProvider } from "../mcp-oauth/provider"
|
||||
import type { OAuthTokenData } from "../mcp-oauth/storage"
|
||||
import { isStepUpRequired, mergeScopes } from "../mcp-oauth/step-up"
|
||||
import type { OAuthProviderFactory, OAuthProviderLike } from "./types"
|
||||
|
||||
export function getOrCreateAuthProvider(
|
||||
authProviders: Map<string, McpOAuthProvider>,
|
||||
authProviders: Map<string, OAuthProviderLike>,
|
||||
serverUrl: string,
|
||||
oauth: NonNullable<ClaudeCodeMcpServer["oauth"]>
|
||||
): McpOAuthProvider {
|
||||
oauth: NonNullable<ClaudeCodeMcpServer["oauth"]>,
|
||||
createOAuthProvider: OAuthProviderFactory = (options) => new McpOAuthProvider(options),
|
||||
): OAuthProviderLike {
|
||||
const existing = authProviders.get(serverUrl)
|
||||
if (existing) return existing
|
||||
|
||||
const provider = new McpOAuthProvider({
|
||||
const provider = createOAuthProvider({
|
||||
serverUrl,
|
||||
clientId: oauth.clientId,
|
||||
scopes: oauth.scopes,
|
||||
@@ -27,7 +29,8 @@ function isTokenExpired(tokenData: OAuthTokenData): boolean {
|
||||
|
||||
export async function buildHttpRequestInit(
|
||||
config: ClaudeCodeMcpServer,
|
||||
authProviders: Map<string, McpOAuthProvider>
|
||||
authProviders: Map<string, OAuthProviderLike>,
|
||||
createOAuthProvider?: OAuthProviderFactory,
|
||||
): Promise<RequestInit | undefined> {
|
||||
const headers: Record<string, string> = {}
|
||||
|
||||
@@ -38,7 +41,7 @@ export async function buildHttpRequestInit(
|
||||
}
|
||||
|
||||
if (config.oauth && config.url) {
|
||||
const provider = getOrCreateAuthProvider(authProviders, config.url, config.oauth)
|
||||
const provider = getOrCreateAuthProvider(authProviders, config.url, config.oauth, createOAuthProvider)
|
||||
let tokenData = provider.tokens()
|
||||
|
||||
if (!tokenData || isTokenExpired(tokenData)) {
|
||||
@@ -60,9 +63,10 @@ export async function buildHttpRequestInit(
|
||||
export async function handleStepUpIfNeeded(params: {
|
||||
error: Error
|
||||
config: ClaudeCodeMcpServer
|
||||
authProviders: Map<string, McpOAuthProvider>
|
||||
authProviders: Map<string, OAuthProviderLike>
|
||||
createOAuthProvider?: OAuthProviderFactory
|
||||
}): Promise<boolean> {
|
||||
const { error, config, authProviders } = params
|
||||
const { error, config, authProviders, createOAuthProvider } = params
|
||||
|
||||
if (!config.oauth || !config.url) {
|
||||
return false
|
||||
@@ -89,7 +93,7 @@ export async function handleStepUpIfNeeded(params: {
|
||||
config.oauth.scopes = mergedScopes
|
||||
|
||||
authProviders.delete(config.url)
|
||||
const provider = getOrCreateAuthProvider(authProviders, config.url, config.oauth)
|
||||
const provider = getOrCreateAuthProvider(authProviders, config.url, config.oauth, createOAuthProvider)
|
||||
|
||||
try {
|
||||
await provider.login()
|
||||
|
||||
@@ -48,6 +48,17 @@ export interface ProcessCleanupHandler {
|
||||
listener: () => void
|
||||
}
|
||||
|
||||
export type OAuthProviderLike = Pick<
|
||||
McpOAuthProvider,
|
||||
"tokens" | "login"
|
||||
>
|
||||
|
||||
export type OAuthProviderFactory = (options: {
|
||||
serverUrl: string
|
||||
clientId?: string
|
||||
scopes?: string[]
|
||||
}) => OAuthProviderLike
|
||||
|
||||
export interface SkillMcpManagerState {
|
||||
clients: Map<string, ManagedClient>
|
||||
pendingConnections: Map<string, Promise<Client>>
|
||||
@@ -60,6 +71,7 @@ export interface SkillMcpManagerState {
|
||||
shutdownGeneration: number
|
||||
inFlightConnections: Map<string, number>
|
||||
disposed: boolean
|
||||
createOAuthProvider: OAuthProviderFactory
|
||||
}
|
||||
|
||||
export interface SkillMcpClientConnectionParams {
|
||||
|
||||
Reference in New Issue
Block a user