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.
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
IAuthProvider interfaceX-API-Key) and system-level (x-mj-api-key) API keys with scope-based access controlSQLServerDataProvider instance for transaction safetygraphql-ws@RegisterClass@RegisterClass + mj.config.cjsMJServer uses a layered configuration system with the following priority (highest to lowest):
mj.config.cjs file (discovered via cosmiconfig)DEFAULT_SERVER_CONFIG hardcoded defaults| 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 |
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
},
},
};
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:
{ operation: '<name>', args: { ... } }Create<X>Input / Update<X>Input that map to an entity column with EntityFieldInfo.Encrypt=true are redacted automatically (metadata-driven).@NoLog decorator are redacted manually (for custom resolvers and non-metadata-bound args).@Arg that is neither metadata-bound nor @NoLog-marked, so authors know which arguments will appear in plaintext while the flag is active.Security notes:
false in every environment regardless of NODE_ENV. Never set it to true in production.@NoLog from @memberjunction/server so they remain redacted even when verbose logging is on.Enabling for a single session:
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 configurationgetSigningKey() -- Retrieves JWKS signing keys with retry logicextractUserInfo() -- Extracts email, name from provider-specific JWT claimsmatchesIssuer() -- Matches JWT iss claim to the providerConfigure 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):
System API Key (x-mj-api-key header):
MJ_API_KEY environment variableAPI 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 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:
| 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 |
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:
| 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 |
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 |
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.
| 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 |
| 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 |
| 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 |
| Export | Description |
|---|---|
ResolverBase |
Base class for all resolvers |
RunViewResolver |
Base resolver for view operations |
PushStatusResolver |
Status update resolver with pub/sub |
| 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 |
| Export | Description |
|---|---|
KeyValuePairInput |
Generic key-value input type |
DeleteOptionsInput |
Delete operation options |
| 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 |
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:
max: 10, min: 2max: 50, min: 5max: 100, min: 10Entity 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.
BaseServerExtension from @memberjunction/server-extensions-core@RegisterClass(BaseServerExtension, 'DriverClassName')mj.config.cjs defines which extensions to loadServerExtensionLoader 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.
| Package | Extensions | Description |
|---|---|---|
@memberjunction/messaging-adapters |
SlackMessagingExtension, TeamsMessagingExtension |
Slack & 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:
Unhandled promise rejections are caught and logged without crashing the server.
| 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 |
| 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 |
| 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 |
| 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 |
| 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=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 fusionVectorizeEntityResolver — Triggers entity vectorization via EntityVectorSyncerPipelineProgressResolver — GraphQL subscription for real-time pipeline progressFetchEntityVectorsResolver — Retrieves vectors and metadata from the vector database for a given entity documentSee 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
}
}
| 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.
See the MemberJunction Contributing Guide for development setup and guidelines.