feat: matrix MCP invite_user tool
Adds an invite_user tool to the per-agent matrix MCP so an agent can invite
another user to a room it's already in (e.g. pull a peer into an existing
chat). Mirrors the existing join_room/send_dm wiring:
- protocol.rs: DaemonRequest::InviteUser { room, user_id }
- handlers.rs: invite_user — parse user_id, resolve room, room.invite_user_by_id
- socket.rs: dispatch arm
- bin/mcp.rs: invite_user tool + InviteUserArgs
room accepts an id or alias; the caller must hold a power level high enough
to invite (the homeserver error is surfaced verbatim otherwise). The invitee
then accepts via join_room. allowedTools is already '*' for the matrix server,
so the tool is exposed without a nix change.
This commit is contained in:
parent
df1ffdd504
commit
c85dcc0dec
4 changed files with 57 additions and 0 deletions
|
|
@ -127,6 +127,15 @@ struct JoinRoomArgs {
|
|||
room: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
struct InviteUserArgs {
|
||||
/// Matrix room id (`!abc:server`) or canonical alias (`#name:server`)
|
||||
/// to invite the user into. You must already be a member.
|
||||
room: String,
|
||||
/// Matrix user id of the invitee (`@user:server`).
|
||||
user_id: String,
|
||||
}
|
||||
|
||||
struct MatrixBridge {
|
||||
#[allow(dead_code)]
|
||||
tool_router: rmcp::handler::server::router::tool::ToolRouter<Self>,
|
||||
|
|
@ -237,6 +246,22 @@ impl MatrixBridge {
|
|||
render(round_trip(DaemonRequest::JoinRoom { room: args.room }).await)
|
||||
}
|
||||
|
||||
#[tool(
|
||||
description = "Invite a user to a matrix room you're already in. `room` is a \
|
||||
room id (!abc:server) or alias (#name:server); `user_id` is the invitee \
|
||||
(@user:server). You must have a high enough power level in the room to \
|
||||
invite. The invitee then sees a pending invite they accept with `join_room`."
|
||||
)]
|
||||
async fn invite_user(&self, Parameters(args): Parameters<InviteUserArgs>) -> String {
|
||||
render(
|
||||
round_trip(DaemonRequest::InviteUser {
|
||||
room: args.room,
|
||||
user_id: args.user_id,
|
||||
})
|
||||
.await,
|
||||
)
|
||||
}
|
||||
|
||||
#[tool(
|
||||
description = "List the members of a matrix room (joined-state only). \
|
||||
Each row carries the user id and resolved display name."
|
||||
|
|
|
|||
|
|
@ -350,6 +350,28 @@ pub async fn join_room(client: &Client, room_ref: &str) -> DaemonResponse {
|
|||
}
|
||||
}
|
||||
|
||||
/// Invite `user_id` to `room_ref`. The calling agent must be a member of
|
||||
/// the room with a power level high enough to invite; otherwise the
|
||||
/// homeserver rejects it and the error is surfaced verbatim.
|
||||
pub async fn invite_user(client: &Client, room_ref: &str, user_id: &str) -> DaemonResponse {
|
||||
let uid: OwnedUserId = match user_id.parse() {
|
||||
Ok(u) => u,
|
||||
Err(e) => return DaemonResponse::error(format!("invalid user_id {user_id}: {e}")),
|
||||
};
|
||||
let room = match resolve_room(client, room_ref).await {
|
||||
Ok(r) => r,
|
||||
Err(e) => return e,
|
||||
};
|
||||
match room.invite_user_by_id(&uid).await {
|
||||
Ok(()) => DaemonResponse::ok(&serde_json::json!({
|
||||
"invited": true,
|
||||
"room_id": room.room_id().to_string(),
|
||||
"user_id": uid.to_string(),
|
||||
})),
|
||||
Err(e) => DaemonResponse::error(format!("invite {uid} to {room_ref}: {e}")),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn read_room(client: &Client, room_ref: &str, limit: Option<usize>) -> DaemonResponse {
|
||||
use matrix_sdk::ruma::api::Direction;
|
||||
use matrix_sdk::ruma::api::client::message::get_message_events;
|
||||
|
|
|
|||
|
|
@ -81,6 +81,13 @@ pub enum DaemonRequest {
|
|||
#[serde(rename = "join_room")]
|
||||
JoinRoom { room: String },
|
||||
|
||||
/// Invite `user_id` (`@user:server`) to `room` (id or alias). The
|
||||
/// calling agent must already be a member with a high enough power
|
||||
/// level to invite. Idempotent-ish: inviting an already-joined or
|
||||
/// already-invited user surfaces the matrix error from the server.
|
||||
#[serde(rename = "invite_user")]
|
||||
InviteUser { room: String, user_id: String },
|
||||
|
||||
/// Return the count of rooms with unread notifications. Used by
|
||||
/// the harness `get_loose_ends` to surface unread matrix activity
|
||||
/// without exposing message content.
|
||||
|
|
|
|||
|
|
@ -81,6 +81,9 @@ async fn dispatch(req: DaemonRequest, client: &Client) -> DaemonResponse {
|
|||
DaemonRequest::ListRooms => handlers::list_rooms(client).await,
|
||||
DaemonRequest::ListInvites => handlers::list_invites(client).await,
|
||||
DaemonRequest::JoinRoom { room } => handlers::join_room(client, &room).await,
|
||||
DaemonRequest::InviteUser { room, user_id } => {
|
||||
handlers::invite_user(client, &room, &user_id).await
|
||||
}
|
||||
DaemonRequest::ListRoomMembers { room } => handlers::list_room_members(client, &room).await,
|
||||
DaemonRequest::ReadRoom { room, limit } => handlers::read_room(client, &room, limit).await,
|
||||
DaemonRequest::UnreadCount => handlers::unread_count(client),
|
||||
|
|
|
|||
Loading…
Reference in a new issue