Files
optimclaw/tools-src/slack/src/lib.rs
T
a35db4d32d feat: Add Google Suite & Telegram WASM tools (#9)
* Add Google Calendar and Gmail WASM tools, and /add-tool skill

Scaffold two new WASM tools that share a single Google OAuth token:
- google-calendar: list/get/create/update/delete calendar events
- gmail: list/search/get/send/draft/reply/trash emails

Both tools use the sandboxed WIT interface with strict HTTP allowlists,
credential injection, and rate limiting. OAuth config requests only
the minimum scopes needed (calendar.events, gmail.modify, gmail.compose).

Also adds the /add-tool skill for scaffolding future WASM or built-in
tools with all boilerplate wired up.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* Document WASM vs MCP server decision guide in CLAUDE.md

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* Add Google Drive WASM tool with full file and sharing management

Supports 12 actions: list/get/download/upload/update files, create
folders, delete/trash, share/list/remove permissions, and list shared
drives. Works with both personal and organizational drives via the
corpora parameter. Uses shared google_oauth_token for auth.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* Add Google Sheets, Docs, and Slides WASM tools

Three new Google Workspace tools sharing google_oauth_token:
- Sheets: create spreadsheets, read/write/append values, manage sheets, format cells
- Docs: create/read/edit documents, text formatting, paragraphs, tables, lists
- Slides: create/edit presentations, shapes, images, text formatting, thumbnails, templates

Also adds tools-src/TOOLS.md tracking implementation status.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* Add Telegram WASM tool with direct MTProto over HTTPS

Replace TDLight Docker dependency with pure-Rust grammers crates
for direct encrypted MTProto communication to Telegram's web
transport endpoints. No middleware, no Docker needed.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* Gitignore Cargo.lock files in WASM tools

Library crates should not commit lock files. Consolidate per-tool
.gitignore into a single one at wasm-tools/ level.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* Flatten tools-src/wasm-tools/ into tools-src/

All tools are WASM, the extra nesting added no value. Moves all tool
crates up one level, updates WIT paths and documentation references.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* Fix Slack tool: add OAuth auth, URL encoding, pin wit-bindgen

- Add OAuth 2.0 auth section to Slack capabilities with proper scopes
  and manual fallback instructions
- URL-encode query parameters in GET requests to prevent injection
- Remove dead SlackApiError struct
- Pin wit-bindgen to =0.36 across all WASM tools for Rust 1.86 compat
- Update add-tool template with pinned version

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
2026-02-09 06:16:27 +00:00

206 lines
7.2 KiB
Rust

//! Slack WASM Tool for IronClaw.
//!
//! This is a standalone WASM component that provides Slack integration.
//! It demonstrates how to build external tools that can be dynamically
//! loaded by the agent runtime.
//!
//! # Capabilities Required
//!
//! - HTTP: `slack.com/api/*` (GET, POST)
//! - Secrets: `slack_bot_token` (injected automatically)
//!
//! # Supported Actions
//!
//! - `send_message`: Send a message to a channel
//! - `list_channels`: List channels the bot has access to
//! - `get_channel_history`: Get recent messages from a channel
//! - `post_reaction`: Add an emoji reaction to a message
//! - `get_user_info`: Get information about a Slack user
//!
//! # Example Usage
//!
//! ```json
//! {"action": "send_message", "channel": "#general", "text": "Hello from the agent!"}
//! ```
mod api;
mod types;
use types::SlackAction;
// Generate bindings from the WIT interface.
// This creates the `bindings` module with types and traits.
wit_bindgen::generate!({
world: "sandboxed-tool",
path: "../../wit/tool.wit",
});
/// Implementation of the tool interface.
struct SlackTool;
impl exports::near::agent::tool::Guest for SlackTool {
fn execute(req: exports::near::agent::tool::Request) -> exports::near::agent::tool::Response {
match execute_inner(&req.params) {
Ok(result) => exports::near::agent::tool::Response {
output: Some(result),
error: None,
},
Err(e) => exports::near::agent::tool::Response {
output: None,
error: Some(e),
},
}
}
fn schema() -> String {
// JSON Schema for the tool's parameters
r#"{
"type": "object",
"required": ["action"],
"oneOf": [
{
"properties": {
"action": { "const": "send_message" },
"channel": {
"type": "string",
"description": "Channel ID or name (e.g., '#general' or 'C1234567890')"
},
"text": {
"type": "string",
"description": "Message text (supports Slack mrkdwn formatting)"
},
"thread_ts": {
"type": "string",
"description": "Optional thread timestamp to reply in a thread"
}
},
"required": ["action", "channel", "text"]
},
{
"properties": {
"action": { "const": "list_channels" },
"limit": {
"type": "integer",
"description": "Maximum number of channels to return (default: 100)",
"default": 100
}
},
"required": ["action"]
},
{
"properties": {
"action": { "const": "get_channel_history" },
"channel": {
"type": "string",
"description": "Channel ID (e.g., 'C1234567890')"
},
"limit": {
"type": "integer",
"description": "Maximum number of messages to return (default: 20)",
"default": 20
}
},
"required": ["action", "channel"]
},
{
"properties": {
"action": { "const": "post_reaction" },
"channel": {
"type": "string",
"description": "Channel ID containing the message"
},
"timestamp": {
"type": "string",
"description": "Timestamp of the message to react to"
},
"emoji": {
"type": "string",
"description": "Emoji name without colons (e.g., 'thumbsup')"
}
},
"required": ["action", "channel", "timestamp", "emoji"]
},
{
"properties": {
"action": { "const": "get_user_info" },
"user_id": {
"type": "string",
"description": "User ID (e.g., 'U1234567890')"
}
},
"required": ["action", "user_id"]
}
]
}"#
.to_string()
}
fn description() -> String {
"Slack integration tool for sending messages, listing channels, reading history, \
adding reactions, and getting user information. Requires a Slack bot token with \
appropriate scopes (chat:write, channels:read, channels:history, reactions:write, \
users:read)."
.to_string()
}
}
/// Inner execution logic with proper error handling.
fn execute_inner(params: &str) -> Result<String, String> {
// Check if the Slack token is configured
if !crate::near::agent::host::secret_exists("slack_bot_token") {
return Err(
"Slack bot token not configured. Please add the 'slack_bot_token' secret.".to_string(),
);
}
// Parse the action from JSON
let action: SlackAction =
serde_json::from_str(params).map_err(|e| format!("Invalid parameters: {}", e))?;
crate::near::agent::host::log(
crate::near::agent::host::LogLevel::Info,
&format!("Executing Slack action: {:?}", action),
);
// Dispatch to the appropriate handler
let result = match action {
SlackAction::SendMessage {
channel,
text,
thread_ts,
} => {
let result = api::send_message(&channel, &text, thread_ts.as_deref())?;
serde_json::to_string(&result).map_err(|e| e.to_string())?
}
SlackAction::ListChannels { limit } => {
let result = api::list_channels(limit)?;
serde_json::to_string(&result).map_err(|e| e.to_string())?
}
SlackAction::GetChannelHistory { channel, limit } => {
let result = api::get_channel_history(&channel, limit)?;
serde_json::to_string(&result).map_err(|e| e.to_string())?
}
SlackAction::PostReaction {
channel,
timestamp,
emoji,
} => {
let result = api::post_reaction(&channel, &timestamp, &emoji)?;
serde_json::to_string(&result).map_err(|e| e.to_string())?
}
SlackAction::GetUserInfo { user_id } => {
let result = api::get_user_info(&user_id)?;
serde_json::to_string(&result).map_err(|e| e.to_string())?
}
};
Ok(result)
}
// Export the tool implementation.
export!(SlackTool);