@smartluvon/mcp 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/README.md +49 -0
- package/dist/api.d.ts +65 -0
- package/dist/api.js +84 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +199 -0
- package/dist/zod-to-json-schema.d.ts +12 -0
- package/dist/zod-to-json-schema.js +81 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Smartluvon MCP
|
|
2
|
+
|
|
3
|
+
MCP-сервер [Smartluvon](https://smartluvon.ru) — генерация изображений, видео и аудио AI-моделями прямо из Claude, Cursor и любого другого MCP-клиента.
|
|
4
|
+
|
|
5
|
+
## Быстрый старт
|
|
6
|
+
|
|
7
|
+
1. Получите API-ключ: профиль на smartluvon.ru → «API-ключи» → «Создать ключ» (ключ вида `sl-...` показывается один раз).
|
|
8
|
+
2. Добавьте сервер в конфиг MCP-клиента:
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"mcpServers": {
|
|
13
|
+
"smartluvon": {
|
|
14
|
+
"command": "npx",
|
|
15
|
+
"args": ["-y", "@smartluvon/mcp"],
|
|
16
|
+
"env": {
|
|
17
|
+
"SMARTLUVON_API_KEY": "sl-..."
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Инструменты
|
|
25
|
+
|
|
26
|
+
| Инструмент | Описание |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `list_models` | Список доступных моделей (фильтр по `type`: image / video / audio) |
|
|
29
|
+
| `generate_image` | Генерация/редактирование изображений (`imageUrls` — входные картинки) |
|
|
30
|
+
| `generate_video` | Генерация видео (text-to-video, image-to-video через `imageUrls`) |
|
|
31
|
+
| `generate_audio` | Музыка и озвучка (TTS) |
|
|
32
|
+
| `get_generation` | Статус и ссылки на результаты по id |
|
|
33
|
+
| `wait_generation` | Дождаться завершения генерации (поллинг) |
|
|
34
|
+
| `list_generations` | Список ваших генераций |
|
|
35
|
+
| `get_balance` | Баланс токенов |
|
|
36
|
+
| `calc_price` | Стоимость генерации в токенах без запуска |
|
|
37
|
+
|
|
38
|
+
Типовой сценарий: `list_models` → `generate_image` → `wait_generation` → ссылки на файлы.
|
|
39
|
+
|
|
40
|
+
## Переменные окружения
|
|
41
|
+
|
|
42
|
+
| Переменная | Описание |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `SMARTLUVON_API_KEY` | Персональный API-ключ (`sl-...`), обязательно |
|
|
45
|
+
| `SMARTLUVON_API_URL` | База API (по умолчанию `https://api.smartluvon.ru`) |
|
|
46
|
+
|
|
47
|
+
## Лицензия
|
|
48
|
+
|
|
49
|
+
MIT
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin client for the Smartluvon public /v1 API.
|
|
3
|
+
* Auth: personal API key (`sl-...`) via Authorization: Bearer.
|
|
4
|
+
*/
|
|
5
|
+
export interface V1Model {
|
|
6
|
+
id: string;
|
|
7
|
+
key: string | null;
|
|
8
|
+
name: string;
|
|
9
|
+
description: string | null;
|
|
10
|
+
type: 'image' | 'video' | 'audio' | 'text' | 'model_3d';
|
|
11
|
+
tags: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface V1GenerationResult {
|
|
14
|
+
url: string;
|
|
15
|
+
type: string;
|
|
16
|
+
thumbnailUrl: string | null;
|
|
17
|
+
}
|
|
18
|
+
export interface V1Generation {
|
|
19
|
+
id: string;
|
|
20
|
+
status: 'pending' | 'processing' | 'completed' | 'failed';
|
|
21
|
+
outputType: string;
|
|
22
|
+
results: V1GenerationResult[];
|
|
23
|
+
costTokens: number;
|
|
24
|
+
errorCodes: string[];
|
|
25
|
+
createdAt: string;
|
|
26
|
+
}
|
|
27
|
+
export interface V1Me {
|
|
28
|
+
email: string;
|
|
29
|
+
name: string;
|
|
30
|
+
tokenBalance: number;
|
|
31
|
+
}
|
|
32
|
+
export interface V1GenerateRequest {
|
|
33
|
+
model: string;
|
|
34
|
+
prompt: string;
|
|
35
|
+
imageUrls?: string[];
|
|
36
|
+
videoUrls?: string[];
|
|
37
|
+
audioUrl?: string;
|
|
38
|
+
numImages?: number;
|
|
39
|
+
options?: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
export declare class SmartluvonApiError extends Error {
|
|
42
|
+
readonly status: number;
|
|
43
|
+
constructor(status: number, message: string);
|
|
44
|
+
}
|
|
45
|
+
export declare class SmartluvonClient {
|
|
46
|
+
private readonly baseUrl;
|
|
47
|
+
private readonly apiKey;
|
|
48
|
+
constructor(baseUrl: string, apiKey: string);
|
|
49
|
+
private request;
|
|
50
|
+
listModels(type?: string): Promise<V1Model[]>;
|
|
51
|
+
me(): Promise<V1Me>;
|
|
52
|
+
generate(body: V1GenerateRequest): Promise<V1Generation>;
|
|
53
|
+
getGeneration(id: string): Promise<V1Generation>;
|
|
54
|
+
listGenerations(params: {
|
|
55
|
+
page?: number;
|
|
56
|
+
limit?: number;
|
|
57
|
+
status?: string;
|
|
58
|
+
}): Promise<{
|
|
59
|
+
items: V1Generation[];
|
|
60
|
+
meta: unknown;
|
|
61
|
+
}>;
|
|
62
|
+
price(body: V1GenerateRequest): Promise<unknown>;
|
|
63
|
+
/** Poll a generation until it completes/fails or the timeout elapses. */
|
|
64
|
+
waitGeneration(id: string, timeoutMs: number, intervalMs?: number): Promise<V1Generation>;
|
|
65
|
+
}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin client for the Smartluvon public /v1 API.
|
|
3
|
+
* Auth: personal API key (`sl-...`) via Authorization: Bearer.
|
|
4
|
+
*/
|
|
5
|
+
export class SmartluvonApiError extends Error {
|
|
6
|
+
status;
|
|
7
|
+
constructor(status, message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.status = status;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export class SmartluvonClient {
|
|
13
|
+
baseUrl;
|
|
14
|
+
apiKey;
|
|
15
|
+
constructor(baseUrl, apiKey) {
|
|
16
|
+
this.baseUrl = baseUrl;
|
|
17
|
+
this.apiKey = apiKey;
|
|
18
|
+
}
|
|
19
|
+
async request(method, path, body) {
|
|
20
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
21
|
+
method,
|
|
22
|
+
headers: {
|
|
23
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
},
|
|
26
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
27
|
+
});
|
|
28
|
+
const text = await response.text();
|
|
29
|
+
let data;
|
|
30
|
+
try {
|
|
31
|
+
data = text ? JSON.parse(text) : undefined;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
data = text;
|
|
35
|
+
}
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const message = typeof data === 'object' && data !== null && 'message' in data
|
|
38
|
+
? String(Array.isArray(data.message) ? data.message.join('; ') : data.message)
|
|
39
|
+
: `HTTP ${response.status}`;
|
|
40
|
+
throw new SmartluvonApiError(response.status, message);
|
|
41
|
+
}
|
|
42
|
+
return data;
|
|
43
|
+
}
|
|
44
|
+
listModels(type) {
|
|
45
|
+
const query = type ? `?type=${encodeURIComponent(type)}` : '';
|
|
46
|
+
return this.request('GET', `/v1/models${query}`);
|
|
47
|
+
}
|
|
48
|
+
me() {
|
|
49
|
+
return this.request('GET', '/v1/me');
|
|
50
|
+
}
|
|
51
|
+
generate(body) {
|
|
52
|
+
return this.request('POST', '/v1/generations', body);
|
|
53
|
+
}
|
|
54
|
+
getGeneration(id) {
|
|
55
|
+
return this.request('GET', `/v1/generations/${encodeURIComponent(id)}`);
|
|
56
|
+
}
|
|
57
|
+
listGenerations(params) {
|
|
58
|
+
const query = new URLSearchParams();
|
|
59
|
+
if (params.page)
|
|
60
|
+
query.set('page', String(params.page));
|
|
61
|
+
if (params.limit)
|
|
62
|
+
query.set('limit', String(params.limit));
|
|
63
|
+
if (params.status)
|
|
64
|
+
query.set('status', params.status);
|
|
65
|
+
const qs = query.toString();
|
|
66
|
+
return this.request('GET', `/v1/generations${qs ? `?${qs}` : ''}`);
|
|
67
|
+
}
|
|
68
|
+
price(body) {
|
|
69
|
+
return this.request('POST', '/v1/price', body);
|
|
70
|
+
}
|
|
71
|
+
/** Poll a generation until it completes/fails or the timeout elapses. */
|
|
72
|
+
async waitGeneration(id, timeoutMs, intervalMs = 3000) {
|
|
73
|
+
const deadline = Date.now() + timeoutMs;
|
|
74
|
+
let generation = await this.getGeneration(id);
|
|
75
|
+
while (generation.status === 'pending' || generation.status === 'processing') {
|
|
76
|
+
if (Date.now() >= deadline) {
|
|
77
|
+
return generation;
|
|
78
|
+
}
|
|
79
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
80
|
+
generation = await this.getGeneration(id);
|
|
81
|
+
}
|
|
82
|
+
return generation;
|
|
83
|
+
}
|
|
84
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { zodToJsonSchema } from './zod-to-json-schema.js';
|
|
7
|
+
import { SmartluvonApiError, SmartluvonClient } from './api.js';
|
|
8
|
+
const API_URL = process.env.SMARTLUVON_API_URL || 'https://api.smartluvon.ru';
|
|
9
|
+
const API_KEY = process.env.SMARTLUVON_API_KEY || '';
|
|
10
|
+
if (!API_KEY) {
|
|
11
|
+
console.error('SMARTLUVON_API_KEY is not set. Create a key in your Smartluvon profile and pass it via the env config of this MCP server.');
|
|
12
|
+
}
|
|
13
|
+
const client = new SmartluvonClient(API_URL, API_KEY);
|
|
14
|
+
const optionsSchema = z
|
|
15
|
+
.record(z.unknown())
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('Extra model options, e.g. { "aspectRatio": "16:9", "resolution": "1080p", "seed": 42 }');
|
|
18
|
+
const toolSchemas = {
|
|
19
|
+
list_models: z.object({
|
|
20
|
+
type: z
|
|
21
|
+
.enum(['image', 'video', 'audio'])
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('Filter models by media type'),
|
|
24
|
+
}),
|
|
25
|
+
generate_image: z.object({
|
|
26
|
+
model: z.string().describe('Model key or id (see list_models with type=image)'),
|
|
27
|
+
prompt: z.string().describe('Text prompt'),
|
|
28
|
+
imageUrls: z
|
|
29
|
+
.array(z.string().url())
|
|
30
|
+
.max(10)
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('Input/reference image URLs (for editing or reference-based generation)'),
|
|
33
|
+
numImages: z.number().int().min(1).max(4).optional().describe('Number of images (default 1)'),
|
|
34
|
+
options: optionsSchema,
|
|
35
|
+
}),
|
|
36
|
+
generate_video: z.object({
|
|
37
|
+
model: z.string().describe('Model key or id (see list_models with type=video)'),
|
|
38
|
+
prompt: z.string().describe('Text prompt'),
|
|
39
|
+
imageUrls: z
|
|
40
|
+
.array(z.string().url())
|
|
41
|
+
.max(10)
|
|
42
|
+
.optional()
|
|
43
|
+
.describe('Input image URLs (start frame / references) for image-to-video'),
|
|
44
|
+
videoUrls: z
|
|
45
|
+
.array(z.string().url())
|
|
46
|
+
.max(4)
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('Input video URLs (for video-to-video models)'),
|
|
49
|
+
options: optionsSchema,
|
|
50
|
+
}),
|
|
51
|
+
generate_audio: z.object({
|
|
52
|
+
model: z.string().describe('Model key or id (see list_models with type=audio)'),
|
|
53
|
+
prompt: z.string().describe('Text to speak (TTS) or music description'),
|
|
54
|
+
audioUrl: z.string().url().optional().describe('Input audio URL (voice change etc.)'),
|
|
55
|
+
options: optionsSchema,
|
|
56
|
+
}),
|
|
57
|
+
get_generation: z.object({
|
|
58
|
+
id: z.string().describe('Generation id'),
|
|
59
|
+
}),
|
|
60
|
+
wait_generation: z.object({
|
|
61
|
+
id: z.string().describe('Generation id'),
|
|
62
|
+
timeoutSec: z
|
|
63
|
+
.number()
|
|
64
|
+
.int()
|
|
65
|
+
.min(5)
|
|
66
|
+
.max(600)
|
|
67
|
+
.optional()
|
|
68
|
+
.describe('Max seconds to wait (default 300)'),
|
|
69
|
+
}),
|
|
70
|
+
list_generations: z.object({
|
|
71
|
+
page: z.number().int().min(1).optional().describe('Page (default 1)'),
|
|
72
|
+
limit: z.number().int().min(1).max(100).optional().describe('Items per page (default 25)'),
|
|
73
|
+
status: z
|
|
74
|
+
.enum(['pending', 'processing', 'completed', 'failed'])
|
|
75
|
+
.optional()
|
|
76
|
+
.describe('Filter by status'),
|
|
77
|
+
}),
|
|
78
|
+
get_balance: z.object({}),
|
|
79
|
+
calc_price: z.object({
|
|
80
|
+
model: z.string().describe('Model key or id'),
|
|
81
|
+
prompt: z.string().optional().describe('Prompt (some models price by length)'),
|
|
82
|
+
numImages: z.number().int().min(1).max(4).optional(),
|
|
83
|
+
options: optionsSchema,
|
|
84
|
+
}),
|
|
85
|
+
};
|
|
86
|
+
const toolDescriptions = {
|
|
87
|
+
list_models: 'List available Smartluvon generation models. Returns model key/id, name, description and media type. Use the key as `model` in generate_* tools.',
|
|
88
|
+
generate_image: 'Start an image generation (text-to-image, or image editing when imageUrls are provided). Returns a generation id — use wait_generation to get result URLs.',
|
|
89
|
+
generate_video: 'Start a video generation (text-to-video, or image-to-video when imageUrls are provided). Returns a generation id — use wait_generation to get result URLs. Video can take several minutes.',
|
|
90
|
+
generate_audio: 'Start an audio generation (music or text-to-speech depending on the model). Returns a generation id — use wait_generation to get result URLs.',
|
|
91
|
+
get_generation: 'Get the current status and result URLs of a generation by id.',
|
|
92
|
+
wait_generation: 'Poll a generation until it completes or fails (or the timeout elapses) and return its status and result URLs.',
|
|
93
|
+
list_generations: 'List your recent generations with optional status filter and pagination.',
|
|
94
|
+
get_balance: 'Get your Smartluvon profile and token balance.',
|
|
95
|
+
calc_price: 'Calculate how many tokens a generation would cost without running it.',
|
|
96
|
+
};
|
|
97
|
+
function formatGeneration(generation) {
|
|
98
|
+
return {
|
|
99
|
+
id: generation.id,
|
|
100
|
+
status: generation.status,
|
|
101
|
+
outputType: generation.outputType,
|
|
102
|
+
costTokens: generation.costTokens,
|
|
103
|
+
results: generation.results,
|
|
104
|
+
...(generation.errorCodes?.length ? { errorCodes: generation.errorCodes } : {}),
|
|
105
|
+
createdAt: generation.createdAt,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async function handleTool(name, args) {
|
|
109
|
+
const parsed = toolSchemas[name].parse(args ?? {});
|
|
110
|
+
switch (name) {
|
|
111
|
+
case 'list_models': {
|
|
112
|
+
const { type } = parsed;
|
|
113
|
+
return client.listModels(type);
|
|
114
|
+
}
|
|
115
|
+
case 'generate_image': {
|
|
116
|
+
const { model, prompt, imageUrls, numImages, options } = parsed;
|
|
117
|
+
const generation = await client.generate({ model, prompt, imageUrls, numImages, options });
|
|
118
|
+
return formatGeneration(generation);
|
|
119
|
+
}
|
|
120
|
+
case 'generate_video': {
|
|
121
|
+
const { model, prompt, imageUrls, videoUrls, options } = parsed;
|
|
122
|
+
const generation = await client.generate({ model, prompt, imageUrls, videoUrls, options });
|
|
123
|
+
return formatGeneration(generation);
|
|
124
|
+
}
|
|
125
|
+
case 'generate_audio': {
|
|
126
|
+
const { model, prompt, audioUrl, options } = parsed;
|
|
127
|
+
const generation = await client.generate({ model, prompt, audioUrl, options });
|
|
128
|
+
return formatGeneration(generation);
|
|
129
|
+
}
|
|
130
|
+
case 'get_generation': {
|
|
131
|
+
const { id } = parsed;
|
|
132
|
+
return formatGeneration(await client.getGeneration(id));
|
|
133
|
+
}
|
|
134
|
+
case 'wait_generation': {
|
|
135
|
+
const { id, timeoutSec = 300 } = parsed;
|
|
136
|
+
const generation = await client.waitGeneration(id, timeoutSec * 1000);
|
|
137
|
+
if (generation.status === 'pending' || generation.status === 'processing') {
|
|
138
|
+
return {
|
|
139
|
+
...formatGeneration(generation),
|
|
140
|
+
note: `Still ${generation.status} after ${timeoutSec}s — call wait_generation again.`,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return formatGeneration(generation);
|
|
144
|
+
}
|
|
145
|
+
case 'list_generations': {
|
|
146
|
+
const { page, limit, status } = parsed;
|
|
147
|
+
const { items, meta } = await client.listGenerations({ page, limit, status });
|
|
148
|
+
return { items: items.map(formatGeneration), meta };
|
|
149
|
+
}
|
|
150
|
+
case 'get_balance':
|
|
151
|
+
return client.me();
|
|
152
|
+
case 'calc_price': {
|
|
153
|
+
const { model, prompt, numImages, options } = parsed;
|
|
154
|
+
return client.price({ model, prompt: prompt ?? '', numImages, options });
|
|
155
|
+
}
|
|
156
|
+
default:
|
|
157
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const server = new Server({ name: 'smartluvon', version: '0.1.0' }, { capabilities: { tools: {} } });
|
|
161
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
162
|
+
tools: Object.keys(toolSchemas).map((name) => ({
|
|
163
|
+
name,
|
|
164
|
+
description: toolDescriptions[name],
|
|
165
|
+
inputSchema: zodToJsonSchema(toolSchemas[name]),
|
|
166
|
+
})),
|
|
167
|
+
}));
|
|
168
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
169
|
+
const { name, arguments: args } = request.params;
|
|
170
|
+
try {
|
|
171
|
+
if (!(name in toolSchemas)) {
|
|
172
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
173
|
+
}
|
|
174
|
+
const result = await handleTool(name, args);
|
|
175
|
+
return {
|
|
176
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
const message = error instanceof SmartluvonApiError
|
|
181
|
+
? `API error ${error.status}: ${error.message}`
|
|
182
|
+
: error instanceof Error
|
|
183
|
+
? error.message
|
|
184
|
+
: String(error);
|
|
185
|
+
return {
|
|
186
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
187
|
+
isError: true,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
async function main() {
|
|
192
|
+
const transport = new StdioServerTransport();
|
|
193
|
+
await server.connect(transport);
|
|
194
|
+
console.error(`Smartluvon MCP server running on stdio (url=${API_URL})`);
|
|
195
|
+
}
|
|
196
|
+
main().catch((error) => {
|
|
197
|
+
console.error('Fatal error:', error);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ZodTypeAny } from 'zod';
|
|
2
|
+
type JsonSchema = {
|
|
3
|
+
type?: string;
|
|
4
|
+
properties?: Record<string, JsonSchema>;
|
|
5
|
+
required?: string[];
|
|
6
|
+
description?: string;
|
|
7
|
+
enum?: string[];
|
|
8
|
+
items?: JsonSchema;
|
|
9
|
+
additionalProperties?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function zodToJsonSchema(schema: ZodTypeAny): JsonSchema;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function zodToJsonSchema(schema) {
|
|
3
|
+
if (schema instanceof z.ZodObject) {
|
|
4
|
+
const shape = schema.shape;
|
|
5
|
+
const properties = {};
|
|
6
|
+
const required = [];
|
|
7
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
8
|
+
const propSchema = zodTypeToJsonSchema(value);
|
|
9
|
+
properties[key] = propSchema;
|
|
10
|
+
if (!isOptional(value)) {
|
|
11
|
+
required.push(key);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties,
|
|
17
|
+
required: required.length > 0 ? required : undefined,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return zodTypeToJsonSchema(schema);
|
|
21
|
+
}
|
|
22
|
+
function isOptional(schema) {
|
|
23
|
+
if (schema instanceof z.ZodOptional)
|
|
24
|
+
return true;
|
|
25
|
+
if (schema instanceof z.ZodNullable)
|
|
26
|
+
return true;
|
|
27
|
+
if (schema._def?.typeName === 'ZodOptional')
|
|
28
|
+
return true;
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
function unwrapOptional(schema) {
|
|
32
|
+
if (schema instanceof z.ZodOptional) {
|
|
33
|
+
return schema.unwrap();
|
|
34
|
+
}
|
|
35
|
+
if (schema instanceof z.ZodNullable) {
|
|
36
|
+
return schema.unwrap();
|
|
37
|
+
}
|
|
38
|
+
return schema;
|
|
39
|
+
}
|
|
40
|
+
function zodTypeToJsonSchema(schema) {
|
|
41
|
+
const unwrapped = unwrapOptional(schema);
|
|
42
|
+
const description = schema._def?.description ||
|
|
43
|
+
unwrapped._def?.description;
|
|
44
|
+
if (unwrapped instanceof z.ZodString) {
|
|
45
|
+
return { type: 'string', description };
|
|
46
|
+
}
|
|
47
|
+
if (unwrapped instanceof z.ZodNumber) {
|
|
48
|
+
return { type: 'number', description };
|
|
49
|
+
}
|
|
50
|
+
if (unwrapped instanceof z.ZodBoolean) {
|
|
51
|
+
return { type: 'boolean', description };
|
|
52
|
+
}
|
|
53
|
+
if (unwrapped instanceof z.ZodEnum) {
|
|
54
|
+
return {
|
|
55
|
+
type: 'string',
|
|
56
|
+
enum: unwrapped._def.values,
|
|
57
|
+
description,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (unwrapped instanceof z.ZodArray) {
|
|
61
|
+
return {
|
|
62
|
+
type: 'array',
|
|
63
|
+
items: zodTypeToJsonSchema(unwrapped._def.type),
|
|
64
|
+
description,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (unwrapped instanceof z.ZodRecord) {
|
|
68
|
+
return {
|
|
69
|
+
type: 'object',
|
|
70
|
+
additionalProperties: true,
|
|
71
|
+
description,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (unwrapped instanceof z.ZodObject) {
|
|
75
|
+
return zodToJsonSchema(unwrapped);
|
|
76
|
+
}
|
|
77
|
+
if (unwrapped instanceof z.ZodUnknown || unwrapped instanceof z.ZodAny) {
|
|
78
|
+
return { description };
|
|
79
|
+
}
|
|
80
|
+
return { type: 'string', description };
|
|
81
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@smartluvon/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Smartluvon MCP server — generate images, video and audio with Smartluvon AI models",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"smartluvon-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"start": "node dist/index.js",
|
|
20
|
+
"dev": "tsx src/index.ts",
|
|
21
|
+
"prepublishOnly": "npm run build",
|
|
22
|
+
"publish:package": "npm run build && npm publish"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"mcp",
|
|
26
|
+
"smartluvon",
|
|
27
|
+
"ai",
|
|
28
|
+
"image-generation",
|
|
29
|
+
"video-generation"
|
|
30
|
+
],
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
37
|
+
"zod": "^3.24.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^22.10.0",
|
|
41
|
+
"tsx": "^4.19.0",
|
|
42
|
+
"typescript": "^5.7.0"
|
|
43
|
+
}
|
|
44
|
+
}
|