Globals
Instance Methods
buildProtectedResourceMetadata()
RFC 9728 protected resource metadata.
authorization_servers is intentionally omitted: Choreo Planer issues MCP access tokens through its own POST /auth/mcp-token endpoint and does not operate an OAuth 2.0 authorization server. RFC 9728 marks the parameter OPTIONAL for exactly this case.
getUserFromRequest()
Extract user info from MCP request handler extra.
The AuthInfo.extra field carries userId, ownerIds, and isAdmin set by the JwtTokenVerifier during authentication.
formatError()
Format an error into an MCP tool result.
Returns a consistent error response shape that the MCP SDK expects.
conditionalBearerAuth()
Enforce bearer auth only for methods not in PUBLIC_MCP_METHODS.
GET (stream resume) and DELETE (session teardown) always require a prior authenticated session, so they stay gated. For POST we inspect the JSON-RPC method and skip auth for the public capability handshake.
errorHandlingMiddleWare(
err: Error,
req: Request,
res: Response,
next: function,
)
Middleware to send an appropriate status code response depending on the occurring error.
NotFoundError: status code 404LineupConflictError: status code 409
Parameters
err(Error) — Error to handle and to logreq(Request) — Incoming request objectres(Response) — Outgoing response objectnext(function) — Next handler function (not called)
loggerMiddleWare(req: Request, res: Response, next: function)
Middleware to log every incoming request
Parameters
req(Request) — Incoming request objectres(Response) — Outgoing response objectnext(function) — Next handler function
errorLoggingMiddleWare(
err: Error,
req: Request,
res: Response,
next: function,
)
Middleware to handle all errors
Parameters
err(Error) — Error to handle and to logreq(Request) — Incoming request objectres(Response) — Outgoing response objectnext(function) — Next handler function (not called)
assertUsableMember(MemberId: string): Promise.<Member>
Load the member a position is being assigned to and assert it is usable. Centralizes the existence check shared by position create, find-or-create, and member reassignment so a position never references a missing member.
Parameters
MemberId(string) — The member being placed.
Returns
Promise.<Member>— The loaded member.
Throws
NotFoundError— If no member exists with the given ID.
canRead(ownerId, actingUserId, isAdmin)
Check if the acting user has read access to resources owned by the given ownerId.
Parameters
ownerId— The ID of the owner whose resources are being accessedactingUserId— The ID of the user attempting to access the resourceisAdmin— Whether the acting user is an admin (admin has full access)
Returns
- Promise - True if the user has read access
canWrite(ownerId, actingUserId, isAdmin)
Check if the acting user has write access to resources owned by the given ownerId. Coaches and assistants have write access.
Parameters
ownerId— The ID of the owner whose resources are being accessedactingUserId— The ID of the user attempting to access the resourceisAdmin— Whether the acting user is an admin (admin has full access)
Returns
- Promise - True if the user has write access
canDelete(ownerId, actingUserId, isAdmin)
Check if the acting user has delete access to resources owned by the given ownerId. Only coaches have delete access.
Parameters
ownerId— The ID of the owner whose resources are being accessedactingUserId— The ID of the user attempting to access the resourceisAdmin— Whether the acting user is an admin (admin has full access)
Returns
- Promise - True if the user has delete access
checkReadAccess(ownerId, actingUserId, isAdmin)
Verify the acting user has read access to resources owned by the given ownerId. Throws AccessDeniedError if access is denied.
Parameters
ownerId— The ID of the owner whose resources are being accessedactingUserId— The ID of the user attempting to access the resourceisAdmin— Whether the acting user is an admin (admin has full access)
Returns
- Promise
Throws
- AccessDeniedError if access is denied
checkWriteAccess(ownerId, actingUserId, isAdmin)
Verify the acting user has write access to resources owned by the given ownerId. Throws AccessDeniedError if access is denied.
Parameters
ownerId— The ID of the owner whose resources are being accessedactingUserId— The ID of the user attempting to access the resourceisAdmin— Whether the acting user is an admin (admin has full access)
Returns
- Promise
Throws
- AccessDeniedError if access is denied
checkDeleteAccess(ownerId, actingUserId, isAdmin)
Verify the acting user has delete access to resources owned by the given ownerId. Throws AccessDeniedError if access is denied.
Parameters
ownerId— The ID of the owner whose resources are being accessedactingUserId— The ID of the user attempting to access the resourceisAdmin— Whether the acting user is an admin (admin has full access)
Returns
- Promise
Throws
- AccessDeniedError if access is denied
filterAccessibleOwnerIds(ownerIds, actingUserId, isAdmin)
Filter ownerIds to only include those the acting user has read access to. This prevents false negatives when checking access for multiple owners.
Parameters
ownerIds— Array of owner IDs to filteractingUserId— The acting user IDisAdmin— Whether the acting user is an admin
Returns
- Promise<string[]> - Filtered array of owner IDs the user can access
isPostgres(): boolean
Returns
boolean— True when the active database dialect is PostgreSQL, which supports the advisory locks used to serialize concurrent guarded mutations.
acquireLock(
transaction: Transaction,
choreoId: string,
): Promise.<void>
Take a transaction-scoped PostgreSQL advisory lock keyed to one choreography, so concurrent guarded mutations on the same choreo run one at a time. No-op on SQLite, which serializes writers and is only used by the sequential test suite.
Parameters
transaction(Transaction) — The transaction the lock is scoped to; it is released automatically when the transaction ends.choreoId(string) — The choreography whose mutations are serialized.
Returns
Promise.<void>
snapshotLineupConflicts(
choreoIds: Array.<string>,
transaction: Transaction,
): Promise.<LineupConflictSnapshot>
Snapshot every lineup consistency violation currently present across the given choreographies: overlapping lineups, members in two overlapping lineups, and duplicate positions. Runs plain SELECTs inside the caller's transaction so the result reflects uncommitted changes made earlier in the same transaction.
Parameters
choreoIds(Array.<string>) — Choreographies to inspect. An empty array yields an empty snapshot without querying.transaction(Transaction) — The transaction the queries run in.
Returns
Promise.<LineupConflictSnapshot>— The violations found, as canonical key sets suitable for diffing against another snapshot.
describeNewViolations(
before: LineupConflictSnapshot,
after: LineupConflictSnapshot,
): Array.<string>
Diff two snapshots and return the violation keys present in after but not in before — i.e. the conflicts a mutation newly introduced. Pre-existing violations appear in both and are omitted, which is what grandfathers them.
Parameters
before(LineupConflictSnapshot) — Snapshot taken before the mutation.after(LineupConflictSnapshot) — Snapshot taken after the mutation.
Returns
Array.<string>— Canonical keys of the newly introduced violations; empty when the mutation introduced none.
withLineupConflictGuard(
choreoIds: Array.<(string|null|undefined)>,
work: function,
): Promise.<T>
Run a mutation inside a transaction that enforces lineup consistency.
The guard takes an advisory lock per choreography (so concurrent writers run one at a time), snapshots existing violations, runs work, snapshots again, and commits only if work introduced no new overlap, member conflict, or duplicate position. Violations that already existed before work are preserved in both snapshots and therefore allowed (stateless grandfathering), so legacy data stays editable as long as the edit does not make it worse.
work must perform its writes on the ambient transaction via getTransaction so they are included in the "after" snapshot and roll back together when a conflict is detected. When choreoIds contains no usable id, work runs directly with no transaction, lock, or snapshot.
Parameters
choreoIds(Array.<(string|null|undefined)>) — Choreographies affected by the mutation; null/undefined/duplicate entries are filtered and the rest are locked in sorted order to avoid deadlocks.work(function) — The mutation to run under the guard.
Returns
Promise.<T>— The value returned bywork.
Throws
LineupConflictError— Ifworkintroduces a new lineup overlap, member conflict, or duplicate position; the transaction is rolled back.
runInTransaction(
transaction: Transaction,
work: function,
): Promise.<T>
Run work with transaction bound as the ambient transaction.
While the returned promise is pending, any code it calls can retrieve the same transaction via getTransaction without it being threaded through every function signature. Used by the lineup conflict guard so a mutation, its before/after conflict snapshots, and its writes all share one connection and roll back together.
Parameters
transaction(Transaction) — The transaction to expose towork.work(function) — The callback run inside the transaction context.
Returns
Promise.<T>— The resolved value ofwork.
getTransaction(): Transaction | undefined
Get the transaction bound by the nearest enclosing runInTransaction.
Returns
Transaction | undefined— The ambient transaction, orundefinedwhen called outside anyrunInTransactionscope (the normal, unguarded path).
Instance Fields
FeatureFlagKey
Keys for available feature flags used by the application.
Other
PUBLIC_MCP_METHODS
JSON-RPC methods that an agent may call before authenticating.
The capability handshake (initialize, tools/list, ping) is left public so scanners and agents can discover the server's WebMCP capabilities and enumerate tools without a bearer token. Everything else — notably tools/call and all resources/* and prompts/* reads — stays behind requireBearerAuth, which answers with a 401 and the RFC 9728 WWW-Authenticate pointer to the protected-resource metadata.
router
router
router
TOKEN_SECRET: string
Secret to encode the JWTs with
JWT_EXPIRES_IN: string
Description string for expiration time of JWTs
defaultColors: Array.<string>
Set of web-safe HEX color codes