@gemini-designer/mcp-server 0.1.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/.prettierrc +9 -0
- package/dist/components/catalog.d.ts +24 -0
- package/dist/components/catalog.d.ts.map +1 -0
- package/dist/components/catalog.js +186 -0
- package/dist/components/catalog.js.map +1 -0
- package/dist/config/index.d.ts +60 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +199 -0
- package/dist/config/index.js.map +1 -0
- package/dist/context/builder.d.ts +32 -0
- package/dist/context/builder.d.ts.map +1 -0
- package/dist/context/builder.js +194 -0
- package/dist/context/builder.js.map +1 -0
- package/dist/context/filter.d.ts +28 -0
- package/dist/context/filter.d.ts.map +1 -0
- package/dist/context/filter.js +136 -0
- package/dist/context/filter.js.map +1 -0
- package/dist/context/grounding.d.ts +27 -0
- package/dist/context/grounding.d.ts.map +1 -0
- package/dist/context/grounding.js +162 -0
- package/dist/context/grounding.js.map +1 -0
- package/dist/context/guards.d.ts +31 -0
- package/dist/context/guards.d.ts.map +1 -0
- package/dist/context/guards.js +76 -0
- package/dist/context/guards.js.map +1 -0
- package/dist/context/repo-hints.d.ts +12 -0
- package/dist/context/repo-hints.d.ts.map +1 -0
- package/dist/context/repo-hints.js +40 -0
- package/dist/context/repo-hints.js.map +1 -0
- package/dist/generation/gemini-client.d.ts +27 -0
- package/dist/generation/gemini-client.d.ts.map +1 -0
- package/dist/generation/gemini-client.js +64 -0
- package/dist/generation/gemini-client.js.map +1 -0
- package/dist/generation/litellm-client.d.ts +16 -0
- package/dist/generation/litellm-client.d.ts.map +1 -0
- package/dist/generation/litellm-client.js +98 -0
- package/dist/generation/litellm-client.js.map +1 -0
- package/dist/generation/remote-client.d.ts +20 -0
- package/dist/generation/remote-client.d.ts.map +1 -0
- package/dist/generation/remote-client.js +69 -0
- package/dist/generation/remote-client.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/output/file-writer.d.ts +39 -0
- package/dist/output/file-writer.d.ts.map +1 -0
- package/dist/output/file-writer.js +153 -0
- package/dist/output/file-writer.js.map +1 -0
- package/dist/output/formatter.d.ts +26 -0
- package/dist/output/formatter.d.ts.map +1 -0
- package/dist/output/formatter.js +156 -0
- package/dist/output/formatter.js.map +1 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +22 -0
- package/dist/server.js.map +1 -0
- package/dist/stack/detect.d.ts +49 -0
- package/dist/stack/detect.d.ts.map +1 -0
- package/dist/stack/detect.js +157 -0
- package/dist/stack/detect.js.map +1 -0
- package/dist/tokens/sync.d.ts +32 -0
- package/dist/tokens/sync.d.ts.map +1 -0
- package/dist/tokens/sync.js +188 -0
- package/dist/tokens/sync.js.map +1 -0
- package/dist/tools/analyze-screenshot-ui.d.ts +18 -0
- package/dist/tools/analyze-screenshot-ui.d.ts.map +1 -0
- package/dist/tools/analyze-screenshot-ui.js +133 -0
- package/dist/tools/analyze-screenshot-ui.js.map +1 -0
- package/dist/tools/analyze-tokens.d.ts +10 -0
- package/dist/tools/analyze-tokens.d.ts.map +1 -0
- package/dist/tools/analyze-tokens.js +107 -0
- package/dist/tools/analyze-tokens.js.map +1 -0
- package/dist/tools/catalog-components.d.ts +14 -0
- package/dist/tools/catalog-components.d.ts.map +1 -0
- package/dist/tools/catalog-components.js +85 -0
- package/dist/tools/catalog-components.js.map +1 -0
- package/dist/tools/create-ui.d.ts +10 -0
- package/dist/tools/create-ui.d.ts.map +1 -0
- package/dist/tools/create-ui.js +167 -0
- package/dist/tools/create-ui.js.map +1 -0
- package/dist/tools/detect-ui-stack.d.ts +15 -0
- package/dist/tools/detect-ui-stack.d.ts.map +1 -0
- package/dist/tools/detect-ui-stack.js +52 -0
- package/dist/tools/detect-ui-stack.js.map +1 -0
- package/dist/tools/generate-component-variants.d.ts +15 -0
- package/dist/tools/generate-component-variants.d.ts.map +1 -0
- package/dist/tools/generate-component-variants.js +199 -0
- package/dist/tools/generate-component-variants.js.map +1 -0
- package/dist/tools/generate-vibes.d.ts +10 -0
- package/dist/tools/generate-vibes.d.ts.map +1 -0
- package/dist/tools/generate-vibes.js +145 -0
- package/dist/tools/generate-vibes.js.map +1 -0
- package/dist/tools/index.d.ts +12 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +36 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/modify-ui.d.ts +11 -0
- package/dist/tools/modify-ui.d.ts.map +1 -0
- package/dist/tools/modify-ui.js +207 -0
- package/dist/tools/modify-ui.js.map +1 -0
- package/dist/tools/scaffold-project.d.ts +10 -0
- package/dist/tools/scaffold-project.d.ts.map +1 -0
- package/dist/tools/scaffold-project.js +122 -0
- package/dist/tools/scaffold-project.js.map +1 -0
- package/dist/tools/snippet-ui.d.ts +11 -0
- package/dist/tools/snippet-ui.d.ts.map +1 -0
- package/dist/tools/snippet-ui.js +194 -0
- package/dist/tools/snippet-ui.js.map +1 -0
- package/dist/tools/sync-design-tokens.d.ts +14 -0
- package/dist/tools/sync-design-tokens.d.ts.map +1 -0
- package/dist/tools/sync-design-tokens.js +233 -0
- package/dist/tools/sync-design-tokens.js.map +1 -0
- package/dist/utils/walk.d.ts +15 -0
- package/dist/utils/walk.d.ts.map +1 -0
- package/dist/utils/walk.js +63 -0
- package/dist/utils/walk.js.map +1 -0
- package/eslint.config.js +37 -0
- package/package.json +56 -0
- package/src/__tests__/builder.test.ts +31 -0
- package/src/__tests__/config.test.ts +52 -0
- package/src/__tests__/filter.test.ts +109 -0
- package/src/components/catalog.ts +214 -0
- package/src/config/index.ts +237 -0
- package/src/context/builder.ts +233 -0
- package/src/context/filter.ts +164 -0
- package/src/context/grounding.ts +191 -0
- package/src/context/guards.ts +94 -0
- package/src/context/repo-hints.ts +43 -0
- package/src/generation/gemini-client.ts +94 -0
- package/src/generation/litellm-client.ts +121 -0
- package/src/generation/remote-client.ts +103 -0
- package/src/index.ts +36 -0
- package/src/output/file-writer.ts +181 -0
- package/src/output/formatter.ts +186 -0
- package/src/server.ts +28 -0
- package/src/stack/detect.ts +204 -0
- package/src/tokens/sync.ts +212 -0
- package/src/tools/analyze-screenshot-ui.ts +150 -0
- package/src/tools/analyze-tokens.ts +123 -0
- package/src/tools/catalog-components.ts +99 -0
- package/src/tools/create-ui.ts +194 -0
- package/src/tools/detect-ui-stack.ts +64 -0
- package/src/tools/generate-component-variants.ts +218 -0
- package/src/tools/generate-vibes.ts +177 -0
- package/src/tools/index.ts +42 -0
- package/src/tools/modify-ui.ts +230 -0
- package/src/tools/scaffold-project.ts +138 -0
- package/src/tools/snippet-ui.ts +222 -0
- package/src/tools/sync-design-tokens.ts +256 -0
- package/src/utils/walk.ts +75 -0
- package/tsconfig.json +34 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote Gateway Client
|
|
3
|
+
*
|
|
4
|
+
* Handles communication with the VPS gateway for remote mode.
|
|
5
|
+
* Transmits only sanitized context to protect user secrets.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Config } from '../config/index.js';
|
|
9
|
+
import type { GeminiUserContent } from './gemini-client.js';
|
|
10
|
+
|
|
11
|
+
interface GatewayRequest {
|
|
12
|
+
systemPrompt: string;
|
|
13
|
+
userPrompt?: string;
|
|
14
|
+
userParts?: Array<{ text: string } | { inlineData: { mimeType: string; data: string } }>;
|
|
15
|
+
toolName?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface GatewayResponse {
|
|
19
|
+
content: string;
|
|
20
|
+
tokensUsed: number;
|
|
21
|
+
inputTokens?: number;
|
|
22
|
+
model?: string;
|
|
23
|
+
costMicrodollars?: number;
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generate content via remote gateway
|
|
29
|
+
*/
|
|
30
|
+
export async function generateWithRemote(
|
|
31
|
+
config: Config,
|
|
32
|
+
systemPrompt: string,
|
|
33
|
+
userPrompt: GeminiUserContent,
|
|
34
|
+
toolName?: string
|
|
35
|
+
): Promise<string> {
|
|
36
|
+
if (!config.remoteEndpoint) {
|
|
37
|
+
throw new Error('Remote endpoint not configured');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!config.remoteApiKey) {
|
|
41
|
+
throw new Error('Remote API key not configured');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const request: GatewayRequest = {
|
|
45
|
+
systemPrompt,
|
|
46
|
+
toolName,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (typeof userPrompt === 'string') {
|
|
50
|
+
request.userPrompt = userPrompt;
|
|
51
|
+
} else {
|
|
52
|
+
request.userParts = userPrompt as any;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const response = await fetch(`${config.remoteEndpoint}/generate`, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: {
|
|
58
|
+
'Content-Type': 'application/json',
|
|
59
|
+
Authorization: `Bearer ${config.remoteApiKey}`,
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify(request),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
const errorText = await response.text();
|
|
66
|
+
throw new Error(`Gateway error (${response.status}): ${errorText}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const data = (await response.json()) as GatewayResponse;
|
|
70
|
+
|
|
71
|
+
if (data.error) {
|
|
72
|
+
throw new Error(`Gateway error: ${data.error}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (config.debug) {
|
|
76
|
+
console.error('[remote] Tokens used:', data.tokensUsed);
|
|
77
|
+
if (typeof data.inputTokens === 'number') console.error('[remote] Input tokens:', data.inputTokens);
|
|
78
|
+
if (data.model) console.error('[remote] Model:', data.model);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return data.content;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check remaining quota with the gateway
|
|
86
|
+
*/
|
|
87
|
+
export async function checkQuota(config: Config): Promise<{ remaining: number; limit: number }> {
|
|
88
|
+
if (!config.remoteEndpoint || !config.remoteApiKey) {
|
|
89
|
+
throw new Error('Remote configuration missing');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const response = await fetch(`${config.remoteEndpoint}/quota`, {
|
|
93
|
+
headers: {
|
|
94
|
+
Authorization: `Bearer ${config.remoteApiKey}`,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
throw new Error(`Failed to check quota: ${response.status}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return (await response.json()) as { remaining: number; limit: number };
|
|
103
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Gemini Designer MCP - Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Open-source MCP server for design-to-code generation.
|
|
6
|
+
* Supports both local (BYO API key) and remote gateway modes.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createServer } from './server.js';
|
|
10
|
+
import { loadConfig } from './config/index.js';
|
|
11
|
+
|
|
12
|
+
async function main() {
|
|
13
|
+
const config = loadConfig();
|
|
14
|
+
|
|
15
|
+
console.error(`[gemini-designer-mcp] Starting in ${config.mode} mode...`);
|
|
16
|
+
|
|
17
|
+
const server = await createServer(config);
|
|
18
|
+
|
|
19
|
+
// Handle graceful shutdown
|
|
20
|
+
process.on('SIGINT', async () => {
|
|
21
|
+
console.error('[gemini-designer-mcp] Shutting down...');
|
|
22
|
+
await server.close();
|
|
23
|
+
process.exit(0);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
process.on('SIGTERM', async () => {
|
|
27
|
+
console.error('[gemini-designer-mcp] Shutting down...');
|
|
28
|
+
await server.close();
|
|
29
|
+
process.exit(0);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
main().catch((error) => {
|
|
34
|
+
console.error('[gemini-designer-mcp] Fatal error:', error);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Writer
|
|
3
|
+
*
|
|
4
|
+
* Writes generated code to files with safety checks.
|
|
5
|
+
* Supports backup creation and git-aware operations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { execSync } from 'node:child_process';
|
|
11
|
+
import { formatCode } from './formatter.js';
|
|
12
|
+
|
|
13
|
+
export interface WriteOptions {
|
|
14
|
+
backup?: boolean;
|
|
15
|
+
format?: boolean;
|
|
16
|
+
createDirs?: boolean;
|
|
17
|
+
gitAdd?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface WriteResult {
|
|
21
|
+
success: boolean;
|
|
22
|
+
filePath: string;
|
|
23
|
+
backupPath?: string;
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Write content to file with safety features
|
|
29
|
+
*/
|
|
30
|
+
export async function writeFile(
|
|
31
|
+
filePath: string,
|
|
32
|
+
content: string,
|
|
33
|
+
options: WriteOptions = {}
|
|
34
|
+
): Promise<WriteResult> {
|
|
35
|
+
const { backup = true, format = true, createDirs = true, gitAdd = false } = options;
|
|
36
|
+
|
|
37
|
+
const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// Create parent directories if needed
|
|
41
|
+
if (createDirs) {
|
|
42
|
+
const dir = path.dirname(absPath);
|
|
43
|
+
if (!fs.existsSync(dir)) {
|
|
44
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Create backup if file exists
|
|
49
|
+
let backupPath: string | undefined;
|
|
50
|
+
if (backup && fs.existsSync(absPath)) {
|
|
51
|
+
backupPath = `${absPath}.bak`;
|
|
52
|
+
fs.copyFileSync(absPath, backupPath);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Format content if requested
|
|
56
|
+
let finalContent = content;
|
|
57
|
+
if (format) {
|
|
58
|
+
try {
|
|
59
|
+
finalContent = await formatCode(content, { filePath: absPath });
|
|
60
|
+
} catch {
|
|
61
|
+
// Keep original if formatting fails
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Write the file
|
|
66
|
+
fs.writeFileSync(absPath, finalContent, 'utf-8');
|
|
67
|
+
|
|
68
|
+
// Git add if requested and in a git repo
|
|
69
|
+
if (gitAdd && isGitRepo(absPath)) {
|
|
70
|
+
try {
|
|
71
|
+
execSync(`git add "${absPath}"`, { cwd: path.dirname(absPath), stdio: 'ignore' });
|
|
72
|
+
} catch {
|
|
73
|
+
// Ignore git errors
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
success: true,
|
|
79
|
+
filePath: absPath,
|
|
80
|
+
backupPath,
|
|
81
|
+
};
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
filePath: absPath,
|
|
86
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Restore file from backup
|
|
93
|
+
*/
|
|
94
|
+
export function restoreFromBackup(filePath: string): boolean {
|
|
95
|
+
const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
|
|
96
|
+
const backupPath = `${absPath}.bak`;
|
|
97
|
+
|
|
98
|
+
if (!fs.existsSync(backupPath)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
fs.copyFileSync(backupPath, absPath);
|
|
104
|
+
fs.unlinkSync(backupPath);
|
|
105
|
+
return true;
|
|
106
|
+
} catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check if path is in a git repository
|
|
113
|
+
*/
|
|
114
|
+
function isGitRepo(filePath: string): boolean {
|
|
115
|
+
try {
|
|
116
|
+
execSync('git rev-parse --git-dir', {
|
|
117
|
+
cwd: path.dirname(filePath),
|
|
118
|
+
stdio: 'ignore',
|
|
119
|
+
});
|
|
120
|
+
return true;
|
|
121
|
+
} catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get git status for a file
|
|
128
|
+
*/
|
|
129
|
+
export function getGitStatus(filePath: string): 'modified' | 'untracked' | 'clean' | 'not-git' {
|
|
130
|
+
const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
|
|
131
|
+
|
|
132
|
+
if (!isGitRepo(absPath)) {
|
|
133
|
+
return 'not-git';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const status = execSync(`git status --porcelain "${absPath}"`, {
|
|
138
|
+
cwd: path.dirname(absPath),
|
|
139
|
+
encoding: 'utf-8',
|
|
140
|
+
}).trim();
|
|
141
|
+
|
|
142
|
+
if (!status) return 'clean';
|
|
143
|
+
if (status.startsWith('??')) return 'untracked';
|
|
144
|
+
return 'modified';
|
|
145
|
+
} catch {
|
|
146
|
+
return 'not-git';
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* List all backup files in a directory
|
|
152
|
+
*/
|
|
153
|
+
export function listBackups(directory: string): string[] {
|
|
154
|
+
try {
|
|
155
|
+
const entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
156
|
+
return entries
|
|
157
|
+
.filter((e) => e.isFile() && e.name.endsWith('.bak'))
|
|
158
|
+
.map((e) => path.join(directory, e.name));
|
|
159
|
+
} catch {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Clean up old backup files
|
|
166
|
+
*/
|
|
167
|
+
export function cleanBackups(directory: string): number {
|
|
168
|
+
const backups = listBackups(directory);
|
|
169
|
+
let cleaned = 0;
|
|
170
|
+
|
|
171
|
+
for (const backup of backups) {
|
|
172
|
+
try {
|
|
173
|
+
fs.unlinkSync(backup);
|
|
174
|
+
cleaned++;
|
|
175
|
+
} catch {
|
|
176
|
+
// Ignore errors
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return cleaned;
|
|
181
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output Formatter
|
|
3
|
+
*
|
|
4
|
+
* Uses Prettier to format generated code for consistency.
|
|
5
|
+
* Auto-detects language from content or file extension.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as prettier from 'prettier';
|
|
9
|
+
|
|
10
|
+
export type SupportedLanguage = 'typescript' | 'javascript' | 'css' | 'html' | 'json' | 'markdown';
|
|
11
|
+
|
|
12
|
+
interface FormatOptions {
|
|
13
|
+
language?: SupportedLanguage;
|
|
14
|
+
filePath?: string;
|
|
15
|
+
printWidth?: number;
|
|
16
|
+
tabWidth?: number;
|
|
17
|
+
useTabs?: boolean;
|
|
18
|
+
semi?: boolean;
|
|
19
|
+
singleQuote?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Format code using Prettier
|
|
24
|
+
*/
|
|
25
|
+
export async function formatCode(code: string, options: FormatOptions = {}): Promise<string> {
|
|
26
|
+
const language = options.language || detectLanguage(code, options.filePath);
|
|
27
|
+
const parser = getParser(language);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const formatted = await prettier.format(code, {
|
|
31
|
+
parser,
|
|
32
|
+
printWidth: options.printWidth ?? 100,
|
|
33
|
+
tabWidth: options.tabWidth ?? 2,
|
|
34
|
+
useTabs: options.useTabs ?? false,
|
|
35
|
+
semi: options.semi ?? true,
|
|
36
|
+
singleQuote: options.singleQuote ?? true,
|
|
37
|
+
trailingComma: 'es5',
|
|
38
|
+
bracketSpacing: true,
|
|
39
|
+
arrowParens: 'always',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return formatted;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
// If Prettier fails, return original code
|
|
45
|
+
console.error('[formatter] Prettier error, returning unformatted:', error);
|
|
46
|
+
return code;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Extract code blocks from LLM response and format them
|
|
52
|
+
*/
|
|
53
|
+
export async function formatLLMResponse(response: string): Promise<string> {
|
|
54
|
+
// Match code blocks with language identifier
|
|
55
|
+
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
|
|
56
|
+
let result = response;
|
|
57
|
+
let match;
|
|
58
|
+
|
|
59
|
+
const replacements: Array<{ original: string; formatted: string }> = [];
|
|
60
|
+
|
|
61
|
+
while ((match = codeBlockRegex.exec(response)) !== null) {
|
|
62
|
+
const [fullMatch, lang, code] = match;
|
|
63
|
+
const language = mapLanguageIdentifier(lang);
|
|
64
|
+
|
|
65
|
+
if (language) {
|
|
66
|
+
try {
|
|
67
|
+
const formatted = await formatCode(code.trim(), { language });
|
|
68
|
+
replacements.push({
|
|
69
|
+
original: fullMatch,
|
|
70
|
+
formatted: `\`\`\`${lang}\n${formatted}\`\`\``,
|
|
71
|
+
});
|
|
72
|
+
} catch {
|
|
73
|
+
// Keep original if formatting fails
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Apply replacements
|
|
79
|
+
for (const { original, formatted } of replacements) {
|
|
80
|
+
result = result.replace(original, formatted);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Detect language from code content or file path
|
|
88
|
+
*/
|
|
89
|
+
function detectLanguage(code: string, filePath?: string): SupportedLanguage {
|
|
90
|
+
if (filePath) {
|
|
91
|
+
const ext = filePath.split('.').pop()?.toLowerCase();
|
|
92
|
+
switch (ext) {
|
|
93
|
+
case 'ts':
|
|
94
|
+
case 'tsx':
|
|
95
|
+
return 'typescript';
|
|
96
|
+
case 'js':
|
|
97
|
+
case 'jsx':
|
|
98
|
+
return 'javascript';
|
|
99
|
+
case 'css':
|
|
100
|
+
case 'scss':
|
|
101
|
+
case 'less':
|
|
102
|
+
return 'css';
|
|
103
|
+
case 'html':
|
|
104
|
+
return 'html';
|
|
105
|
+
case 'json':
|
|
106
|
+
return 'json';
|
|
107
|
+
case 'md':
|
|
108
|
+
return 'markdown';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Detect from content
|
|
113
|
+
if (code.includes('import React') || code.includes('from "react"') || code.includes("from 'react'")) {
|
|
114
|
+
return code.includes(': React.') || code.includes('<') ? 'typescript' : 'javascript';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (code.includes('interface ') || code.includes(': string') || code.includes(': number')) {
|
|
118
|
+
return 'typescript';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (code.includes('function ') || code.includes('const ') || code.includes('let ')) {
|
|
122
|
+
return 'javascript';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (code.includes('{') && (code.includes('color:') || code.includes('margin:') || code.includes('display:'))) {
|
|
126
|
+
return 'css';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (code.startsWith('{') || code.startsWith('[')) {
|
|
130
|
+
try {
|
|
131
|
+
JSON.parse(code);
|
|
132
|
+
return 'json';
|
|
133
|
+
} catch {
|
|
134
|
+
// Not JSON
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (code.includes('<html') || code.includes('<!DOCTYPE')) {
|
|
139
|
+
return 'html';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Default to TypeScript for better JSX support
|
|
143
|
+
return 'typescript';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get Prettier parser for language
|
|
148
|
+
*/
|
|
149
|
+
function getParser(language: SupportedLanguage): string {
|
|
150
|
+
const parsers: Record<SupportedLanguage, string> = {
|
|
151
|
+
typescript: 'typescript',
|
|
152
|
+
javascript: 'babel',
|
|
153
|
+
css: 'css',
|
|
154
|
+
html: 'html',
|
|
155
|
+
json: 'json',
|
|
156
|
+
markdown: 'markdown',
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
return parsers[language];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Map common language identifiers to supported types
|
|
164
|
+
*/
|
|
165
|
+
function mapLanguageIdentifier(identifier?: string): SupportedLanguage | null {
|
|
166
|
+
if (!identifier) return null;
|
|
167
|
+
|
|
168
|
+
const lower = identifier.toLowerCase();
|
|
169
|
+
const mapping: Record<string, SupportedLanguage> = {
|
|
170
|
+
ts: 'typescript',
|
|
171
|
+
tsx: 'typescript',
|
|
172
|
+
typescript: 'typescript',
|
|
173
|
+
js: 'javascript',
|
|
174
|
+
jsx: 'javascript',
|
|
175
|
+
javascript: 'javascript',
|
|
176
|
+
css: 'css',
|
|
177
|
+
scss: 'css',
|
|
178
|
+
less: 'css',
|
|
179
|
+
html: 'html',
|
|
180
|
+
json: 'json',
|
|
181
|
+
md: 'markdown',
|
|
182
|
+
markdown: 'markdown',
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return mapping[lower] || null;
|
|
186
|
+
}
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates and configures the MCP server with all tools registered.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
|
+
import { Config } from './config/index.js';
|
|
10
|
+
import { registerTools } from './tools/index.js';
|
|
11
|
+
|
|
12
|
+
export async function createServer(config: Config) {
|
|
13
|
+
const server = new McpServer({
|
|
14
|
+
name: 'gemini-designer-mcp',
|
|
15
|
+
version: '0.1.0',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Register all MCP tools
|
|
19
|
+
registerTools(server, config);
|
|
20
|
+
|
|
21
|
+
// Use stdio transport (standard for MCP)
|
|
22
|
+
const transport = new StdioServerTransport();
|
|
23
|
+
await server.connect(transport);
|
|
24
|
+
|
|
25
|
+
console.error('[gemini-designer-mcp] Server connected via stdio');
|
|
26
|
+
|
|
27
|
+
return server;
|
|
28
|
+
}
|