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 +10 -8
- package/dist/mcp.js +18 -6
- package/dist/tools/setup.d.ts +10 -0
- package/dist/tools/setup.js +181 -0
- package/package.json +1 -1
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
|
|
52
|
+
claude mcp add figmanage -- npx -y figmanage
|
|
53
53
|
|
|
54
|
-
# Claude Desktop /
|
|
55
|
-
# Add to
|
|
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"
|
|
60
|
+
"args": ["-y", "figmanage"]
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 >
|
|
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
|
-
|
|
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