diff --git a/.prettierignore b/.prettierignore index ceba17dc8..b559c8619 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,8 +1,11 @@ dist node_modules *.lock +pnpm-lock.yaml resources/ src/ui/lib/loaders.ts +packages/apps/src/lib/loaders.ts +packages/apps/src/lib/apps/ temp **/reports/*.api.md .github/workflows/*.lock.yml diff --git a/api-extractor/reports/mongodb-mcp-server.public.api.md b/api-extractor/reports/mongodb-mcp-server.public.api.md index e35430ed0..8ddd74a26 100644 --- a/api-extractor/reports/mongodb-mcp-server.public.api.md +++ b/api-extractor/reports/mongodb-mcp-server.public.api.md @@ -1,1020 +1,1028 @@ -## Public API Report File for "mongodb-mcp-server" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import type { AggregationCursor } from 'mongodb'; -import { AnyToolBase } from '@mongodb-js/mcp-core'; -import { ApiClient } from '@mongodb-js/mcp-atlas-api-client'; -import { ApiClientOptions } from '@mongodb-js/mcp-atlas-api-client'; -import type { AtlasLocalClientFactoryFn } from '@mongodb-js/mcp-tools-atlas-local'; -import { AtlasTelemetry } from '@mongodb-js/mcp-atlas-telemetry'; -import { AuthProvider } from '@mongodb-js/mcp-atlas-api-client'; -import { TelemetryBaseEvent as BaseEvent } from '@mongodb-js/mcp-atlas-telemetry'; -import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; -import type { Client } from '@mongodb-js/atlas-local'; -import type { CloseableTransport } from '@mongodb-js/mcp-types'; -import { TelemetryCommonProperties as CommonProperties } from '@mongodb-js/mcp-atlas-telemetry'; -import { CompositeLogger } from '@mongodb-js/mcp-core'; -import { ConnectionInfo } from '@mongosh/arg-parser'; -import { ConsoleLogger } from '@mongodb-js/mcp-logging'; -import { Counter } from '@mongodb-js/mcp-metrics'; -import { createAtlasLocalClient } from '@mongodb-js/mcp-tools-atlas-local'; -import { createDefaultMetrics } from '@mongodb-js/mcp-metrics'; -import { Credentials } from '@mongodb-js/mcp-atlas-api-client'; -import { DefaultEventMap } from '@mongodb-js/mcp-core'; -import { DefaultMetrics } from '@mongodb-js/mcp-metrics'; -import { defaultParserOptions as defaultParserOptions_2 } from '@mongosh/arg-parser/arg-parser'; -import type { ElicitRequestFormParams } from '@modelcontextprotocol/sdk/types.js'; -import { EventCache } from '@mongodb-js/mcp-atlas-telemetry'; -import EventEmitter from 'events'; -import { EventMap } from '@mongodb-js/mcp-core'; -import express from 'express'; -import type { FindCursor } from 'mongodb'; -import { Gauge } from '@mongodb-js/mcp-metrics'; -import { Histogram } from '@mongodb-js/mcp-metrics'; -import type http from 'http'; -import type { IDeviceId } from '@mongodb-js/mcp-types'; -import type { Implementation } from '@modelcontextprotocol/sdk/types.js'; -import { Keychain } from '@mongodb-js/mcp-core'; -import { LoggerBase } from '@mongodb-js/mcp-core'; -import { LoggerType } from '@mongodb-js/mcp-core'; -import { LogLevel } from '@mongodb-js/mcp-core'; -import { LogPayload } from '@mongodb-js/mcp-core'; -import { McpLogger } from '@mongodb-js/mcp-logging'; -import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { MetricDefinitions } from '@mongodb-js/mcp-metrics'; -import { Metrics } from '@mongodb-js/mcp-metrics'; -import { NodeDriverServiceProvider } from '@mongosh/service-provider-node-driver'; -import { NoopLogger } from '@mongodb-js/mcp-core'; -import { PrometheusMetrics } from '@mongodb-js/mcp-metrics'; -import { PrometheusMetricsOptions } from '@mongodb-js/mcp-metrics'; -import { registerGlobalSecretToRedact } from '@mongodb-js/mcp-core'; -import { Registry } from '@mongodb-js/mcp-metrics'; -import { RequestContext } from '@mongodb-js/mcp-atlas-api-client'; -import { Secret } from 'mongodb-redact'; -import type { SessionCloseReason } from '@mongodb-js/mcp-types'; -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; -import { TelemetryConfig } from '@mongodb-js/mcp-atlas-telemetry'; -import { TelemetryEvent } from '@mongodb-js/mcp-atlas-telemetry'; -import { TelemetryEvents } from '@mongodb-js/mcp-atlas-telemetry'; -import { ToolCategory } from '@mongodb-js/mcp-core'; -import { ToolClass } from '@mongodb-js/mcp-core'; -import { ToolExecutionContext } from '@mongodb-js/mcp-core'; -import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; -import type { TransportRequestContext } from '@mongodb-js/mcp-types'; -import { UIRegistry } from '@mongodb-js/mcp-ui'; -import { UIRegistryOptions } from '@mongodb-js/mcp-ui'; -import { z } from 'zod'; - -// @public (undocumented) -export type AnyConnectionState = ConnectionStateConnected | ConnectionStateConnecting | ConnectionStateDisconnected | ConnectionStateErrored; - -export { AnyToolBase } - -// @public (undocumented) -export type AnyToolClass = ToolClass; - -export { ApiClient } - -export { ApiClientOptions } - -// @public -export function applyConfigOverrides(input: { - baseConfig: TUserConfig; - request?: RequestContext_2; -}): TUserConfig; - -export { AtlasTelemetry } - -export { AuthProvider } - -export { BaseEvent } - -export { CloseableTransport } - -export { CommonProperties } - -export { CompositeLogger } - -// @public (undocumented) -export class ConfigOverrideError extends Error { - constructor(message: string); -} - -// Warning: (ae-forgotten-export) The symbol "ConfigFieldMeta" needs to be exported by the entry point lib.d.ts -// -// @public (undocumented) -export const configRegistry: z.core.$ZodRegistry>>; - -// @public (undocumented) -export type ConnectionErrorHandled = { - errorHandled: true; - result: CallToolResult; -}; - -// @public (undocumented) -export type ConnectionErrorHandler = (error: MongoDBError, additionalContext: ConnectionErrorHandlerContext) => ConnectionErrorUnhandled | ConnectionErrorHandled | Promise; - -// @public (undocumented) -export const connectionErrorHandler: ConnectionErrorHandler; - -// @public (undocumented) -export type ConnectionErrorHandlerContext = { - availableTools: AnyToolBase[]; - connectionState: AnyConnectionState; -}; - -// @public (undocumented) -export type ConnectionErrorUnhandled = { - errorHandled: false; -}; - -// @public (undocumented) -export abstract class ConnectionManager { - constructor(); - // (undocumented) - protected changeState(event: Event, newState: State): State; - // (undocumented) - clientName: string; - // (undocumented) - abstract close(): Promise; - // (undocumented) - abstract connect(settings: ConnectionSettings): Promise; - // (undocumented) - get currentConnectionState(): AnyConnectionState; - // (undocumented) - abstract disconnect(): Promise; - // (undocumented) - readonly events: Pick, "on" | "off" | "once">; - // (undocumented) - protected readonly _events: EventEmitter; - // (undocumented) - setClientName(clientName: string): void; -} - -// @public (undocumented) -export interface ConnectionManagerEvents { - // (undocumented) - "connection-close": [ConnectionStateDisconnected]; - // (undocumented) - "connection-error": [ConnectionStateErrored]; - // (undocumented) - "connection-request": [AnyConnectionState]; - // (undocumented) - "connection-success": [ConnectionStateConnected]; - // (undocumented) - "connection-time-out": [ConnectionStateErrored]; - // (undocumented) - close: [AnyConnectionState]; -} - -// @public -export type ConnectionManagerFactoryFn = (createParams: { - logger: LoggerBase; - deviceId: DeviceId; - userConfig: UserConfig; -}) => Promise; - -// @public (undocumented) -export interface ConnectionSettings extends Omit { - // Warning: (ae-forgotten-export) The symbol "AtlasClusterConnectionInfo" needs to be exported by the entry point lib.d.ts - // - // (undocumented) - atlas?: AtlasClusterConnectionInfo; - // (undocumented) - driverOptions?: ConnectionInfo["driverOptions"]; -} - -// @public (undocumented) -export interface ConnectionState { - // (undocumented) - connectedAtlasCluster?: AtlasClusterConnectionInfo; - // Warning: (ae-forgotten-export) The symbol "ConnectionStringInfo" needs to be exported by the entry point lib.d.ts - // - // (undocumented) - connectionStringInfo?: ConnectionStringInfo; - // (undocumented) - tag: ConnectionTag; -} - -// @public (undocumented) -export class ConnectionStateConnected implements ConnectionState { - constructor(serviceProvider: NodeDriverServiceProvider, connectionStringInfo?: ConnectionStringInfo | undefined, connectedAtlasCluster?: AtlasClusterConnectionInfo | undefined); - // (undocumented) - connectedAtlasCluster?: AtlasClusterConnectionInfo | undefined; - // (undocumented) - connectionStringInfo?: ConnectionStringInfo | undefined; - // (undocumented) - isSearchSupported(logger: LoggerBase): Promise; - // (undocumented) - serviceProvider: NodeDriverServiceProvider; - // (undocumented) - tag: "connected"; -} - -// @public (undocumented) -export interface ConnectionStateConnecting extends ConnectionState { - // (undocumented) - oidcConnectionType: OIDCConnectionAuthType; - // (undocumented) - oidcLoginUrl?: string; - // (undocumented) - oidcUserCode?: string; - // (undocumented) - serviceProvider: Promise; - // (undocumented) - tag: "connecting"; -} - -// @public (undocumented) -export interface ConnectionStateDisconnected extends ConnectionState { - // (undocumented) - tag: "disconnected"; -} - -// @public (undocumented) -export interface ConnectionStateErrored extends ConnectionState { - // (undocumented) - errorReason: string; - // (undocumented) - tag: "errored"; -} - -// @public (undocumented) -export type ConnectionTag = "connected" | "connecting" | "disconnected" | "errored"; - -export { ConsoleLogger } - -export { Counter } - -export { createAtlasLocalClient } - -// @public -export const createDefaultMcpHttpServer: (args: MCPHttpServerConstructorArgs) => MCPHttpServer; - -export { createDefaultMetrics } - -// @public -export const createDefaultMonitoringServer: (args: MonitoringServerConstructorArgs) => MonitoringServer; - -// @public -export function createDefaultSessionStore(params: SessionStoreConstructorArgs): SessionStore; - -// @public @deprecated (undocumented) -export const createMCPConnectionManager: ConnectionManagerFactoryFn; - -// @public -export type CreateMcpHttpServerFn = (args: MCPHttpServerConstructorArgs) => MCPHttpServer; - -// @public -export type CreateMonitoringServerFn = (args: MonitoringServerConstructorArgs) => MonitoringServer | undefined; - -// @public -export type CreateSessionConfigFn = (context: { - userConfig: TUserConfig; - request?: TransportRequestContext; -}) => Promise | TUserConfig; - -// @public -export type CreateSessionStoreFn = (args: SessionStoreConstructorArgs) => ISessionStore; - -export { Credentials } - -// @public (undocumented) -export type CustomizableServerOptions = Partial, "uiRegistry" | "tools" | "toolContext" | "elicitation">> & { - telemetryProperties?: Partial; -}; - -// @public (undocumented) -export type CustomizableSessionOptions = Partial, "userConfig" | "apiClient" | "atlasLocalClient" | "connectionManager" | "connectionErrorHandler">>; - -// @public (undocumented) -export const defaultCreateConnectionManager: ConnectionManagerFactoryFn; - -export { DefaultEventMap } - -export { DefaultMetrics } - -// @public (undocumented) -export const defaultParserOptions: { - config: string; - envPrefix: string; - configuration: { - "populate--": true; - "boolean-negation"?: boolean | undefined; - "camel-case-expansion"?: boolean | undefined; - "combine-arrays"?: boolean | undefined; - "dot-notation"?: boolean | undefined; - "duplicate-arguments-array"?: boolean | undefined; - "flatten-duplicate-arrays"?: boolean | undefined; - "greedy-arrays"?: boolean | undefined; - "nargs-eats-options"?: boolean | undefined; - "halt-at-non-option"?: boolean | undefined; - "negation-prefix"?: string | undefined; - "parse-numbers"?: boolean | undefined; - "parse-positional-numbers"?: boolean | undefined; - "set-placeholder-key"?: boolean | undefined; - "short-option-groups"?: boolean | undefined; - "strip-aliased"?: boolean | undefined; - "strip-dashed"?: boolean | undefined; - "unknown-options-as-args"?: boolean | undefined; - }; -}; - -// @public (undocumented) -export class DeviceId implements IDeviceId { - close(): void; - // (undocumented) - static create(logger: LoggerBase, timeout?: number): DeviceId; - get(): Promise; -} - -// @public (undocumented) -export class Elicitation { - constructor(input: { - server: McpServer["server"]; - }); - static CONFIRMATION_SCHEMA: { - type: "object"; - properties: { - confirmation: { - type: "string"; - title: string; - description: string; - enum: string[]; - enumNames: string[]; - }; - }; - required: string[]; - }; - requestConfirmation(message: string): Promise; - // Warning: (ae-forgotten-export) The symbol "ElicitedInputResult" needs to be exported by the entry point lib.d.ts - requestInput(message: string, schema: ElicitRequestFormParams["requestedSchema"]): Promise; - supportsElicitation(): boolean; -} - -// @public (undocumented) -export enum ErrorCodes { - // (undocumented) - AtlasSearchNotSupported = 1000004, - // (undocumented) - AtlasVectorSearchIndexNotFound = 1000006, - // (undocumented) - AtlasVectorSearchInvalidQuery = 1000007, - // (undocumented) - ForbiddenCollscan = 1000002, - // (undocumented) - ForbiddenWriteOperation = 1000003, - // (undocumented) - InvalidPipeline = 1000008, - // (undocumented) - MisconfiguredConnectionString = 1000001, - // (undocumented) - NotConnectedToMongoDB = 1000000 -} - -export { EventCache } - -export { EventMap } - -// Warning: (ae-forgotten-export) The symbol "ExportsManagerEvents" needs to be exported by the entry point lib.d.ts -// -// @public (undocumented) -export class ExportsManager extends EventEmitter { - // Warning: (ae-forgotten-export) The symbol "AvailableExport" needs to be exported by the entry point lib.d.ts - // - // (undocumented) - get availableExports(): AvailableExport[]; - // (undocumented) - close(): Promise; - // (undocumented) - createJSONExport(input: { - input: FindCursor | AggregationCursor; - exportName: string; - exportTitle: string; - jsonExportFormat: JSONExportFormat; - }): Promise; - // (undocumented) - protected init(): void; - // Warning: (ae-forgotten-export) The symbol "ExportsManagerConfig" needs to be exported by the entry point lib.d.ts - // - // (undocumented) - static init(config: ExportsManagerConfig, logger: LoggerBase, sessionId?: string): ExportsManager; - // (undocumented) - readExport(exportName: string): Promise<{ - content: string; - docsTransformed: number; - }>; -} - -export { Gauge } - -export { Histogram } - -// @public -export interface ISessionStore { - // (undocumented) - addSession(params: { - sessionId: string; - transport: T; - logger: LoggerBase; - }): Promise; - // (undocumented) - closeAllSessions(): Promise; - // (undocumented) - closeSession(params: { - sessionId: string; - reason?: SessionCloseReason; - }): Promise; - // (undocumented) - getSession(sessionId: string): Promise; -} - -export { Keychain } - -export { LoggerBase } - -export { LoggerType } - -export { LogLevel } - -export { LogPayload } - -// Warning: (ae-forgotten-export) The symbol "ExpressBasedHttpServer" needs to be exported by the entry point lib.d.ts -// -// @public (undocumented) -export class MCPHttpServer extends ExpressBasedHttpServer { - constructor(input: MCPHttpServerConstructorArgs); - // (undocumented) - protected setupMiddlewares(): void; - // (undocumented) - protected setupRoutes(): Promise; - // (undocumented) - stop(): Promise; - // (undocumented) - protected readonly userConfig: TUserConfig; -} - -// @public (undocumented) -export type MCPHttpServerConstructorArgs = { - userConfig: TUserConfig; - createServerForRequest: (createParams: { - request: TransportRequestContext; - serverOptions?: CustomizableServerOptions; - sessionOptions?: CustomizableSessionOptions; - }) => Promise>; - logger: LoggerBase; - serverOptions?: CustomizableServerOptions; - sessionOptions?: CustomizableSessionOptions; - metrics: Metrics; - sessionStore: ISessionStore; -}; - -export { McpLogger } - -export { MetricDefinitions } - -export { Metrics } - -// @public (undocumented) -export class MongoDBError extends Error { - constructor(code: ErrorCode, message: string); - // (undocumented) - code: ErrorCode; -} - -// @public (undocumented) -export class MonitoringServer extends ExpressBasedHttpServer { - constructor(input: { - host: string; - port: number; - features: MonitoringServerFeature[]; - logger: LoggerBase; - metrics: Metrics; - }); - // (undocumented) - static fromConfig(input: { - userConfig: UserConfig; - logger: LoggerBase; - metrics: Metrics; - }): MonitoringServer | undefined; - // (undocumented) - protected setupRoutes(): Promise; -} - -// @public -export type MonitoringServerConfig = { - monitoringServerHost?: string; - monitoringServerPort?: number; - healthCheckHost?: string; - healthCheckPort?: number; - monitoringServerFeatures: MonitoringServerFeature[]; -}; - -// @public -export type MonitoringServerConstructorArgs = { - host: string; - port: number; - features: MonitoringServerFeature[]; - logger: LoggerBase; - metrics: Metrics; -}; - -// Warning: (ae-forgotten-export) The symbol "monitoringServerFeatureValues" needs to be exported by the entry point lib.d.ts -// -// @public (undocumented) -export type MonitoringServerFeature = (typeof monitoringServerFeatureValues)[number]; - -export { NoopLogger } - -// @public (undocumented) -export type OIDCConnectionAuthType = "oidc-auth-flow" | "oidc-device-flow"; - -// @public @deprecated (undocumented) -export function parseArgsWithCliOptions(cliArguments: string[]): { - warnings: string[]; - parsed: UserConfig | undefined; - error: string | undefined; -}; - -// @public (undocumented) -export type ParserOptions = typeof defaultParserOptions_2; - -// @public (undocumented) -export function parseUserConfig(input: { - args: string[]; - overrides?: z.ZodRawShape; - parserOptions?: ParserOptions; -}): { - warnings: string[]; - parsed: UserConfig | undefined; - error: string | undefined; -}; - -export { PrometheusMetrics } - -export { PrometheusMetricsOptions } - -export { registerGlobalSecretToRedact } - -export { Registry } - -export { RequestContext } - -export { Secret } - -// @public (undocumented) -export class Server { - constructor(input: ServerOptions); - // (undocumented) - close(): Promise; - // (undocumented) - connect(transport: Transport): Promise; - // (undocumented) - readonly connectionErrorHandler: ConnectionErrorHandler; - // (undocumented) - readonly elicitation: Elicitation; - // (undocumented) - isToolCategoryAvailable(name: ToolCategory): boolean; - // (undocumented) - get mcpLogLevel(): LogLevel; - // (undocumented) - readonly mcpServer: McpServer; - // (undocumented) - readonly metrics: Metrics; - // (undocumented) - registerResources(): void; - // (undocumented) - registerTools(): void; - // (undocumented) - sendResourceListChanged(): void; - // (undocumented) - sendResourceUpdated(uri: string): void; - // (undocumented) - readonly session: Session; - // (undocumented) - readonly toolContext?: TContext; - // (undocumented) - readonly tools: AnyToolBase[]; - // (undocumented) - readonly uiRegistry?: UIRegistry; - // (undocumented) - readonly userConfig: TUserConfig; -} - -// @public (undocumented) -export interface ServerOptions { - // @deprecated (undocumented) - connectionErrorHandler: ConnectionErrorHandler; - // (undocumented) - elicitation: Elicitation; - // (undocumented) - mcpServer: McpServer; - // (undocumented) - metrics: Metrics; - // (undocumented) - session: Session; - // (undocumented) - telemetry: AtlasTelemetry; - toolContext?: TContext; - tools?: AnyToolClass[]; - // (undocumented) - uiRegistry?: UIRegistry; - // (undocumented) - userConfig: TUserConfig; -} - -// @public (undocumented) -export class Session extends EventEmitter { - constructor(input: SessionOptions); - // (undocumented) - readonly apiClient: ApiClient; - // (undocumented) - assertSearchSupported(): Promise; - // (undocumented) - readonly atlasLocalClient?: Client; - // (undocumented) - close(): Promise; - // (undocumented) - get connectedAtlasCluster(): AtlasClusterConnectionInfo | undefined; - // (undocumented) - readonly connectionErrorHandler: ConnectionErrorHandler; - // (undocumented) - readonly connectionManager: ConnectionManager; - // (undocumented) - get connectionStringInfo(): ConnectionStringInfo | undefined; - // (undocumented) - connectToConfiguredConnection(): Promise; - // (undocumented) - connectToMongoDB(settings: ConnectionSettings): Promise; - // (undocumented) - disconnect(): Promise; - // (undocumented) - readonly exportsManager: ExportsManager; - // (undocumented) - get isConnectedToMongoDB(): boolean; - // (undocumented) - isSearchSupported(): Promise; - // (undocumented) - readonly keychain: Keychain; - // (undocumented) - readonly logger: CompositeLogger; - // (undocumented) - mcpClient?: { - name?: string; - version?: string; - title?: string; - }; - // (undocumented) - get serviceProvider(): NodeDriverServiceProvider; - // (undocumented) - readonly sessionId: string; - // (undocumented) - setMcpClient(mcpClient: Implementation | undefined): void; -} - -export { SessionCloseReason } - -// @public (undocumented) -export type SessionEvents = { - connect: []; - close: []; - disconnect: []; - "connection-error": [ConnectionStateErrored]; -}; - -// @public (undocumented) -export interface SessionOptions { - // (undocumented) - apiClient: ApiClient; - // (undocumented) - atlasLocalClient?: Client; - // (undocumented) - connectionErrorHandler: ConnectionErrorHandler; - // (undocumented) - connectionManager: ConnectionManager; - // (undocumented) - exportsManager: ExportsManager; - // (undocumented) - keychain: Keychain; - // (undocumented) - logger: CompositeLogger; - // (undocumented) - userConfig: TUserConfig; -} - -// @public (undocumented) -export class SessionStore implements ISessionStore { - constructor(params: { - options: { - idleTimeoutMS: number; - notificationTimeoutMS: number; - }; - logger: LoggerBase; - metrics: Metrics; - }); - // (undocumented) - addSession(params: { - sessionId: string; - transport: T; - logger: LoggerBase; - }): Promise; - // (undocumented) - closeAllSessions(): Promise; - // (undocumented) - closeSession(input: { - sessionId: string; - reason?: SessionCloseReason; - }): Promise; - // (undocumented) - getSession(sessionId: string): Promise; -} - -// @public -export type SessionStoreConstructorArgs = { - options: { - idleTimeoutMS: number; - notificationTimeoutMS: number; - }; - logger: LoggerBase; - metrics: Metrics; -}; - -// @public (undocumented) -export class StdioRunner extends TransportRunnerBase { - constructor(config: TransportRunnerConfig); - // (undocumented) - closeTransport(): Promise; - // (undocumented) - start(input?: { - serverOptions?: CustomizableServerOptions; - sessionOptions?: CustomizableSessionOptions; - }): Promise; -} - -// @public (undocumented) -export class StreamableHttpRunner extends TransportRunnerBase { - constructor(config: StreamableHttpTransportRunnerConfig); - // (undocumented) - closeTransport(): Promise; - protected createServerForRequest(input: { - request: RequestContext_2; - serverOptions?: CustomizableServerOptions; - sessionOptions?: CustomizableSessionOptions; - }): Promise>; - start(input?: { - serverOptions?: CustomizableServerOptions; - sessionOptions?: CustomizableSessionOptions; - }): Promise; -} - -export { StreamableHTTPServerTransport } - -// @public -export type StreamableHttpTransportRunnerConfig = TransportRunnerConfig & { - createMonitoringServer?: CreateMonitoringServerFn; - createSessionStore?: CreateSessionStoreFn; - createMcpHttpServer?: CreateMcpHttpServerFn; -}; - -export { TelemetryConfig } - -export { TelemetryEvent } - -export { TelemetryEvents } - -export { ToolCategory } - -export { ToolExecutionContext } - -export { TransportRequestContext } - -// @public (undocumented) -export abstract class TransportRunnerBase { - protected constructor(input: TransportRunnerConfig); - // (undocumented) - close(): Promise; - // (undocumented) - abstract closeTransport(): Promise; - // @deprecated (undocumented) - protected readonly connectionErrorHandler: ConnectionErrorHandler; - // Warning: (ae-forgotten-export) The symbol "ApiClientFactoryFn" needs to be exported by the entry point lib.d.ts - // - // @deprecated (undocumented) - protected readonly createApiClient: ApiClientFactoryFn; - // @deprecated (undocumented) - protected readonly createAtlasLocalClient: AtlasLocalClientFactoryFn; - // @deprecated (undocumented) - protected readonly createConnectionManager: ConnectionManagerFactoryFn; - protected createServer(input?: { - userConfig?: TUserConfig; - logger?: CompositeLogger; - serverOptions?: CustomizableServerOptions; - sessionOptions?: CustomizableSessionOptions; - }): Promise>; - // @deprecated (undocumented) - protected readonly createSessionConfig?: CreateSessionConfigFn; - // (undocumented) - deviceId: DeviceId; - // (undocumented) - protected static getInstructions(config: UserConfig): string; - // (undocumented) - logger: CompositeLogger; - // (undocumented) - metrics: Metrics; - // @deprecated (undocumented) - protected setupServer(request?: RequestContext_2, input?: { - serverOptions?: CustomizableServerOptions; - }): Promise>; - // (undocumented) - abstract start(input: { - serverOptions?: ServerOptions; - sessionOptions?: SessionOptions; - }): Promise; - // @deprecated (undocumented) - protected readonly telemetryProperties: Partial; - // @deprecated (undocumented) - protected readonly tools?: AnyToolClass[]; - protected readonly userConfig: TUserConfig; -} - -// @public -export type TransportRunnerConfig = { - userConfig: TUserConfig; - createConnectionManager?: ConnectionManagerFactoryFn; - connectionErrorHandler?: ConnectionErrorHandler; - createAtlasLocalClient?: AtlasLocalClientFactoryFn; - loggers?: LoggerBase[]; - metrics?: Metrics; - telemetryProperties?: Partial; - tools?: AnyToolClass[]; - createSessionConfig?: CreateSessionConfigFn; - createApiClient?: ApiClientFactoryFn; -}; - -export { UIRegistry } - -export { UIRegistryOptions } - -// @public (undocumented) -export type UserConfig = z.infer; - -// @public (undocumented) -export const UserConfigSchema: z.ZodObject<{ - apiBaseUrl: z.ZodDefault; - assistantBaseUrl: z.ZodDefault; - apiClientId: z.ZodOptional; - apiClientSecret: z.ZodOptional; - connectionString: z.ZodOptional; - loggers: z.ZodDefault>>>; - logPath: z.ZodDefault; - mcpClientLogLevel: z.ZodDefault>; - disabledTools: z.ZodDefault>>; - confirmationRequiredTools: z.ZodDefault>>; - readOnly: z.ZodDefault>; - indexCheck: z.ZodDefault>; - telemetry: z.ZodDefault>; - transport: z.ZodDefault>; - httpPort: z.ZodDefault>; - httpHost: z.ZodDefault; - httpHeaders: z.ZodDefault>; - httpBodyLimit: z.ZodDefault>; - idleTimeoutMs: z.ZodDefault>; - notificationTimeoutMs: z.ZodDefault>; - maxBytesPerQuery: z.ZodDefault>; - maxDocumentsPerQuery: z.ZodDefault>; - maxTimeMS: z.ZodOptional>; - exportsPath: z.ZodDefault; - exportTimeoutMs: z.ZodDefault>; - exportCleanupIntervalMs: z.ZodDefault>; - atlasTemporaryDatabaseUserLifetimeMs: z.ZodDefault>; - voyageApiKey: z.ZodDefault; - previewFeatures: z.ZodDefault>>>; - allowRequestOverrides: z.ZodDefault>; - dryRun: z.ZodDefault; - externallyManagedSessions: z.ZodDefault; - httpResponseType: z.ZodDefault>; - healthCheckPort: z.ZodOptional; - healthCheckHost: z.ZodOptional; - monitoringServerPort: z.ZodOptional; - monitoringServerHost: z.ZodOptional; - monitoringServerFeatures: z.ZodDefault>>>; - gssapiHostName: z.ZodOptional; - sslFIPSMode: z.ZodOptional; - ssl: z.ZodOptional; - sslAllowInvalidCertificates: z.ZodOptional; - sslAllowInvalidHostnames: z.ZodOptional; - sslPEMKeyFile: z.ZodOptional; - sslPEMKeyPassword: z.ZodOptional; - sslCAFile: z.ZodOptional; - sslCertificateSelector: z.ZodOptional; - sslCRLFile: z.ZodOptional; - sslDisabledProtocols: z.ZodOptional; - apiVersion: z.ZodOptional; - authenticationDatabase: z.ZodOptional; - authenticationMechanism: z.ZodOptional; - awsAccessKeyId: z.ZodOptional; - awsIamSessionToken: z.ZodOptional; - awsSecretAccessKey: z.ZodOptional; - awsSessionToken: z.ZodOptional; - csfleLibraryPath: z.ZodOptional; - cryptSharedLibPath: z.ZodOptional; - deepInspect: z.ZodOptional>; - db: z.ZodOptional; - gssapiServiceName: z.ZodOptional; - sspiHostnameCanonicalization: z.ZodOptional; - sspiRealmOverride: z.ZodOptional; - jsContext: z.ZodOptional>; - host: z.ZodOptional; - keyVaultNamespace: z.ZodOptional; - kmsURL: z.ZodOptional; - locale: z.ZodOptional; - oidcFlows: z.ZodOptional; - oidcRedirectUri: z.ZodOptional; - password: z.ZodOptional; - port: z.ZodOptional; - username: z.ZodOptional; - tlsCAFile: z.ZodOptional; - tlsCertificateKeyFile: z.ZodOptional; - tlsCertificateKeyFilePassword: z.ZodOptional; - tlsCertificateSelector: z.ZodOptional; - tlsCRLFile: z.ZodOptional; - tlsDisabledProtocols: z.ZodOptional; - apiDeprecationErrors: z.ZodOptional; - apiStrict: z.ZodOptional; - buildInfo: z.ZodOptional; - exposeAsyncRewriter: z.ZodOptional; - help: z.ZodOptional; - ipv6: z.ZodOptional; - nodb: z.ZodOptional; - norc: z.ZodOptional; - oidcTrustedEndpoint: z.ZodOptional; - oidcIdTokenAsAccessToken: z.ZodOptional; - oidcNoNonce: z.ZodOptional; - quiet: z.ZodOptional; - retryWrites: z.ZodOptional; - shell: z.ZodOptional; - skipStartupWarnings: z.ZodOptional; - verbose: z.ZodOptional; - version: z.ZodOptional; - smokeTests: z.ZodOptional; - perfTests: z.ZodOptional; - tls: z.ZodOptional; - tlsAllowInvalidCertificates: z.ZodOptional; - tlsAllowInvalidHostnames: z.ZodOptional; - tlsFIPSMode: z.ZodOptional; - tlsUseSystemCA: z.ZodOptional; - eval: z.ZodOptional>; - file: z.ZodOptional>; - json: z.ZodOptional]>>; - oidcDumpTokens: z.ZodOptional]>>; - browser: z.ZodOptional, z.ZodString]>>; -}, z.core.$strip>; - -// Warnings were encountered during analysis: -// -// src/common/config/configOverrides.ts:29:5 - (ae-forgotten-export) The symbol "RequestContext_2" needs to be exported by the entry point lib.d.ts -// src/common/exportsManager.ts:165:9 - (ae-forgotten-export) The symbol "JSONExportFormat" needs to be exported by the entry point lib.d.ts - -// (No @packageDocumentation comment for this package) - -``` +## Public API Report File for "mongodb-mcp-server" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import type { AggregationCursor } from 'mongodb'; +import { AnyToolBase } from '@mongodb-js/mcp-core'; +import { ApiClient } from '@mongodb-js/mcp-atlas-api-client'; +import { ApiClientOptions } from '@mongodb-js/mcp-atlas-api-client'; +import type { AppRegistry } from '@mongodb-js/mcp-apps'; +import type { AtlasLocalClientFactoryFn } from '@mongodb-js/mcp-tools-atlas-local'; +import { AtlasTelemetry } from '@mongodb-js/mcp-atlas-telemetry'; +import { AuthProvider } from '@mongodb-js/mcp-atlas-api-client'; +import { TelemetryBaseEvent as BaseEvent } from '@mongodb-js/mcp-atlas-telemetry'; +import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; +import type { Client } from '@mongodb-js/atlas-local'; +import type { CloseableTransport } from '@mongodb-js/mcp-types'; +import { TelemetryCommonProperties as CommonProperties } from '@mongodb-js/mcp-atlas-telemetry'; +import { CompositeLogger } from '@mongodb-js/mcp-core'; +import { ConnectionInfo } from '@mongosh/arg-parser'; +import { ConsoleLogger } from '@mongodb-js/mcp-logging'; +import { Counter } from '@mongodb-js/mcp-metrics'; +import { createAtlasLocalClient } from '@mongodb-js/mcp-tools-atlas-local'; +import { createDefaultMetrics } from '@mongodb-js/mcp-metrics'; +import { Credentials } from '@mongodb-js/mcp-atlas-api-client'; +import { DefaultEventMap } from '@mongodb-js/mcp-core'; +import { DefaultMetrics } from '@mongodb-js/mcp-metrics'; +import { defaultParserOptions as defaultParserOptions_2 } from '@mongosh/arg-parser/arg-parser'; +import type { ElicitRequestFormParams } from '@modelcontextprotocol/sdk/types.js'; +import { EventCache } from '@mongodb-js/mcp-atlas-telemetry'; +import EventEmitter from 'events'; +import { EventMap } from '@mongodb-js/mcp-core'; +import express from 'express'; +import type { FindCursor } from 'mongodb'; +import { Gauge } from '@mongodb-js/mcp-metrics'; +import { Histogram } from '@mongodb-js/mcp-metrics'; +import type http from 'http'; +import type { IDeviceId } from '@mongodb-js/mcp-types'; +import type { Implementation } from '@modelcontextprotocol/sdk/types.js'; +import { Keychain } from '@mongodb-js/mcp-core'; +import { LoggerBase } from '@mongodb-js/mcp-core'; +import { LoggerType } from '@mongodb-js/mcp-core'; +import { LogLevel } from '@mongodb-js/mcp-core'; +import { LogPayload } from '@mongodb-js/mcp-core'; +import { McpLogger } from '@mongodb-js/mcp-logging'; +import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { MetricDefinitions } from '@mongodb-js/mcp-metrics'; +import { Metrics } from '@mongodb-js/mcp-metrics'; +import { NodeDriverServiceProvider } from '@mongosh/service-provider-node-driver'; +import { NoopLogger } from '@mongodb-js/mcp-core'; +import { PrometheusMetrics } from '@mongodb-js/mcp-metrics'; +import { PrometheusMetricsOptions } from '@mongodb-js/mcp-metrics'; +import { registerGlobalSecretToRedact } from '@mongodb-js/mcp-core'; +import { Registry } from '@mongodb-js/mcp-metrics'; +import { RequestContext } from '@mongodb-js/mcp-atlas-api-client'; +import { Secret } from 'mongodb-redact'; +import type { SessionCloseReason } from '@mongodb-js/mcp-types'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import { TelemetryConfig } from '@mongodb-js/mcp-atlas-telemetry'; +import { TelemetryEvent } from '@mongodb-js/mcp-atlas-telemetry'; +import { TelemetryEvents } from '@mongodb-js/mcp-atlas-telemetry'; +import { ToolCategory } from '@mongodb-js/mcp-core'; +import { ToolClass } from '@mongodb-js/mcp-core'; +import { ToolExecutionContext } from '@mongodb-js/mcp-core'; +import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; +import type { TransportRequestContext } from '@mongodb-js/mcp-types'; +import { UIRegistry } from '@mongodb-js/mcp-ui'; +import { UIRegistryOptions } from '@mongodb-js/mcp-ui'; +import { z } from 'zod'; + +// @public (undocumented) +export type AnyConnectionState = ConnectionStateConnected | ConnectionStateConnecting | ConnectionStateDisconnected | ConnectionStateErrored; + +export { AnyToolBase } + +// @public (undocumented) +export type AnyToolClass = ToolClass; + +export { ApiClient } + +export { ApiClientOptions } + +// @public +export function applyConfigOverrides(input: { + baseConfig: TUserConfig; + request?: RequestContext_2; +}): TUserConfig; + +export { AtlasTelemetry } + +export { AuthProvider } + +export { BaseEvent } + +export { CloseableTransport } + +export { CommonProperties } + +export { CompositeLogger } + +// @public (undocumented) +export class ConfigOverrideError extends Error { + constructor(message: string); +} + +// Warning: (ae-forgotten-export) The symbol "ConfigFieldMeta" needs to be exported by the entry point lib.d.ts +// +// @public (undocumented) +export const configRegistry: z.core.$ZodRegistry>>; + +// @public (undocumented) +export type ConnectionErrorHandled = { + errorHandled: true; + result: CallToolResult; +}; + +// @public (undocumented) +export type ConnectionErrorHandler = (error: MongoDBError, additionalContext: ConnectionErrorHandlerContext) => ConnectionErrorUnhandled | ConnectionErrorHandled | Promise; + +// @public (undocumented) +export const connectionErrorHandler: ConnectionErrorHandler; + +// @public (undocumented) +export type ConnectionErrorHandlerContext = { + availableTools: AnyToolBase[]; + connectionState: AnyConnectionState; +}; + +// @public (undocumented) +export type ConnectionErrorUnhandled = { + errorHandled: false; +}; + +// @public (undocumented) +export abstract class ConnectionManager { + constructor(); + // (undocumented) + protected changeState(event: Event, newState: State): State; + // (undocumented) + clientName: string; + // (undocumented) + abstract close(): Promise; + // (undocumented) + abstract connect(settings: ConnectionSettings): Promise; + // (undocumented) + get currentConnectionState(): AnyConnectionState; + // (undocumented) + abstract disconnect(): Promise; + // (undocumented) + readonly events: Pick, "on" | "off" | "once">; + // (undocumented) + protected readonly _events: EventEmitter; + // (undocumented) + setClientName(clientName: string): void; +} + +// @public (undocumented) +export interface ConnectionManagerEvents { + // (undocumented) + "connection-close": [ConnectionStateDisconnected]; + // (undocumented) + "connection-error": [ConnectionStateErrored]; + // (undocumented) + "connection-request": [AnyConnectionState]; + // (undocumented) + "connection-success": [ConnectionStateConnected]; + // (undocumented) + "connection-time-out": [ConnectionStateErrored]; + // (undocumented) + close: [AnyConnectionState]; +} + +// @public +export type ConnectionManagerFactoryFn = (createParams: { + logger: LoggerBase; + deviceId: DeviceId; + userConfig: UserConfig; +}) => Promise; + +// @public (undocumented) +export interface ConnectionSettings extends Omit { + // Warning: (ae-forgotten-export) The symbol "AtlasClusterConnectionInfo" needs to be exported by the entry point lib.d.ts + // + // (undocumented) + atlas?: AtlasClusterConnectionInfo; + // (undocumented) + driverOptions?: ConnectionInfo["driverOptions"]; +} + +// @public (undocumented) +export interface ConnectionState { + // (undocumented) + connectedAtlasCluster?: AtlasClusterConnectionInfo; + // Warning: (ae-forgotten-export) The symbol "ConnectionStringInfo" needs to be exported by the entry point lib.d.ts + // + // (undocumented) + connectionStringInfo?: ConnectionStringInfo; + // (undocumented) + tag: ConnectionTag; +} + +// @public (undocumented) +export class ConnectionStateConnected implements ConnectionState { + constructor(serviceProvider: NodeDriverServiceProvider, connectionStringInfo?: ConnectionStringInfo | undefined, connectedAtlasCluster?: AtlasClusterConnectionInfo | undefined); + // (undocumented) + connectedAtlasCluster?: AtlasClusterConnectionInfo | undefined; + // (undocumented) + connectionStringInfo?: ConnectionStringInfo | undefined; + // (undocumented) + isSearchSupported(logger: LoggerBase): Promise; + // (undocumented) + serviceProvider: NodeDriverServiceProvider; + // (undocumented) + tag: "connected"; +} + +// @public (undocumented) +export interface ConnectionStateConnecting extends ConnectionState { + // (undocumented) + oidcConnectionType: OIDCConnectionAuthType; + // (undocumented) + oidcLoginUrl?: string; + // (undocumented) + oidcUserCode?: string; + // (undocumented) + serviceProvider: Promise; + // (undocumented) + tag: "connecting"; +} + +// @public (undocumented) +export interface ConnectionStateDisconnected extends ConnectionState { + // (undocumented) + tag: "disconnected"; +} + +// @public (undocumented) +export interface ConnectionStateErrored extends ConnectionState { + // (undocumented) + errorReason: string; + // (undocumented) + tag: "errored"; +} + +// @public (undocumented) +export type ConnectionTag = "connected" | "connecting" | "disconnected" | "errored"; + +export { ConsoleLogger } + +export { Counter } + +export { createAtlasLocalClient } + +// @public +export const createDefaultMcpHttpServer: (args: MCPHttpServerConstructorArgs) => MCPHttpServer; + +export { createDefaultMetrics } + +// @public +export const createDefaultMonitoringServer: (args: MonitoringServerConstructorArgs) => MonitoringServer; + +// @public +export function createDefaultSessionStore(params: SessionStoreConstructorArgs): SessionStore; + +// @public @deprecated (undocumented) +export const createMCPConnectionManager: ConnectionManagerFactoryFn; + +// @public +export type CreateMcpHttpServerFn = (args: MCPHttpServerConstructorArgs) => MCPHttpServer; + +// @public +export type CreateMonitoringServerFn = (args: MonitoringServerConstructorArgs) => MonitoringServer | undefined; + +// @public +export type CreateSessionConfigFn = (context: { + userConfig: TUserConfig; + request?: TransportRequestContext; +}) => Promise | TUserConfig; + +// @public +export type CreateSessionStoreFn = (args: SessionStoreConstructorArgs) => ISessionStore; + +export { Credentials } + +// @public (undocumented) +export type CustomizableServerOptions = Partial, "uiRegistry" | "appRegistry" | "tools" | "toolContext" | "elicitation">> & { + telemetryProperties?: Partial; +}; + +// @public (undocumented) +export type CustomizableSessionOptions = Partial, "userConfig" | "apiClient" | "atlasLocalClient" | "connectionManager" | "connectionErrorHandler">>; + +// @public (undocumented) +export const defaultCreateConnectionManager: ConnectionManagerFactoryFn; + +export { DefaultEventMap } + +export { DefaultMetrics } + +// @public (undocumented) +export const defaultParserOptions: { + config: string; + envPrefix: string; + configuration: { + "populate--": true; + "boolean-negation"?: boolean | undefined; + "camel-case-expansion"?: boolean | undefined; + "combine-arrays"?: boolean | undefined; + "dot-notation"?: boolean | undefined; + "duplicate-arguments-array"?: boolean | undefined; + "flatten-duplicate-arrays"?: boolean | undefined; + "greedy-arrays"?: boolean | undefined; + "nargs-eats-options"?: boolean | undefined; + "halt-at-non-option"?: boolean | undefined; + "negation-prefix"?: string | undefined; + "parse-numbers"?: boolean | undefined; + "parse-positional-numbers"?: boolean | undefined; + "set-placeholder-key"?: boolean | undefined; + "short-option-groups"?: boolean | undefined; + "strip-aliased"?: boolean | undefined; + "strip-dashed"?: boolean | undefined; + "unknown-options-as-args"?: boolean | undefined; + }; +}; + +// @public (undocumented) +export class DeviceId implements IDeviceId { + close(): void; + // (undocumented) + static create(logger: LoggerBase, timeout?: number): DeviceId; + get(): Promise; +} + +// @public (undocumented) +export class Elicitation { + constructor(input: { + server: McpServer["server"]; + }); + static CONFIRMATION_SCHEMA: { + type: "object"; + properties: { + confirmation: { + type: "string"; + title: string; + description: string; + enum: string[]; + enumNames: string[]; + }; + }; + required: string[]; + }; + requestConfirmation(message: string): Promise; + // Warning: (ae-forgotten-export) The symbol "ElicitedInputResult" needs to be exported by the entry point lib.d.ts + requestInput(message: string, schema: ElicitRequestFormParams["requestedSchema"]): Promise; + supportsElicitation(): boolean; +} + +// @public (undocumented) +export enum ErrorCodes { + // (undocumented) + AtlasSearchNotSupported = 1000004, + // (undocumented) + AtlasVectorSearchIndexNotFound = 1000006, + // (undocumented) + AtlasVectorSearchInvalidQuery = 1000007, + // (undocumented) + ForbiddenCollscan = 1000002, + // (undocumented) + ForbiddenWriteOperation = 1000003, + // (undocumented) + InvalidPipeline = 1000008, + // (undocumented) + MisconfiguredConnectionString = 1000001, + // (undocumented) + NotConnectedToMongoDB = 1000000 +} + +export { EventCache } + +export { EventMap } + +// Warning: (ae-forgotten-export) The symbol "ExportsManagerEvents" needs to be exported by the entry point lib.d.ts +// +// @public (undocumented) +export class ExportsManager extends EventEmitter { + // Warning: (ae-forgotten-export) The symbol "AvailableExport" needs to be exported by the entry point lib.d.ts + // + // (undocumented) + get availableExports(): AvailableExport[]; + // (undocumented) + close(): Promise; + // (undocumented) + createJSONExport(input: { + input: FindCursor | AggregationCursor; + exportName: string; + exportTitle: string; + jsonExportFormat: JSONExportFormat; + }): Promise; + // (undocumented) + protected init(): void; + // Warning: (ae-forgotten-export) The symbol "ExportsManagerConfig" needs to be exported by the entry point lib.d.ts + // + // (undocumented) + static init(config: ExportsManagerConfig, logger: LoggerBase, sessionId?: string): ExportsManager; + // (undocumented) + readExport(exportName: string): Promise<{ + content: string; + docsTransformed: number; + }>; +} + +export { Gauge } + +export { Histogram } + +// @public +export interface ISessionStore { + // (undocumented) + addSession(params: { + sessionId: string; + transport: T; + logger: LoggerBase; + }): Promise; + // (undocumented) + closeAllSessions(): Promise; + // (undocumented) + closeSession(params: { + sessionId: string; + reason?: SessionCloseReason; + }): Promise; + // (undocumented) + getSession(sessionId: string): Promise; +} + +export { Keychain } + +export { LoggerBase } + +export { LoggerType } + +export { LogLevel } + +export { LogPayload } + +// Warning: (ae-forgotten-export) The symbol "ExpressBasedHttpServer" needs to be exported by the entry point lib.d.ts +// +// @public (undocumented) +export class MCPHttpServer extends ExpressBasedHttpServer { + constructor(input: MCPHttpServerConstructorArgs); + // (undocumented) + protected setupMiddlewares(): void; + // (undocumented) + protected setupRoutes(): Promise; + // (undocumented) + stop(): Promise; + // (undocumented) + protected readonly userConfig: TUserConfig; +} + +// @public (undocumented) +export type MCPHttpServerConstructorArgs = { + userConfig: TUserConfig; + createServerForRequest: (createParams: { + request: TransportRequestContext; + serverOptions?: CustomizableServerOptions; + sessionOptions?: CustomizableSessionOptions; + }) => Promise>; + logger: LoggerBase; + serverOptions?: CustomizableServerOptions; + sessionOptions?: CustomizableSessionOptions; + metrics: Metrics; + sessionStore: ISessionStore; +}; + +export { McpLogger } + +export { MetricDefinitions } + +export { Metrics } + +// @public (undocumented) +export class MongoDBError extends Error { + constructor(code: ErrorCode, message: string); + // (undocumented) + code: ErrorCode; +} + +// @public (undocumented) +export class MonitoringServer extends ExpressBasedHttpServer { + constructor(input: { + host: string; + port: number; + features: MonitoringServerFeature[]; + logger: LoggerBase; + metrics: Metrics; + }); + // (undocumented) + static fromConfig(input: { + userConfig: UserConfig; + logger: LoggerBase; + metrics: Metrics; + }): MonitoringServer | undefined; + // (undocumented) + protected setupRoutes(): Promise; +} + +// @public +export type MonitoringServerConfig = { + monitoringServerHost?: string; + monitoringServerPort?: number; + healthCheckHost?: string; + healthCheckPort?: number; + monitoringServerFeatures: MonitoringServerFeature[]; +}; + +// @public +export type MonitoringServerConstructorArgs = { + host: string; + port: number; + features: MonitoringServerFeature[]; + logger: LoggerBase; + metrics: Metrics; +}; + +// Warning: (ae-forgotten-export) The symbol "monitoringServerFeatureValues" needs to be exported by the entry point lib.d.ts +// +// @public (undocumented) +export type MonitoringServerFeature = (typeof monitoringServerFeatureValues)[number]; + +export { NoopLogger } + +// @public (undocumented) +export type OIDCConnectionAuthType = "oidc-auth-flow" | "oidc-device-flow"; + +// @public @deprecated (undocumented) +export function parseArgsWithCliOptions(cliArguments: string[]): { + warnings: string[]; + parsed: UserConfig | undefined; + error: string | undefined; +}; + +// @public (undocumented) +export type ParserOptions = typeof defaultParserOptions_2; + +// @public (undocumented) +export function parseUserConfig(input: { + args: string[]; + overrides?: z.ZodRawShape; + parserOptions?: ParserOptions; +}): { + warnings: string[]; + parsed: UserConfig | undefined; + error: string | undefined; +}; + +export { PrometheusMetrics } + +export { PrometheusMetricsOptions } + +export { registerGlobalSecretToRedact } + +export { Registry } + +export { RequestContext } + +export { Secret } + +// @public (undocumented) +export class Server { + constructor(input: ServerOptions); + // (undocumented) + readonly appRegistry?: AppRegistry; + // (undocumented) + close(): Promise; + // (undocumented) + connect(transport: Transport): Promise; + // (undocumented) + readonly connectionErrorHandler: ConnectionErrorHandler; + // (undocumented) + readonly elicitation: Elicitation; + // (undocumented) + isToolCategoryAvailable(name: ToolCategory): boolean; + // (undocumented) + get mcpLogLevel(): LogLevel; + // (undocumented) + readonly mcpServer: McpServer; + // (undocumented) + readonly metrics: Metrics; + // (undocumented) + registerAppResources(): Promise; + // (undocumented) + registerResources(): void; + // (undocumented) + registerTools(): void; + // (undocumented) + sendResourceListChanged(): void; + // (undocumented) + sendResourceUpdated(uri: string): void; + // (undocumented) + readonly session: Session; + // (undocumented) + readonly toolContext?: TContext; + // (undocumented) + readonly tools: AnyToolBase[]; + // (undocumented) + readonly uiRegistry?: UIRegistry; + // (undocumented) + readonly userConfig: TUserConfig; +} + +// @public (undocumented) +export interface ServerOptions { + // (undocumented) + appRegistry?: AppRegistry; + // @deprecated (undocumented) + connectionErrorHandler: ConnectionErrorHandler; + // (undocumented) + elicitation: Elicitation; + // (undocumented) + mcpServer: McpServer; + // (undocumented) + metrics: Metrics; + // (undocumented) + session: Session; + // (undocumented) + telemetry: AtlasTelemetry; + toolContext?: TContext; + tools?: AnyToolClass[]; + // (undocumented) + uiRegistry?: UIRegistry; + // (undocumented) + userConfig: TUserConfig; +} + +// @public (undocumented) +export class Session extends EventEmitter { + constructor(input: SessionOptions); + // (undocumented) + readonly apiClient: ApiClient; + // (undocumented) + assertSearchSupported(): Promise; + // (undocumented) + readonly atlasLocalClient?: Client; + // (undocumented) + close(): Promise; + // (undocumented) + get connectedAtlasCluster(): AtlasClusterConnectionInfo | undefined; + // (undocumented) + readonly connectionErrorHandler: ConnectionErrorHandler; + // (undocumented) + readonly connectionManager: ConnectionManager; + // (undocumented) + get connectionStringInfo(): ConnectionStringInfo | undefined; + // (undocumented) + connectToConfiguredConnection(): Promise; + // (undocumented) + connectToMongoDB(settings: ConnectionSettings): Promise; + // (undocumented) + disconnect(): Promise; + // (undocumented) + readonly exportsManager: ExportsManager; + // (undocumented) + get isConnectedToMongoDB(): boolean; + // (undocumented) + isSearchSupported(): Promise; + // (undocumented) + readonly keychain: Keychain; + // (undocumented) + readonly logger: CompositeLogger; + // (undocumented) + mcpClient?: { + name?: string; + version?: string; + title?: string; + }; + // (undocumented) + get serviceProvider(): NodeDriverServiceProvider; + // (undocumented) + readonly sessionId: string; + // (undocumented) + setMcpClient(mcpClient: Implementation | undefined): void; +} + +export { SessionCloseReason } + +// @public (undocumented) +export type SessionEvents = { + connect: []; + close: []; + disconnect: []; + "connection-error": [ConnectionStateErrored]; +}; + +// @public (undocumented) +export interface SessionOptions { + // (undocumented) + apiClient: ApiClient; + // (undocumented) + atlasLocalClient?: Client; + // (undocumented) + connectionErrorHandler: ConnectionErrorHandler; + // (undocumented) + connectionManager: ConnectionManager; + // (undocumented) + exportsManager: ExportsManager; + // (undocumented) + keychain: Keychain; + // (undocumented) + logger: CompositeLogger; + // (undocumented) + userConfig: TUserConfig; +} + +// @public (undocumented) +export class SessionStore implements ISessionStore { + constructor(params: { + options: { + idleTimeoutMS: number; + notificationTimeoutMS: number; + }; + logger: LoggerBase; + metrics: Metrics; + }); + // (undocumented) + addSession(params: { + sessionId: string; + transport: T; + logger: LoggerBase; + }): Promise; + // (undocumented) + closeAllSessions(): Promise; + // (undocumented) + closeSession(input: { + sessionId: string; + reason?: SessionCloseReason; + }): Promise; + // (undocumented) + getSession(sessionId: string): Promise; +} + +// @public +export type SessionStoreConstructorArgs = { + options: { + idleTimeoutMS: number; + notificationTimeoutMS: number; + }; + logger: LoggerBase; + metrics: Metrics; +}; + +// @public (undocumented) +export class StdioRunner extends TransportRunnerBase { + constructor(config: TransportRunnerConfig); + // (undocumented) + closeTransport(): Promise; + // (undocumented) + start(input?: { + serverOptions?: CustomizableServerOptions; + sessionOptions?: CustomizableSessionOptions; + }): Promise; +} + +// @public (undocumented) +export class StreamableHttpRunner extends TransportRunnerBase { + constructor(config: StreamableHttpTransportRunnerConfig); + // (undocumented) + closeTransport(): Promise; + protected createServerForRequest(input: { + request: RequestContext_2; + serverOptions?: CustomizableServerOptions; + sessionOptions?: CustomizableSessionOptions; + }): Promise>; + start(input?: { + serverOptions?: CustomizableServerOptions; + sessionOptions?: CustomizableSessionOptions; + }): Promise; +} + +export { StreamableHTTPServerTransport } + +// @public +export type StreamableHttpTransportRunnerConfig = TransportRunnerConfig & { + createMonitoringServer?: CreateMonitoringServerFn; + createSessionStore?: CreateSessionStoreFn; + createMcpHttpServer?: CreateMcpHttpServerFn; +}; + +export { TelemetryConfig } + +export { TelemetryEvent } + +export { TelemetryEvents } + +export { ToolCategory } + +export { ToolExecutionContext } + +export { TransportRequestContext } + +// @public (undocumented) +export abstract class TransportRunnerBase { + protected constructor(input: TransportRunnerConfig); + // (undocumented) + close(): Promise; + // (undocumented) + abstract closeTransport(): Promise; + // @deprecated (undocumented) + protected readonly connectionErrorHandler: ConnectionErrorHandler; + // Warning: (ae-forgotten-export) The symbol "ApiClientFactoryFn" needs to be exported by the entry point lib.d.ts + // + // @deprecated (undocumented) + protected readonly createApiClient: ApiClientFactoryFn; + // @deprecated (undocumented) + protected readonly createAtlasLocalClient: AtlasLocalClientFactoryFn; + // @deprecated (undocumented) + protected readonly createConnectionManager: ConnectionManagerFactoryFn; + protected createServer(input?: { + userConfig?: TUserConfig; + logger?: CompositeLogger; + serverOptions?: CustomizableServerOptions; + sessionOptions?: CustomizableSessionOptions; + }): Promise>; + // @deprecated (undocumented) + protected readonly createSessionConfig?: CreateSessionConfigFn; + // (undocumented) + deviceId: DeviceId; + // (undocumented) + protected static getInstructions(config: UserConfig): string; + // (undocumented) + logger: CompositeLogger; + // (undocumented) + metrics: Metrics; + // @deprecated (undocumented) + protected setupServer(request?: RequestContext_2, input?: { + serverOptions?: CustomizableServerOptions; + }): Promise>; + // (undocumented) + abstract start(input: { + serverOptions?: ServerOptions; + sessionOptions?: SessionOptions; + }): Promise; + // @deprecated (undocumented) + protected readonly telemetryProperties: Partial; + // @deprecated (undocumented) + protected readonly tools?: AnyToolClass[]; + protected readonly userConfig: TUserConfig; +} + +// @public +export type TransportRunnerConfig = { + userConfig: TUserConfig; + createConnectionManager?: ConnectionManagerFactoryFn; + connectionErrorHandler?: ConnectionErrorHandler; + createAtlasLocalClient?: AtlasLocalClientFactoryFn; + loggers?: LoggerBase[]; + metrics?: Metrics; + telemetryProperties?: Partial; + tools?: AnyToolClass[]; + createSessionConfig?: CreateSessionConfigFn; + createApiClient?: ApiClientFactoryFn; +}; + +export { UIRegistry } + +export { UIRegistryOptions } + +// @public (undocumented) +export type UserConfig = z.infer; + +// @public (undocumented) +export const UserConfigSchema: z.ZodObject<{ + apiBaseUrl: z.ZodDefault; + assistantBaseUrl: z.ZodDefault; + apiClientId: z.ZodOptional; + apiClientSecret: z.ZodOptional; + connectionString: z.ZodOptional; + loggers: z.ZodDefault>>>; + logPath: z.ZodDefault; + mcpClientLogLevel: z.ZodDefault>; + disabledTools: z.ZodDefault>>; + confirmationRequiredTools: z.ZodDefault>>; + readOnly: z.ZodDefault>; + indexCheck: z.ZodDefault>; + telemetry: z.ZodDefault>; + transport: z.ZodDefault>; + httpPort: z.ZodDefault>; + httpHost: z.ZodDefault; + httpHeaders: z.ZodDefault>; + httpBodyLimit: z.ZodDefault>; + idleTimeoutMs: z.ZodDefault>; + notificationTimeoutMs: z.ZodDefault>; + maxBytesPerQuery: z.ZodDefault>; + maxDocumentsPerQuery: z.ZodDefault>; + maxTimeMS: z.ZodOptional>; + exportsPath: z.ZodDefault; + exportTimeoutMs: z.ZodDefault>; + exportCleanupIntervalMs: z.ZodDefault>; + atlasTemporaryDatabaseUserLifetimeMs: z.ZodDefault>; + voyageApiKey: z.ZodDefault; + previewFeatures: z.ZodDefault>>>; + allowRequestOverrides: z.ZodDefault>; + dryRun: z.ZodDefault; + externallyManagedSessions: z.ZodDefault; + httpResponseType: z.ZodDefault>; + healthCheckPort: z.ZodOptional; + healthCheckHost: z.ZodOptional; + monitoringServerPort: z.ZodOptional; + monitoringServerHost: z.ZodOptional; + monitoringServerFeatures: z.ZodDefault>>>; + gssapiHostName: z.ZodOptional; + sslFIPSMode: z.ZodOptional; + ssl: z.ZodOptional; + sslAllowInvalidCertificates: z.ZodOptional; + sslAllowInvalidHostnames: z.ZodOptional; + sslPEMKeyFile: z.ZodOptional; + sslPEMKeyPassword: z.ZodOptional; + sslCAFile: z.ZodOptional; + sslCertificateSelector: z.ZodOptional; + sslCRLFile: z.ZodOptional; + sslDisabledProtocols: z.ZodOptional; + apiVersion: z.ZodOptional; + authenticationDatabase: z.ZodOptional; + authenticationMechanism: z.ZodOptional; + awsAccessKeyId: z.ZodOptional; + awsIamSessionToken: z.ZodOptional; + awsSecretAccessKey: z.ZodOptional; + awsSessionToken: z.ZodOptional; + csfleLibraryPath: z.ZodOptional; + cryptSharedLibPath: z.ZodOptional; + deepInspect: z.ZodOptional>; + db: z.ZodOptional; + gssapiServiceName: z.ZodOptional; + sspiHostnameCanonicalization: z.ZodOptional; + sspiRealmOverride: z.ZodOptional; + jsContext: z.ZodOptional>; + host: z.ZodOptional; + keyVaultNamespace: z.ZodOptional; + kmsURL: z.ZodOptional; + locale: z.ZodOptional; + oidcFlows: z.ZodOptional; + oidcRedirectUri: z.ZodOptional; + password: z.ZodOptional; + port: z.ZodOptional; + username: z.ZodOptional; + tlsCAFile: z.ZodOptional; + tlsCertificateKeyFile: z.ZodOptional; + tlsCertificateKeyFilePassword: z.ZodOptional; + tlsCertificateSelector: z.ZodOptional; + tlsCRLFile: z.ZodOptional; + tlsDisabledProtocols: z.ZodOptional; + apiDeprecationErrors: z.ZodOptional; + apiStrict: z.ZodOptional; + buildInfo: z.ZodOptional; + exposeAsyncRewriter: z.ZodOptional; + help: z.ZodOptional; + ipv6: z.ZodOptional; + nodb: z.ZodOptional; + norc: z.ZodOptional; + oidcTrustedEndpoint: z.ZodOptional; + oidcIdTokenAsAccessToken: z.ZodOptional; + oidcNoNonce: z.ZodOptional; + quiet: z.ZodOptional; + retryWrites: z.ZodOptional; + shell: z.ZodOptional; + skipStartupWarnings: z.ZodOptional; + verbose: z.ZodOptional; + version: z.ZodOptional; + smokeTests: z.ZodOptional; + perfTests: z.ZodOptional; + tls: z.ZodOptional; + tlsAllowInvalidCertificates: z.ZodOptional; + tlsAllowInvalidHostnames: z.ZodOptional; + tlsFIPSMode: z.ZodOptional; + tlsUseSystemCA: z.ZodOptional; + eval: z.ZodOptional>; + file: z.ZodOptional>; + json: z.ZodOptional]>>; + oidcDumpTokens: z.ZodOptional]>>; + browser: z.ZodOptional, z.ZodString]>>; +}, z.core.$strip>; + +// Warnings were encountered during analysis: +// +// src/common/config/configOverrides.ts:29:5 - (ae-forgotten-export) The symbol "RequestContext_2" needs to be exported by the entry point lib.d.ts +// src/common/exportsManager.ts:165:9 - (ae-forgotten-export) The symbol "JSONExportFormat" needs to be exported by the entry point lib.d.ts + +// (No @packageDocumentation comment for this package) + +``` diff --git a/api-extractor/reports/tools.public.api.md b/api-extractor/reports/tools.public.api.md index 0ed32ae1a..b1edff13d 100644 --- a/api-extractor/reports/tools.public.api.md +++ b/api-extractor/reports/tools.public.api.md @@ -7,6 +7,7 @@ import type { AggregationCursor } from 'mongodb'; import { AnyToolBase } from '@mongodb-js/mcp-core'; import type { ApiClient } from '@mongodb-js/mcp-atlas-api-client'; +import type { AppRegistry } from '@mongodb-js/mcp-apps'; import type { AtlasTelemetry } from '@mongodb-js/mcp-atlas-telemetry'; import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; import type { Client } from '@mongodb-js/atlas-local'; @@ -101,6 +102,11 @@ export class AggregateTool extends MongoDBToolBase { // (undocumented) static operationType: OperationType; // (undocumented) + outputSchema: { + documents: z.ZodArray>; + totalCount: z.ZodOptional; + }; + // (undocumented) static toolName: string; } @@ -221,6 +227,8 @@ export class ConnectTool extends MongoDBToolBase { // (undocumented) protected execute(input: ToolArgs): Promise; // (undocumented) + protected handleError(error: unknown, args: ToolArgs): Promise; + // (undocumented) static operationType: OperationType; // Warning: (ae-forgotten-export) The symbol "Server" needs to be exported by the entry point index.d.ts // @@ -424,6 +432,65 @@ export class DeleteManyTool extends MongoDBToolBase { static toolName: string; } +// @public (undocumented) +export class DocumentBrowserTool extends MongoDBToolBase { + // (undocumented) + argsShape: { + query: z.ZodUnion>; + projection: z.ZodOptional>; + limit: z.ZodDefault>; + sort: z.ZodOptional, z.ZodLiteral<-1>, z.ZodLiteral<"asc">, z.ZodLiteral<"desc">, z.ZodLiteral<"ascending">, z.ZodLiteral<"descending">, z.ZodObject<{ + $meta: z.ZodString; + }, z.core.$strip>]>>>; + }, z.core.$strip>; + }, z.core.$strip>, z.ZodObject<{ + aggregate: z.ZodObject<{ + pipeline: z.ZodArray>; + index: z.ZodString; + path: z.ZodString; + numCandidates: z.ZodOptional; + limit: z.ZodDefault>; + filter: z.ZodOptional>; + queryVector: z.ZodArray; + }, z.core.$strip>, z.ZodObject<{ + exact: z.ZodDefault>; + index: z.ZodString; + path: z.ZodString; + numCandidates: z.ZodOptional; + limit: z.ZodDefault>; + filter: z.ZodOptional>; + query: z.ZodObject<{ + text: z.ZodString; + }, z.core.$strip>; + model: z.ZodOptional>; + }, z.core.$strip>]>; + }, z.core.$strip>, z.ZodRecord]>>; + }, z.core.$strip>; + }, z.core.$strip>]>; + collection: z.ZodString; + database: z.ZodString; + }; + // (undocumented) + description: string; + // (undocumented) + protected execute(input: ToolArgs): Promise; + // (undocumented) + static operationType: OperationType; + // (undocumented) + protected get toolMeta(): Record; + // (undocumented) + static toolName: string; +} + // Warning: (ae-forgotten-export) The symbol "DropCollectionOutputSchema" needs to be exported by the entry point index.d.ts // // @public (undocumented) @@ -684,6 +751,11 @@ export class FindTool extends MongoDBToolBase { // (undocumented) static operationType: OperationType; // (undocumented) + outputSchema: { + documents: z.ZodArray>; + totalCount: z.ZodOptional; + }; + // (undocumented) static toolName: string; } diff --git a/api-extractor/reports/web.public.api.md b/api-extractor/reports/web.public.api.md index 22a6a8166..f34b1a0a4 100644 --- a/api-extractor/reports/web.public.api.md +++ b/api-extractor/reports/web.public.api.md @@ -8,6 +8,7 @@ import type { AggregationCursor } from 'mongodb'; import { AnyToolBase } from '@mongodb-js/mcp-core'; import { ApiClient } from '@mongodb-js/mcp-atlas-api-client'; import { ApiClientOptions } from '@mongodb-js/mcp-atlas-api-client'; +import type { AppRegistry } from '@mongodb-js/mcp-apps'; import { AtlasLocalClientFactoryFn } from '@mongodb-js/mcp-tools-atlas-local'; import { AtlasLocalToolMetadata } from '@mongodb-js/mcp-atlas-telemetry'; import { AtlasMetadata } from '@mongodb-js/mcp-atlas-telemetry'; @@ -286,7 +287,7 @@ export type CreateSessionConfigFn = export { Credentials } // @public (undocumented) -export type CustomizableServerOptions = Partial, "uiRegistry" | "tools" | "toolContext" | "elicitation">> & { +export type CustomizableServerOptions = Partial, "uiRegistry" | "appRegistry" | "tools" | "toolContext" | "elicitation">> & { telemetryProperties?: Partial; }; @@ -447,7 +448,7 @@ export { PerfAdvisorToolMetadata } export type PreviewFeature = (typeof previewFeatureValues)[number]; // @public (undocumented) -export const previewFeatureValues: readonly ["mcpUI"]; +export const previewFeatureValues: readonly ["mcpUI", "mcpApps"]; // @public (undocumented) export interface ReadyExport extends CommonExportData { @@ -467,6 +468,8 @@ export { Secret } export class Server { constructor(input: ServerOptions); // (undocumented) + readonly appRegistry?: AppRegistry; + // (undocumented) close(): Promise; // (undocumented) connect(transport: Transport): Promise; @@ -483,6 +486,8 @@ export class Server; // (undocumented) + registerAppResources(): Promise; + // (undocumented) registerResources(): void; // (undocumented) registerTools(): void; @@ -504,6 +509,8 @@ export class Server { + // (undocumented) + appRegistry?: AppRegistry; // @deprecated (undocumented) connectionErrorHandler: ConnectionErrorHandler; // (undocumented) @@ -758,6 +765,7 @@ export const UserConfigSchema: z.ZodObject<{ voyageApiKey: z.ZodDefault; previewFeatures: z.ZodDefault>>>; allowRequestOverrides: z.ZodDefault>; dryRun: z.ZodDefault; diff --git a/eslint.config.js b/eslint.config.js index c9d2c606c..191b67922 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -118,6 +118,18 @@ export default defineConfig([ }, }, }, + { + // packages/apps uses moduleResolution:bundler and is excluded from the root tsconfig. + // Point ESLint at its own tsconfig for the source files it includes. + files: ["packages/apps/src/**/*.ts", "packages/apps/src/**/*.tsx"], + extends: [tseslint.configs.recommendedTypeChecked], + languageOptions: { + parserOptions: { + project: "./packages/apps/tsconfig.json", + tsconfigRootDir: import.meta.dirname, + }, + }, + }, globalIgnores([ "node_modules", "**/dist/**", @@ -135,6 +147,16 @@ export default defineConfig([ "tests/browser/polyfills/**", "eslint-rules", ".yalc", + // packages/apps files excluded from its own tsconfig (scripts, config, test files, mount) + "packages/apps/scripts/**", + "packages/apps/vite.apps.config.ts", + "packages/apps/vitest.config.ts", + "packages/apps/src/**/*.test.ts", + "packages/apps/src/**/*.test.tsx", + "packages/apps/src/build/mount.tsx", + // Auto-generated by the build script — contain minified HTML bundles + "packages/apps/src/lib/apps/**", + "packages/apps/src/lib/loaders.ts", ]), eslintPluginPrettierRecommended, ]); diff --git a/knip.ts b/knip.ts index cb4b7e795..b55731b7b 100644 --- a/knip.ts +++ b/knip.ts @@ -50,9 +50,49 @@ const config: KnipConfig = { "vite-plugin-singlefile", ], }, + "packages/apps": { + // App components are Vite entry points, not TypeScript module entries. + entry: ["src/index.ts!", "src/apps/**/*.tsx!", "scripts/buildApps.ts!"], + // index.ts files are loaded via import.meta.glob in mount.tsx — knip can't trace Vite globs. + ignore: [ + "src/apps/*/index.ts", + "src/build/mount.tsx", + "src/styles/**", + "src/test-setup.ts", + "vite.apps.config.ts", + "dist/**", + ], + ignoreDependencies: [ + "@modelcontextprotocol/ext-apps", + "@modelcontextprotocol/sdk", + "@tailwindcss/vite", + "@testing-library/jest-dom", + "@testing-library/react", + "@testing-library/user-event", + "@types/react", + "@types/react-dom", + "@vitejs/plugin-react", + "happy-dom", + "react", + "react-dom", + "tailwindcss", + "tsx", + "vite", + "vite-plugin-node-polyfills", + "vite-plugin-singlefile", + "vitest", + ], + }, "packages/ui": { entry: ["src/index.ts!"], - ignore: ["src/build/mount.tsx", "src/test-setup.ts", "src/components/**", "vite.ui.config.ts", "dist/**"], + ignore: [ + "src/build/mount.tsx", + "src/test-setup.ts", + "src/components/**", + "src/styles/**", + "vite.ui.config.ts", + "dist/**", + ], ignoreDependencies: ["@lg-mcp/embeddable-uis", "@lg-mcp/hooks", "@testing-library/jest-dom/vitest"], }, // Type-only package — deps are used via `import type` so knip can't detect runtime usage diff --git a/package.json b/package.json index 11fbd73b4..14a82829e 100644 --- a/package.json +++ b/package.json @@ -161,6 +161,7 @@ "@mongodb-js/mcp-metrics": "workspace:*", "@mongodb-js/mcp-tools-atlas": "workspace:*", "@mongodb-js/mcp-tools-atlas-local": "workspace:*", + "@mongodb-js/mcp-apps": "workspace:*", "@mongodb-js/mcp-types": "workspace:*", "@mongodb-js/mcp-ui": "workspace:*", "@mongosh/arg-parser": "^5.0.2", diff --git a/packages/apps/api-extractor.json b/packages/apps/api-extractor.json new file mode 100644 index 000000000..de11b685c --- /dev/null +++ b/packages/apps/api-extractor.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + + "mainEntryPointFilePath": "/dist/index.d.ts", + + "compiler": { + "tsconfigFilePath": "/tsconfig.json" + }, + + "apiReport": { + "enabled": true, + "reportFolder": "/api-extractor/reports/", + "reportFileName": "apps" + }, + + "docModel": { + "enabled": false + }, + + "dtsRollup": { + "enabled": false + }, + + "tsdocMetadata": { + "enabled": false + }, + + "messages": { + "compilerMessageReporting": { + "default": { + "logLevel": "warning" + } + }, + "extractorMessageReporting": { + "default": { + "logLevel": "warning" + }, + "ae-missing-release-tag": { + "logLevel": "none" + }, + "ae-unresolved-link": { + "logLevel": "warning" + } + }, + "tsdocMessageReporting": { + "default": { + "logLevel": "none" + } + } + } +} diff --git a/packages/apps/api-extractor/reports/apps.api.md b/packages/apps/api-extractor/reports/apps.api.md new file mode 100644 index 000000000..48260cf9e --- /dev/null +++ b/packages/apps/api-extractor/reports/apps.api.md @@ -0,0 +1,26 @@ +## API Report File for "@mongodb-js/mcp-apps" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { AppRegistryOptions } from '@mongodb-js/mcp-types'; +import { IAppRegistry } from '@mongodb-js/mcp-types'; + +// @public +export const appLoaders: Record Promise>; + +// @public +export class AppRegistry implements IAppRegistry { + constructor(options?: AppRegistryOptions); + appNames(): string[]; + get(appName: string): Promise; +} + +export { AppRegistryOptions } + +export { IAppRegistry } + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/apps/package.json b/packages/apps/package.json new file mode 100644 index 000000000..8fefe2e0d --- /dev/null +++ b/packages/apps/package.json @@ -0,0 +1,50 @@ +{ + "name": "@mongodb-js/mcp-apps", + "version": "1.11.0-prerelease.1", + "private": true, + "type": "module", + "description": "MCP Apps for MongoDB MCP server", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "compile": "tsx scripts/buildApps.ts && tsc --build tsconfig.json", + "check:api": "api-extractor run --config api-extractor.json", + "update:api": "api-extractor run --local --config api-extractor.json", + "test": "vitest --run" + }, + "dependencies": { + "@mongodb-js/mcp-types": "workspace:*", + "bson": "^7.2.0", + "zod": "^3.25.0" + }, + "devDependencies": { + "@modelcontextprotocol/ext-apps": "^1.2.0", + "@tailwindcss/vite": "^4.1.0", + "tailwindcss": "^4.1.0", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "happy-dom": "^20.9.0", + "vitest": "^4.1.4", + "@types/node": "^25.6.0", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "react": "^18.3.0", + "react-dom": "^18.3.0", + "tsx": "^4.20.5", + "typescript": "^5.9.2", + "vite": "^8.0.8", + "vite-plugin-node-polyfills": "^0.26.0", + "vite-plugin-singlefile": "^2.3.2" + } +} diff --git a/packages/apps/scripts/buildApps.ts b/packages/apps/scripts/buildApps.ts new file mode 100644 index 000000000..5e31a71fc --- /dev/null +++ b/packages/apps/scripts/buildApps.ts @@ -0,0 +1,178 @@ +/** + * Programmatic build script for MCP Apps. + * + * Each app must be a self-contained single HTML file (viteSingleFile), which requires + * inlineDynamicImports / codeSplitting:false. Rollup rejects that option when there are + * multiple inputs in one build, so we run one Vite build per app and combine the outputs + * afterward via generateAppModules(). + */ +import { build } from "vite"; +import react from "@vitejs/plugin-react"; +import tailwindcss from "@tailwindcss/vite"; +import { viteSingleFile } from "vite-plugin-singlefile"; +import { nodePolyfills } from "vite-plugin-node-polyfills"; +import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync, rmSync } from "fs"; +import { join, resolve, dirname } from "path"; +import { fileURLToPath } from "url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const rootDir = resolve(__dirname, ".."); + +const appsDir = resolve(rootDir, "src/apps"); +const entriesDir = resolve(rootDir, "node_modules/.cache/mcp-apps/app-entries"); +const templatePath = resolve(rootDir, "src/build/template.html"); +const mountPath = resolve(rootDir, "src/build/mount.tsx"); +const generatedDir = resolve(rootDir, "src/lib"); +const appsDistPath = resolve(rootDir, "dist/apps"); + +function toKebabCase(pascalCase: string): string { + return pascalCase + .replace(/([a-z0-9])([A-Z])/g, "$1-$2") + .replace(/([A-Z])([A-Z][a-z])/g, "$1-$2") + .toLowerCase(); +} + +function discoverApps(): { apps: string[]; appNameToComponentMap: Record } { + const apps: string[] = []; + const appNameToComponentMap: Record = {}; + for (const entry of readdirSync(appsDir)) { + const entryPath = join(appsDir, entry); + const indexPath = join(entryPath, "index.ts"); + if (statSync(entryPath).isDirectory() && existsSync(indexPath)) { + apps.push(entry); + appNameToComponentMap[toKebabCase(entry)] = entry; + } + } + return { apps, appNameToComponentMap }; +} + +function generateHtmlEntries(apps: string[]): void { + const template = readFileSync(templatePath, "utf-8"); + if (!existsSync(entriesDir)) { + mkdirSync(entriesDir, { recursive: true }); + } + for (const componentName of apps) { + const html = template + .replace("{{COMPONENT_NAME}}", componentName) + .replace("{{TITLE}}", componentName.replace(/([A-Z])/g, " $1").trim()) + .replace("{{MOUNT_PATH}}", mountPath); + const outputPath = join(entriesDir, `${componentName}.html`); + writeFileSync(outputPath, html); + console.log(`[generate-html-entries] Generated ${componentName}.html`); + } +} + +function generateAppModules(appNameToComponentMap: Record): void { + if (!existsSync(appsDistPath)) { + console.warn("[generate-app-modules] dist/apps not found, skipping module generation"); + return; + } + + const appsLibDir = join(generatedDir, "apps"); + mkdirSync(appsLibDir, { recursive: true }); + const existingAppFiles = readdirSync(appsLibDir).filter((file) => file.endsWith(".ts")); + + const generatedApps: string[] = []; + + for (const [appName, componentName] of Object.entries(appNameToComponentMap)) { + const htmlFile = join(appsDistPath, `${componentName}.html`); + if (!existsSync(htmlFile)) { + console.warn( + `[generate-app-modules] HTML file not found for component "${componentName}" (app: "${appName}")` + ); + continue; + } + const html = readFileSync(htmlFile, "utf-8"); + const appModuleContent = `/** + * AUTO-GENERATED FILE - DO NOT EDIT MANUALLY + * Generated by: pnpm compile + * App: ${appName} + * Component: ${componentName} + */ +export const ${componentName}Html = ${JSON.stringify(html)}; +`; + writeFileSync(join(appsLibDir, `${appName}.ts`), appModuleContent); + generatedApps.push(appName); + } + + const loaderEntries = generatedApps + .map((appName) => { + const componentName = appNameToComponentMap[appName]; + return ` "${appName}": async () => { + if (process.env.MCPAPP_DEV) return devReadApp(join(__dirname, "apps/${appName}.js")); + const mod = await import("./apps/${appName}.js"); + return mod.${componentName}Html; + }`; + }) + .join(",\n"); + + const loadersContent = `/** + * AUTO-GENERATED FILE - DO NOT EDIT MANUALLY + * Generated by: pnpm compile + * + * Lazy loaders for app modules. Each loader returns a Promise with the HTML. + */ +import { readFile } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; +import { join, dirname } from "node:path"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +async function devReadApp(filePath: string): Promise { + const content = await readFile(filePath, "utf-8"); + const match = /export const \\w+Html = ("(?:[^"\\\\]|\\\\[\\s\\S])*");/.exec(content); + if (!match?.[1]) throw new Error(\`Could not extract HTML from \${filePath}\`); + return JSON.parse(match[1]) as string; +} + +export const appLoaders: Record Promise> = { +${loaderEntries} +}; +`; + writeFileSync(join(generatedDir, "loaders.ts"), loadersContent); + + console.log(`[generate-app-modules] Generated ${generatedApps.length} app module(s): ${generatedApps.join(", ")}`); + console.log(`[generate-app-modules] Generated loaders.ts with ${generatedApps.length} loader(s)`); + + const staleApps = existingAppFiles.filter((file) => !generatedApps.includes(file.replace(/\.ts$/, ""))); + for (const staleApp of staleApps) { + rmSync(join(appsLibDir, staleApp)); + console.log(`[generate-app-modules] Removed stale app module: ${staleApp}`); + } + + if (existsSync(appsDistPath)) { + rmSync(appsDistPath, { recursive: true }); + console.log("[generate-app-modules] Cleaned up intermediate dist/apps directory"); + } +} + +const { apps, appNameToComponentMap } = discoverApps(); + +generateHtmlEntries(apps); + +for (const [i, componentName] of apps.entries()) { + await build({ + root: entriesDir, + plugins: [ + nodePolyfills({ include: ["buffer", "stream"], globals: { Buffer: true } }), + react(), + tailwindcss(), + viteSingleFile({ removeViteModuleLoader: true }), + ], + build: { + outDir: appsDistPath, + emptyOutDir: i === 0, + rollupOptions: { + input: { [componentName]: resolve(entriesDir, `${componentName}.html`) }, + }, + assetsInlineLimit: 100000000, + sourcemap: false, + minify: "esbuild", + }, + resolve: { + alias: { "@apps": resolve(rootDir, "src") }, + }, + }); +} + +generateAppModules(appNameToComponentMap); diff --git a/packages/apps/src/apps/ConnectForm/ConnectForm.test.tsx b/packages/apps/src/apps/ConnectForm/ConnectForm.test.tsx new file mode 100644 index 000000000..73c13f822 --- /dev/null +++ b/packages/apps/src/apps/ConnectForm/ConnectForm.test.tsx @@ -0,0 +1,121 @@ +import { describe, it, expect, vi, afterEach } from "vitest"; +import { render, screen, waitFor, act, cleanup } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { ConnectForm } from "./ConnectForm.js"; + +vi.mock("@modelcontextprotocol/ext-apps/react", () => ({ + useApp: vi.fn(), + useHostStyles: vi.fn(), +})); + +import { useApp } from "@modelcontextprotocol/ext-apps/react"; +const mockUseApp = vi.mocked(useApp); + +function makeApp(callServerTool: ReturnType = vi.fn()) { + return { callServerTool, getHostContext: vi.fn().mockReturnValue(null) }; +} + +afterEach(() => { + cleanup(); + vi.clearAllMocks(); +}); + +describe("ConnectForm", () => { + it("should render heading, pre-filled input, and Connect button", () => { + mockUseApp.mockReturnValue({ app: makeApp(), isConnected: true, error: null }); + + render(); + + expect(screen.getByText("Connect to MongoDB")).toBeInTheDocument(); + expect(screen.getByLabelText("Connection String")).toHaveValue("mongodb://localhost:27017"); + expect(screen.getByRole("button", { name: "Connect" })).toBeInTheDocument(); + }); + + it("should disable input and button when not connected", () => { + mockUseApp.mockReturnValue({ app: makeApp(), isConnected: false, error: null }); + + render(); + + expect(screen.getByLabelText("Connection String")).toBeDisabled(); + expect(screen.getByRole("button", { name: "Connect" })).toBeDisabled(); + }); + + it("should show host error message when useApp returns an error", () => { + mockUseApp.mockReturnValue({ + app: null, + isConnected: false, + error: new Error("host unavailable"), + }); + + render(); + + expect(screen.getByText(/Failed to connect to MCP host: host unavailable/)).toBeInTheDocument(); + }); + + it("should show success state after a successful callServerTool", async () => { + const callServerTool = vi.fn().mockResolvedValue({ isError: false, content: [] }); + mockUseApp.mockReturnValue({ app: makeApp(callServerTool), isConnected: true, error: null }); + + render(); + + await userEvent.click(screen.getByRole("button", { name: "Connect" })); + + await waitFor(() => { + expect(screen.getByText("Connected successfully!")).toBeInTheDocument(); + }); + + expect(screen.queryByRole("form")).not.toBeInTheDocument(); + }); + + it("should show error text when callServerTool returns isError: true", async () => { + const callServerTool = vi.fn().mockResolvedValue({ + isError: true, + content: [{ type: "text", text: "auth failed" }], + }); + mockUseApp.mockReturnValue({ app: makeApp(callServerTool), isConnected: true, error: null }); + + render(); + + await userEvent.click(screen.getByRole("button", { name: "Connect" })); + + await waitFor(() => { + expect(screen.getByText("auth failed")).toBeInTheDocument(); + }); + + expect(screen.getByRole("button", { name: "Connect" })).toBeInTheDocument(); + }); + + it("should show error text when callServerTool throws", async () => { + const callServerTool = vi.fn().mockRejectedValue(new Error("network error")); + mockUseApp.mockReturnValue({ app: makeApp(callServerTool), isConnected: true, error: null }); + + render(); + + await userEvent.click(screen.getByRole("button", { name: "Connect" })); + + await waitFor(() => { + expect(screen.getByText("network error")).toBeInTheDocument(); + }); + }); + + it("should show 'Connecting…' and disable controls while in flight", async () => { + let resolve!: (v: unknown) => void; + const deferred = new Promise((res) => (resolve = res)); + const callServerTool = vi.fn().mockReturnValue(deferred); + mockUseApp.mockReturnValue({ app: makeApp(callServerTool), isConnected: true, error: null }); + + render(); + + await userEvent.click(screen.getByRole("button", { name: "Connect" })); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Connecting…" })).toBeDisabled(); + }); + expect(screen.getByLabelText("Connection String")).toBeDisabled(); + + // Resolve the promise so the component settles before cleanup + await act(async () => { + resolve({ isError: false, content: [] }); + }); + }); +}); diff --git a/packages/apps/src/apps/ConnectForm/ConnectForm.tsx b/packages/apps/src/apps/ConnectForm/ConnectForm.tsx new file mode 100644 index 000000000..57e9841e2 --- /dev/null +++ b/packages/apps/src/apps/ConnectForm/ConnectForm.tsx @@ -0,0 +1,115 @@ +import React, { useState, useEffect, type ReactElement } from "react"; +import { useApp, useHostStyles } from "@modelcontextprotocol/ext-apps/react"; +import { AppShell, Label, Input, Button, ErrorText, Success, Heading } from "../../components/elements.js"; + +export const ConnectForm = (): ReactElement => { + const { + app, + isConnected, + error: hostError, + } = useApp({ + appInfo: { name: "connect-form", version: "1.0.0" }, + capabilities: {}, + }); + + useHostStyles(app, app?.getHostContext()); + + // ext-apps 1.2.0 bug: useHostFonts overwrites useHostStyleVariables' onhostcontextchanged + // subscription, so runtime theme/variable changes are silently dropped. Chain on top. + useEffect(() => { + if (!app) return; + const prev = app.onhostcontextchanged; + app.onhostcontextchanged = (ctx) => { + prev?.(ctx); + if (ctx.theme) { + document.documentElement.setAttribute("data-theme", ctx.theme); + document.documentElement.style.colorScheme = ctx.theme; + } + if (ctx.styles?.variables) { + for (const [k, v] of Object.entries(ctx.styles.variables)) { + if (v != null) document.documentElement.style.setProperty(k, v); + } + } + }; + return () => { + app.onhostcontextchanged = prev; + }; + }, [app]); + + const [connectionString, setConnectionString] = useState("mongodb://localhost:27017"); + const [status, setStatus] = useState<"idle" | "connecting" | "success" | "error">("idle"); + const [errorMessage, setErrorMessage] = useState(null); + + const handleSubmit = async (e: React.SubmitEvent): Promise => { + e.preventDefault(); + if (!app) return; + + setStatus("connecting"); + setErrorMessage(null); + + try { + let result; + try { + result = await app.callServerTool({ name: "connect", arguments: { connectionString } }); + } catch (err) { + // connect is disabled when already connected — swap to switch-connection + if (err instanceof Error && err.message === "Tool connect disabled") { + result = await app.callServerTool({ name: "switch-connection", arguments: { connectionString } }); + } else { + throw err; + } + } + if (result.isError) { + const text = result.content.find((c) => c.type === "text"); + setStatus("error"); + setErrorMessage(text && "text" in text ? text.text : "Connection failed"); + } else { + setStatus("success"); + } + } catch (err) { + setStatus("error"); + setErrorMessage(err instanceof Error ? err.message : "Connection failed"); + } + }; + + if (hostError) { + return ( + + Failed to connect to MCP host: {hostError.message} + + ); + } + + return ( + + Connect to MongoDB + {status === "success" ? ( + Connected successfully! + ) : ( + <> +
{ + void handleSubmit(e); + }} + className="flex w-full flex-col gap-2" + > + + setConnectionString(e.target.value)} + placeholder="mongodb://localhost:27017" + required + disabled={!isConnected || status === "connecting"} + /> + +
+ {status === "error" && errorMessage && {errorMessage}} + + )} +
+ ); +}; diff --git a/packages/apps/src/apps/ConnectForm/index.ts b/packages/apps/src/apps/ConnectForm/index.ts new file mode 100644 index 000000000..b8a79cb5d --- /dev/null +++ b/packages/apps/src/apps/ConnectForm/index.ts @@ -0,0 +1 @@ +export { ConnectForm } from "./ConnectForm.js"; diff --git a/packages/apps/src/apps/DocumentBrowser/DocumentBrowser.test.tsx b/packages/apps/src/apps/DocumentBrowser/DocumentBrowser.test.tsx new file mode 100644 index 000000000..29b0da8f5 --- /dev/null +++ b/packages/apps/src/apps/DocumentBrowser/DocumentBrowser.test.tsx @@ -0,0 +1,176 @@ +import { describe, it, expect, vi, afterEach } from "vitest"; +import { render, screen, cleanup, act } from "@testing-library/react"; +import { DocumentBrowser } from "./DocumentBrowser.js"; +import type { App } from "@modelcontextprotocol/ext-apps/react"; + +vi.mock("@modelcontextprotocol/ext-apps/react", () => ({ + useApp: vi.fn(), + useHostStyles: vi.fn(), +})); + +import { useApp } from "@modelcontextprotocol/ext-apps/react"; +const mockUseApp = vi.mocked(useApp); + +const INITIAL_RENDER_DATA_KEY = "mcpui.dev/ui-initial-render-data"; + +function makeApp( + hostContext: Record | null = null, + callServerToolImpl?: () => Promise +): Partial { + return { + getHostContext: vi.fn().mockReturnValue(hostContext), + onhostcontextchanged: null, + callServerTool: vi.fn().mockImplementation(callServerToolImpl ?? (() => new Promise(() => {}))), + }; +} + +/** Renders DocumentBrowser and captures the onAppCreated callback so tests can trigger ontoolinput. */ +function renderWithOnAppCreated( + appOverride: Partial = makeApp(), + isConnected = true +): { triggerToolInput: (args: unknown) => void } { + let capturedOnAppCreated: ((app: App) => void) | undefined; + + mockUseApp.mockImplementation(({ onAppCreated }) => { + capturedOnAppCreated = onAppCreated; + return { app: appOverride as App, isConnected, error: null }; + }); + + render(); + + return { + triggerToolInput: (args: unknown) => { + capturedOnAppCreated?.(appOverride as App); + (appOverride as { ontoolinput?: (p: { arguments: unknown }) => void }).ontoolinput?.({ + arguments: args, + }); + }, + }; +} + +function makeFindRenderData(): Record { + return { + [INITIAL_RENDER_DATA_KEY]: { + database: "mydb", + collection: "mycoll", + query: { find: { filter: { status: "active" }, limit: 10 } }, + }, + }; +} + +afterEach(() => { + cleanup(); + vi.clearAllMocks(); +}); + +describe("DocumentBrowser — ontoolinput path (standard, e.g. MCP Inspector)", () => { + it("shows 'Connecting…' before connected", () => { + mockUseApp.mockReturnValue({ app: makeApp() as App, isConnected: false, error: null }); + render(); + expect(screen.getByText("Connecting…")).toBeInTheDocument(); + }); + + it("shows 'No query parameters provided.' when connected but no tool input received", () => { + mockUseApp.mockReturnValue({ app: makeApp({}) as App, isConnected: true, error: null }); + render(); + expect(screen.getByText("No query parameters provided.")).toBeInTheDocument(); + }); + + it("renders collection and find params after ontoolinput fires", async () => { + const { triggerToolInput } = renderWithOnAppCreated(); + + await act(async () => { + triggerToolInput({ database: "mydb", collection: "mycoll", query: { find: { filter: {} } } }); + }); + + expect(screen.getByText("mydb.mycoll")).toBeInTheDocument(); + expect(screen.getByText("Find")).toBeInTheDocument(); + }); + + it("renders pipeline params after ontoolinput fires with aggregate query", async () => { + const { triggerToolInput } = renderWithOnAppCreated(); + + await act(async () => { + triggerToolInput({ + database: "mydb", + collection: "mycoll", + query: { aggregate: { pipeline: [{ $match: { active: true } }] } }, + }); + }); + + expect(screen.getByText("Pipeline")).toBeInTheDocument(); + }); + + it("shows invalid-params error when ontoolinput carries an unrecognised query shape", async () => { + const { triggerToolInput } = renderWithOnAppCreated(); + + await act(async () => { + triggerToolInput({ database: "db", collection: "coll", query: { bad: true } }); + }); + + expect(screen.getByText(/Invalid query parameters/)).toBeInTheDocument(); + }); + + it("calls find tool and shows results after ontoolinput", async () => { + const mockResult = { + isError: false, + content: [{ type: "text", text: '[{"_id":"1"}]' }], + }; + const app = makeApp(null, () => Promise.resolve(mockResult)); + const { triggerToolInput } = renderWithOnAppCreated(app); + + await act(async () => { + triggerToolInput({ database: "mydb", collection: "mycoll", query: { find: { filter: {}, limit: 5 } } }); + await Promise.resolve(); + }); + + expect(app.callServerTool).toHaveBeenCalledWith({ + name: "find", + arguments: { database: "mydb", collection: "mycoll", filter: {}, limit: 5 }, + }); + expect(screen.getByText('[{"_id":"1"}]')).toBeInTheDocument(); + }); +}); + +describe("DocumentBrowser — initial-render-data fallback (e.g. Claude.ai)", () => { + it("renders collection and find params from host context initial-render-data", () => { + mockUseApp.mockReturnValue({ + app: makeApp(makeFindRenderData()) as App, + isConnected: true, + error: null, + }); + + render(); + + expect(screen.getByText("mydb.mycoll")).toBeInTheDocument(); + expect(screen.getByText("Find")).toBeInTheDocument(); + }); + + it("calls find tool on mount via initial-render-data", async () => { + const mockResult = { isError: false, content: [{ type: "text", text: "[]" }] }; + const app = makeApp(makeFindRenderData(), () => Promise.resolve(mockResult)); + mockUseApp.mockReturnValue({ app: app as App, isConnected: true, error: null }); + + render(); + + await act(async () => { + await Promise.resolve(); + }); + + expect(app.callServerTool).toHaveBeenCalled(); + }); +}); + +describe("DocumentBrowser — host error", () => { + it("shows host error message when useApp returns an error", () => { + mockUseApp.mockReturnValue({ + app: null, + isConnected: false, + error: new Error("host unavailable"), + }); + + render(); + + expect(screen.getByText(/Failed to connect to MCP host: host unavailable/)).toBeInTheDocument(); + }); +}); diff --git a/packages/apps/src/apps/DocumentBrowser/DocumentBrowser.tsx b/packages/apps/src/apps/DocumentBrowser/DocumentBrowser.tsx new file mode 100644 index 000000000..a4f7eac54 --- /dev/null +++ b/packages/apps/src/apps/DocumentBrowser/DocumentBrowser.tsx @@ -0,0 +1,225 @@ +import React, { useEffect, useState, useRef, type ReactElement } from "react"; +import { useApp, useHostStyles } from "@modelcontextprotocol/ext-apps/react"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { z } from "zod"; +import { EJSON } from "bson"; +import { AppShell, Label, Text, Loading, ErrorText, Code, Heading, TreeValue } from "../../components/elements.js"; + +// Mirrors src/tools/mongodb/read/find.ts FindArgs +// TODO: deduplicate with FindArgsSchema in src/tools/mongodb/read/find.ts once +// the circular dependency issues are resolved +const FindArgsSchema = z.object({ + filter: z.record(z.string(), z.unknown()).optional(), + projection: z.object({}).passthrough().optional(), + limit: z.number().optional(), + sort: z.record(z.string(), z.union([z.literal(1), z.literal(-1), z.literal("asc"), z.literal("desc")])).optional(), +}); + +// Mirrors src/tools/mongodb/read/aggregate.ts AggregateArgs +// TODO: deduplicate with AggregateArgsSchema in +// src/tools/mongodb/read/aggregate.ts once the circular dependency issues are +// resolved +const AggregateArgsSchema = z.object({ + pipeline: z.array(z.record(z.string(), z.unknown())), +}); + +const FindQuerySchema = z.object({ find: FindArgsSchema }); +const AggregateQuerySchema = z.object({ aggregate: AggregateArgsSchema }); +const QuerySchema = z.union([FindQuerySchema, AggregateQuerySchema]); + +const RenderDataSchema = z.object({ + database: z.string(), + collection: z.string(), + query: QuerySchema, +}); + +type FindQuery = z.infer; +type AggregateQuery = z.infer; +type RenderData = z.infer; + +function isFindQuery(q: z.infer): q is FindQuery { + return "find" in q; +} + +function isAggregateQuery(q: z.infer): q is AggregateQuery { + return "aggregate" in q; +} + +// Passed via resource._meta by the tool for hosts that support it +const INITIAL_RENDER_DATA_KEY = "mcpui.dev/ui-initial-render-data"; + +export const DocumentBrowser = (): ReactElement => { + const [renderData, setRenderData] = useState(null); + const [renderDataError, setRenderDataError] = useState(null); + + const { + app, + isConnected, + error: hostError, + } = useApp({ + appInfo: { name: "document-browser", version: "1.0.0" }, + capabilities: {}, + // Register ontoolinput before the connection handshake so we don't miss it. + // This is the standard mechanism hosts use to deliver tool arguments to the app. + onAppCreated: (createdApp) => { + createdApp.ontoolinput = (params) => { + const parsed = RenderDataSchema.safeParse(params.arguments); + if (parsed.success) { + setRenderData(parsed.data); + } else { + setRenderDataError(parsed.error.issues.map((i) => i.message).join(", ")); + } + }; + }, + }); + + useHostStyles(app, app?.getHostContext()); + + // ext-apps 1.2.0 bug: useHostFonts overwrites useHostStyleVariables' onhostcontextchanged + // subscription, so runtime theme/variable changes are silently dropped. Chain on top. + useEffect(() => { + if (!app) return; + const prev = app.onhostcontextchanged; + app.onhostcontextchanged = (ctx) => { + prev?.(ctx); + if (ctx.theme) { + document.documentElement.setAttribute("data-theme", ctx.theme); + document.documentElement.style.colorScheme = ctx.theme; + } + if (ctx.styles?.variables) { + for (const [k, v] of Object.entries(ctx.styles.variables)) { + if (v != null) document.documentElement.style.setProperty(k, v); + } + } + }; + return () => { + app.onhostcontextchanged = prev; + }; + }, [app]); + + // Fallback: some hosts embed the tool args in resource._meta under this key + useEffect(() => { + if (!isConnected || renderData) return; + const parsed = RenderDataSchema.safeParse(app?.getHostContext()?.[INITIAL_RENDER_DATA_KEY]); + if (parsed.success) { + setRenderData(parsed.data); + } + }, [app, isConnected, renderData]); + + const [status, setStatus] = useState<"idle" | "loading" | "done">("idle"); + const [result, setResult] = useState(null); + const [toolError, setToolError] = useState(null); + const hasRun = useRef(false); + + useEffect(() => { + if (!app || !isConnected || !renderData || hasRun.current) return; + + hasRun.current = true; + const { database, collection, query } = renderData; + + const toolParams = isFindQuery(query) + ? { name: "find", arguments: { database, collection, ...query.find } } + : { name: "aggregate", arguments: { database, collection, ...query.aggregate } }; + + setStatus("loading"); + app.callServerTool(toolParams) + .then((r) => { + setResult(r); + setStatus("done"); + }) + .catch((err: unknown) => { + setToolError(err instanceof Error ? err.message : String(err)); + setStatus("done"); + }); + }, [app, isConnected, renderData]); + + if (hostError) { + return ( + + Failed to connect to MCP host: {hostError.message} + + ); + } + + if (!renderData) { + if (renderDataError) { + return ( + + Invalid query parameters: {renderDataError} + + ); + } + return ( + + {isConnected ? "No query parameters provided." : "Connecting…"} + + ); + } + + const { database, collection, query }: RenderData = renderData; + + return ( + +
+ + {database}.{collection}. + {isFindQuery(query) ? "find" : isAggregateQuery(query) ? "aggregate" : "query"} + + + {isFindQuery(query) + ? JSON.stringify(query.find, null, 2) + : JSON.stringify(query.aggregate.pipeline, null, 2)} + +
+ +
+ {status === "loading" && Running query…} + {status === "done" && toolError && {toolError}} + {status === "done" && + result && + (() => { + const sc = result.structuredContent as + | { documents?: unknown[]; totalCount?: number } + | undefined; + const docs = Array.isArray(sc?.documents) ? sc.documents : null; + if (docs) { + const bsonDocs = EJSON.deserialize(docs) as unknown[]; + return ( + <> + + + {"[\n"} + {bsonDocs.map((d, i) => ( + + + {i < bsonDocs.length - 1 ? ",\n" : ""} + + ))} + {"\n]"} + + + ); + } + // TODO: Not sure about this. It is an error case, right? + return ( + <> + + + {result.content + .filter( + (c): c is { type: "text"; text: string } => c.type === "text" && "text" in c + ) + .map((c) => c.text) + .join("\n")} + + + ); + })()} +
+
+ ); +}; diff --git a/packages/apps/src/apps/DocumentBrowser/index.ts b/packages/apps/src/apps/DocumentBrowser/index.ts new file mode 100644 index 000000000..3c6b1ff1c --- /dev/null +++ b/packages/apps/src/apps/DocumentBrowser/index.ts @@ -0,0 +1 @@ +export { DocumentBrowser } from "./DocumentBrowser.js"; diff --git a/packages/apps/src/build/mount.tsx b/packages/apps/src/build/mount.tsx new file mode 100644 index 000000000..bfb89d6c3 --- /dev/null +++ b/packages/apps/src/build/mount.tsx @@ -0,0 +1,61 @@ +/// +import "../styles/global.css"; +import React from "react"; +import { createRoot } from "react-dom/client"; + +type AppModule = Record; + +// Auto-import all app components using Vite's glob import +// Each app folder must have an index.ts that exports the component matching the folder name +const appModules: Record = import.meta.glob("../apps/*/index.ts", { + eager: true, +}); + +const apps: Record = {}; + +for (const [path, module] of Object.entries(appModules)) { + const match = path.match(/\.\.\/apps\/([^/]+)\/index\.ts$/); + if (match) { + const appName = match[1]; + if (!appName) continue; + const Component = module[appName]; + if (Component) { + apps[appName] = Component; + } else { + console.warn( + `[mount] App "${appName}" not found in ${path}. ` + + `Make sure to export it as: export { ${appName} } from "./${appName}.js"` + ); + } + } +} + +function mount(): void { + const container = document.getElementById("root"); + if (!container) { + console.error("[mount] No #root element found"); + return; + } + + const componentName = container.dataset.component; + if (!componentName) { + console.error("[mount] No data-component attribute found on #root"); + return; + } + + const Component = apps[componentName]; + if (!Component) { + console.error(`[mount] Unknown app: ${componentName}`); + console.error(`[mount] Available apps: ${Object.keys(apps).join(", ")}`); + return; + } + + const root = createRoot(container); + root.render( + + + + ); +} + +mount(); diff --git a/packages/apps/src/build/template.html b/packages/apps/src/build/template.html new file mode 100644 index 000000000..9a9d6ec06 --- /dev/null +++ b/packages/apps/src/build/template.html @@ -0,0 +1,12 @@ + + + + + + {{TITLE}} + + +
+ + + diff --git a/packages/apps/src/components/elements.tsx b/packages/apps/src/components/elements.tsx new file mode 100644 index 000000000..a33fbda1e --- /dev/null +++ b/packages/apps/src/components/elements.tsx @@ -0,0 +1,190 @@ +import React, { Children, JSX } from "react"; + +export const AppShell = ({ className, ...props }: React.HTMLAttributes) => ( +
+); + +export const Heading = ({ className, ...props }: React.HTMLAttributes) => ( +

+); + +export const Label = ({ className, ...props }: React.LabelHTMLAttributes) => ( +