Skip to content

@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.

Terminal window
npm install @memberjunction/server

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
  • 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 IAuthProvider interface
  • 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 SQLServerDataProvider instance 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

MJServer uses a layered configuration system with the following priority (highest to lowest):

  1. Environment variables
  2. mj.config.cjs file (discovered via cosmiconfig)
  3. DEFAULT_SERVER_CONFIG hardcoded defaults
VariableDescriptionDefault
DB_HOSTDatabase server hostnamelocalhost
DB_PORTDatabase server port1433
DB_DATABASEDatabase name(required)
DB_USERNAMEDatabase username(required)
DB_PASSWORDDatabase password(required)
DB_READ_ONLY_USERNAMERead-only connection username(optional)
DB_READ_ONLY_PASSWORDRead-only connection password(optional)
DB_TRUST_SERVER_CERTIFICATETrust self-signed certsfalse
DB_INSTANCE_NAMENamed SQL Server instance(optional)
MJ_CORE_SCHEMAMJ metadata schema name__mj
GRAPHQL_PORTServer listen port4000
GRAPHQL_ROOT_PATHGraphQL endpoint path/
GRAPHQL_BASE_URLServer base URLhttp://localhost
MJAPI_PUBLIC_URLPublic callback URL (e.g. ngrok)(optional)
ENABLE_INTROSPECTIONAllow GraphQL introspectionfalse
MJ_API_KEYSystem-level API key(optional)
TENANT_IDAzure AD tenant ID(optional)
WEB_CLIENT_IDAzure AD client ID(optional)
AUTH0_DOMAINAuth0 domain(optional)
AUTH0_CLIENT_IDAuth0 client ID(optional)
AUTH0_CLIENT_SECRETAuth0 client secret(optional)
WEBSITE_RUN_FROM_PACKAGEAzure read-only filesystem flag(optional)
MJ_REST_API_ENABLEDEnable/disable REST API(from config)
MJ_REST_API_INCLUDE_ENTITIESComma-separated entity include list(optional)
MJ_REST_API_EXCLUDE_ENTITIESComma-separated entity exclude list(optional)
MJ_TELEMETRY_ENABLEDEnable server telemetrytrue
METADATA_CACHE_REFRESH_INTERVALMetadata refresh interval (ms)180000
MJ_LOG_GRAPHQL_VARIABLESEnable redacted verbose echo of GraphQL variables to stdout — see Debugging GraphQL requestsfalse
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
},
},
};

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>Input that map to an entity column with EntityFieldInfo.Encrypt=true are redacted automatically (metadata-driven).
  • Fields or parameters explicitly marked with the @NoLog decorator are redacted manually (for custom resolvers and non-metadata-bound args).
  • A boot-time audit warns about every custom-resolver @Arg that 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 false in every environment regardless of NODE_ENV. Never set it to true in 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 @NoLog from @memberjunction/server so they remain redacted even when verbose logging is on.

Enabling for a single session:

Terminal window
MJ_LOG_GRAPHQL_VARIABLES=true npm run start

Enabling 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;
}

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));

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);

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);
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

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
];

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 configuration
  • getSigningKey() — Retrieves JWKS signing keys with retry logic
  • extractUserInfo() — Extracts email, name from provider-specific JWT claims
  • matchesIssuer() — Matches JWT iss claim to the provider

Configure providers in mj.config.cjs under authProviders or via environment variables (see Configuration section).

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_KEY environment variable
  • Authenticates as the system user with elevated privileges
  • Used for server-to-server communication

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.

ScopeDescription
full_accessBypass all scope checks
entity:readRead entity records
entity:createCreate records
entity:updateUpdate records
entity:deleteDelete records
view:runExecute RunView queries
agent:executeExecute AI agents
agent:monitorCheck agent run status
action:executeExecute MJ Actions
prompt:executeExecute AI prompts
query:runExecute queries
metadata:entities:readRead entity metadata
metadata:agents:readRead agent metadata
communication:sendSend 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 function
await CheckAPIKeyScope(ctx.userPayload.apiKeyId, 'view:run', ctx.userPayload.userRecord, {
resource: 'Users',
});
// Pre-built scope checker
const requireViewRun = RequireScope('view:run');
await requireViewRun(ctx);
  • @RequireSystemUser: Restricts a field or mutation to system-user-only access. Applied at the schema level; non-system users receive an AuthorizationError.
  • @Public: Marks a field as publicly accessible without authentication. All other fields require an active, authenticated user by default.

The server includes resolvers for the following domains:

ResolverOperations
EntityResolverCRUD operations for all entities
RunViewResolverRunView 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.
RunAIPromptResolverExecute AI prompts, simple prompts, text embeddings
RunAIAgentResolverExecute AI agents with session and streaming support
ActionResolverExecute MJ Actions
QueryResolverExecute and create saved queries
AdhocQueryResolverExecute ad-hoc SQL queries (SELECT/WITH only, read-only connection)
ReportResolverRun and manage reports
DatasetResolverDataset operations
UserViewResolverUser view management
UserResolverUser profile operations
UserFavoriteResolverFavorite record management
MergeRecordsResolverRecord merge operations
SyncDataResolver / SyncRolesUsersResolverData synchronization
FileResolver / FileCategoryResolverFile and category management
EntityCommunicationsResolverEntity-level communications
EntityRecordNameResolverRecord name resolution
TransactionGroupResolverTransaction group management
ComponentRegistryResolverComponent registry queries
MCPResolverMCP server operations
APIKeyResolverAPI key management
SqlLoggingConfigResolverSQL logging session management
TelemetryResolverServer telemetry queries
ColorResolverColor palette operations
InfoResolverServer info queries
PotentialDuplicateRecordResolverDuplicate detection
RunTestResolverTest execution
RunTemplateResolverTemplate execution
TaskResolverTask orchestration

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.

Real-time updates are supported via WebSocket at the same path as the GraphQL endpoint:

// Server-side: already configured
const webSocketServer = new WebSocketServer({
server: httpServer,
path: graphqlRootPath,
});

All built-in resolvers extend ResolverBase, which provides:

MethodDescription
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

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:

EndpointMethodDescription
/api/v1/entities/:entityNameGETList entity records
/api/v1/entities/:entityNamePOSTCreate a record
/api/v1/entities/:entityName/:idGETGet a record by ID
/api/v1/entities/:entityName/:idPUTUpdate a record
/api/v1/entities/:entityName/:idDELETEDelete a record
/api/v1/views/:entityNamePOSTRun a view
/api/v1/views/batchPOSTBatch view execution
/api/v1/metadata/entitiesGETList entity metadata
/api/v1/users/currentGETGet current user

The server includes a runtime SQL logging system for debugging and migration generation. Requires Owner-level user privileges.

# Start a logging session
mutation {
startSqlLogging(input: {
fileName: "debug-session.sql"
filterToCurrentUser: true
options: {
sessionName: "Debug Session"
prettyPrint: true
statementTypes: "both"
formatAsMigration: false
}
}) {
id
filePath
sessionName
}
}
# Query active sessions
query {
activeSqlLoggingSessions {
id
sessionName
statementCount
}
}
# Stop a session
mutation {
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,
},
}

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.

mutation {
RunAIPrompt(input: {
PromptName: "Summarize Content"
ModelID: "model-guid"
Temperature: 0.7
Messages: [{ role: "user", content: "Summarize this article..." }]
}) {
Success
Result
TokenUsage { InputTokens OutputTokens }
}
}
mutation {
ExecuteSimplePrompt(input: {
SystemPrompt: "You are a helpful assistant."
UserMessage: "What is MemberJunction?"
ModelPowerLevel: "Standard"
}) {
Success
Result
}
}
mutation {
EmbedText(input: {
Texts: ["Hello world", "MemberJunction framework"]
ModelSize: "small"
}) {
Success
Embeddings
Dimensions
Model
}
}
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.

ExportDescription
serve(resolverPaths, app?, options?)Main server initialization function
createApp()Creates a new Express application instance
ExportDescription
AppContextGraphQL resolver context type
UserPayloadAuthenticated user payload
DataSourceInfoDatabase connection descriptor
ProviderInfoPer-request provider descriptor
MJServerOptionsOptions for serve()
ConfigInfoFull server configuration type
MJServerEventServer lifecycle event type
ExportDescription
IAuthProviderAuthentication provider interface
AuthProviderFactoryProvider registry and factory
NewUserBaseBase class for custom new user handling
TokenExpiredErrorToken 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
ExportDescription
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
RequireViewRunPre-built scope checker for view:run
RequireQueryRunPre-built scope checker for query:run
RequireAgentExecutePre-built scope checker for agent:execute
ExportDescription
ResolverBaseBase class for all resolvers
RunViewResolverBase resolver for view operations
PushStatusResolverStatus update resolver with pub/sub
ExportDescription
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
ExportDescription
KeyValuePairInputGeneric key-value input type
DeleteOptionsInputDelete operation options
ExportDescription
RequireSystemUserDecorator restricting access to system users
PublicDecorator marking endpoints as publicly accessible
configInfoParsed server configuration singleton
DEFAULT_SERVER_CONFIGDefault configuration values

Responses larger than 1KB are automatically compressed using gzip at compression level 6. Binary content types (images, video, audio) are excluded.

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

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
}

Validated JWT tokens are cached using an LRU cache, avoiding repeated cryptographic verification for the same token within its lifetime.

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.

  1. Extensions implement BaseServerExtension from @memberjunction/server-extensions-core
  2. Extensions register via @RegisterClass(BaseServerExtension, 'DriverClassName')
  3. Configuration in mj.config.cjs defines which extensions to load
  4. MJServer’s ServerExtensionLoader discovers and initializes all enabled extensions at startup
mj.config.cjs
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,
}
}
]
};

MJServer exposes an aggregate health check endpoint for all loaded extensions:

GET /health/extensions

Returns 200 when all extensions are healthy, 503 when any extension reports unhealthy.

PackageExtensionsDescription
@memberjunction/messaging-adaptersSlackMessagingExtension, TeamsMessagingExtensionSlack & Teams integration for MJ AI agents

See @memberjunction/server-extensions-core for documentation on building custom extensions.

The server registers handlers for SIGTERM and SIGINT to:

  1. Shut down all server extensions (in reverse order of loading)
  2. Stop the scheduled jobs service
  3. Close the HTTP server
  4. Force-exit after a 10-second timeout if graceful shutdown stalls

Unhandled promise rejections are caught and logged without crashing the server.

PackagePurpose
@memberjunction/coreCore metadata, entities, RunView
@memberjunction/core-entitiesGenerated entity classes
@memberjunction/globalClassFactory, event system
@memberjunction/sqlserver-dataproviderSQL Server data provider
@memberjunction/graphql-dataproviderGraphQL field mapping
@memberjunction/configConfiguration utilities
@memberjunction/api-keysAPI key engine and scope evaluation
@memberjunction/encryptionField-level encryption engine
PackagePurpose
@memberjunction/aiAI engine abstraction
@memberjunction/ai-promptsAI prompt execution
@memberjunction/ai-agentsAI agent framework
@memberjunction/ai-core-plusAI prompt parameters
@memberjunction/aiengineAI engine orchestration
@memberjunction/ai-provider-bundleBundled AI providers
PackagePurpose
@memberjunction/scheduling-engineScheduled job execution
@memberjunction/actionsAction framework
@memberjunction/templatesTemplate engine
@memberjunction/notificationsNotification system
@memberjunction/storageFile storage
@memberjunction/communication-ms-graphMS Graph communications
@memberjunction/communication-sendgridSendGrid communications
PackagePurpose
@apollo/serverGraphQL server
expressHTTP framework
type-graphqlTypeScript GraphQL decorators
mssqlSQL Server client
jsonwebtoken / jwks-rsaJWT verification
graphql-ws / wsWebSocket subscriptions
compressionResponse compression
cosmiconfigConfiguration file discovery
cloudeventsCloudEvent emission
zodConfiguration schema validation
IssueSolution
Authentication errorsVerify environment variables for your auth provider are set
Database connection failuresCheck DB_HOST, DB_PORT, DB_USERNAME, DB_PASSWORD
No resolvers foundVerify resolver paths passed to serve() are absolute and use correct glob patterns
Transaction errorsReview mutation logic; check that entity operations are within the per-request provider
SQL logging access deniedEnsure user has Type = 'Owner' in the Users table
Metadata not loadingCheck MJ_CORE_SCHEMA matches your database schema name
Token expired errorsExpected behavior for long-lived sessions; client should refresh tokens

Enable verbose logging:

Terminal window
DEBUG=mj:*
NODE_ENV=development

This package includes GraphQL resolvers for the Knowledge Hub:

  • SearchKnowledgeResolver — Unified search combining vector similarity (Pinecone) with full-text search via Metadata.FullTextSearch() and RRF fusion
  • VectorizeEntityResolver — Triggers entity vectorization via EntityVectorSyncer
  • PipelineProgressResolver — GraphQL subscription for real-time pipeline progress
  • FetchEntityVectorsResolver — Retrieves vectors and metadata from the vector database for a given entity document

See the Full-Text Search Guide for the complete FTS architecture.

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
}
}
ArgumentTypeDefaultDescription
entityDocumentIDString!The entity document whose vector index to query
maxRecordsInt1000Maximum number of vectors to return
filterStringReserved 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.

See the MemberJunction Contributing Guide for development setup and guidelines.