markdown formatting in messages, sharper tool descriptions
This commit is contained in:
parent
829a60854f
commit
ef461797ad
4 changed files with 65 additions and 35 deletions
25
Cargo.lock
generated
25
Cargo.lock
generated
|
|
@ -2623,6 +2623,24 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"memchr",
|
||||
"pulldown-cmark-escape",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark-escape"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
|
|
@ -3011,6 +3029,7 @@ dependencies = [
|
|||
"js_int",
|
||||
"js_option",
|
||||
"percent-encoding",
|
||||
"pulldown-cmark",
|
||||
"regex",
|
||||
"ruma-common",
|
||||
"ruma-identifiers-validation",
|
||||
|
|
@ -4130,6 +4149,12 @@ dependencies = [
|
|||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ pedantic = { level = "warn", priority = -1 }
|
|||
module_name_repetitions = "allow"
|
||||
|
||||
[dependencies]
|
||||
matrix-sdk = { version = "0.14", features = ["e2e-encryption", "sqlite"] }
|
||||
matrix-sdk = { version = "0.14", features = ["e2e-encryption", "sqlite", "markdown"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
|
|
|||
|
|
@ -83,69 +83,74 @@ use protocol_inline::{DaemonRequest, DaemonResponse};
|
|||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
struct SendMessageParams {
|
||||
/// The message text to send.
|
||||
/// Message text. Plain text - markdown isn't specially rendered. Keep it
|
||||
/// short, you're rate-limited and terse is on-character.
|
||||
body: String,
|
||||
/// Target room ID (e.g. !abc:server). Defaults to the room that triggered
|
||||
/// this invocation if omitted.
|
||||
/// Target room ID like `!abc:server.com`. Omit to send to the room that
|
||||
/// triggered this invocation (the common case).
|
||||
#[serde(default)]
|
||||
room_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
struct SendDmParams {
|
||||
/// The Matrix user ID to DM (e.g. @alice:server).
|
||||
/// Full Matrix user ID like `@alice:server.com`. Must include the leading
|
||||
/// `@` and the server suffix.
|
||||
user_id: String,
|
||||
/// The message text to send.
|
||||
/// DM text. Plain text.
|
||||
body: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
struct SendReactionParams {
|
||||
/// The event ID to react to. Can be the shortened form shown in the
|
||||
/// timeline (e.g. $abc123de...).
|
||||
/// Event ID of the message to react to. Shortened form from the prompt
|
||||
/// (`$abc12345…`) is fine - resolved by prefix match against recent
|
||||
/// timeline.
|
||||
event_id: String,
|
||||
/// The reaction emoji (e.g. fire, eyes, heart).
|
||||
/// The actual emoji character to react with, e.g. `🔥` or `👀` or `❤️`.
|
||||
/// Not a keyword name like "fire".
|
||||
key: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
struct ListRoomMembersParams {
|
||||
/// The room ID to list members for.
|
||||
/// Room ID like `!abc:server.com`.
|
||||
room_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
struct SendReplyParams {
|
||||
/// The event ID to reply to. Can be the shortened form shown in the
|
||||
/// timeline (e.g. $abc123de...).
|
||||
/// Event ID of the message you're replying to. Shortened form from the
|
||||
/// prompt (`$abc12345…`) is fine.
|
||||
event_id: String,
|
||||
/// The reply text.
|
||||
/// Reply text. Matrix clients render the original above as a quote, so
|
||||
/// don't repeat its content - just reply.
|
||||
body: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
struct GetRoomHistoryParams {
|
||||
/// The room ID to fetch history for.
|
||||
/// Room ID like `!abc:server.com`.
|
||||
room_id: String,
|
||||
/// Number of recent timeline items to return. Default 20, max 100.
|
||||
/// How many timeline items to return (oldest-first, most recent included).
|
||||
/// Default 20, max 100. Backfills from the homeserver if the local cache
|
||||
/// is short.
|
||||
#[serde(default)]
|
||||
limit: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
struct FetchEventParams {
|
||||
/// The event ID to fetch. Can be the shortened form shown in the
|
||||
/// timeline (e.g. $abc123de...) or a full ID. Use this to dereference
|
||||
/// reply targets shown as [reply to $abc...] in the prompt, or to look
|
||||
/// up any specific event by ID.
|
||||
/// Event ID to fetch. Shortened form from the prompt (`$abc12345…`) or a
|
||||
/// full ID. Use this to dereference `[reply to $...]` markers or any
|
||||
/// event_id referenced in chat that isn't in your current window.
|
||||
event_id: String,
|
||||
/// Number of context messages BEFORE the target to include. Default 0.
|
||||
/// Pass 5-10 for conversational context. The response also includes an
|
||||
/// `earlier_handle` event_id you can pass to fetch_event to page further
|
||||
/// back into history.
|
||||
/// How many messages BEFORE the target to include for conversational
|
||||
/// context. Default 0, max 50. Pass 5-10 for typical "what was the
|
||||
/// conversation around this".
|
||||
#[serde(default)]
|
||||
context_before: Option<u32>,
|
||||
/// Room ID to look in. Defaults to source room if omitted.
|
||||
/// Room to look in. Omit for the source room (the common case).
|
||||
#[serde(default)]
|
||||
room_id: Option<String>,
|
||||
}
|
||||
|
|
@ -213,7 +218,7 @@ impl MatrixBridge {
|
|||
|
||||
#[tool_router(server_handler)]
|
||||
impl MatrixBridge {
|
||||
#[tool(description = "Send a message to a Matrix room. Defaults to the room that triggered this invocation.")]
|
||||
#[tool(description = "Send a top-level message to a Matrix room. The default target is the room that triggered this invocation. For replies to a specific message in a busy room, use send_reply instead. For private 1:1, use send_dm.")]
|
||||
async fn send_message(
|
||||
&self,
|
||||
Parameters(params): Parameters<SendMessageParams>,
|
||||
|
|
@ -230,7 +235,7 @@ impl MatrixBridge {
|
|||
Self::response_to_result(resp)
|
||||
}
|
||||
|
||||
#[tool(description = "Send a direct message to a Matrix user. Creates the DM room if needed.")]
|
||||
#[tool(description = "Send a private direct message to a Matrix user. Reuses an existing DM room with that user, or creates a new one. Works even if you don't currently share any room with them. Use for private 1:1 - in a room channel, prefer send_message.")]
|
||||
async fn send_dm(
|
||||
&self,
|
||||
Parameters(params): Parameters<SendDmParams>,
|
||||
|
|
@ -244,7 +249,7 @@ impl MatrixBridge {
|
|||
Self::response_to_result(resp)
|
||||
}
|
||||
|
||||
#[tool(description = "React to a message with an emoji. Use the event ID shown in the timeline.")]
|
||||
#[tool(description = "React to a specific message in the source room with an emoji. Lower friction than a reply when you just want to acknowledge or signal. Reactions are visible to everyone in the room.")]
|
||||
async fn send_reaction(
|
||||
&self,
|
||||
Parameters(params): Parameters<SendReactionParams>,
|
||||
|
|
@ -259,13 +264,13 @@ impl MatrixBridge {
|
|||
Self::response_to_result(resp)
|
||||
}
|
||||
|
||||
#[tool(description = "List all Matrix rooms the bot has joined.")]
|
||||
#[tool(description = "List all Matrix rooms the bot has joined, with display names. Returns JSON array of {room_id, name}. Use to find a room ID for cross-room tools (get_room_history, send_message with room_id).")]
|
||||
async fn list_rooms(&self) -> Result<CallToolResult, McpError> {
|
||||
let resp = self.call(&DaemonRequest::ListRooms {}).await?;
|
||||
Self::response_to_result(resp)
|
||||
}
|
||||
|
||||
#[tool(description = "List members of a Matrix room.")]
|
||||
#[tool(description = "List currently joined members of a Matrix room. Returns JSON array of {user_id, display_name}. Useful for finding a user_id to DM, or for confirming who's actually in a room.")]
|
||||
async fn list_room_members(
|
||||
&self,
|
||||
Parameters(params): Parameters<ListRoomMembersParams>,
|
||||
|
|
@ -278,7 +283,7 @@ impl MatrixBridge {
|
|||
Self::response_to_result(resp)
|
||||
}
|
||||
|
||||
#[tool(description = "Reply to a specific message in the source room. Threads via m.in_reply_to relation so clients render the quote.")]
|
||||
#[tool(description = "Reply to a specific message in the source room with proper m.in_reply_to threading. Matrix clients render the original message as a quote above your reply, so don't repeat its content. Use this when there are multiple parallel conversations and a top-level message would be ambiguous - otherwise prefer send_message.")]
|
||||
async fn send_reply(
|
||||
&self,
|
||||
Parameters(params): Parameters<SendReplyParams>,
|
||||
|
|
@ -293,7 +298,7 @@ impl MatrixBridge {
|
|||
Self::response_to_result(resp)
|
||||
}
|
||||
|
||||
#[tool(description = "Get recent message history for any joined room. Returns JSON list of messages and reactions with timestamps. Backfills via /messages if cache is short.")]
|
||||
#[tool(description = "Fetch recent timeline (messages + reactions, oldest-first) for any joined room. Backfills from the homeserver if the local cache is short. For looking up ONE specific event by ID, use fetch_event - it's lighter and gives you context around the target.")]
|
||||
async fn get_room_history(
|
||||
&self,
|
||||
Parameters(params): Parameters<GetRoomHistoryParams>,
|
||||
|
|
@ -307,7 +312,7 @@ impl MatrixBridge {
|
|||
Self::response_to_result(resp)
|
||||
}
|
||||
|
||||
#[tool(description = "Fetch a specific event by ID with optional context messages before it. Use to dereference [reply to $...] markers or arbitrary event IDs. Response includes earlier_handle for paging further back.")]
|
||||
#[tool(description = "Look up an event by ID with optional N messages of context before it. Bypasses the local cache via the homeserver /context endpoint - works even for events older than your timeline window. TWO MAIN USES: (1) dereference `[reply to $...]` markers in the prompt so you can see what someone is actually responding to. (2) page through historical context: pass `context_before` (e.g. 10), get back the events plus an `earlier_handle` event_id - call fetch_event again with that handle to walk further back, and keep chaining to read arbitrarily far into the past.")]
|
||||
async fn fetch_event(
|
||||
&self,
|
||||
Parameters(params): Parameters<FetchEventParams>,
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ async fn send_message(client: &Client, room_id: &str, body: &str) -> DaemonRespo
|
|||
let Some(room) = client.get_room(&rid) else {
|
||||
return DaemonResponse::err(format!("room {rid} not found"));
|
||||
};
|
||||
let content = RoomMessageEventContent::text_plain(body);
|
||||
let content = RoomMessageEventContent::text_markdown(body);
|
||||
match room.send(content).await {
|
||||
Ok(_) => {
|
||||
tracing::info!(room = %rid, "mcp: sent message");
|
||||
|
|
@ -112,7 +112,7 @@ async fn send_dm(client: &Client, user_id: &str, body: &str) -> DaemonResponse {
|
|||
Ok(r) => r,
|
||||
Err(e) => return DaemonResponse::err(format!("failed to get/create DM: {e}")),
|
||||
};
|
||||
let content = RoomMessageEventContent::text_plain(body);
|
||||
let content = RoomMessageEventContent::text_markdown(body);
|
||||
match room.send(content).await {
|
||||
Ok(_) => {
|
||||
tracing::info!(user = %uid, "mcp: sent DM");
|
||||
|
|
@ -220,7 +220,7 @@ async fn send_reply(client: &Client, room_id: &str, event_id: &str, body: &str)
|
|||
return DaemonResponse::err(format!("event {event_id} not found in timeline"));
|
||||
};
|
||||
|
||||
let content = RoomMessageEventContent::text_plain(body).into();
|
||||
let content = RoomMessageEventContent::text_markdown(body).into();
|
||||
let reply = Reply {
|
||||
event_id: full_eid.clone(),
|
||||
enforce_thread: EnforceThread::MaybeThreaded,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue