feat(core): improve mcp management (#15221)

#### PR Dependency Tree


* **PR #15221** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added MCP credential management (create/reveal, list, rotate, revoke)
with expiration and status tracking.
* Introduced read-only vs read/write access modes, with read/write
tooling enabled only when permitted.
* Added workspace MCP credential configuration UI, including token
reveal and setup generation.
  * Added MCP credential GraphQL APIs to back the UI.
* **Changes**
* Replaced legacy access-token support with MCP credentials across
authentication and realtime updates.
* **Bug Fixes**
* MCP authentication now reliably rejects revoked, rotated, expired, or
disabled-user credentials.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-07-12 19:30:44 +08:00
committed by GitHub
parent abf37d3dfa
commit 9b81c6debd
57 changed files with 2180 additions and 1213 deletions
@@ -1,9 +0,0 @@
mutation generateUserAccessToken($input: GenerateAccessTokenInput!) {
generateUserAccessToken(input: $input) {
id
name
token
createdAt
expiresAt
}
}
@@ -1,3 +0,0 @@
mutation revokeUserAccessToken($id: String!) {
revokeUserAccessToken(id: $id)
}
+79 -22
View File
@@ -67,28 +67,6 @@ export const licenseBodyFragment = `fragment licenseBody on License {
validatedAt
variant
}`;
export const generateUserAccessTokenMutation = {
id: 'generateUserAccessTokenMutation' as const,
op: 'generateUserAccessToken',
query: `mutation generateUserAccessToken($input: GenerateAccessTokenInput!) {
generateUserAccessToken(input: $input) {
id
name
token
createdAt
expiresAt
}
}`,
};
export const revokeUserAccessTokenMutation = {
id: 'revokeUserAccessTokenMutation' as const,
op: 'revokeUserAccessToken',
query: `mutation revokeUserAccessToken($id: String!) {
revokeUserAccessToken(id: $id)
}`,
};
export const adminAllSharedLinksQuery = {
id: 'adminAllSharedLinksQuery' as const,
op: 'adminAllSharedLinks',
@@ -2574,6 +2552,85 @@ export const listNotificationsQuery = {
}`,
};
export const createMcpCredentialMutation = {
id: 'createMcpCredentialMutation' as const,
op: 'createMcpCredential',
query: `mutation createMcpCredential($input: CreateMcpCredentialInput!) {
createMcpCredential(input: $input) {
credential {
id
name
workspaceId
accessMode
fingerprint
createdAt
expiresAt
lastUsedAt
revokedAt
graceEndsAt
status
}
token
}
}`,
};
export const mcpCredentialsQuery = {
id: 'mcpCredentialsQuery' as const,
op: 'mcpCredentials',
query: `query mcpCredentials($workspaceId: String!) {
mcpCredentialReadWriteAvailable
mcpCredentials(workspaceId: $workspaceId) {
id
name
workspaceId
accessMode
fingerprint
createdAt
expiresAt
lastUsedAt
revokedAt
graceEndsAt
status
}
}`,
};
export const revokeMcpCredentialMutation = {
id: 'revokeMcpCredentialMutation' as const,
op: 'revokeMcpCredential',
query: `mutation revokeMcpCredential($id: ID!, $workspaceId: String!) {
revokeMcpCredential(id: $id, workspaceId: $workspaceId)
}`,
};
export const rotateMcpCredentialMutation = {
id: 'rotateMcpCredentialMutation' as const,
op: 'rotateMcpCredential',
query: `mutation rotateMcpCredential($id: ID!, $workspaceId: String!, $expirationDays: Int!) {
rotateMcpCredential(
id: $id
workspaceId: $workspaceId
expirationDays: $expirationDays
) {
credential {
id
name
workspaceId
accessMode
fingerprint
createdAt
expiresAt
lastUsedAt
revokedAt
graceEndsAt
status
}
token
}
}`,
};
export const mentionUserMutation = {
id: 'mentionUserMutation' as const,
op: 'mentionUser',
@@ -0,0 +1,18 @@
mutation createMcpCredential($input: CreateMcpCredentialInput!) {
createMcpCredential(input: $input) {
credential {
id
name
workspaceId
accessMode
fingerprint
createdAt
expiresAt
lastUsedAt
revokedAt
graceEndsAt
status
}
token
}
}
@@ -0,0 +1,16 @@
query mcpCredentials($workspaceId: String!) {
mcpCredentialReadWriteAvailable
mcpCredentials(workspaceId: $workspaceId) {
id
name
workspaceId
accessMode
fingerprint
createdAt
expiresAt
lastUsedAt
revokedAt
graceEndsAt
status
}
}
@@ -0,0 +1,3 @@
mutation revokeMcpCredential($id: ID!, $workspaceId: String!) {
revokeMcpCredential(id: $id, workspaceId: $workspaceId)
}
@@ -0,0 +1,26 @@
mutation rotateMcpCredential(
$id: ID!
$workspaceId: String!
$expirationDays: Int!
) {
rotateMcpCredential(
id: $id
workspaceId: $workspaceId
expirationDays: $expirationDays
) {
credential {
id
name
workspaceId
accessMode
fingerprint
createdAt
expiresAt
lastUsedAt
revokedAt
graceEndsAt
status
}
token
}
}
+169 -68
View File
@@ -37,14 +37,6 @@ export interface Scalars {
Upload: { input: File; output: File };
}
export interface AccessToken {
__typename?: 'AccessToken';
createdAt: Scalars['DateTime']['output'];
expiresAt: Maybe<Scalars['DateTime']['output']>;
id: Scalars['String']['output'];
name: Scalars['String']['output'];
}
export interface AddContextBlobInput {
blobId: Scalars['String']['input'];
contextId: Scalars['String']['input'];
@@ -954,6 +946,13 @@ export interface CreateCheckoutSessionInput {
variant?: InputMaybe<SubscriptionVariant>;
}
export interface CreateMcpCredentialInput {
accessMode?: McpAccessMode;
expirationDays?: Scalars['Int']['input'];
name: Scalars['String']['input'];
workspaceId: Scalars['String']['input'];
}
export interface CreateUserInput {
email: Scalars['String']['input'];
name?: InputMaybe<Scalars['String']['input']>;
@@ -1424,11 +1423,6 @@ export interface ForkChatSessionInput {
workspaceId: Scalars['String']['input'];
}
export interface GenerateAccessTokenInput {
expiresAt?: InputMaybe<Scalars['DateTime']['input']>;
name: Scalars['String']['input'];
}
export interface GrantDocUserRolesInput {
docId: Scalars['String']['input'];
role: DocRole;
@@ -1754,6 +1748,34 @@ export interface ManageUserInput {
name?: InputMaybe<Scalars['String']['input']>;
}
export enum McpAccessMode {
READ_ONLY = 'READ_ONLY',
READ_WRITE = 'READ_WRITE',
}
export enum McpCredentialStatus {
ACTIVE = 'ACTIVE',
EXPIRED = 'EXPIRED',
EXPIRING = 'EXPIRING',
REVOKED = 'REVOKED',
ROTATING = 'ROTATING',
}
export interface McpCredentialType {
__typename?: 'McpCredentialType';
accessMode: McpAccessMode;
createdAt: Scalars['DateTime']['output'];
expiresAt: Scalars['DateTime']['output'];
fingerprint: Scalars['String']['output'];
graceEndsAt: Maybe<Scalars['DateTime']['output']>;
id: Scalars['ID']['output'];
lastUsedAt: Maybe<Scalars['DateTime']['output']>;
name: Scalars['String']['output'];
revokedAt: Maybe<Scalars['DateTime']['output']>;
status: McpCredentialStatus;
workspaceId: Scalars['String']['output'];
}
export interface MeetingActionItemType {
__typename?: 'MeetingActionItemType';
deadline: Maybe<Scalars['String']['output']>;
@@ -1870,6 +1892,7 @@ export interface Mutation {
/** Create a stripe customer portal to manage payment methods */
createCustomerPortal: Scalars['String']['output'];
createInviteLink: InviteLink;
createMcpCredential: RevealedMcpCredentialType;
createReply: ReplyObjectType;
createSelfhostWorkspaceCustomerPortal: Scalars['String']['output'];
/** Create a new user */
@@ -1894,7 +1917,6 @@ export interface Mutation {
/** Create a chat session */
forkCopilotSession: Scalars['String']['output'];
generateLicenseKey: Scalars['String']['output'];
generateUserAccessToken: RevealedAccessToken;
grantCommercialEntitlement: Scalars['Boolean']['output'];
grantDocUserRoles: Scalars['Boolean']['output'];
grantMember: Scalars['Boolean']['output'];
@@ -1941,10 +1963,11 @@ export interface Mutation {
revokeCommercialEntitlement: Scalars['Boolean']['output'];
revokeDocUserRoles: Scalars['Boolean']['output'];
revokeInviteLink: Scalars['Boolean']['output'];
revokeMcpCredential: Scalars['Boolean']['output'];
revokeMember: Scalars['Boolean']['output'];
revokePublicDoc: DocType;
revokeUserAccessToken: Scalars['Boolean']['output'];
rotateAuthSigningKey: Array<AuthSigningKeyType>;
rotateMcpCredential: RevealedMcpCredentialType;
sendChangeEmail: Scalars['Boolean']['output'];
sendChangePasswordEmail: Scalars['Boolean']['output'];
sendSetPasswordEmail: Scalars['Boolean']['output'];
@@ -2115,6 +2138,10 @@ export interface MutationCreateInviteLinkArgs {
workspaceId: Scalars['String']['input'];
}
export interface MutationCreateMcpCredentialArgs {
input: CreateMcpCredentialInput;
}
export interface MutationCreateReplyArgs {
input: ReplyCreateInput;
}
@@ -2183,10 +2210,6 @@ export interface MutationGenerateLicenseKeyArgs {
sessionId: Scalars['String']['input'];
}
export interface MutationGenerateUserAccessTokenArgs {
input: GenerateAccessTokenInput;
}
export interface MutationGrantCommercialEntitlementArgs {
plan: Scalars['String']['input'];
quantity?: InputMaybe<Scalars['Int']['input']>;
@@ -2322,6 +2345,11 @@ export interface MutationRevokeInviteLinkArgs {
workspaceId: Scalars['String']['input'];
}
export interface MutationRevokeMcpCredentialArgs {
id: Scalars['ID']['input'];
workspaceId: Scalars['String']['input'];
}
export interface MutationRevokeMemberArgs {
userId: Scalars['String']['input'];
workspaceId: Scalars['String']['input'];
@@ -2332,14 +2360,16 @@ export interface MutationRevokePublicDocArgs {
workspaceId: Scalars['String']['input'];
}
export interface MutationRevokeUserAccessTokenArgs {
id: Scalars['String']['input'];
}
export interface MutationRotateAuthSigningKeyArgs {
expectedActiveKeyId: Scalars['String']['input'];
}
export interface MutationRotateMcpCredentialArgs {
expirationDays?: Scalars['Int']['input'];
id: Scalars['ID']['input'];
workspaceId: Scalars['String']['input'];
}
export interface MutationSendChangeEmailArgs {
callbackUrl: Scalars['String']['input'];
}
@@ -2706,6 +2736,8 @@ export interface Query {
error: ErrorDataUnion;
/** get workspace invitation info */
getInviteInfo: InvitationType;
mcpCredentialReadWriteAvailable: Scalars['Boolean']['output'];
mcpCredentials: Array<McpCredentialType>;
prices: Array<SubscriptionPrice>;
/** Get public user by id */
publicUserById: Maybe<PublicUserType>;
@@ -2714,8 +2746,6 @@ export interface Query {
* @deprecated Use realtime subscription "workspace.embedding.progress.changed" instead.
*/
queryWorkspaceEmbeddingStatus: ContextWorkspaceEmbeddingStatus;
/** @deprecated use currentUser.revealedAccessTokens */
revealedAccessTokens: Array<RevealedAccessToken>;
/** server config */
serverConfig: ServerConfigType;
/** Get user by email */
@@ -2774,6 +2804,10 @@ export interface QueryGetInviteInfoArgs {
inviteId: Scalars['String']['input'];
}
export interface QueryMcpCredentialsArgs {
workspaceId: Scalars['String']['input'];
}
export interface QueryPublicUserByIdArgs {
id: Scalars['String']['input'];
}
@@ -2914,12 +2948,9 @@ export interface ResponseTooLargeErrorDataType {
receivedBytes: Scalars['Int']['output'];
}
export interface RevealedAccessToken {
__typename?: 'RevealedAccessToken';
createdAt: Scalars['DateTime']['output'];
expiresAt: Maybe<Scalars['DateTime']['output']>;
id: Scalars['String']['output'];
name: Scalars['String']['output'];
export interface RevealedMcpCredentialType {
__typename?: 'RevealedMcpCredentialType';
credential: McpCredentialType;
token: Scalars['String']['output'];
}
@@ -3450,7 +3481,6 @@ export interface UserSettingsType {
export interface UserType {
__typename?: 'UserType';
accessTokens: Array<AccessToken>;
/** User avatar url */
avatarUrl: Maybe<Scalars['String']['output']>;
calendarAccounts: Array<CalendarAccountObjectType>;
@@ -3480,7 +3510,6 @@ export interface UserType {
notifications: PaginatedNotificationObjectType;
quota: UserQuotaType;
quotaUsage: UserQuotaUsageType;
revealedAccessTokens: Array<RevealedAccessToken>;
/** Get user settings */
settings: UserSettingsType;
subscriptions: Array<SubscriptionType>;
@@ -3842,31 +3871,6 @@ export interface TokenType {
token: Scalars['String']['output'];
}
export type GenerateUserAccessTokenMutationVariables = Exact<{
input: GenerateAccessTokenInput;
}>;
export type GenerateUserAccessTokenMutation = {
__typename?: 'Mutation';
generateUserAccessToken: {
__typename?: 'RevealedAccessToken';
id: string;
name: string;
token: string;
createdAt: string;
expiresAt: string | null;
};
};
export type RevokeUserAccessTokenMutationVariables = Exact<{
id: Scalars['String']['input'];
}>;
export type RevokeUserAccessTokenMutation = {
__typename?: 'Mutation';
revokeUserAccessToken: boolean;
};
export type AdminAllSharedLinksQueryVariables = Exact<{
pagination: PaginationInput;
filter?: InputMaybe<AdminAllSharedLinksFilterInput>;
@@ -7227,6 +7231,93 @@ export type ListNotificationsQuery = {
} | null;
};
export type CreateMcpCredentialMutationVariables = Exact<{
input: CreateMcpCredentialInput;
}>;
export type CreateMcpCredentialMutation = {
__typename?: 'Mutation';
createMcpCredential: {
__typename?: 'RevealedMcpCredentialType';
token: string;
credential: {
__typename?: 'McpCredentialType';
id: string;
name: string;
workspaceId: string;
accessMode: McpAccessMode;
fingerprint: string;
createdAt: string;
expiresAt: string;
lastUsedAt: string | null;
revokedAt: string | null;
graceEndsAt: string | null;
status: McpCredentialStatus;
};
};
};
export type McpCredentialsQueryVariables = Exact<{
workspaceId: Scalars['String']['input'];
}>;
export type McpCredentialsQuery = {
__typename?: 'Query';
mcpCredentialReadWriteAvailable: boolean;
mcpCredentials: Array<{
__typename?: 'McpCredentialType';
id: string;
name: string;
workspaceId: string;
accessMode: McpAccessMode;
fingerprint: string;
createdAt: string;
expiresAt: string;
lastUsedAt: string | null;
revokedAt: string | null;
graceEndsAt: string | null;
status: McpCredentialStatus;
}>;
};
export type RevokeMcpCredentialMutationVariables = Exact<{
id: Scalars['ID']['input'];
workspaceId: Scalars['String']['input'];
}>;
export type RevokeMcpCredentialMutation = {
__typename?: 'Mutation';
revokeMcpCredential: boolean;
};
export type RotateMcpCredentialMutationVariables = Exact<{
id: Scalars['ID']['input'];
workspaceId: Scalars['String']['input'];
expirationDays: Scalars['Int']['input'];
}>;
export type RotateMcpCredentialMutation = {
__typename?: 'Mutation';
rotateMcpCredential: {
__typename?: 'RevealedMcpCredentialType';
token: string;
credential: {
__typename?: 'McpCredentialType';
id: string;
name: string;
workspaceId: string;
accessMode: McpAccessMode;
fingerprint: string;
createdAt: string;
expiresAt: string;
lastUsedAt: string | null;
revokedAt: string | null;
graceEndsAt: string | null;
status: McpCredentialStatus;
};
};
};
export type MentionUserMutationVariables = Exact<{
input: MentionInput;
}>;
@@ -8262,6 +8353,11 @@ export type Queries =
variables: ListNotificationsQueryVariables;
response: ListNotificationsQuery;
}
| {
name: 'mcpCredentialsQuery';
variables: McpCredentialsQueryVariables;
response: McpCredentialsQuery;
}
| {
name: 'pricesQuery';
variables: PricesQueryVariables;
@@ -8304,16 +8400,6 @@ export type Queries =
};
export type Mutations =
| {
name: 'generateUserAccessTokenMutation';
variables: GenerateUserAccessTokenMutationVariables;
response: GenerateUserAccessTokenMutation;
}
| {
name: 'revokeUserAccessTokenMutation';
variables: RevokeUserAccessTokenMutationVariables;
response: RevokeUserAccessTokenMutation;
}
| {
name: 'adminUpdateWorkspaceMutation';
variables: AdminUpdateWorkspaceMutationVariables;
@@ -8669,6 +8755,21 @@ export type Mutations =
variables: PreviewLicenseMutationVariables;
response: PreviewLicenseMutation;
}
| {
name: 'createMcpCredentialMutation';
variables: CreateMcpCredentialMutationVariables;
response: CreateMcpCredentialMutation;
}
| {
name: 'revokeMcpCredentialMutation';
variables: RevokeMcpCredentialMutationVariables;
response: RevokeMcpCredentialMutation;
}
| {
name: 'rotateMcpCredentialMutation';
variables: RotateMcpCredentialMutationVariables;
response: RotateMcpCredentialMutation;
}
| {
name: 'mentionUserMutation';
variables: MentionUserMutationVariables;