@memberjunction/server
The MemberJunction server package provides the complete API server infrastructure for MemberJunction applications. It delivers both GraphQL and REST APIs with a unified authentication layer, per-request transaction isolation, pluggable authentication providers, scope-based API key authorization, scheduled job orchestration, SQL logging, and built-in AI operation endpoints. This package is the primary integration point between client applications and the MemberJunction data layer.
Installation
Section titled “Installation”npm install @memberjunction/serverOverview
Section titled “Overview”MJServer acts as the central API gateway for MemberJunction, sitting between client applications (MJExplorer, external integrations, AI agents) and the SQL Server database via the @memberjunction/sqlserver-dataprovider. It initializes database connections, loads entity metadata, builds a GraphQL schema from dynamically discovered resolver modules, optionally exposes a REST API, and manages the full request lifecycle including authentication, per-request provider isolation, and transaction management.
flowchart TD
subgraph Clients["Client Applications"]
EX[MJ Explorer]
EXT[External Apps]
AI[AI Agents / MCP]
end
subgraph MJServer["@memberjunction/server"]
direction TB
AUTH[Authentication Layer]
GQL[GraphQL API - Apollo Server]
REST[REST API - Express Routes]
CTX[Request Context & Provider Factory]
RES[Resolver Layer]
SCHED[Scheduled Jobs Service]
SQLLOG[SQL Logging Manager]
end
subgraph Data["Data Layer"]
SQLP[SQL Server Data Provider]
DB[(SQL Server Database)]
end
Clients --> AUTH
AUTH --> GQL
AUTH --> REST
GQL --> CTX
REST --> CTX
CTX --> RES
RES --> SQLP
SQLP --> DB
SCHED --> SQLP
style Clients fill:#2d6a9f,stroke:#1a4971,color:#fff
style MJServer fill:#7c5295,stroke:#563a6b,color:#fff
style Data fill:#2d8659,stroke:#1a5c3a,color:#fff
Key Features
Section titled “Key Features”- Dual API Support: GraphQL (Apollo Server) and REST APIs with consistent authentication and authorization
- Pluggable Authentication: Support for Azure AD/Entra ID (MSAL), Auth0, Okta, AWS Cognito, and Google via the
IAuthProviderinterface - API Key Authorization: User-level (
X-API-Key) and system-level (x-mj-api-key) API keys with scope-based access control - Per-Request Provider Isolation: Each GraphQL request receives its own
SQLServerDataProviderinstance for transaction safety - Multi-Database Support: Separate read-write and read-only database connection pools
- Transaction Management: Automatic transaction wrapping for GraphQL mutations with savepoint support
- Scheduled Jobs: Built-in job scheduler with configurable polling, concurrency limits, and lock management
- SQL Logging: Real-time SQL statement capture with session management, user filtering, and migration-format output
- AI Integration: Resolvers for AI prompt execution, agent orchestration, text embeddings, and Skip AI
- Real-time Support: WebSocket subscriptions via
graphql-ws - Response Compression: Built-in gzip compression with configurable thresholds
- Encryption Handling: Transparent field-level encryption/decryption with configurable API exposure policies
- CloudEvents: Optional CloudEvent emission for entity lifecycle events
- Telemetry: Configurable server-side telemetry with multiple verbosity levels
- Extensible Architecture: Custom resolvers, entity subclasses, and new user handling via
@RegisterClass - Server Extensions: Plugin architecture for auto-discovering and loading extension modules (webhooks, messaging adapters, custom integrations) via
@RegisterClass+mj.config.cjs
Configuration
Section titled “Configuration”MJServer uses a layered configuration system with the following priority (highest to lowest):
- Environment variables
mj.config.cjsfile (discovered via cosmiconfig)DEFAULT_SERVER_CONFIGhardcoded defaults
Environment Variables
Section titled “Environment Variables”| Variable | Description | Default |
|---|---|---|
DB_HOST | Database server hostname | localhost |
DB_PORT | Database server port | 1433 |
DB_DATABASE | Database name | (required) |
DB_USERNAME | Database username | (required) |
DB_PASSWORD | Database password | (required) |
DB_READ_ONLY_USERNAME | Read-only connection username | (optional) |
DB_READ_ONLY_PASSWORD | Read-only connection password | (optional) |
DB_TRUST_SERVER_CERTIFICATE | Trust self-signed certs | false |
DB_INSTANCE_NAME | Named SQL Server instance | (optional) |
MJ_CORE_SCHEMA | MJ metadata schema name | __mj |
GRAPHQL_PORT | Server listen port | 4000 |
GRAPHQL_ROOT_PATH | GraphQL endpoint path | / |
GRAPHQL_BASE_URL | Server base URL | http://localhost |
MJAPI_PUBLIC_URL | Public callback URL (e.g. ngrok) | (optional) |
ENABLE_INTROSPECTION | Allow GraphQL introspection | false |
MJ_API_KEY | System-level API key | (optional) |
TENANT_ID | Azure AD tenant ID | (optional) |
WEB_CLIENT_ID | Azure AD client ID | (optional) |
AUTH0_DOMAIN | Auth0 domain | (optional) |
AUTH0_CLIENT_ID | Auth0 client ID | (optional) |
AUTH0_CLIENT_SECRET | Auth0 client secret | (optional) |
WEBSITE_RUN_FROM_PACKAGE | Azure read-only filesystem flag | (optional) |
MJ_REST_API_ENABLED | Enable/disable REST API | (from config) |
MJ_REST_API_INCLUDE_ENTITIES | Comma-separated entity include list | (optional) |
MJ_REST_API_EXCLUDE_ENTITIES | Comma-separated entity exclude list | (optional) |
MJ_TELEMETRY_ENABLED | Enable server telemetry | true |
METADATA_CACHE_REFRESH_INTERVAL | Metadata refresh interval (ms) | 180000 |
MJ_LOG_GRAPHQL_VARIABLES | Enable redacted verbose echo of GraphQL variables to stdout — see Debugging GraphQL requests | false |
Configuration File (mj.config.cjs)
Section titled “Configuration File (mj.config.cjs)”module.exports = { dbHost: 'myserver.database.windows.net', dbDatabase: 'MemberJunction', dbUsername: 'mj_user', dbPassword: 'secret', mjCoreSchema: '__mj', graphqlPort: 4000,
userHandling: { autoCreateNewUsers: true, newUserLimitedToAuthorizedDomains: false, newUserRoles: ['UI', 'Developer'], contextUserForNewUserCreation: 'admin@example.com', CreateUserApplicationRecords: true, },
databaseSettings: { connectionTimeout: 45000, requestTimeout: 30000, metadataCacheRefreshInterval: 180000, connectionPool: { max: 50, min: 5, idleTimeoutMillis: 30000, acquireTimeoutMillis: 30000, }, },
// Engine pre-warm during startup: 'full' (all @RegisterForStartup engines run at // boot — MJAPI's default) or 'task' (skip pre-warm; engines lazy-load on first // touch — the CLI/mj-sync/CodeGen default). MJ_STARTUP_MODE overrides per // invocation. See the root CLAUDE.md "Startup Mode" section for the full // precedence chain and trade-offs. startup: { mode: 'full', },
restApiOptions: { enabled: true, includeEntities: ['User*', 'Entity*'], excludeEntities: ['Password', 'APIKey*'], includeSchemas: ['public'], excludeSchemas: ['internal'], },
scheduledJobs: { enabled: true, systemUserEmail: 'system@example.com', maxConcurrentJobs: 5, },
sqlLogging: { enabled: true, allowedLogDirectory: './logs/sql', maxActiveSessions: 5, sessionTimeout: 3600000, },
telemetry: { enabled: true, level: 'standard', // 'minimal' | 'standard' | 'verbose' | 'debug' },
// Debugging — see "Debugging GraphQL requests" below loggingSettings: { graphql: { logVariables: false, // env override: MJ_LOG_GRAPHQL_VARIABLES }, },};Debugging GraphQL requests
Section titled “Debugging GraphQL requests”By default, MJServer logs only the GraphQL operationName for each incoming request. The variables payload is never logged in the default configuration — this protects credentials and other sensitive material from leaking into container logs, cloud log aggregators, or tail -f sessions.
For local debugging, you can enable a redacted verbose echo of variables via the loggingSettings.graphql.logVariables flag (or the MJ_LOG_GRAPHQL_VARIABLES=true environment variable). When enabled:
- A second log line is emitted per root resolver call:
{ operation: '<name>', args: { ... } } - Fields on
Create<X>Input/Update<X>Inputthat map to an entity column withEntityFieldInfo.Encrypt=trueare redacted automatically (metadata-driven). - Fields or parameters explicitly marked with the
@NoLogdecorator are redacted manually (for custom resolvers and non-metadata-bound args). - A boot-time audit warns about every custom-resolver
@Argthat is neither metadata-bound nor@NoLog-marked, so authors know which arguments will appear in plaintext while the flag is active. - The first call into any such resolver also emits a one-time runtime warning naming the un-decorated args.
Security notes:
- This flag is opt-in and defaults to
falsein every environment regardless ofNODE_ENV. Never set it totruein production. - It does NOT re-enable the historical variables leak. The default-config log line emits operation name only; the verbose echo is a separate, redaction-aware path.
- Custom resolvers that accept sensitive arguments (e.g. credential-test mutations) should mark those parameters with
@NoLogfrom@memberjunction/serverso they remain redacted even when verbose logging is on.
Enabling for a single session:
MJ_LOG_GRAPHQL_VARIABLES=true npm run startEnabling persistently via mj.config.cjs:
loggingSettings: { graphql: { logVariables: true, },}Applying @NoLog to a custom resolver:
import { NoLog } from '@memberjunction/server';
@Mutation(() => Boolean)async TestCredential( @Arg('accessToken') @NoLog accessToken: string, @Ctx() ctx: AppContext,): Promise<boolean> { // ...}import { InputType, Field } from 'type-graphql';import { NoLog } from '@memberjunction/server';
@InputType()export class MyInput { @Field(() => String) @NoLog Token: string; @Field(() => String) Description: string;}Basic Server Setup
Section titled “Basic Server Setup”Import the serve function and provide paths to your custom resolver modules. The server automatically includes its own built-in resolvers.
import { serve } from '@memberjunction/server';import { resolve } from 'node:path';
const localPath = (p: string) => resolve(__dirname, p);
const resolverPaths = [ 'resolvers/**/*Resolver.{js,ts}', 'generic/*Resolver.{js,ts}', 'generated/generated.ts',];
serve(resolverPaths.map(localPath));Advanced Server Options
Section titled “Advanced Server Options”The serve function accepts an optional MJServerOptions object for lifecycle hooks and REST API overrides.
import { serve, createApp, MJServerOptions } from '@memberjunction/server';
const options: MJServerOptions = { onBeforeServe: async () => { // Custom initialization after schema is built, before HTTP listen console.log('Server is about to start...'); }, restApiOptions: { enabled: true, includeEntities: ['User*', 'Entity*'], excludeEntities: ['Password', 'APIKey*'], },};
serve(resolverPaths.map(localPath), createApp(), options);Custom New User Handling
Section titled “Custom New User Handling”Override the default new user creation behavior by subclassing NewUserBase and registering it with a higher priority.
import { RegisterClass } from '@memberjunction/global';import { NewUserBase } from '@memberjunction/server';
@RegisterClass(NewUserBase, undefined, 1)export class CustomNewUserHandler extends NewUserBase { public override async createNewUser( firstName: string, lastName: string, email: string ) { // Custom logic: create linked records, assign roles, etc. return super.createNewUser(firstName, lastName, email, 'Other'); }}Import the file before calling serve to ensure registration:
import './auth/customNewUserHandler';import { serve } from '@memberjunction/server';// ...serve(resolverPaths);Architecture
Section titled “Architecture”Request Lifecycle
Section titled “Request Lifecycle”sequenceDiagram
participant C as Client
participant A as Auth Layer
participant CF as Context Factory
participant R as Resolver
participant P as SQLServerDataProvider
participant DB as SQL Server
C->>A: Request (Bearer token / API key)
A->>A: Validate JWT or API key
A->>A: Resolve UserInfo from UserCache
A->>CF: Create request context
CF->>P: New per-request provider instance
P->>P: Reuse cached metadata
CF->>R: Pass AppContext with providers
R->>P: Execute operations
P->>DB: SQL queries within transaction
DB-->>P: Results
P-->>R: Entity objects / data
R-->>C: GraphQL / REST response
Note over P,DB: Transaction auto-committed on success, rolled back on error
Per-Request Provider Isolation
Section titled “Per-Request Provider Isolation”Each GraphQL request receives its own SQLServerDataProvider instance, ensuring complete transaction isolation between concurrent requests. Metadata is cached and reused across providers for efficiency.
// Automatically created per request in context.ts:const provider = new SQLServerDataProvider();await provider.Config({ connectionPool: pool, MJCoreSchemaName: '__mj', ignoreExistingMetadata: false, // Reuse cached metadata});
// Included in AppContext for resolvers:context.providers = [ { provider: readWriteProvider, type: 'Read-Write' }, { provider: readOnlyProvider, type: 'Read-Only' }, // if configured];Authentication Architecture
Section titled “Authentication Architecture”MJServer uses a pluggable authentication provider system built on the IAuthProvider interface and AuthProviderFactory.
flowchart LR
subgraph Providers["Auth Providers"]
MSAL[MSAL / Azure AD]
A0[Auth0]
OK[Okta]
COG[AWS Cognito]
GOOG[Google]
end
subgraph Factory["AuthProviderFactory"]
REG[Provider Registry]
CACHE[Issuer Cache]
end
subgraph Auth["Authentication Flow"]
JWT[JWT Token]
APIKEY[API Key]
SYS[System API Key]
end
JWT --> REG
REG --> Providers
APIKEY --> VAL[API Key Engine]
SYS --> SYSUSER[System User Lookup]
style Providers fill:#2d6a9f,stroke:#1a4971,color:#fff
style Factory fill:#7c5295,stroke:#563a6b,color:#fff
style Auth fill:#b8762f,stroke:#8a5722,color:#fff
Providers are registered via @RegisterClass(BaseAuthProvider, 'type-key') and automatically discovered at startup. Each provider implements:
validateConfig()— Validates required configurationgetSigningKey()— Retrieves JWKS signing keys with retry logicextractUserInfo()— Extracts email, name from provider-specific JWT claimsmatchesIssuer()— Matches JWTissclaim to the provider
Configure providers in mj.config.cjs under authProviders or via environment variables (see Configuration section).
API Key Authentication
Section titled “API Key Authentication”The server supports two types of API key authentication:
User API Keys (X-API-Key header, mj_sk_* format):
- Authenticate as a specific user
- Support expiration dates and individual revocation
- Subject to scope-based authorization
- Usage is logged for audit
System API Key (x-mj-api-key header):
- Single shared key set via
MJ_API_KEYenvironment variable - Authenticates as the system user with elevated privileges
- Used for server-to-server communication
Scope-Based Authorization
Section titled “Scope-Based Authorization”API keys are subject to a two-level scope evaluation: the application ceiling (maximum allowed scopes for MJAPI, MCP Server, etc.) and the individual key’s assigned scopes.
| Scope | Description |
|---|---|
full_access | Bypass all scope checks |
entity:read | Read entity records |
entity:create | Create records |
entity:update | Update records |
entity:delete | Delete records |
view:run | Execute RunView queries |
agent:execute | Execute AI agents |
agent:monitor | Check agent run status |
action:execute | Execute MJ Actions |
prompt:execute | Execute AI prompts |
query:run | Execute queries |
metadata:entities:read | Read entity metadata |
metadata:agents:read | Read agent metadata |
communication:send | Send emails/messages |
Add scope checks to custom resolvers:
import { ResolverBase } from '@memberjunction/server';
@Resolver()export class MyResolver extends ResolverBase { @Mutation(() => MyResult) async myOperation(@Ctx() ctx: AppContext): Promise<MyResult> { // Checks scope for API key auth; no-op for JWT auth await this.CheckAPIKeyScopeAuthorization('my:scope', 'resource-name', ctx.userPayload); // Proceed with operation... }}Alternatively, use the standalone utility functions:
import { CheckAPIKeyScope, RequireScope } from '@memberjunction/server';
// Standalone functionawait CheckAPIKeyScope(ctx.userPayload.apiKeyId, 'view:run', ctx.userPayload.userRecord, { resource: 'Users',});
// Pre-built scope checkerconst requireViewRun = RequireScope('view:run');await requireViewRun(ctx);GraphQL API
Section titled “GraphQL API”Custom Directives
Section titled “Custom Directives”@RequireSystemUser: Restricts a field or mutation to system-user-only access. Applied at the schema level; non-system users receive anAuthorizationError.@Public: Marks a field as publicly accessible without authentication. All other fields require an active, authenticated user by default.
Built-in Resolvers
Section titled “Built-in Resolvers”The server includes resolvers for the following domains:
| Resolver | Operations |
|---|---|
EntityResolver | CRUD operations for all entities |
RunViewResolver | RunView by ID, name, or dynamic entity. All four input types (RunViewByIDInput, RunViewByNameInput, RunDynamicViewInput, RunViewGenericInput) expose an optional AfterKey: CompositeKeyInputType for keyset (seek) pagination — see KEYSET_PAGINATION_GUIDE.md. |
RunAIPromptResolver | Execute AI prompts, simple prompts, text embeddings |
RunAIAgentResolver | Execute AI agents with session and streaming support |
ActionResolver | Execute MJ Actions |
QueryResolver | Execute and create saved queries |
AdhocQueryResolver | Execute ad-hoc SQL queries (SELECT/WITH only, read-only connection) |
ReportResolver | Run and manage reports |
DatasetResolver | Dataset operations |
UserViewResolver | User view management |
UserResolver | User profile operations |
UserFavoriteResolver | Favorite record management |
MergeRecordsResolver | Record merge operations |
SyncDataResolver / SyncRolesUsersResolver | Data synchronization |
FileResolver / FileCategoryResolver | File and category management |
EntityCommunicationsResolver | Entity-level communications |
EntityRecordNameResolver | Record name resolution |
TransactionGroupResolver | Transaction group management |
ComponentRegistryResolver | Component registry queries |
MCPResolver | MCP server operations |
APIKeyResolver | API key management |
SqlLoggingConfigResolver | SQL logging session management |
TelemetryResolver | Server telemetry queries |
ColorResolver | Color palette operations |
InfoResolver | Server info queries |
PotentialDuplicateRecordResolver | Duplicate detection |
RunTestResolver | Test execution |
RunTemplateResolver | Template execution |
TaskResolver | Task orchestration |
Transaction Management
Section titled “Transaction Management”GraphQL mutations are automatically wrapped in transactions through the per-request provider:
mutation { CreateUser(input: { FirstName: "John", LastName: "Doe" }) { ID } CreateUserRole(input: { UserID: "...", RoleID: "..." }) { ID }}# Both operations execute within the same provider's transaction scope.# Success: both committed together. Error: both rolled back.WebSocket Subscriptions
Section titled “WebSocket Subscriptions”Real-time updates are supported via WebSocket at the same path as the GraphQL endpoint:
// Server-side: already configuredconst webSocketServer = new WebSocketServer({ server: httpServer, path: graphqlRootPath,});ResolverBase
Section titled “ResolverBase”All built-in resolvers extend ResolverBase, which provides:
| Method | Description |
|---|---|
CreateRecord() | Create entity with before/after hooks |
UpdateRecord() | Update with optimistic concurrency detection |
DeleteRecord() | Delete with before/after hooks |
RunViewByIDGeneric() | Execute a saved view by ID |
RunViewByNameGeneric() | Execute a saved view by name |
RunDynamicViewGeneric() | Execute an ad-hoc view on an entity |
RunViewsGeneric() | Batch-execute multiple views |
RunViewGenericInternal() | Shared internal: extracts and forwards all RunView params including AfterKey (keyset pagination — see KEYSET_PAGINATION_GUIDE.md) |
CheckUserReadPermissions() | Validate entity-level read access |
CheckAPIKeyScopeAuthorization() | Validate API key scope |
MapFieldNamesToCodeNames() | Map field names for GraphQL transport |
FilterEncryptedFieldsForAPI() | Handle encryption policy for API responses |
EmitCloudEvent() | Emit CloudEvents for entity lifecycle |
ListenForEntityMessages() | Subscribe to entity event messages |
BeforeCreate() / AfterCreate() | Lifecycle hooks for create |
BeforeUpdate() / AfterUpdate() | Lifecycle hooks for update |
BeforeDelete() / AfterDelete() | Lifecycle hooks for delete |
REST API
Section titled “REST API”In addition to GraphQL, MJServer provides a REST API at /api/v1/. By default it is disabled and can be enabled via configuration.
For comprehensive REST API documentation including endpoints, security configuration, wildcard filtering, and examples, see REST_API.md.
Key endpoints:
| Endpoint | Method | Description |
|---|---|---|
/api/v1/entities/:entityName | GET | List entity records |
/api/v1/entities/:entityName | POST | Create a record |
/api/v1/entities/:entityName/:id | GET | Get a record by ID |
/api/v1/entities/:entityName/:id | PUT | Update a record |
/api/v1/entities/:entityName/:id | DELETE | Delete a record |
/api/v1/views/:entityName | POST | Run a view |
/api/v1/views/batch | POST | Batch view execution |
/api/v1/metadata/entities | GET | List entity metadata |
/api/v1/users/current | GET | Get current user |
SQL Logging
Section titled “SQL Logging”The server includes a runtime SQL logging system for debugging and migration generation. Requires Owner-level user privileges.
# Start a logging sessionmutation { startSqlLogging(input: { fileName: "debug-session.sql" filterToCurrentUser: true options: { sessionName: "Debug Session" prettyPrint: true statementTypes: "both" formatAsMigration: false } }) { id filePath sessionName }}
# Query active sessionsquery { activeSqlLoggingSessions { id sessionName statementCount }}
# Stop a sessionmutation { stopSqlLogging(sessionId: "session-id")}Configuration in mj.config.cjs:
sqlLogging: { enabled: true, allowedLogDirectory: './logs/sql', maxActiveSessions: 5, sessionTimeout: 3600000, autoCleanupEmptyFiles: true, defaultOptions: { formatAsMigration: false, statementTypes: 'both', // 'queries' | 'mutations' | 'both' prettyPrint: true, logRecordChangeMetadata: false, },}Scheduled Jobs
Section titled “Scheduled Jobs”MJServer integrates with the @memberjunction/scheduling-engine to execute scheduled jobs defined in MemberJunction metadata.
flowchart LR
subgraph Config["Configuration"]
CFG[mj.config.cjs]
end
subgraph Service["ScheduledJobsService"]
INIT[Initialize]
POLL[Start Polling]
STOP[Stop Polling]
end
subgraph Engine["SchedulingEngine"]
JOBS[Active Jobs]
EXEC[Job Execution]
LOCK[Lock Management]
end
CFG --> INIT
INIT --> POLL
POLL --> JOBS
JOBS --> EXEC
EXEC --> LOCK
style Config fill:#b8762f,stroke:#8a5722,color:#fff
style Service fill:#2d6a9f,stroke:#1a4971,color:#fff
style Engine fill:#2d8659,stroke:#1a5c3a,color:#fff
Configuration:
scheduledJobs: { enabled: true, systemUserEmail: 'system@example.com', maxConcurrentJobs: 5, defaultLockTimeout: 600000, // 10 minutes staleLockCleanupInterval: 300000, // 5 minutes}The service starts automatically during server initialization and shuts down gracefully on SIGTERM/SIGINT.
AI Integration
Section titled “AI Integration”AI Prompt Execution
Section titled “AI Prompt Execution”mutation { RunAIPrompt(input: { PromptName: "Summarize Content" ModelID: "model-guid" Temperature: 0.7 Messages: [{ role: "user", content: "Summarize this article..." }] }) { Success Result TokenUsage { InputTokens OutputTokens } }}Simple Prompt Execution
Section titled “Simple Prompt Execution”mutation { ExecuteSimplePrompt(input: { SystemPrompt: "You are a helpful assistant." UserMessage: "What is MemberJunction?" ModelPowerLevel: "Standard" }) { Success Result }}Text Embeddings
Section titled “Text Embeddings”mutation { EmbedText(input: { Texts: ["Hello world", "MemberJunction framework"] ModelSize: "small" }) { Success Embeddings Dimensions Model }}AI Agent Execution
Section titled “AI Agent Execution”mutation { RunAIAgent(input: { AgentID: "agent-guid" SessionID: "session-guid" UserMessage: "Find all active users" }) { Success Result SessionID }}All AI operations have system-user variants (e.g., RunAIPromptSystemUser) that use the @RequireSystemUser directive for server-to-server operations.
API Reference
Section titled “API Reference”Core Exports
Section titled “Core Exports”Server Functions
Section titled “Server Functions”| Export | Description |
|---|---|
serve(resolverPaths, app?, options?) | Main server initialization function |
createApp() | Creates a new Express application instance |
| Export | Description |
|---|---|
AppContext | GraphQL resolver context type |
UserPayload | Authenticated user payload |
DataSourceInfo | Database connection descriptor |
ProviderInfo | Per-request provider descriptor |
MJServerOptions | Options for serve() |
ConfigInfo | Full server configuration type |
MJServerEvent | Server lifecycle event type |
Authentication
Section titled “Authentication”| Export | Description |
|---|---|
IAuthProvider | Authentication provider interface |
AuthProviderFactory | Provider registry and factory |
NewUserBase | Base class for custom new user handling |
TokenExpiredError | Token expiration error class |
getSystemUser(dataSource?) | Retrieve the system user |
getSigningKeys(issuer) | Get JWT signing keys for an issuer |
extractUserInfoFromPayload(payload) | Extract user info from JWT claims |
verifyUserRecord(email, ...) | Verify and optionally create a user record |
Scope Authorization
Section titled “Scope Authorization”| Export | Description |
|---|---|
CheckAPIKeyScope(apiKeyId, scopePath, contextUser, options?) | Check API key scope |
CheckAPIKeyScopeAndLog(apiKeyId, scopePath, contextUser, usageDetails, options?) | Check scope with usage logging |
RequireScope(scopePath, options?) | Create a reusable scope checker |
RequireViewRun | Pre-built scope checker for view:run |
RequireQueryRun | Pre-built scope checker for query:run |
RequireAgentExecute | Pre-built scope checker for agent:execute |
Resolver Base Classes
Section titled “Resolver Base Classes”| Export | Description |
|---|---|
ResolverBase | Base class for all resolvers |
RunViewResolver | Base resolver for view operations |
PushStatusResolver | Status update resolver with pub/sub |
Utility Functions
Section titled “Utility Functions”| Export | Description |
|---|---|
GetReadOnlyDataSource(dataSources, options?) | Get read-only connection pool |
GetReadWriteDataSource(dataSources) | Get read-write connection pool |
GetReadOnlyProvider(providers, options?) | Get read-only provider instance |
GetReadWriteProvider(providers, options?) | Get read-write provider instance |
GraphQL Inputs
Section titled “GraphQL Inputs”| Export | Description |
|---|---|
KeyValuePairInput | Generic key-value input type |
DeleteOptionsInput | Delete operation options |
Directives
Section titled “Directives”| Export | Description |
|---|---|
RequireSystemUser | Decorator restricting access to system users |
Public | Decorator marking endpoints as publicly accessible |
configInfo | Parsed server configuration singleton |
DEFAULT_SERVER_CONFIG | Default configuration values |
Performance
Section titled “Performance”Response Compression
Section titled “Response Compression”Responses larger than 1KB are automatically compressed using gzip at compression level 6. Binary content types (images, video, audio) are excluded.
Connection Pooling
Section titled “Connection Pooling”Database connections are managed via mssql connection pools. Configure pool size in databaseSettings.connectionPool:
connectionPool: { max: 50, // Maximum connections min: 5, // Minimum connections idleTimeoutMillis: 30000, acquireTimeoutMillis: 30000,}Recommended settings:
- Development:
max: 10, min: 2 - Production:
max: 50, min: 5 - High load:
max: 100, min: 10
Metadata Caching
Section titled “Metadata Caching”Entity metadata is loaded once at startup and shared across all per-request provider instances. The cache refresh interval is configurable:
databaseSettings: { metadataCacheRefreshInterval: 180000, // 3 minutes}Authentication Token Caching
Section titled “Authentication Token Caching”Validated JWT tokens are cached using an LRU cache, avoiding repeated cryptographic verification for the same token within its lifetime.
Server Extensions
Section titled “Server Extensions”MJServer supports a plugin architecture that enables auto-discovery and lifecycle management of extension modules. Extensions register Express routes, handle their own authentication, and participate in health checks and graceful shutdown — all without modifying MJServer source code.
How It Works
Section titled “How It Works”- Extensions implement
BaseServerExtensionfrom@memberjunction/server-extensions-core - Extensions register via
@RegisterClass(BaseServerExtension, 'DriverClassName') - Configuration in
mj.config.cjsdefines which extensions to load - MJServer’s
ServerExtensionLoaderdiscovers and initializes all enabled extensions at startup
Configuration
Section titled “Configuration”module.exports = { serverExtensions: [ { Enabled: true, DriverClass: 'SlackMessagingExtension', RootPath: '/webhook/slack', Settings: { AgentID: 'your-agent-guid', BotToken: process.env.SLACK_BOT_TOKEN, SigningSecret: process.env.SLACK_SIGNING_SECRET, } }, { Enabled: true, DriverClass: 'TeamsMessagingExtension', RootPath: '/webhook/teams', Settings: { AgentID: 'your-agent-guid', MicrosoftAppId: process.env.MICROSOFT_APP_ID, MicrosoftAppPassword: process.env.MICROSOFT_APP_PASSWORD, } } ]};Health Monitoring
Section titled “Health Monitoring”MJServer exposes an aggregate health check endpoint for all loaded extensions:
GET /health/extensionsReturns 200 when all extensions are healthy, 503 when any extension reports unhealthy.
Available Extensions
Section titled “Available Extensions”| Package | Extensions | Description |
|---|---|---|
@memberjunction/messaging-adapters | SlackMessagingExtension, TeamsMessagingExtension | Slack & Teams integration for MJ AI agents |
Creating Custom Extensions
Section titled “Creating Custom Extensions”See @memberjunction/server-extensions-core for documentation on building custom extensions.
Graceful Shutdown
Section titled “Graceful Shutdown”The server registers handlers for SIGTERM and SIGINT to:
- Shut down all server extensions (in reverse order of loading)
- Stop the scheduled jobs service
- Close the HTTP server
- Force-exit after a 10-second timeout if graceful shutdown stalls
Unhandled promise rejections are caught and logged without crashing the server.
Dependencies
Section titled “Dependencies”Core MemberJunction Packages
Section titled “Core MemberJunction Packages”| Package | Purpose |
|---|---|
| @memberjunction/core | Core metadata, entities, RunView |
| @memberjunction/core-entities | Generated entity classes |
| @memberjunction/global | ClassFactory, event system |
| @memberjunction/sqlserver-dataprovider | SQL Server data provider |
| @memberjunction/graphql-dataprovider | GraphQL field mapping |
| @memberjunction/config | Configuration utilities |
| @memberjunction/api-keys | API key engine and scope evaluation |
| @memberjunction/encryption | Field-level encryption engine |
AI Packages
Section titled “AI Packages”| Package | Purpose |
|---|---|
| @memberjunction/ai | AI engine abstraction |
| @memberjunction/ai-prompts | AI prompt execution |
| @memberjunction/ai-agents | AI agent framework |
| @memberjunction/ai-core-plus | AI prompt parameters |
| @memberjunction/aiengine | AI engine orchestration |
| @memberjunction/ai-provider-bundle | Bundled AI providers |
Infrastructure Packages
Section titled “Infrastructure Packages”| Package | Purpose |
|---|---|
| @memberjunction/scheduling-engine | Scheduled job execution |
| @memberjunction/actions | Action framework |
| @memberjunction/templates | Template engine |
| @memberjunction/notifications | Notification system |
| @memberjunction/storage | File storage |
| @memberjunction/communication-ms-graph | MS Graph communications |
| @memberjunction/communication-sendgrid | SendGrid communications |
Third-Party Dependencies
Section titled “Third-Party Dependencies”| Package | Purpose |
|---|---|
@apollo/server | GraphQL server |
express | HTTP framework |
type-graphql | TypeScript GraphQL decorators |
mssql | SQL Server client |
jsonwebtoken / jwks-rsa | JWT verification |
graphql-ws / ws | WebSocket subscriptions |
compression | Response compression |
cosmiconfig | Configuration file discovery |
cloudevents | CloudEvent emission |
zod | Configuration schema validation |
Related Packages
Section titled “Related Packages”- @memberjunction/server-bootstrap — Pre-built class registration manifest for tree-shaking prevention
- MJAPI — Reference server application that consumes this package
- @memberjunction/core — Core framework that MJServer exposes via API
- Caching & Pub/Sub Guide — Comprehensive guide to caching architecture, Redis cross-server sync, and GraphQL cache invalidation subscriptions
Troubleshooting
Section titled “Troubleshooting”| Issue | Solution |
|---|---|
| Authentication errors | Verify environment variables for your auth provider are set |
| Database connection failures | Check DB_HOST, DB_PORT, DB_USERNAME, DB_PASSWORD |
| No resolvers found | Verify resolver paths passed to serve() are absolute and use correct glob patterns |
| Transaction errors | Review mutation logic; check that entity operations are within the per-request provider |
| SQL logging access denied | Ensure user has Type = 'Owner' in the Users table |
| Metadata not loading | Check MJ_CORE_SCHEMA matches your database schema name |
| Token expired errors | Expected behavior for long-lived sessions; client should refresh tokens |
Enable verbose logging:
DEBUG=mj:*NODE_ENV=developmentKnowledge Hub Resolvers
Section titled “Knowledge Hub Resolvers”This package includes GraphQL resolvers for the Knowledge Hub:
SearchKnowledgeResolver— Unified search combining vector similarity (Pinecone) with full-text search viaMetadata.FullTextSearch()and RRF fusionVectorizeEntityResolver— Triggers entity vectorization viaEntityVectorSyncerPipelineProgressResolver— GraphQL subscription for real-time pipeline progressFetchEntityVectorsResolver— Retrieves vectors and metadata from the vector database for a given entity document
See the Full-Text Search Guide for the complete FTS architecture.
FetchEntityVectorsResolver
Section titled “FetchEntityVectorsResolver”Fetches vectors and their associated metadata from a vector database (e.g., Pinecone) for a specific entity document. Used by the clustering dashboard to obtain raw vectors for visualization.
How it works: The resolver performs a zero-vector query against the vector index with an entity metadata filter (Entity: { $eq: entityName }). Since the query vector is all zeros, similarity scores are meaningless — the purpose is purely to retrieve vectors matching the entity filter. This approach is used because Pinecone’s list API does not support metadata filtering, but the query API does.
query { FetchEntityVectors( entityDocumentID: "doc-uuid" maxRecords: 500 filter: "" ) { Success Results { ID Values Metadata } TotalCount ElapsedMs ErrorMessage }}| Argument | Type | Default | Description |
|---|---|---|---|
entityDocumentID | String! | — | The entity document whose vector index to query |
maxRecords | Int | 1000 | Maximum number of vectors to return |
filter | String | — | Reserved for future metadata filter extensions |
The resolver resolves the vector index for the entity document using a fallback chain: explicit VectorIndexID on the document, then matching by VectorDatabaseID + EmbeddingModelID.
Contributing
Section titled “Contributing”See the MemberJunction Contributing Guide for development setup and guidelines.