Skip to main content

figma_mcp/figma/
client.rs

1use reqwest::{header::HeaderMap, header::HeaderValue, Client};
2use serde_json::Value;
3
4use crate::{Error, Result};
5
6const FIGMA_API_BASE: &str = "https://api.figma.com/v1";
7
8#[derive(Debug, Clone)]
9pub struct FigmaClient {
10    client: Client,
11    token: String,
12}
13
14impl FigmaClient {
15    pub fn new(token: String) -> Result<Self> {
16        let mut headers = HeaderMap::new();
17        headers.insert(
18            "X-Figma-Token",
19            HeaderValue::from_str(&token)
20                .map_err(|_| Error::Auth("Invalid token format".to_string()))?,
21        );
22
23        let client = Client::builder()
24            .default_headers(headers)
25            .build()
26            .map_err(|e| Error::Network(e))?;
27
28        Ok(Self { client, token })
29    }
30
31    pub async fn get_file(&self, file_id: &str, depth: Option<u32>) -> Result<Value> {
32        let mut url = format!("{}/files/{}", FIGMA_API_BASE, file_id);
33        if let Some(depth) = depth {
34            url.push_str(&format!("?depth={}", depth));
35        }
36        let response = self.client.get(&url).send().await?;
37
38        if !response.status().is_success() {
39            let status = response.status();
40            let text = response.text().await.unwrap_or_default();
41            return Err(Error::FigmaApi(format!("HTTP {}: {}", status, text)));
42        }
43
44        let json: Value = response.json().await?;
45
46        if let Some(err) = json.get("err") {
47            if !err.is_null() {
48                return Err(Error::FigmaApi(err.to_string()));
49            }
50        }
51
52        Ok(json)
53    }
54
55    pub async fn get_file_nodes(
56        &self,
57        file_id: &str,
58        node_ids: &[String],
59        depth: Option<u32>,
60    ) -> Result<Value> {
61        let ids = node_ids.join(",");
62        let mut url = format!("{}/files/{}/nodes?ids={}", FIGMA_API_BASE, file_id, ids);
63        if let Some(depth) = depth {
64            url.push_str(&format!("&depth={}", depth));
65        }
66        let response = self.client.get(&url).send().await?;
67
68        if !response.status().is_success() {
69            let status = response.status();
70            let text = response.text().await.unwrap_or_default();
71            return Err(Error::FigmaApi(format!("HTTP {}: {}", status, text)));
72        }
73
74        let json: Value = response.json().await?;
75
76        if let Some(err) = json.get("err") {
77            if !err.is_null() {
78                return Err(Error::FigmaApi(err.to_string()));
79            }
80        }
81
82        Ok(json)
83    }
84
85    pub async fn export_images(
86        &self,
87        file_id: &str,
88        node_ids: &[String],
89        format: &str,
90        scale: Option<f64>,
91    ) -> Result<Value> {
92        let ids = node_ids.join(",");
93        let mut url = format!(
94            "{}/images/{}?ids={}&format={}",
95            FIGMA_API_BASE, file_id, ids, format
96        );
97
98        if let Some(scale) = scale {
99            url.push_str(&format!("&scale={}", scale));
100        }
101
102        let response = self.client.get(&url).send().await?;
103
104        if !response.status().is_success() {
105            let status = response.status();
106            let text = response.text().await.unwrap_or_default();
107            return Err(Error::FigmaApi(format!("HTTP {}: {}", status, text)));
108        }
109
110        let json: Value = response.json().await?;
111
112        if let Some(err) = json.get("err") {
113            if !err.is_null() {
114                return Err(Error::FigmaApi(err.to_string()));
115            }
116        }
117
118        Ok(json)
119    }
120
121    pub async fn get_me(&self) -> Result<Value> {
122        let url = format!("{}/me", FIGMA_API_BASE);
123        let response = self.client.get(&url).send().await?;
124
125        if !response.status().is_success() {
126            let status = response.status();
127            let text = response.text().await.unwrap_or_default();
128            return Err(Error::FigmaApi(format!("HTTP {}: {}", status, text)));
129        }
130
131        let json: Value = response.json().await?;
132
133        if let Some(err) = json.get("err") {
134            if !err.is_null() {
135                return Err(Error::FigmaApi(err.to_string()));
136            }
137        }
138
139        Ok(json)
140    }
141
142    pub fn get_token(&self) -> &str {
143        &self.token
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[tokio::test]
152    async fn test_client_creation() {
153        let client = FigmaClient::new("test-token".to_string());
154        assert!(client.is_ok());
155    }
156
157    #[tokio::test]
158    async fn test_invalid_token_format() {
159        let client = FigmaClient::new("invalid\ntoken".to_string());
160        assert!(client.is_err());
161    }
162}