figmanage 1.2.9 → 1.3.0

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.
package/README.md CHANGED
@@ -49,23 +49,25 @@ figmanage runs as an MCP server for Claude, ChatGPT, Cursor, and other AI assist
49
49
 
50
50
  ```bash
51
51
  # Claude Code
52
- claude mcp add figmanage -- npx -y figmanage --mcp
52
+ claude mcp add figmanage -- npx -y figmanage
53
53
 
54
- # Claude Desktop / Cowork
55
- # Add to claude_desktop_config.json:
54
+ # Claude Desktop / Cursor / other clients
55
+ # Add to your MCP config:
56
56
  {
57
57
  "mcpServers": {
58
58
  "figmanage": {
59
59
  "command": "npx",
60
- "args": ["-y", "figmanage", "--mcp"]
60
+ "args": ["-y", "figmanage"]
61
61
  }
62
62
  }
63
63
  }
64
64
  ```
65
65
 
66
- Credentials are read from `~/.config/figmanage/` (set up via `figmanage login`). Env vars (`FIGMA_PAT`, `FIGMA_AUTH_COOKIE`, etc.) override the config file for backwards compatibility.
66
+ On first run, figmanage walks you through setup right in the conversation -- extracts your Chrome session cookie, asks you to create a PAT, and stores credentials locally. No env vars, no JSON editing, no separate terminal step.
67
67
 
68
- MCP mode exposes all 85 tools to the AI assistant. HTTP transport available via `--mcp --http <port>`.
68
+ If you prefer CLI setup or need to reconfigure: `npx figmanage login`.
69
+
70
+ Env vars (`FIGMA_PAT`, `FIGMA_AUTH_COOKIE`, etc.) override the config file for backwards compatibility. HTTP transport available via `--mcp --http <port>`.
69
71
 
70
72
  ### toolset presets (MCP only)
71
73
 
@@ -87,9 +89,9 @@ figmanage uses two Figma API surfaces:
87
89
  | Internal API | Session cookie | Workspace management, search, permissions, org admin, seats, billing |
88
90
  | Public API | Personal Access Token | Comments, export, file reading, components, versions, webhooks, variables |
89
91
 
90
- Cookie auth unlocks all 85 tools. PAT-only gives ~30 (reading, comments, export, components). Both together is recommended.
92
+ Both together give full access to all 85 tools. Cookie-only or PAT-only work but limit available tools.
91
93
 
92
- Auth resolution: env vars > config file > prompt to run `figmanage login`.
94
+ Auth resolution: env vars > config file > interactive setup (MCP) or `figmanage login` (CLI).
93
95
 
94
96
  ## commands
95
97
 
package/dist/mcp.js CHANGED
@@ -5,6 +5,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
5
5
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
6
6
  import { loadAuthConfig, hasPat, hasCookie } from './auth/client.js';
7
7
  import { registerTools } from './tools/register.js';
8
+ import { registerSetupTools } from './tools/setup.js';
8
9
  // Import tool modules (side-effect: registers via defineTool)
9
10
  import './tools/navigate.js';
10
11
  import './tools/files.js';
@@ -64,16 +65,27 @@ export async function startMcpServer() {
64
65
  const config = loadAuthConfig();
65
66
  const readOnly = process.env.FIGMA_READ_ONLY === '1' || process.env.FIGMA_READ_ONLY === 'true';
66
67
  const enabledToolsets = parseToolsets(process.env.FIGMA_TOOLSETS);
67
- if (!hasPat(config) && !hasCookie(config)) {
68
- console.error('No auth configured. Set FIGMA_PAT for public API access, or ' +
69
- 'FIGMA_AUTH_COOKIE + FIGMA_USER_ID for internal API access.');
70
- process.exit(1);
71
- }
72
68
  const server = new McpServer({
73
69
  name: 'figmanage',
74
70
  version: '0.1.0',
75
71
  });
76
- registerTools(server, config, enabledToolsets, readOnly);
72
+ const fullyConfigured = hasPat(config) && hasCookie(config);
73
+ if (fullyConfigured) {
74
+ registerTools(server, config, enabledToolsets, readOnly);
75
+ }
76
+ else {
77
+ // No auth or partial auth: register setup tools.
78
+ // If env vars provide some auth, also register whatever tools are available.
79
+ if (hasPat(config) || hasCookie(config)) {
80
+ registerTools(server, config, enabledToolsets, readOnly);
81
+ }
82
+ registerSetupTools(server, () => {
83
+ // Re-load config after setup wrote credentials to disk
84
+ const newConfig = loadAuthConfig();
85
+ registerTools(server, newConfig, enabledToolsets, readOnly);
86
+ server.server.sendToolListChanged();
87
+ });
88
+ }
77
89
  const httpPort = parseHttpPort(process.argv);
78
90
  if (httpPort) {
79
91
  // Bearer token auth for HTTP transport
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Interactive setup tools for first-run MCP configuration.
3
+ *
4
+ * Guides the user through cookie extraction and PAT creation
5
+ * via conversational tool calls. Registered when auth is missing;
6
+ * replaced by the full toolset once setup completes.
7
+ */
8
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
+ export declare function registerSetupTools(server: McpServer, onSetupComplete: () => void): void;
10
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Interactive setup tools for first-run MCP configuration.
3
+ *
4
+ * Guides the user through cookie extraction and PAT creation
5
+ * via conversational tool calls. Registered when auth is missing;
6
+ * replaced by the full toolset once setup completes.
7
+ */
8
+ import { platform } from 'node:os';
9
+ import { z } from 'zod';
10
+ import { extractCookies, validateSession, validatePat, resolveAccountInfo, } from '../auth/cookie.js';
11
+ import { setActiveWorkspace, getActiveWorkspace } from '../config.js';
12
+ import { loadAuthConfig, hasPat, hasCookie } from '../auth/client.js';
13
+ // State shared across setup steps within a single session
14
+ let pendingAccounts = [];
15
+ export function registerSetupTools(server, onSetupComplete) {
16
+ server.tool('setup_status', 'Check figmanage authentication status and get setup instructions. Always call this before using any other tool.', {}, async () => {
17
+ const config = loadAuthConfig();
18
+ if (hasPat(config) && hasCookie(config)) {
19
+ return { content: [{ type: 'text', text: 'figmanage is fully configured. All tools are available.' }] };
20
+ }
21
+ const os = platform();
22
+ const lines = [
23
+ 'figmanage needs two credentials to give you full access to all 85 Figma workspace tools:',
24
+ '',
25
+ '1. Browser cookie -- extracted automatically from Chrome',
26
+ '2. Personal Access Token (PAT) -- created in Figma settings',
27
+ '',
28
+ ];
29
+ if (!hasCookie(config)) {
30
+ lines.push('NEXT: Cookie extraction');
31
+ lines.push('The user must be logged into figma.com in Chrome before proceeding.');
32
+ if (os === 'darwin') {
33
+ lines.push('');
34
+ lines.push('When they proceed, a macOS Keychain prompt will appear asking to access');
35
+ lines.push('"Chrome Safe Storage". They need to click Allow.');
36
+ }
37
+ else if (os === 'linux') {
38
+ lines.push('');
39
+ lines.push('On Linux, Chrome cookies are decrypted using the system keyring or a default key.');
40
+ }
41
+ lines.push('');
42
+ lines.push('Ask the user to confirm they are logged into figma.com in Chrome, then call setup_extract_cookies.');
43
+ }
44
+ else {
45
+ lines.push('Cookie auth is configured.');
46
+ lines.push('');
47
+ lines.push('NEXT: Personal Access Token');
48
+ lines.push('Ask the user to create a PAT at: https://www.figma.com/settings');
49
+ lines.push('(Security > Personal access tokens)');
50
+ lines.push('Then call setup_save_pat with the token value.');
51
+ }
52
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
53
+ });
54
+ server.tool('setup_extract_cookies', 'Extract Figma session cookies from Chrome. On macOS this triggers a Keychain prompt. IMPORTANT: Before calling, confirm the user is logged into figma.com in Chrome and knows a system prompt may appear.', {}, async () => {
55
+ try {
56
+ const accounts = extractCookies();
57
+ if (accounts.length === 0) {
58
+ return {
59
+ content: [{
60
+ type: 'text',
61
+ text: 'No Figma cookies found in Chrome. The user needs to log into figma.com in Chrome first, then try again.',
62
+ }],
63
+ };
64
+ }
65
+ pendingAccounts = accounts;
66
+ const infos = await Promise.all(accounts.map(a => resolveAccountInfo(a)));
67
+ const lines = [
68
+ `Found ${accounts.length} Figma account${accounts.length > 1 ? 's' : ''}:`,
69
+ '',
70
+ ];
71
+ for (let i = 0; i < accounts.length; i++) {
72
+ const info = infos[i];
73
+ const label = info.figmaEmail || `User ${accounts[i].userId}`;
74
+ lines.push(` ${i + 1}. ${label} (Chrome profile: ${info.profileName})`);
75
+ }
76
+ lines.push('');
77
+ lines.push('Ask the user which account to use, then call setup_select_account with the chosen number.');
78
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
79
+ }
80
+ catch (e) {
81
+ return {
82
+ isError: true,
83
+ content: [{ type: 'text', text: `Cookie extraction failed: ${e.message}` }],
84
+ };
85
+ }
86
+ });
87
+ server.tool('setup_select_account', 'Select a Figma account and validate the session. Call after setup_extract_cookies.', {
88
+ account_index: z.number().int().min(1).describe('Account number from the list returned by setup_extract_cookies'),
89
+ }, async ({ account_index }) => {
90
+ if (pendingAccounts.length === 0) {
91
+ return {
92
+ isError: true,
93
+ content: [{ type: 'text', text: 'No accounts available. Call setup_extract_cookies first.' }],
94
+ };
95
+ }
96
+ const idx = account_index - 1;
97
+ if (idx < 0 || idx >= pendingAccounts.length) {
98
+ return {
99
+ isError: true,
100
+ content: [{ type: 'text', text: `Invalid selection. Choose 1-${pendingAccounts.length}.` }],
101
+ };
102
+ }
103
+ const account = pendingAccounts[idx];
104
+ try {
105
+ const session = await validateSession(account.cookieValue, account.userId);
106
+ let orgId = session.orgId;
107
+ if (!orgId && session.orgs.length > 0) {
108
+ orgId = session.orgs[0].id;
109
+ }
110
+ const workspaceName = orgId || account.userId || 'default';
111
+ setActiveWorkspace(workspaceName, {
112
+ cookie: account.cookieValue,
113
+ user_id: account.userId,
114
+ org_id: orgId || undefined,
115
+ cookie_extracted_at: new Date().toISOString(),
116
+ });
117
+ const lines = [`Session valid (user ${account.userId}).`];
118
+ if (session.orgs.length > 0) {
119
+ const orgName = session.orgs.find(o => o.id === orgId)?.name;
120
+ lines.push(`Workspace: ${orgName ? `${orgName} (${orgId})` : orgId}`);
121
+ }
122
+ if (session.teams.length > 0) {
123
+ lines.push(`Teams: ${session.teams.map(t => t.name).join(', ')}`);
124
+ }
125
+ lines.push('');
126
+ lines.push('Cookie saved. Now need a Personal Access Token for full access.');
127
+ lines.push('Ask the user to create one at: https://www.figma.com/settings');
128
+ lines.push('(Security > Personal access tokens)');
129
+ lines.push('Then call setup_save_pat with the token.');
130
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
131
+ }
132
+ catch (e) {
133
+ const status = e.response?.status;
134
+ if (status === 401 || status === 403) {
135
+ return {
136
+ isError: true,
137
+ content: [{ type: 'text', text: 'Cookie expired or invalid. Log into figma.com in Chrome and run setup_extract_cookies again.' }],
138
+ };
139
+ }
140
+ return {
141
+ isError: true,
142
+ content: [{ type: 'text', text: `Session validation failed: ${e.message}` }],
143
+ };
144
+ }
145
+ });
146
+ server.tool('setup_save_pat', 'Validate and save a Figma Personal Access Token. Completes setup and activates all tools.', {
147
+ pat: z.string().min(1).describe('Figma Personal Access Token (starts with figd_)'),
148
+ }, async ({ pat }) => {
149
+ try {
150
+ const patUser = await validatePat(pat);
151
+ // Update the active workspace with the PAT
152
+ const workspace = getActiveWorkspace();
153
+ if (!workspace) {
154
+ return {
155
+ isError: true,
156
+ content: [{ type: 'text', text: 'No workspace configured. Run setup_extract_cookies and setup_select_account first.' }],
157
+ };
158
+ }
159
+ workspace.pat = pat;
160
+ const workspaceName = workspace.org_id || workspace.user_id || 'default';
161
+ setActiveWorkspace(workspaceName, workspace);
162
+ // Clear setup state
163
+ pendingAccounts = [];
164
+ // Trigger full tool registration
165
+ onSetupComplete();
166
+ return {
167
+ content: [{
168
+ type: 'text',
169
+ text: `PAT valid (${patUser}). Setup complete -- all 85 figmanage tools are now available.`,
170
+ }],
171
+ };
172
+ }
173
+ catch {
174
+ return {
175
+ isError: true,
176
+ content: [{ type: 'text', text: 'PAT invalid or expired. Check the token and try again.' }],
177
+ };
178
+ }
179
+ });
180
+ }
181
+ //# sourceMappingURL=setup.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "figmanage",
3
3
  "mcpName": "io.github.dannykeane/figmanage",
4
- "version": "1.2.9",
4
+ "version": "1.3.0",
5
5
  "description": "MCP server for managing your Figma workspace from the terminal.",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",