@atezer/figma-mcp-bridge 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +99 -9
  3. package/dist/cloudflare/cloud-cors.js +40 -0
  4. package/dist/cloudflare/cloud-mode-kv.js +86 -0
  5. package/dist/cloudflare/cloud-mode-routes.js +97 -0
  6. package/dist/cloudflare/cloud-relay-session.js +141 -0
  7. package/dist/cloudflare/core/config.js +1 -1
  8. package/dist/cloudflare/core/figma-url.js +48 -0
  9. package/dist/cloudflare/core/plugin-bridge-connector.js +52 -43
  10. package/dist/cloudflare/core/plugin-bridge-server.js +211 -87
  11. package/dist/cloudflare/index.js +243 -4
  12. package/dist/core/config.js +1 -1
  13. package/dist/core/config.js.map +1 -1
  14. package/dist/core/figma-url.d.ts +10 -0
  15. package/dist/core/figma-url.d.ts.map +1 -0
  16. package/dist/core/figma-url.js +49 -0
  17. package/dist/core/figma-url.js.map +1 -0
  18. package/dist/core/plugin-bridge-connector.d.ts +6 -1
  19. package/dist/core/plugin-bridge-connector.d.ts.map +1 -1
  20. package/dist/core/plugin-bridge-connector.js +52 -43
  21. package/dist/core/plugin-bridge-connector.js.map +1 -1
  22. package/dist/core/plugin-bridge-server.d.ts +47 -14
  23. package/dist/core/plugin-bridge-server.d.ts.map +1 -1
  24. package/dist/core/plugin-bridge-server.js +211 -87
  25. package/dist/core/plugin-bridge-server.js.map +1 -1
  26. package/dist/local-plugin-only.d.ts.map +1 -1
  27. package/dist/local-plugin-only.js +163 -43
  28. package/dist/local-plugin-only.js.map +1 -1
  29. package/f-mcp-plugin/README.md +13 -5
  30. package/f-mcp-plugin/code.js +216 -2
  31. package/f-mcp-plugin/manifest.json +6 -2
  32. package/f-mcp-plugin/ui.html +694 -213
  33. package/package.json +7 -6
package/CHANGELOG.md ADDED
@@ -0,0 +1,62 @@
1
+ # Changelog
2
+
3
+ Bu dosya [Keep a Changelog](https://keepachangelog.com/tr/1.1.0/) bicimine uygundur. Surum numaralari [`package.json`](package.json) ile uyumludur.
4
+
5
+ **Surum takibi (kullanicilar icin):**
6
+
7
+ | Kanal | Aciklama |
8
+ |-------|----------|
9
+ | [GitHub Releases](https://github.com/atezer/FMCP/releases) | Surum etiketleri ve (yayimlandiginda) derlenmis notlar |
10
+ | [npm - @atezer/figma-mcp-bridge](https://www.npmjs.com/package/@atezer/figma-mcp-bridge) | Yayinlanan paket surumu; `npm view @atezer/figma-mcp-bridge version` ile kontrol |
11
+ | Bu dosya | Repoda her surum icin ozet degisiklik listesi |
12
+
13
+ Bu changelog'a ekleme oncesi surumlerin tam ayrintilari icin `git log` kullanilabilir.
14
+
15
+ ## [Unreleased]
16
+
17
+ (Yaklasan degisiklikler buraya.)
18
+
19
+ ## [1.2.2] - 2026-04-01
20
+
21
+ GitHub Release: [v1.2.2](https://github.com/atezer/FMCP/releases/tag/v1.2.2); govde: [docs/releases/v1.2.2-body.md](docs/releases/v1.2.2-body.md).
22
+
23
+ ### Plugin (F-MCP Bridge)
24
+
25
+ - **Multi-client `fileKey`:** UI WebSocket `onopen` bazen plugin ana iş parçacığından gelen `FILE_IDENTITY` mesajından önce çalışıyordu; ilk `ready` `fileKey`/`fileName` olmadan gidince köprü (`PluginBridgeServer`) o client’ı `null` anahtarla listeliyordu. `pushBridgeFileIdentity()` eklendi: kimlik geldikten sonra açık soket varsa `ready` yeniden gönderiliyor; `figma_list_connected_files` ve `fileKey` ile yönlendirme tüm pencerelerde (FigJam + birden fazla Figma tarayıcı sekmesi) tutarlı çalışır.
26
+
27
+ ### Dokumantasyon
28
+
29
+ - [README.md](README.md): Multi-client bölümünde kimlik zamanlaması notu (1.2.2+).
30
+ - [FUTURE.md](FUTURE.md): Sürüm satırı ve tamamlanan madde özeti.
31
+
32
+ ## [1.2.1] - 2026-04-01
33
+
34
+ GitHub Release: [v1.2.1](https://github.com/atezer/FMCP/releases/tag/v1.2.1); govde: [docs/releases/v1.2.1-body.md](docs/releases/v1.2.1-body.md).
35
+
36
+ ### Bridge
37
+
38
+ - **`figma_search_components`:** Cikti ozetine bileşen **`key`** alani eklendi; `figma_instantiate_component(componentKey)` akisi ile uyum.
39
+ - **`prepublishOnly`:** `npm publish` oncesi `build:local` + `validate:fmcp-skills` (Worker `build:cloudflare` ayri; npm paketi bin'leri `dist/local*.js`).
40
+
41
+ ### Dokumantasyon
42
+
43
+ - **`docs/TOOLS.md`**, **`docs/TOOLS_FULL_LIST.md`:** `dist/local-plugin-only.js` ile parite; `figma_search_assets` / `figma_get_code_connect` / `figma_use` bu build'de kayitli degildir notu; `figma_search_components` + `key` aciklamasi.
44
+ - **`docs/FMCP_AGENT_CANVAS_COMPAT.md`:** Bolum 3 guncel envanter / planlanan ayrimi.
45
+ - **`docs/FIGMA_USE_STRUCTURED_INTENT.md`:** `figma_use` taslak; canli araç `figma_execute` notu.
46
+
47
+ ## [1.2.0] - 2026-03-27
48
+
49
+ ### Dokumantasyon
50
+
51
+ - Surum takibi ve guncelleme adimlari: [README.md](README.md#surum-ve-guncellemeler), [KURULUM.md](KURULUM.md#surum-takibi-ve-guncelleme-notlari).
52
+ - Bu changelog dosyasi eklendi; GitHub Releases ve npm ile birlikte tek referans olarak kullanilmalidir.
53
+ - GitHub [Release v1.2.0](https://github.com/atezer/FMCP/releases/tag/v1.2.0); govde: [docs/releases/v1.2.0-body.md](docs/releases/v1.2.0-body.md). Bakimci akisi: [docs/RELEASE_NOTES_TEMPLATE.md](docs/RELEASE_NOTES_TEMPLATE.md).
54
+
55
+ ### Bakim ve dogrulama (2026-03)
56
+
57
+ - [FUTURE.md](FUTURE.md) kod taramasi: npm `@atezer/figma-mcp-bridge@1.2.0` dogrulandi; `dist/` ile `docs/TOOLS.md` Agent Canvas uclusu uyumsuzlugu not edildi (dokuman duzeltmesi S7'de acik).
58
+ - Figma Organization private plugin yayini tamamlandi (FUTURE S5).
59
+
60
+ ### Not
61
+
62
+ Bu surum, npm paketi `@atezer/figma-mcp-bridge@1.2.0` ve depo kokundeki `package.json` ile hizalidir. Onceki surumlerin ayrintili kaydi bu dosyada baslamaktadir.
package/README.md CHANGED
@@ -1,11 +1,17 @@
1
1
  <p align="center">
2
- <img src="../lmcp.png" alt="F-MCP Bridge Logo" width="280" />
2
+ <img src="assets/logo.png" alt="F-MCP Bridge Logo" width="280" />
3
3
  </p>
4
4
 
5
5
  # F-MCP (Figma MCP Bridge)
6
6
 
7
7
  Figma tasarım verilerini ve işlemlerini Model Context Protocol (MCP) ile AI asistanlarına (Claude, Cursor vb.) açan MCP sunucusu ve Figma plugin'i. Bu repo MCP sunucusu ve **F-MCP Bridge** Figma plugin kaynağını içerir.
8
8
 
9
+ **Proje düzeni:** `src/`, `dist/` ve `f-mcp-plugin` bu depo **kökündedir.** Yerel MCP config ve dokümanlardaki yollar `…/<clone>/dist/...` ve `…/<clone>/f-mcp-plugin/...` şeklinde olmalıdır.
10
+
11
+ **Eski kurulum:** MCP `args` içinde `…/f-mcp-bridge/dist/...` varsa `…/<clone-kökü>/dist/...` yapın. Launch Agent, `.app` ve ayrıntılı adımlar için [KURULUM.md](KURULUM.md) içindeki **«Eski f-mcp-bridge alt yolundan geçiş»** bölümüne bakın.
12
+
13
+ **Claude Desktop config:** `env.FIGMA_PLUGIN_BRIDGE_PORT` kullanıyorsanız Figma plugin’deki port ile aynı olmalı; tam örnekler [docs/CLAUDE_DESKTOP_CONFIG.md](docs/CLAUDE_DESKTOP_CONFIG.md).
14
+
9
15
  ### Figma API token tüketmiyor
10
16
 
11
17
  figma-mcp-bridge, Figma'nın **REST API'sini kullanmıyor**. Akış:
@@ -45,7 +51,7 @@ REST API çağrısı ve Figma'ya tasarım verisi aktarımı yoktur. Bu sayede ku
45
51
  | Tasarım envanteri ve analiz | `figma_get_design_system_summary`, `figma_get_file_data` | Özet, bileşen sayıları, token koleksiyonları; büyük dosyada varsayılan **currentPageOnly** (timeout önlemi) |
46
52
  | Kabul kriterleri ve dokümantasyon | `figma_get_component_for_development`, `figma_capture_screenshot` | Bileşen spec + görsel; test ve kabul için referans |
47
53
  | Design–code uyumu (gap analizi) | `figma_check_design_parity` | Figma token'ları ile kod token'larını karşılaştırır; kurumsal raporlama ve test kriterleri |
48
- | Keşif ve durum | `figma_search_components`, `figma_get_status` | Bileşen arama (varsayılan currentPageOnly), bağlantı kontrolü |
54
+ | Keşif ve durum | `figma_search_components`, `figma_get_status`, `figma_list_connected_files` | Bileşen arama, bağlantı kontrolü, bağlı dosya listesi (multi-client) |
49
55
 
50
56
 
51
57
  ### Geliştiriciler
@@ -71,6 +77,31 @@ REST API çağrısı ve Figma'ya tasarım verisi aktarımı yoktur. Bu sayede ku
71
77
 
72
78
  Kurulum: **En basit (repo indirmeden):** aşağıdaki [En basit kurulum](#en-basit-kurulum-npx--repo-indirmeden) adımları. **Detaylı:** [Kurulum rehberi (Onboarding)](docs/ONBOARDING.md). **Windows:** [WINDOWS-INSTALLATION.md](docs/WINDOWS-INSTALLATION.md) (Node veya Python bridge).
73
79
 
80
+ ### Çalışma modları (hangi binary?)
81
+
82
+ | Mod | NPM / `node` girişi | Ne zaman |
83
+ | --- | --- | --- |
84
+ | **Plugin-only (önerilen)** | `figma-mcp-bridge-plugin` veya `dist/local-plugin-only.js` | Figma’da **F-MCP ATezer Bridge** plugin’i ile çalışmak; REST token gerekmez; debug portu gerekmez. |
85
+ | **Tam (CDP + REST)** | `figma-mcp-bridge` veya `dist/local.js` | Console log, ekran görüntüsü CDP üzerinden, `FIGMA_ACCESS_TOKEN`, Figma `--remote-debugging-port=9222` vb. |
86
+
87
+ Varsayılan NPM `main` ve `figma-mcp-bridge` komutu **tam mod**dur; plugin ile yetiniyorsanız config’te **`figma-mcp-bridge-plugin`** kullanın (NPX örnekleri aşağıda).
88
+
89
+ ## Sürüm ve güncellemeler
90
+
91
+ | Ne | Nerede |
92
+ | --- | --- |
93
+ | **Sürüm numarası** | [`package.json`](package.json) içindeki `version` (ör. **1.2.2**) |
94
+ | **Değişiklik özeti** | [CHANGELOG.md](CHANGELOG.md) |
95
+ | **Yayın bildirimi** | GitHub’da [Releases](https://github.com/atezer/FMCP/releases) — *Watch* → *Custom* → *Releases* ile e-posta bildirimi |
96
+ | **npm paketi** | [@atezer/figma-mcp-bridge](https://www.npmjs.com/package/@atezer/figma-mcp-bridge) — sürüm geçmişi npm sayfasında |
97
+
98
+ **Zaten kurulu yapıyı güncellemek (özet):**
99
+
100
+ - **Repo clone + `node …/dist/local-plugin-only.js`:** `git pull` → gerekirse `npm install` → `npm run build:local` → Cursor/Claude’u yeniden başlatın. Figma plugin kaynağı (`f-mcp-plugin/`) değiştiyse Development’tan manifest’i yeniden import edin veya plugin’i yeniden çalıştırın.
101
+ - **NPX:** Config’te `@latest` kullanıyorsanız yeni npm sürümü yayınlandıktan sonra bir sonraki MCP başlatmada indirilir; takılmada yukarıdaki önbellek notuna bakın. Sabit sürüm kullanıyorsanız `package.json`/CHANGELOG ile uyumlu sürüm numarasını elle güncelleyin.
102
+
103
+ Ayrıntılı adımlar: [KURULUM.md — Sürüm takibi ve güncelleme notları](KURULUM.md#sürüm-takibi-ve-güncelleme-notları).
104
+
74
105
  ## Hızlı başlangıç
75
106
 
76
107
  Plugin'in **"ready (:5454)"** olması için **önce** MCP bridge sunucusu çalışıyor olmalı; **sonra** Figma'da plugin'i açarsınız.
@@ -79,14 +110,14 @@ Plugin'in **"ready (:5454)"** olması için **önce** MCP bridge sunucusu çalı
79
110
 
80
111
  ### En basit kurulum (NPX — repo indirmeden)
81
112
 
82
- Repo klonlamadan, sadece Node.js ve tek bir config ile kurulum. Güncellemek için paket otomatik güncellenir (`@latest`).
113
+ Repo klonlamadan, sadece Node.js ve tek bir config ile kurulum. **NPX güncelleme:** `@latest` bir sonraki çalıştırmada genelde yeni sürümü indirir; `npx` önbelleği eski paketi tutuyorsa `npx clear-npx-cache` (veya belirli sürüm: `@atezer/figma-mcp-bridge@1.2.2`) kullanın. Ayrıntı: [Sürüm ve güncellemeler](#sürüm-ve-güncellemeler).
83
114
 
84
115
 
85
116
  | Adım | Yapılacak |
86
117
  | ---- | ------------------------------------------------------------------------------------------------------------ |
87
118
  | 1 | **Node.js kur** — [nodejs.org](https://nodejs.org) LTS. Terminalde `node -v` ile kontrol edin. |
88
119
  | 2 | **MCP config ekle** — Aşağıdaki JSON bloğunu Cursor veya Claude config dosyasına ekleyin. |
89
- | 3 | **Cursor veya Claude'u yeniden başlatın** — MCP sunucusu port 5454'te otomatik başlar. |
120
+ | 3 | **Cursor veya Claude'u yeniden başlatın** — köprü varsayılan olarak **5454**’te dinler (meşgulse **5454–5470** arasında sabit port; port mesgulse acik hata mesaji verir). Farkli porta gecmek icin `FIGMA_PLUGIN_BRIDGE_PORT` env var kullanin. |
90
121
  | 4 | **Figma'da plugini açın** — Plugins → **F-MCP ATezer Bridge** → **"ready (:5454)"** görünene kadar bekleyin. |
91
122
 
92
123
 
@@ -97,7 +128,7 @@ Repo klonlamadan, sadece Node.js ve tek bir config ile kurulum. Güncellemek iç
97
128
  "mcpServers": {
98
129
  "figma-mcp-bridge": {
99
130
  "command": "npx",
100
- "args": ["-y", "@atezer/figma-mcp-bridge@latest"]
131
+ "args": ["-y", "@atezer/figma-mcp-bridge@latest", "figma-mcp-bridge-plugin"]
101
132
  }
102
133
  }
103
134
  }
@@ -110,12 +141,29 @@ Repo klonlamadan, sadece Node.js ve tek bir config ile kurulum. Güncellemek iç
110
141
  "mcpServers": {
111
142
  "figma-mcp-bridge": {
112
143
  "command": "npx",
113
- "args": ["-y", "@atezer/figma-mcp-bridge@latest"]
144
+ "args": ["-y", "@atezer/figma-mcp-bridge@latest", "figma-mcp-bridge-plugin"]
114
145
  }
115
146
  }
116
147
  }
117
148
  ```
118
149
 
150
+ **Cursor ve Claude aynı makinede:** İkisi de varsayılan 5454’ü kullandığı için “Port 5454 is already used” / “Server disconnected” alırsınız. Claude tarafında **ayrı port** kullanın; Figma plugin’de de aynı portu seçin.
151
+
152
+ - **Cursor:** Config’e dokunmayın (5454 kalır).
153
+ - **Claude:** Config’te `figma-mcp-bridge` için `env` ekleyin; plugin’de Port’u **5455** yapın.
154
+
155
+ ```json
156
+ "figma-mcp-bridge": {
157
+ "command": "npx",
158
+ "args": ["-y", "@atezer/figma-mcp-bridge@latest", "figma-mcp-bridge-plugin"],
159
+ "env": {
160
+ "FIGMA_PLUGIN_BRIDGE_PORT": "5455"
161
+ }
162
+ }
163
+ ```
164
+
165
+ Repo ile (clone + build) kullanıyorsanız: `"command": "node"`, `"args": ["<PROJE-YOLU>/dist/local-plugin-only.js"]` ile aynı `"env": { "FIGMA_PLUGIN_BRIDGE_PORT": "5455" }` ekleyin. Claude’u yeniden başlatın; Figma’da plugini açıp Port alanına **5455** yazın → **"ready (:5455)"** görünmeli.
166
+
119
167
  İlk çalıştırmada `npx` paketi indirir; sonraki açılışlarda cache'den çalışır. **Plugin'i Figma'da ilk kez kullanıyorsanız** [Plugin'i Figma'ya yükleyin](#plugini-figmaya-yükleyin-ilk-seferde) adımına bakın.
120
168
 
121
169
  ### A) Clone + build ile (Cursor / Claude)
@@ -175,16 +223,20 @@ npm run dev:local
175
223
 
176
224
  ## Claude / Cursor ile bağlama (detay)
177
225
 
178
- **NPX:** Paket npm'de **@atezer/figma-mcp-bridge** adıyla yayınlı. `npx @atezer/figma-mcp-bridge@latest` ile clone yapmadan kullanılabilir. Bkz. [NPX-INSTALLATION.md](docs/NPX-INSTALLATION.md).
226
+ **NPX:** Paket npm'de **@atezer/figma-mcp-bridge** adıyla yayınlı. Plugin-only için: `npx -y @atezer/figma-mcp-bridge@latest figma-mcp-bridge-plugin` (tam mod için son argümanı atlayıp varsayılan `figma-mcp-bridge` binary’si kullanılır). Bkz. [NPX-INSTALLATION.md](docs/NPX-INSTALLATION.md).
179
227
 
180
228
  **Tam mod (console/screenshot):** Config'te `dist/local-plugin-only.js` yerine `dist/local.js` kullanın; Figma'yı `--remote-debugging-port=9222` ile açın.
181
229
 
182
- **Çoklu kullanıcı (multi-instance):** Aynı anda birden fazla kişi kullanacaksa her kullanıcı farklı port (5454, 5455, … 5470) seçer; MCP config'e `"env": { "FIGMA_PLUGIN_BRIDGE_PORT": "5455" }` ekleyin, plugin'de aynı portu girin. Detay: [MULTI_INSTANCE.md](docs/MULTI_INSTANCE.md).
230
+ **Paralel görevler / çoklu kullanıcı:** Aynı anda farklı Figma dosyalarında (Figma Desktop + FigJam + Figma Browser gibi) paralel AI görevleri çalıştırabilirsiniz. Her hat için ayrı port (5454, 5455, 5456, …) ve ayrı bridge process başlatın. MCP config'e `"env": { "FIGMA_PLUGIN_BRIDGE_PORT": "5455" }` ekleyin, plugin'de aynı portu girin. Port durumunu kontrol: `npm run check-ports`. Detay: [MULTI_INSTANCE.md](docs/MULTI_INSTANCE.md).
183
231
 
184
232
  **Enterprise:** Audit log (`FIGMA_MCP_AUDIT_LOG_PATH`), air-gap kurulum ve Organization plugin: [ENTERPRISE.md](docs/ENTERPRISE.md).
185
233
 
186
234
  **"Server disconnected" / "wrong server"?** (1) Port 5454'te başka bir process var mı kontrol edin: `lsof -i :5454`. (2) Cursor/Claude Desktop kullanıyorsanız `npm run dev:local` çalışmıyor olmalı. (3) Build güncel mi: `npm run build:local`.
187
235
 
236
+ **Claude'da "connectedClients: 0" / "Plugin Figma Desktop'ta çalışmıyor"?** Claude, Cursor ile aynı anda kullanılıyorsa 5455 portunda çalışır. Plugin ise varsayılan 5454'e bağlanır (Cursor’un bridge’i). Bu yüzden Claude tarafında 0 bağlantı görünür. **Çözüm:** Figma’da plugini açın → **Port** alanına **5455** yazın → Bağlan’a tıklayın. "ready (:5455)" görününce Claude’daki araçlar çalışır. Sadece Claude kullanıyorsanız ve port 5454 ise, pluginde Port’un **5454** olduğundan emin olun.
237
+
238
+ **Birden fazla dosya/board açık, hepsi "ready" ama link verince hep aynı dosya dönüyor?** Plugin'in `fileKey` gönderebilmesi için manifest'te `enablePrivatePluginApi: true` gerekir (bu repoda ekli). Plugin'i **Development → Import plugin from manifest** ile yeniden seçin; sonra her iki sekmede de plugini tekrar çalıştırıp "ready" yapın. Böylece board ve design dosyası ayrı fileKey ile kaydedilir, URL ile doğru dosyaya yönlenir.
239
+
188
240
  ### Browser Figma desteği
189
241
 
190
242
  Plugin, Figma'nın **tarayıcı sürümünde** de (figma.com) çalışır. Desktop uygulaması zorunlu değildir.
@@ -215,6 +267,39 @@ Plugin, Figma'nın **tarayıcı sürümünde** de (figma.com) çalışır. Deskt
215
267
 
216
268
  Detay: [ONBOARDING.md](docs/ONBOARDING.md) (Dev Mode bölümü).
217
269
 
270
+ ### Multi-client: Aynı anda birden fazla dosya
271
+
272
+ F-MCP Bridge **aynı anda birden fazla Figma/FigJam plugin bağlantısını** destekler. Üç ortam birlikte kullanılabilir:
273
+
274
+ - **Figma Desktop** — bir veya daha fazla design dosyasında plugin açık
275
+ - **FigJam browser** — tarayıcıda FigJam board'unda plugin açık
276
+ - **Figma browser** — tarayıcıda figma.com design dosyasında plugin açık
277
+
278
+ Hangi **linki** verirseniz, istek o linkteki dosyaya yönlendirilir; diğer pencereler etkilenmez. **Çoklu ajan:** Farklı dosya linkleri kullanarak birden fazla ajan veya oturum aynı anda farklı dosyalarda çalışabilir.
279
+
280
+ **Link ile kullanım:** Verdiğiniz Figma veya FigJam linki, ilgili tool çağrılarında `figmaUrl` parametresi olarak verilebilir; bridge linkten dosyayı tespit edip o dosyadaki plugin'e yönlendirir. Örneğin: "Bu FigJam linkine bak: https://figma.com/board/XYZ/..." → AI `figma_get_design_context({ figmaUrl: "https://..." })` ile çağırır.
281
+
282
+ **Manuel fileKey:** Her plugin bağlantısı kendini `fileKey` ile tanıtır. `figma_list_connected_files` ile bağlı dosyaları listeleyip, diğer tool'larda `fileKey` parametresi ile hedef dosyayı belirtebilirsiniz. `fileKey` ve `figmaUrl` belirtilmezse en son bağlanan dosyaya gider (geriye uyumlu).
283
+
284
+ **Kimlik zamanlaması (1.2.2+):** WebSocket bazen ana iş parçacığından gelen dosya kimliğinden önce açılabiliyordu; köprüde bir bağlantı `fileKey: null` görünebiliyordu. Plugin artık `FILE_IDENTITY` geldikten sonra `ready` mesajını yeniden göndererek sunucu kaydını güncelliyor. Çoklu sekme kullanıyorsanız bu sürümdeki `f-mcp-plugin` kaynağını yeniden import edin veya npm paketindeki `f-mcp-plugin` ile hizalayın ([CHANGELOG.md](CHANGELOG.md) **\[1.2.2\]**).
285
+
286
+ **Kullanım:**
287
+
288
+ 1. Birden fazla Figma/FigJam dosyasında (Desktop veya tarayıcı) plugin'i açın → her biri **"ready (:5454)"** gösterir.
289
+ 2. İstediğiniz dosyanın linkini Claude/Cursor'a verin veya `figma_list_connected_files` ile bağlı dosyaları listeleyin.
290
+ 3. Tool çağrılarında `figmaUrl` (link) veya `fileKey` ile hedef dosyayı belirtin.
291
+
292
+ ```
293
+ // Bağlı dosyaları listele
294
+ figma_list_connected_files
295
+
296
+ // Link ile: belirli dosyadaki design context
297
+ figma_get_design_context { "figmaUrl": "https://www.figma.com/board/XYZ/...", "depth": 2 }
298
+
299
+ // veya fileKey ile
300
+ figma_get_design_context { "fileKey": "abc123...", "depth": 2 }
301
+ ```
302
+
218
303
  **Plugin–MCP bağlantı özeti:** İki mod var; debug portu zorunlu değil. **Plugin-only (önerilen):** Config'te `dist/local-plugin-only.js`, Figma normal açılır, token yok. **Tam mod:** Config'te `dist/local.js`, Figma `--remote-debugging-port=9222` ile açılır (console/screenshot için). Ayrıntı: [PLUGIN-MCP-BAGLANTI.md](docs/PLUGIN-MCP-BAGLANTI.md).
219
304
 
220
305
  ## Detaylı Rehber
@@ -248,6 +333,8 @@ Plugin'in MCP ile nasıl konuştuğu, veri akışı, Design/Dev mode ve sorun gi
248
333
  | [DEVELOPER_FIGMA_CAPABILITIES.md](docs/DEVELOPER_FIGMA_CAPABILITIES.md) | **Cursor + F-MCP:** Neyi alır/almaz, birebir çıkartma, code-ready/SUI/token referansı, ileride |
249
334
  | [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) | Sorun giderme |
250
335
  | [NPX-INSTALLATION.md](docs/NPX-INSTALLATION.md) | NPX ile kurulum |
336
+ | [CHANGELOG.md](CHANGELOG.md) | **Sürüm geçmişi** — npm/GitHub Releases ile birlikte referans |
337
+ | [RELEASE_NOTES_TEMPLATE.md](docs/RELEASE_NOTES_TEMPLATE.md) | **Yeni sürüm yayını** — GitHub Release gövdesi şablonu ve `gh release create` ipuçları |
251
338
  | [OAUTH_SETUP.md](docs/OAUTH_SETUP.md) | OAuth (remote sunucu) |
252
339
  | [SELF_HOSTING.md](docs/SELF_HOSTING.md) | Kendi sunucunda host |
253
340
  | [DEPLOYMENT_COMPARISON.md](docs/DEPLOYMENT_COMPARISON.md) | Dağıtım karşılaştırma |
@@ -256,8 +343,11 @@ Plugin'in MCP ile nasıl konuştuğu, veri akışı, Design/Dev mode ve sorun gi
256
343
  | [RECONSTRUCTION_FORMAT.md](docs/RECONSTRUCTION_FORMAT.md) | Reconstruction format |
257
344
  | [BITBUCKET-README.md](docs/BITBUCKET-README.md) | Bitbucket README şablonu |
258
345
  | [PORT-5454-KAPALI.md](docs/PORT-5454-KAPALI.md) | Port 5454 kapalı sorun giderme |
259
- | [MULTI_INSTANCE.md](docs/MULTI_INSTANCE.md) | **Çoklu kullanıcı** — Aynı anda birden fazla kişi (port 5454–5470) |
346
+ | [MULTI_INSTANCE.md](docs/MULTI_INSTANCE.md) | **Paralel görevler & çoklu kullanıcı** — sabit port, paralel hatlar, Claude çoklu MCP |
347
+ | [CLAUDE_DESKTOP_CONFIG.md](docs/CLAUDE_DESKTOP_CONFIG.md) | Claude Desktop config örnekleri (tek ve çoklu MCP sunucusu) |
348
+ | [DEPENDENCY_LAYERS.md](docs/DEPENDENCY_LAYERS.md) | Bağımlılık katmanları (plugin-only / tam / Cloudflare) ve olası paket ayrımı taslağı |
260
349
  | [ENTERPRISE.md](docs/ENTERPRISE.md) | **Enterprise** — Audit log, air-gap, Organization plugin |
350
+ | [SECURITY_AUDIT.md](docs/SECURITY_AUDIT.md) | **Güvenlik denetimi** — bulgular checklist ([FUTURE.md](FUTURE.md) §10) |
261
351
  | [PUBLISH-PLUGIN.md](docs/PUBLISH-PLUGIN.md) | **Publish plugin** — Figma'da yayınlama: Data security cevapları, final details, Plugin ID |
262
352
  | | |
263
353
 
@@ -0,0 +1,40 @@
1
+ /**
2
+ * CORS allowlist for Cloud Mode + remote MCP (claude.ai, v0, Lovable, etc.).
3
+ */
4
+ const DEFAULT_ALLOWED_ORIGINS = [
5
+ "https://claude.ai",
6
+ "https://www.claude.ai",
7
+ "https://v0.dev",
8
+ "https://www.v0.dev",
9
+ "https://lovable.dev",
10
+ "https://www.lovable.dev",
11
+ ];
12
+ export function getAllowedCorsOrigin(request, extra) {
13
+ const origin = request.headers.get("Origin");
14
+ if (!origin)
15
+ return null;
16
+ const set = new Set([...DEFAULT_ALLOWED_ORIGINS, ...(extra ?? [])]);
17
+ return set.has(origin) ? origin : null;
18
+ }
19
+ export function corsHeaders(request, extraOrigins) {
20
+ const o = getAllowedCorsOrigin(request, extraOrigins);
21
+ const base = {
22
+ "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
23
+ "Access-Control-Allow-Headers": "Content-Type, Accept, Authorization, mcp-session-id, Mcp-Protocol-Version, mcp-protocol-version",
24
+ "Access-Control-Expose-Headers": "mcp-session-id",
25
+ "Access-Control-Max-Age": "86400",
26
+ };
27
+ if (o)
28
+ base["Access-Control-Allow-Origin"] = o;
29
+ else
30
+ base["Access-Control-Allow-Origin"] = "*";
31
+ return base;
32
+ }
33
+ export function withCors(request, response, extraOrigins) {
34
+ const h = new Headers(response.headers);
35
+ const ch = corsHeaders(request, extraOrigins);
36
+ for (const [k, v] of Object.entries(ch)) {
37
+ h.set(k, v);
38
+ }
39
+ return new Response(response.body, { status: response.status, headers: h });
40
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Cloud Mode pairing / bind / rate-limit helpers (KV: reuses OAUTH_STATE binding).
3
+ * Key prefixes are reserved to avoid collisions with OAuth state tokens (64+ hex).
4
+ */
5
+ export const FMCP_PAIR_PREFIX = "fmcp_pair:";
6
+ export const FMCP_BIND_PREFIX = "fmcp_bind:";
7
+ export const FMCP_RL_PREFIX = "fmcp_rl:";
8
+ export const PAIRING_TTL_SEC = 300;
9
+ export const BIND_TTL_SEC = 86400;
10
+ export const PAIRING_CODE_LENGTH = 6;
11
+ const CODE_CHARS = "23456789ABCDEFGHJKMNPQRSTVWXYZ";
12
+ export function generatePairingCode() {
13
+ let out = "";
14
+ const arr = new Uint8Array(PAIRING_CODE_LENGTH);
15
+ crypto.getRandomValues(arr);
16
+ for (let i = 0; i < PAIRING_CODE_LENGTH; i++) {
17
+ out += CODE_CHARS[arr[i] % CODE_CHARS.length];
18
+ }
19
+ return out;
20
+ }
21
+ export function generatePairingSecret() {
22
+ const a = new Uint8Array(16);
23
+ crypto.getRandomValues(a);
24
+ return Array.from(a, (b) => b.toString(16).padStart(2, "0")).join("");
25
+ }
26
+ export async function putPairing(kv, code, record) {
27
+ await kv.put(`${FMCP_PAIR_PREFIX}${code}`, JSON.stringify(record), {
28
+ expirationTtl: PAIRING_TTL_SEC,
29
+ });
30
+ }
31
+ export async function getPairing(kv, code) {
32
+ const raw = await kv.get(`${FMCP_PAIR_PREFIX}${code}`, "text");
33
+ if (!raw)
34
+ return null;
35
+ try {
36
+ return JSON.parse(raw);
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
42
+ export async function deletePairing(kv, code) {
43
+ await kv.delete(`${FMCP_PAIR_PREFIX}${code}`);
44
+ }
45
+ export async function putBind(kv, mcpSessionId, record) {
46
+ await kv.put(`${FMCP_BIND_PREFIX}${mcpSessionId}`, JSON.stringify(record), {
47
+ expirationTtl: BIND_TTL_SEC,
48
+ });
49
+ }
50
+ export async function getBind(kv, mcpSessionId) {
51
+ const raw = await kv.get(`${FMCP_BIND_PREFIX}${mcpSessionId}`, "text");
52
+ if (!raw)
53
+ return null;
54
+ try {
55
+ return JSON.parse(raw);
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ export async function deleteBind(kv, mcpSessionId) {
62
+ await kv.delete(`${FMCP_BIND_PREFIX}${mcpSessionId}`);
63
+ }
64
+ export async function rateLimitAllow(kv, key, limit, windowSec) {
65
+ const now = Date.now();
66
+ const raw = await kv.get(key, "text");
67
+ let stamps = [];
68
+ if (raw) {
69
+ try {
70
+ stamps = JSON.parse(raw).t ?? [];
71
+ }
72
+ catch {
73
+ stamps = [];
74
+ }
75
+ }
76
+ const windowMs = windowSec * 1000;
77
+ stamps = stamps.filter((ts) => now - ts < windowMs);
78
+ if (stamps.length >= limit)
79
+ return false;
80
+ stamps.push(now);
81
+ await kv.put(key, JSON.stringify({ t: stamps }), { expirationTtl: Math.max(windowSec * 2, 120) });
82
+ return true;
83
+ }
84
+ export function clientIp(request) {
85
+ return request.headers.get("CF-Connecting-IP") || request.headers.get("X-Forwarded-For")?.split(",")[0]?.trim() || "unknown";
86
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * HTTP + WebSocket routes for FMCP Cloud Mode (pairing, plugin bridge).
3
+ */
4
+ import { corsHeaders, getAllowedCorsOrigin, withCors } from "./cloud-cors.js";
5
+ import { clientIp, FMCP_RL_PREFIX, generatePairingCode, generatePairingSecret, PAIRING_TTL_SEC, putPairing, rateLimitAllow, getPairing, } from "./cloud-mode-kv.js";
6
+ const PAIRING_HTTP_LIMIT = 20;
7
+ const PAIRING_HTTP_WINDOW_SEC = 60;
8
+ function json(data, status = 200, request) {
9
+ const headers = { "Content-Type": "application/json" };
10
+ if (request)
11
+ Object.assign(headers, corsHeaders(request));
12
+ return new Response(JSON.stringify(data), { status, headers });
13
+ }
14
+ function baseUrl(request) {
15
+ const u = new URL(request.url);
16
+ return `${u.protocol}//${u.host}`;
17
+ }
18
+ export async function handleCloudModeRoutes(request, env) {
19
+ const url = new URL(request.url);
20
+ if (url.pathname === "/fmcp-cloud/plugin") {
21
+ if (request.method === "OPTIONS") {
22
+ return new Response(null, { headers: corsHeaders(request) });
23
+ }
24
+ if (request.headers.get("Upgrade") !== "websocket") {
25
+ return json({ error: "expected_websocket_upgrade" }, 426, request);
26
+ }
27
+ const code = url.searchParams.get("code")?.trim().toUpperCase();
28
+ const secret = url.searchParams.get("secret");
29
+ if (!code || !secret) {
30
+ return json({ error: "missing_code_or_secret" }, 400, request);
31
+ }
32
+ const pair = await getPairing(env.OAUTH_STATE, code);
33
+ if (!pair || pair.secret !== secret) {
34
+ return json({ error: "invalid_or_expired_pairing" }, 401, request);
35
+ }
36
+ const id = env.FMCP_RELAY.idFromName(`pair:${code}`);
37
+ const res = await env.FMCP_RELAY.get(id).fetch(request);
38
+ return withCors(request, res);
39
+ }
40
+ if (url.pathname === "/fmcp-cloud/pairing") {
41
+ if (request.method === "OPTIONS") {
42
+ return new Response(null, { headers: corsHeaders(request) });
43
+ }
44
+ if (request.method !== "POST") {
45
+ return json({ error: "method_not_allowed" }, 405, request);
46
+ }
47
+ const token = env.FMCP_PAIRING_TOKEN;
48
+ if (token) {
49
+ const auth = request.headers.get("Authorization");
50
+ const expected = `Bearer ${token}`;
51
+ if (auth !== expected) {
52
+ return json({ error: "unauthorized" }, 401, request);
53
+ }
54
+ }
55
+ const ip = clientIp(request);
56
+ const rlKey = `${FMCP_RL_PREFIX}pair:${ip}`;
57
+ const ok = await rateLimitAllow(env.OAUTH_STATE, rlKey, PAIRING_HTTP_LIMIT, PAIRING_HTTP_WINDOW_SEC);
58
+ if (!ok) {
59
+ return json({ error: "rate_limited" }, 429, request);
60
+ }
61
+ const code = generatePairingCode();
62
+ const secret = generatePairingSecret();
63
+ const record = { secret, createdAt: Date.now() };
64
+ await putPairing(env.OAUTH_STATE, code, record);
65
+ const origin = baseUrl(request);
66
+ const wsProto = url.protocol === "https:" ? "wss:" : "ws:";
67
+ const wsHost = url.host;
68
+ const pluginWsUrl = `${wsProto}//${wsHost}/fmcp-cloud/plugin?code=${encodeURIComponent(code)}&secret=${encodeURIComponent(secret)}`;
69
+ return json({
70
+ ok: true,
71
+ code,
72
+ secret,
73
+ expiresInSeconds: PAIRING_TTL_SEC,
74
+ pluginWebSocketUrl: pluginWsUrl,
75
+ hint: "Paste code and secret into F-MCP plugin Cloud Mode, or share code + secret with your AI to call fmcp_cloud_bind.",
76
+ }, 200, request);
77
+ }
78
+ if (url.pathname === "/fmcp-cloud/health") {
79
+ return json({ ok: true, cloudMode: true }, 200, request);
80
+ }
81
+ return null;
82
+ }
83
+ /** Merge restrictive CORS onto MCP / SSE responses when Origin matches allowlist. */
84
+ export function maybeTightenMcpCors(request, response) {
85
+ const origin = request.headers.get("Origin");
86
+ if (!origin)
87
+ return response;
88
+ const allowed = getAllowedCorsOrigin(request);
89
+ if (!allowed)
90
+ return response;
91
+ const h = new Headers(response.headers);
92
+ h.set("Access-Control-Allow-Origin", allowed);
93
+ if (!h.has("Access-Control-Expose-Headers")) {
94
+ h.set("Access-Control-Expose-Headers", "mcp-session-id");
95
+ }
96
+ return new Response(response.body, { status: response.status, headers: h });
97
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * FMCP Cloud Mode — per-pairing-code Durable Object.
3
+ * Holds the Figma plugin WebSocket and forwards PluginBridge RPC to the plugin.
4
+ */
5
+ const RPC_TIMEOUT_MS = 120_000;
6
+ export class FmcpRelaySession {
7
+ constructor(ctx, env) {
8
+ this.ctx = ctx;
9
+ this.pending = new Map();
10
+ this.env = env;
11
+ }
12
+ relayNameFromId() {
13
+ return this.ctx.id.toString();
14
+ }
15
+ async fetch(request) {
16
+ const url = new URL(request.url);
17
+ if (request.method === "POST" && url.pathname.endsWith("/disconnect")) {
18
+ for (const s of this.ctx.getWebSockets()) {
19
+ try {
20
+ s.close(1000, "disconnect");
21
+ }
22
+ catch {
23
+ /* ignore */
24
+ }
25
+ }
26
+ return Response.json({ ok: true, closed: true });
27
+ }
28
+ if (request.method === "POST" && url.pathname.endsWith("/rpc")) {
29
+ return this.handleRpc(request);
30
+ }
31
+ if (request.method === "GET" && url.pathname.endsWith("/status")) {
32
+ const sockets = this.ctx.getWebSockets();
33
+ const connected = sockets.length > 0;
34
+ return Response.json({
35
+ ok: true,
36
+ pluginConnected: connected,
37
+ relayId: this.relayNameFromId(),
38
+ });
39
+ }
40
+ if (request.headers.get("Upgrade") !== "websocket") {
41
+ return new Response("Not found", { status: 404 });
42
+ }
43
+ const pair = new WebSocketPair();
44
+ const [client, server] = Object.values(pair);
45
+ this.ctx.acceptWebSocket(server);
46
+ return new Response(null, { status: 101, webSocket: client });
47
+ }
48
+ async handleRpc(request) {
49
+ let body;
50
+ try {
51
+ body = (await request.json());
52
+ }
53
+ catch {
54
+ return Response.json({ ok: false, error: "invalid_json" }, { status: 400 });
55
+ }
56
+ const method = body.method;
57
+ if (!method || typeof method !== "string") {
58
+ return Response.json({ ok: false, error: "missing_method" }, { status: 400 });
59
+ }
60
+ const sockets = this.ctx.getWebSockets();
61
+ if (!sockets || sockets.length === 0) {
62
+ return Response.json({ ok: false, error: "plugin_not_connected" }, { status: 503 });
63
+ }
64
+ const ws = sockets[0];
65
+ const id = `req_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
66
+ const payload = JSON.stringify({
67
+ id,
68
+ method,
69
+ params: body.params ?? {},
70
+ });
71
+ try {
72
+ const result = await new Promise((resolve, reject) => {
73
+ const timeout = setTimeout(() => {
74
+ this.pending.delete(id);
75
+ reject(new Error(`Plugin bridge request '${method}' timed out after ${RPC_TIMEOUT_MS}ms`));
76
+ }, RPC_TIMEOUT_MS);
77
+ this.pending.set(id, { resolve, reject, timeout });
78
+ ws.send(payload);
79
+ });
80
+ return Response.json({ ok: true, result });
81
+ }
82
+ catch (e) {
83
+ const msg = e instanceof Error ? e.message : String(e);
84
+ return Response.json({ ok: false, error: msg }, { status: 500 });
85
+ }
86
+ }
87
+ async webSocketMessage(ws, message) {
88
+ const text = typeof message === "string" ? message : new TextDecoder().decode(message);
89
+ let msg;
90
+ try {
91
+ msg = JSON.parse(text);
92
+ }
93
+ catch {
94
+ return;
95
+ }
96
+ const t = msg.type;
97
+ if (t === "pong" || t === "keepalive")
98
+ return;
99
+ if (t === "ready") {
100
+ try {
101
+ ws.send(JSON.stringify({
102
+ type: "welcome",
103
+ bridgeVersion: "cloud-1.0.0",
104
+ port: 0,
105
+ clientId: `cloud_${Date.now()}`,
106
+ multiClient: false,
107
+ }));
108
+ }
109
+ catch {
110
+ /* ignore */
111
+ }
112
+ return;
113
+ }
114
+ const mid = msg.id;
115
+ if (mid && this.pending.has(mid)) {
116
+ const p = this.pending.get(mid);
117
+ this.pending.delete(mid);
118
+ clearTimeout(p.timeout);
119
+ if (msg.error) {
120
+ p.reject(new Error(String(msg.error)));
121
+ }
122
+ else {
123
+ p.resolve(msg.result);
124
+ }
125
+ }
126
+ }
127
+ async webSocketClose(_ws, _code, _reason, _wasClean) {
128
+ for (const [rid, p] of this.pending) {
129
+ clearTimeout(p.timeout);
130
+ p.reject(new Error("Plugin disconnected"));
131
+ this.pending.delete(rid);
132
+ }
133
+ }
134
+ async webSocketError(_ws, _error) {
135
+ for (const [rid, p] of this.pending) {
136
+ clearTimeout(p.timeout);
137
+ p.reject(new Error("Plugin WebSocket error"));
138
+ this.pending.delete(rid);
139
+ }
140
+ }
141
+ }
@@ -51,7 +51,7 @@ const DEFAULT_CONFIG = {
51
51
  local: {
52
52
  debugHost: process.env.FIGMA_DEBUG_HOST || 'localhost',
53
53
  debugPort: parseInt(process.env.FIGMA_DEBUG_PORT || '9222', 10),
54
- pluginBridgePort: parseInt(process.env.FIGMA_PLUGIN_BRIDGE_PORT || '5454', 10),
54
+ pluginBridgePort: parseInt(process.env.FIGMA_MCP_BRIDGE_PORT || process.env.FIGMA_PLUGIN_BRIDGE_PORT || '5454', 10),
55
55
  auditLogPath: process.env.FIGMA_MCP_AUDIT_LOG_PATH || undefined,
56
56
  },
57
57
  };