@seed-design/mcp 0.0.6 → 0.0.15
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/bin/main.mjs +16904 -0
- package/bin/socket.mjs +155 -0
- package/package.json +8 -3
- package/src/bin/main.ts +52 -0
- package/src/bin/socket.ts +194 -0
- package/src/logger.ts +32 -34
- package/src/prompts.ts +27 -0
- package/src/responses.ts +90 -0
- package/src/tools.ts +436 -0
- package/src/types.ts +67 -0
- package/src/websocket.ts +248 -0
- package/bin/index.mjs +0 -4964
- package/src/bin/index.ts +0 -34
- package/src/config.ts +0 -166
- package/src/figma.ts +0 -211
- package/src/index.ts +0 -36
- package/src/save-image.ts +0 -16
- package/src/server.ts +0 -288
package/src/bin/index.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { startServer } from "../index";
|
|
4
|
-
import { ConsoleLogger } from "../logger";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Main CLI entry point
|
|
8
|
-
*/
|
|
9
|
-
async function main(): Promise<void> {
|
|
10
|
-
try {
|
|
11
|
-
// Set environment to indicate CLI mode
|
|
12
|
-
process.env["NODE_ENV"] = "cli";
|
|
13
|
-
|
|
14
|
-
// Start server
|
|
15
|
-
await startServer();
|
|
16
|
-
} catch (error) {
|
|
17
|
-
handleStartupError(error);
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Handles and logs startup errors
|
|
24
|
-
*/
|
|
25
|
-
function handleStartupError(error: unknown): void {
|
|
26
|
-
if (error instanceof Error) {
|
|
27
|
-
ConsoleLogger.error("Failed to start server:", error.message);
|
|
28
|
-
} else {
|
|
29
|
-
ConsoleLogger.error("Failed to start server with unknown error:", error);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Run the application
|
|
34
|
-
main().catch(handleStartupError);
|
package/src/config.ts
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import yargs from "yargs";
|
|
2
|
-
import { hideBin } from "yargs/helpers";
|
|
3
|
-
import type { Logger } from "./logger";
|
|
4
|
-
import { NoOpLogger } from "./logger";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Configuration for the Figma MCP Server
|
|
8
|
-
*/
|
|
9
|
-
export interface ServerConfig {
|
|
10
|
-
figmaApiKey: string;
|
|
11
|
-
port: number;
|
|
12
|
-
configSources: {
|
|
13
|
-
figmaApiKey: ConfigSource;
|
|
14
|
-
port: ConfigSource;
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Source of configuration value
|
|
20
|
-
*/
|
|
21
|
-
type ConfigSource = "cli" | "env" | "default";
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Command line arguments
|
|
25
|
-
*/
|
|
26
|
-
interface CliArgs {
|
|
27
|
-
"figma-api-key"?: string;
|
|
28
|
-
port?: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Configuration manager for the Figma MCP Server
|
|
33
|
-
*/
|
|
34
|
-
export class ConfigManager {
|
|
35
|
-
private readonly isStdioMode: boolean;
|
|
36
|
-
private readonly logger: Logger;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Creates a new ConfigManager instance
|
|
40
|
-
*/
|
|
41
|
-
constructor(options: { isStdioMode: boolean; logger?: Logger }) {
|
|
42
|
-
this.isStdioMode = options.isStdioMode;
|
|
43
|
-
this.logger = options.logger || NoOpLogger;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Gets the server configuration from command line arguments and environment variables
|
|
48
|
-
*/
|
|
49
|
-
getServerConfig(): ServerConfig {
|
|
50
|
-
// Parse command line arguments
|
|
51
|
-
const argv = this.parseCommandLineArgs();
|
|
52
|
-
|
|
53
|
-
// Initialize config with default values
|
|
54
|
-
const config: ServerConfig = {
|
|
55
|
-
figmaApiKey: "",
|
|
56
|
-
port: 3333,
|
|
57
|
-
configSources: {
|
|
58
|
-
figmaApiKey: "env",
|
|
59
|
-
port: "default",
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// Handle FIGMA_API_KEY
|
|
64
|
-
this.configureFigmaApiKey(config, argv);
|
|
65
|
-
|
|
66
|
-
// Handle PORT
|
|
67
|
-
this.configurePort(config, argv);
|
|
68
|
-
|
|
69
|
-
// Validate configuration
|
|
70
|
-
this.validateConfig(config);
|
|
71
|
-
|
|
72
|
-
// Log configuration sources
|
|
73
|
-
this.logConfig(config);
|
|
74
|
-
|
|
75
|
-
return config;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Parses command line arguments
|
|
80
|
-
*/
|
|
81
|
-
private parseCommandLineArgs(): CliArgs {
|
|
82
|
-
return yargs(hideBin(process.argv))
|
|
83
|
-
.options({
|
|
84
|
-
"figma-api-key": {
|
|
85
|
-
type: "string",
|
|
86
|
-
description: "Figma API key",
|
|
87
|
-
},
|
|
88
|
-
port: {
|
|
89
|
-
type: "number",
|
|
90
|
-
description: "Port to run the server on",
|
|
91
|
-
},
|
|
92
|
-
})
|
|
93
|
-
.help()
|
|
94
|
-
.parseSync() as CliArgs;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Configures the Figma API key
|
|
99
|
-
*/
|
|
100
|
-
private configureFigmaApiKey(config: ServerConfig, argv: CliArgs): void {
|
|
101
|
-
if (argv["figma-api-key"]) {
|
|
102
|
-
config.figmaApiKey = argv["figma-api-key"];
|
|
103
|
-
config.configSources.figmaApiKey = "cli";
|
|
104
|
-
} else if (process.env["FIGMA_API_KEY"]) {
|
|
105
|
-
config.figmaApiKey = process.env["FIGMA_API_KEY"];
|
|
106
|
-
config.configSources.figmaApiKey = "env";
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Configures the server port
|
|
112
|
-
*/
|
|
113
|
-
private configurePort(config: ServerConfig, argv: CliArgs): void {
|
|
114
|
-
if (argv.port) {
|
|
115
|
-
config.port = argv.port;
|
|
116
|
-
config.configSources.port = "cli";
|
|
117
|
-
} else if (process.env["PORT"]) {
|
|
118
|
-
config.port = Number.parseInt(process.env["PORT"], 10);
|
|
119
|
-
config.configSources.port = "env";
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Validates the configuration
|
|
125
|
-
*/
|
|
126
|
-
private validateConfig(config: ServerConfig): void {
|
|
127
|
-
if (!config.figmaApiKey) {
|
|
128
|
-
this.logger.error(
|
|
129
|
-
"FIGMA_API_KEY is required (via CLI argument --figma-api-key or .env file)",
|
|
130
|
-
);
|
|
131
|
-
process.exit(1);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Logs the configuration
|
|
137
|
-
*/
|
|
138
|
-
private logConfig(config: ServerConfig): void {
|
|
139
|
-
if (this.isStdioMode) {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
this.logger.log("\nConfiguration:");
|
|
144
|
-
this.logger.log(
|
|
145
|
-
`- FIGMA_API_KEY: ${this.maskApiKey(config.figmaApiKey)} (source: ${config.configSources.figmaApiKey})`,
|
|
146
|
-
);
|
|
147
|
-
this.logger.log(`- PORT: ${config.port} (source: ${config.configSources.port})`);
|
|
148
|
-
this.logger.log(""); // Empty line for better readability
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Masks an API key for secure logging
|
|
153
|
-
*/
|
|
154
|
-
private maskApiKey(key: string): string {
|
|
155
|
-
if (key.length <= 4) return "****";
|
|
156
|
-
return `****${key.slice(-4)}`;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Gets the server configuration
|
|
162
|
-
*/
|
|
163
|
-
export function getServerConfig(isStdioMode: boolean): ServerConfig {
|
|
164
|
-
const configManager = new ConfigManager({ isStdioMode });
|
|
165
|
-
return configManager.getServerConfig();
|
|
166
|
-
}
|
package/src/figma.ts
DELETED
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import type { GetFileNodesResponse, GetImagesResponse } from "@figma/rest-api-spec";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import { generateCode, createRestNormalizer } from "@seed-design/figma";
|
|
4
|
-
import type { Logger } from "./logger";
|
|
5
|
-
import { NoOpLogger } from "./logger";
|
|
6
|
-
|
|
7
|
-
export interface FigmaError {
|
|
8
|
-
status: number;
|
|
9
|
-
err: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface FetchImageParams {
|
|
13
|
-
/**
|
|
14
|
-
* The Node in Figma that will either be rendered or have its background image downloaded
|
|
15
|
-
*/
|
|
16
|
-
nodeId: string;
|
|
17
|
-
/**
|
|
18
|
-
* The file mimetype for the image
|
|
19
|
-
*/
|
|
20
|
-
fileType: "png" | "svg";
|
|
21
|
-
/**
|
|
22
|
-
* The filename to save the image as
|
|
23
|
-
*/
|
|
24
|
-
name: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface FigmaImage {
|
|
28
|
-
nodeId: string;
|
|
29
|
-
name: string;
|
|
30
|
-
blob: Buffer;
|
|
31
|
-
fileType: "png" | "svg";
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface SimplifiedDesign {
|
|
35
|
-
name: string;
|
|
36
|
-
lastModified: string;
|
|
37
|
-
code: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface FigmaServiceOptions {
|
|
41
|
-
apiKey: string;
|
|
42
|
-
baseUrl?: string;
|
|
43
|
-
logger?: Logger;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export class FigmaService {
|
|
47
|
-
private readonly apiKey: string;
|
|
48
|
-
private readonly baseUrl: string;
|
|
49
|
-
private readonly logger: Logger;
|
|
50
|
-
|
|
51
|
-
constructor(options: FigmaServiceOptions) {
|
|
52
|
-
this.apiKey = options.apiKey;
|
|
53
|
-
this.baseUrl = options.baseUrl || "https://api.figma.com/v1";
|
|
54
|
-
this.logger = options.logger || NoOpLogger;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
private async request<T>(endpoint: string): Promise<T> {
|
|
58
|
-
try {
|
|
59
|
-
this.logger.log(`Calling ${this.baseUrl}${endpoint}`);
|
|
60
|
-
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
61
|
-
headers: {
|
|
62
|
-
"X-Figma-Token": this.apiKey,
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
if (!response.ok) {
|
|
67
|
-
const errorData = (await response.json().catch(() => ({}))) as { err?: string };
|
|
68
|
-
throw {
|
|
69
|
-
status: response.status,
|
|
70
|
-
err: errorData.err || "Unknown error",
|
|
71
|
-
} as FigmaError;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return response.json() as Promise<T>;
|
|
75
|
-
} catch (error) {
|
|
76
|
-
if (error instanceof Error) {
|
|
77
|
-
throw new Error(`Failed to make request to Figma API: ${error.message}`);
|
|
78
|
-
}
|
|
79
|
-
throw error;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async getImages(fileKey: string, nodes: FetchImageParams[]): Promise<FigmaImage[]> {
|
|
84
|
-
const pngNodes = nodes.filter(({ fileType }) => fileType === "png");
|
|
85
|
-
const svgNodes = nodes.filter(({ fileType }) => fileType === "svg");
|
|
86
|
-
|
|
87
|
-
const imagesMap = await this.fetchImageUrls(fileKey, pngNodes, svgNodes);
|
|
88
|
-
|
|
89
|
-
const images = await this.fetchImage(nodes, imagesMap);
|
|
90
|
-
|
|
91
|
-
return images;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
private async fetchImageUrls(
|
|
95
|
-
fileKey: string,
|
|
96
|
-
pngNodes: FetchImageParams[],
|
|
97
|
-
svgNodes: FetchImageParams[],
|
|
98
|
-
): Promise<Record<string, string>> {
|
|
99
|
-
const pngIds = pngNodes.map(({ nodeId }) => nodeId);
|
|
100
|
-
const pngFiles =
|
|
101
|
-
pngIds.length > 0
|
|
102
|
-
? this.request<GetImagesResponse>(
|
|
103
|
-
`/images/${fileKey}?ids=${pngIds.join(",")}&scale=2&format=png`,
|
|
104
|
-
).then(({ images = {} }) => images)
|
|
105
|
-
: ({} as GetImagesResponse["images"]);
|
|
106
|
-
|
|
107
|
-
const svgIds = svgNodes.map(({ nodeId }) => nodeId);
|
|
108
|
-
const svgFiles =
|
|
109
|
-
svgIds.length > 0
|
|
110
|
-
? this.request<GetImagesResponse>(
|
|
111
|
-
`/images/${fileKey}?ids=${svgIds.join(",")}&format=svg`,
|
|
112
|
-
).then(({ images = {} }) => images)
|
|
113
|
-
: ({} as GetImagesResponse["images"]);
|
|
114
|
-
|
|
115
|
-
const [pngImages, svgImages] = await Promise.all([pngFiles, svgFiles]);
|
|
116
|
-
const combinedImages: Record<string, string> = {};
|
|
117
|
-
|
|
118
|
-
Object.entries({ ...pngImages, ...svgImages }).forEach(([key, value]) => {
|
|
119
|
-
if (value !== null) {
|
|
120
|
-
combinedImages[key] = value;
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
return combinedImages;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
private async fetchImage(
|
|
128
|
-
nodes: FetchImageParams[],
|
|
129
|
-
imagesMap: Record<string, string>,
|
|
130
|
-
): Promise<FigmaImage[]> {
|
|
131
|
-
const fetchPromises = nodes
|
|
132
|
-
.map(({ nodeId, name, fileType }) => {
|
|
133
|
-
const imageUrl = imagesMap[nodeId];
|
|
134
|
-
if (imageUrl) {
|
|
135
|
-
return fetch(imageUrl)
|
|
136
|
-
.then((response) => response.arrayBuffer())
|
|
137
|
-
.then((arrayBuffer) => Buffer.from(arrayBuffer))
|
|
138
|
-
.then((buffer) => {
|
|
139
|
-
return {
|
|
140
|
-
nodeId,
|
|
141
|
-
name,
|
|
142
|
-
blob: buffer,
|
|
143
|
-
fileType,
|
|
144
|
-
};
|
|
145
|
-
})
|
|
146
|
-
.catch((error) => {
|
|
147
|
-
this.logger.error(`Failed to fetch image for ${nodeId}:`, error);
|
|
148
|
-
return undefined;
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
return undefined;
|
|
152
|
-
})
|
|
153
|
-
.filter((x): x is Promise<FigmaImage> => x !== undefined);
|
|
154
|
-
|
|
155
|
-
return Promise.all(fetchPromises);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async getGeneratedCode(
|
|
159
|
-
fileKey: string,
|
|
160
|
-
nodeId: string,
|
|
161
|
-
depth?: number,
|
|
162
|
-
): Promise<SimplifiedDesign> {
|
|
163
|
-
const endpoint = `/files/${fileKey}/nodes?ids=${nodeId}${depth ? `&depth=${depth}` : ""}`;
|
|
164
|
-
const response = await this.request<GetFileNodesResponse>(endpoint);
|
|
165
|
-
|
|
166
|
-
this.writeDebugLogs("figma-raw.json", response);
|
|
167
|
-
|
|
168
|
-
const node = Object.values(response.nodes)[0]!;
|
|
169
|
-
|
|
170
|
-
const normalizer = createRestNormalizer({
|
|
171
|
-
styles: node.styles,
|
|
172
|
-
components: node.components,
|
|
173
|
-
componentSets: node.componentSets,
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
const normalizedNode = normalizer(node.document);
|
|
177
|
-
const code = generateCode(normalizedNode) ?? "";
|
|
178
|
-
|
|
179
|
-
const result = {
|
|
180
|
-
name: node.document.name,
|
|
181
|
-
lastModified: response.lastModified,
|
|
182
|
-
code,
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
this.writeDebugLogs("figma-result.json", result);
|
|
186
|
-
return result;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
private writeDebugLogs(name: string, value: any): void {
|
|
190
|
-
try {
|
|
191
|
-
if (process.env["NODE_ENV"] !== "development") return;
|
|
192
|
-
|
|
193
|
-
const logsDir = "logs";
|
|
194
|
-
|
|
195
|
-
try {
|
|
196
|
-
fs.accessSync(process.cwd(), fs.constants.W_OK);
|
|
197
|
-
} catch (error) {
|
|
198
|
-
this.logger.error("Failed to write logs:", error);
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (!fs.existsSync(logsDir)) {
|
|
203
|
-
fs.mkdirSync(logsDir);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
fs.writeFileSync(`${logsDir}/${name}`, JSON.stringify(value, null, 2));
|
|
207
|
-
} catch (error) {
|
|
208
|
-
this.logger.error("Failed to write logs:", error);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2
|
-
import { FigmaMcpServer } from "./server";
|
|
3
|
-
import { getServerConfig } from "./config";
|
|
4
|
-
import { ConsoleLogger } from "./logger";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Determines if the server should run in stdio mode
|
|
8
|
-
*/
|
|
9
|
-
function isRunningInStdioMode(): boolean {
|
|
10
|
-
return process.env["NODE_ENV"] === "cli" || process.argv.includes("--stdio");
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Starts the Figma MCP server
|
|
15
|
-
*/
|
|
16
|
-
export async function startServer(): Promise<void> {
|
|
17
|
-
// Determine server mode (stdio or HTTP)
|
|
18
|
-
const stdioMode = isRunningInStdioMode();
|
|
19
|
-
|
|
20
|
-
// Get configuration
|
|
21
|
-
const config = getServerConfig(stdioMode);
|
|
22
|
-
|
|
23
|
-
// Create server instance
|
|
24
|
-
const server = new FigmaMcpServer(config.figmaApiKey);
|
|
25
|
-
|
|
26
|
-
// Start in appropriate mode
|
|
27
|
-
if (stdioMode) {
|
|
28
|
-
// Connect to stdio transport for CLI mode
|
|
29
|
-
const transport = new StdioServerTransport();
|
|
30
|
-
await server.connect(transport);
|
|
31
|
-
} else {
|
|
32
|
-
// Start HTTP server for web mode
|
|
33
|
-
ConsoleLogger.log(`Initializing Figma MCP Server in HTTP mode on port ${config.port}...`);
|
|
34
|
-
await server.startHttpServer(config.port);
|
|
35
|
-
}
|
|
36
|
-
}
|
package/src/save-image.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import * as os from "node:os";
|
|
4
|
-
|
|
5
|
-
const defaultDownloadsPath = path.join(os.homedir(), "Downloads");
|
|
6
|
-
|
|
7
|
-
export function saveImage(image: Buffer, name: string, type: "png" | "svg") {
|
|
8
|
-
if (!fs.existsSync(defaultDownloadsPath)) {
|
|
9
|
-
fs.mkdirSync(defaultDownloadsPath, { recursive: true });
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const filePath = path.join(defaultDownloadsPath, `${name}.${type}`);
|
|
13
|
-
fs.writeFileSync(filePath, image);
|
|
14
|
-
|
|
15
|
-
return filePath;
|
|
16
|
-
}
|