feat(mcpd): McpToken schema + CRUD routes + introspection

Adds a new McpToken Prisma model (project-scoped, SHA-256 hashed at rest,
optional expiry, revocable) plus backing repository, service, and REST
routes. Tokens are a first-class RBAC subject: new 'McpToken' kind is
added to the subject enum and the service auto-creates an RbacDefinition
with subject McpToken:<sha> when bindings are provided.

Creator-permission ceiling: the service rejects any requested binding
the creator cannot already satisfy themselves (re-uses
rbacService.canAccess / canRunOperation). rbacMode=clone snapshots the
creator's full permissions into the token.

Routes:
  POST   /api/v1/mcptokens              create (returns raw token once)
  GET    /api/v1/mcptokens              list (filter by project)
  GET    /api/v1/mcptokens/:id          describe (no secret in response)
  POST   /api/v1/mcptokens/:id/revoke   soft-delete + remove RbacDef
  DELETE /api/v1/mcptokens/:id          hard-delete
  GET    /api/v1/mcptokens/introspect   validate raw bearer (used by mcplocal)

Extends AuditEvent with optional tokenName/tokenSha fields (indexed) so
token-driven activity can be filtered later. Adds token helpers in
@mcpctl/shared: TOKEN_PREFIX='mcpctl_pat_', generateToken, hashToken,
isMcpToken, timingSafeEqualHex.

Follow-up PRs add the auth-hook dispatch on the prefix, the CLI verbs,
and the HTTP-mode mcplocal that calls /introspect.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michal
2026-04-17 01:00:04 +01:00
parent 3149ea3ae7
commit 2ddb493bb0
17 changed files with 949 additions and 11 deletions

View File

@@ -25,6 +25,7 @@ model User {
auditLogs AuditLog[]
ownedProjects Project[]
groupMemberships GroupMember[]
mcpTokens McpToken[]
@@index([email])
}
@@ -187,6 +188,7 @@ model Project {
servers ProjectServer[]
prompts Prompt[]
promptRequests PromptRequest[]
mcpTokens McpToken[]
@@index([name])
@@index([ownerId])
@@ -204,6 +206,36 @@ model ProjectServer {
@@unique([projectId, serverId])
}
// ── MCP Tokens (bearer credentials for HTTP-mode mcplocal) ──
//
// Raw value format: `mcpctl_pat_<32 base62 chars>`. The raw value is shown
// exactly once at create time; only the SHA-256 hash is persisted. Tokens are
// scoped to exactly one project — they're only valid at
// `/projects/<that-project>/mcp`. Creator's RBAC is the ceiling; the service
// rejects bindings that exceed what the creator themselves can do.
model McpToken {
id String @id @default(cuid())
name String
projectId String
tokenHash String @unique
tokenPrefix String
ownerId String
description String @default("")
createdAt DateTime @default(now())
expiresAt DateTime?
lastUsedAt DateTime?
revokedAt DateTime?
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
@@unique([name, projectId])
@@index([tokenHash])
@@index([projectId])
@@index([ownerId])
}
// ── MCP Instances (running containers) ──
model McpInstance {
@@ -288,6 +320,8 @@ model AuditEvent {
correlationId String?
parentEventId String?
userName String?
tokenName String?
tokenSha String?
payload Json
createdAt DateTime @default(now())
@@ -297,6 +331,7 @@ model AuditEvent {
@@index([timestamp])
@@index([eventKind])
@@index([userName])
@@index([tokenSha])
}
// ── Backup Pending Queue ──