/**
* MySQL Group Replication Tools
*
* Tools for managing MySQL Group Replication.
* 5 tools total: status, members, primary, transactions, flow control.
*/
import { z } from "zod";
import type { MySQLAdapter } from "../../MySQLAdapter.js";
import type {
ToolDefinition,
RequestContext,
} from "../../../../types/index.js";
// =============================================================================
// Schemas
// =============================================================================
const MemberSchema = z.object({
memberId: z.string().optional().describe("Filter by specific member UUID"),
});
// =============================================================================
// Tool Creation Functions
// =============================================================================
/**
* Get Group Replication status
*/
export function createGRStatusTool(adapter: MySQLAdapter): ToolDefinition {
return {
name: "mysql_gr_status",
title: "MySQL Group Replication Status",
description:
"Get comprehensive Group Replication status including mode and member state.",
group: "cluster",
inputSchema: z.object({}),
requiredScopes: ["read"],
annotations: {
readOnlyHint: true,
idempotentHint: true,
},
handler: async (_params: unknown, _context: RequestContext) => {
// Check if GR is running
const pluginResult = await adapter.executeQuery(
"SELECT PLUGIN_STATUS FROM information_schema.PLUGINS WHERE PLUGIN_NAME = 'group_replication'",
);
if (pluginResult.rows?.[0]?.["PLUGIN_STATUS"] !== "ACTIVE") {
return {
enabled: false,
message: "Group Replication plugin is not active",
};
}
const statusResult = await adapter.executeQuery(`
SELECT
@@group_replication_group_name as groupName,
@@group_replication_single_primary_mode as singlePrimaryMode,
@@group_replication_local_address as localAddress,
@@group_replication_group_seeds as groupSeeds,
@@group_replication_bootstrap_group as bootstrapGroup
`);
const config = statusResult.rows?.[0];
// Get member status from performance_schema
const memberResult = await adapter.executeQuery(`
SELECT
CHANNEL_NAME,
MEMBER_ID,
MEMBER_HOST,
MEMBER_PORT,
MEMBER_STATE,
MEMBER_ROLE,
MEMBER_VERSION
FROM performance_schema.replication_group_members
`);
// Get local member info
const localResult = await adapter.executeQuery(`
SELECT @@server_uuid as serverUuid
`);
const localUuid = localResult.rows?.[0]?.["serverUuid"] as string;
const members = memberResult.rows ?? [];
const localMember = members.find((m) => m["MEMBER_ID"] === localUuid);
return {
enabled: members.length > 0,
groupName: config?.["groupName"] ?? null,
singlePrimaryMode: config?.["singlePrimaryMode"] === 1,
localAddress: config?.["localAddress"] ?? null,
localMember: localMember ?? null,
memberCount: members.length,
members: members.map((m) => {
const member = m;
return {
id: member["MEMBER_ID"],
host: member["MEMBER_HOST"],
port: member["MEMBER_PORT"],
state: member["MEMBER_STATE"],
role: member["MEMBER_ROLE"],
version: member["MEMBER_VERSION"],
isLocal: member["MEMBER_ID"] === localUuid,
};
}),
};
},
};
}
/**
* Get Group Replication members
*/
export function createGRMembersTool(adapter: MySQLAdapter): ToolDefinition {
return {
name: "mysql_gr_members",
title: "MySQL GR Members",
description:
"List all Group Replication members with detailed state information.",
group: "cluster",
inputSchema: MemberSchema,
requiredScopes: ["read"],
annotations: {
readOnlyHint: true,
idempotentHint: true,
},
handler: async (params: unknown, _context: RequestContext) => {
const { memberId } = MemberSchema.parse(params);
// Check if GR is running
const pluginResult = await adapter.executeQuery(
"SELECT PLUGIN_STATUS FROM information_schema.PLUGINS WHERE PLUGIN_NAME = 'group_replication'",
);
if (pluginResult.rows?.[0]?.["PLUGIN_STATUS"] !== "ACTIVE") {
return {
members: [],
count: 0,
message: "Group Replication not active",
};
}
let query = `
SELECT
m.MEMBER_ID as memberId,
m.MEMBER_HOST as host,
m.MEMBER_PORT as port,
m.MEMBER_STATE as state,
m.MEMBER_ROLE as role,
m.MEMBER_VERSION as version,
s.COUNT_TRANSACTIONS_IN_QUEUE as txInQueue,
s.COUNT_TRANSACTIONS_CHECKED as txChecked,
s.COUNT_CONFLICTS_DETECTED as conflictsDetected,
s.COUNT_TRANSACTIONS_ROWS_VALIDATING as rowsValidating
FROM performance_schema.replication_group_members m
LEFT JOIN performance_schema.replication_group_member_stats s
ON m.MEMBER_ID = s.MEMBER_ID
`;
const queryParams: unknown[] = [];
if (memberId) {
query += " WHERE m.MEMBER_ID = ?";
queryParams.push(memberId);
}
const result = await adapter.executeQuery(query, queryParams);
return {
members: result.rows ?? [],
count: result.rows?.length ?? 0,
};
},
};
}
/**
* Identify current primary
*/
export function createGRPrimaryTool(adapter: MySQLAdapter): ToolDefinition {
return {
name: "mysql_gr_primary",
title: "MySQL GR Primary",
description:
"Identify the current primary member in a single-primary GR cluster.",
group: "cluster",
inputSchema: z.object({}),
requiredScopes: ["read"],
annotations: {
readOnlyHint: true,
idempotentHint: true,
},
handler: async (_params: unknown, _context: RequestContext) => {
const result = await adapter.executeQuery(`
SELECT
MEMBER_ID as memberId,
MEMBER_HOST as host,
MEMBER_PORT as port,
MEMBER_STATE as state,
MEMBER_VERSION as version
FROM performance_schema.replication_group_members
WHERE MEMBER_ROLE = 'PRIMARY'
`);
const primary = result.rows?.[0];
// Check if we are the primary
const localResult = await adapter.executeQuery(
"SELECT @@server_uuid as serverUuid",
);
const localUuid = localResult.rows?.[0]?.["serverUuid"];
return {
primary: primary ?? null,
hasPrimary: !!primary,
isLocalPrimary: primary?.["memberId"] === localUuid,
};
},
};
}
/**
* Get transaction status
*/
export function createGRTransactionsTool(
adapter: MySQLAdapter,
): ToolDefinition {
return {
name: "mysql_gr_transactions",
title: "MySQL GR Transactions",
description:
"Get Group Replication transaction statistics and pending transactions.",
group: "cluster",
inputSchema: z.object({}),
requiredScopes: ["read"],
annotations: {
readOnlyHint: true,
idempotentHint: true,
},
handler: async (_params: unknown, _context: RequestContext) => {
// Check if GR is running
const pluginResult = await adapter.executeQuery(
"SELECT PLUGIN_STATUS FROM information_schema.PLUGINS WHERE PLUGIN_NAME = 'group_replication'",
);
if (pluginResult.rows?.[0]?.["PLUGIN_STATUS"] !== "ACTIVE") {
return {
memberStats: [],
gtid: { executed: "", purged: "" },
message: "Group Replication not active",
};
}
// Get transaction statistics
const statsResult = await adapter.executeQuery(`
SELECT
MEMBER_ID as memberId,
COUNT_TRANSACTIONS_IN_QUEUE as txInQueue,
COUNT_TRANSACTIONS_CHECKED as txChecked,
COUNT_CONFLICTS_DETECTED as conflictsDetected,
COUNT_TRANSACTIONS_ROWS_VALIDATING as rowsValidating,
COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE as remoteInApplierQueue,
COUNT_TRANSACTIONS_REMOTE_APPLIED as remoteApplied,
COUNT_TRANSACTIONS_LOCAL_PROPOSED as localProposed,
COUNT_TRANSACTIONS_LOCAL_ROLLBACK as localRollback
FROM performance_schema.replication_group_member_stats
`);
// Get GTID info
const gtidResult = await adapter.executeQuery(`
SELECT
@@gtid_executed as gtidExecuted,
@@gtid_purged as gtidPurged
`);
const gtid = gtidResult.rows?.[0];
return {
memberStats: statsResult.rows ?? [],
gtid: {
executed: gtid?.["gtidExecuted"] ?? "",
purged: gtid?.["gtidPurged"] ?? "",
},
};
},
};
}
/**
* Get flow control statistics
*/
export function createGRFlowControlTool(adapter: MySQLAdapter): ToolDefinition {
return {
name: "mysql_gr_flow_control",
title: "MySQL GR Flow Control",
description:
"Get Group Replication flow control statistics and throttling info.",
group: "cluster",
inputSchema: z.object({}),
requiredScopes: ["read"],
annotations: {
readOnlyHint: true,
idempotentHint: true,
},
handler: async (_params: unknown, _context: RequestContext) => {
// Check if GR is running
const pluginResult = await adapter.executeQuery(
"SELECT PLUGIN_STATUS FROM information_schema.PLUGINS WHERE PLUGIN_NAME = 'group_replication'",
);
if (pluginResult.rows?.[0]?.["PLUGIN_STATUS"] !== "ACTIVE") {
return {
configuration: {},
memberQueues: [],
isThrottling: false,
message: "Group Replication not active",
};
}
// Get flow control configuration
const configResult = await adapter.executeQuery(`
SELECT
@@group_replication_flow_control_mode as flowControlMode,
@@group_replication_flow_control_certifier_threshold as certifierThreshold,
@@group_replication_flow_control_applier_threshold as applierThreshold,
@@group_replication_flow_control_min_quota as minQuota,
@@group_replication_flow_control_min_recovery_quota as minRecoveryQuota,
@@group_replication_flow_control_max_quota as maxQuota
`);
const config = configResult.rows?.[0];
// Get current queue depths
const queueResult = await adapter.executeQuery(`
SELECT
MEMBER_ID as memberId,
COUNT_TRANSACTIONS_IN_QUEUE as certifyQueue,
COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE as applierQueue
FROM performance_schema.replication_group_member_stats
`);
// Determine if flow control is active
const isThrottling = (queueResult.rows ?? []).some((row) => {
const r = row;
const certQueue = r["certifyQueue"] as number;
const appQueue = r["applierQueue"] as number;
const certThreshold =
(config?.["certifierThreshold"] as number) ?? 25000;
const appThreshold = (config?.["applierThreshold"] as number) ?? 25000;
return certQueue > certThreshold || appQueue > appThreshold;
});
return {
configuration: config ?? {},
memberQueues: queueResult.rows ?? [],
isThrottling,
recommendation: isThrottling
? "Flow control is active. Consider investigating slow members or adjusting thresholds."
: "Flow control is not currently throttling.",
};
},
};
}