// mcp/src/tools/roomTools.ts
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
import type { WhkerDBClient } from '../client/WhkerDBClient.js';
import type { SocketClient } from '../client/SocketClient.js';
/**
* 房间管理工具定义
*/
export const roomTools: Tool[] = [
{
name: 'whkerdb_create_room',
description: '创建一个新的 WhkerDB 房间,返回房间 ID 和邀请码',
inputSchema: {
type: 'object',
properties: {
roomId: {
type: 'string',
description: '可选的房间 ID,不提供则自动生成',
},
roomName: {
type: 'string',
description: '可选的房间名称',
},
},
},
},
{
name: 'whkerdb_join_room',
description: '加入一个 WhkerDB 房间,可以使用房间 ID 或邀请码',
inputSchema: {
type: 'object',
properties: {
roomIdOrInviteCode: {
type: 'string',
description: '房间 ID 或 6 位邀请码',
},
userId: {
type: 'string',
description: '可选的用户 ID,不提供则自动生成',
},
username: {
type: 'string',
description: '可选的用户名,默认为 "MCP Client"',
},
},
required: ['roomIdOrInviteCode'],
},
},
{
name: 'whkerdb_get_room_info',
description: '获取房间的基本信息',
inputSchema: {
type: 'object',
properties: {
roomId: {
type: 'string',
description: '房间 ID',
},
},
required: ['roomId'],
},
},
{
name: 'whkerdb_list_rooms',
description: '列出当前 MCP 客户端已加入的房间',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'whkerdb_export_snapshot',
description: '导出房间的完整快照(JSON 格式)',
inputSchema: {
type: 'object',
properties: {
roomId: {
type: 'string',
description: '房间 ID(必须是已加入的房间)',
},
},
required: ['roomId'],
},
},
{
name: 'whkerdb_leave_room',
description: '离开当前加入的房间',
inputSchema: {
type: 'object',
properties: {},
},
},
];
/**
* 房间工具处理器
*/
export function createRoomToolHandlers(
httpClient: WhkerDBClient,
getSocketClient: () => SocketClient | null,
setSocketClient: (client: SocketClient | null) => void,
createSocketClient: (userId: string, username: string) => SocketClient
) {
return {
whkerdb_create_room: async (args: { roomId?: string; roomName?: string }) => {
const result = await httpClient.createRoom(args.roomId);
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(
{
success: true,
roomId: result.roomId,
inviteCode: result.inviteCode,
isNew: result.isNew,
message: result.message,
},
null,
2
),
},
],
};
},
whkerdb_join_room: async (args: {
roomIdOrInviteCode: string;
userId?: string;
username?: string;
}) => {
const { roomIdOrInviteCode, userId, username = 'MCP Client' } = args;
// 如果已连接到其它房间,先断开,避免残留连接
const existing = getSocketClient();
if (existing?.isConnected()) {
existing.disconnect();
setSocketClient(null);
}
// 判断是邀请码还是房间 ID
let roomId = roomIdOrInviteCode;
if (roomIdOrInviteCode.length === 6 && /^[A-Z0-9]+$/.test(roomIdOrInviteCode.toUpperCase())) {
// 可能是邀请码,尝试获取房间 ID
try {
const roomInfo = await httpClient.getRoomByInviteCode(roomIdOrInviteCode);
roomId = roomInfo.roomId;
} catch {
// 不是有效邀请码,当作房间 ID 处理
}
}
// 创建 Socket 客户端并连接
const socketClient = createSocketClient(
userId || `mcp-user-${Date.now()}`,
username
);
await socketClient.connect();
const result = await socketClient.joinRoom(roomId);
setSocketClient(socketClient);
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(
{
success: true,
roomId: result.snapshot.id,
inviteCode: result.snapshot.inviteCode,
userId: result.userId,
version: result.snapshot.version,
nodeCount: result.snapshot.noteTree.metadata.totalNodes,
pdfCount: result.snapshot.pdfs.length,
imageCount: result.snapshot.images.length,
sessionCount: result.snapshot.sessions.length,
},
null,
2
),
},
],
};
},
whkerdb_get_room_info: async (args: { roomId: string }) => {
const result = await httpClient.getRoomInfo(args.roomId);
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(result, null, 2),
},
],
};
},
whkerdb_list_rooms: async () => {
const socketClient = getSocketClient();
if (!socketClient?.isConnected()) {
return {
content: [
{
type: 'text' as const,
text: JSON.stringify({ rooms: [], message: '未加入任何房间' }, null, 2),
},
],
};
}
const roomId = socketClient.getCurrentRoomId();
const snapshot = socketClient.getSnapshot();
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(
{
rooms: roomId
? [
{
roomId,
inviteCode: snapshot?.inviteCode,
version: snapshot?.version,
nodeCount: snapshot?.noteTree.metadata.totalNodes,
},
]
: [],
},
null,
2
),
},
],
};
},
whkerdb_export_snapshot: async (args: { roomId: string }) => {
const socketClient = getSocketClient();
if (!socketClient?.isConnected()) {
throw new Error('未连接到任何房间');
}
if (socketClient.getCurrentRoomId() !== args.roomId) {
throw new Error(`未加入房间 ${args.roomId}`);
}
const snapshot = socketClient.getSnapshot();
if (!snapshot) {
throw new Error('无法获取房间快照');
}
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(snapshot, null, 2),
},
],
};
},
whkerdb_leave_room: async () => {
const socketClient = getSocketClient();
if (socketClient?.isConnected()) {
socketClient.disconnect();
setSocketClient(null);
}
return {
content: [
{
type: 'text' as const,
text: JSON.stringify({ success: true, message: '已离开房间' }, null, 2),
},
],
};
},
};
}