@rendshot/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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test.log +14 -0
- package/dist/__tests__/tools.test.d.ts +2 -0
- package/dist/__tests__/tools.test.d.ts.map +1 -0
- package/dist/__tests__/tools.test.js +221 -0
- package/dist/__tests__/tools.test.js.map +1 -0
- package/dist/api-client.d.ts +31 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +31 -0
- package/dist/api-client.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +22 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/generateImage.d.ts +24 -0
- package/dist/tools/generateImage.d.ts.map +1 -0
- package/dist/tools/generateImage.js +28 -0
- package/dist/tools/generateImage.js.map +1 -0
- package/dist/tools/screenshotUrl.d.ts +23 -0
- package/dist/tools/screenshotUrl.d.ts.map +1 -0
- package/dist/tools/screenshotUrl.js +27 -0
- package/dist/tools/screenshotUrl.js.map +1 -0
- package/package.json +23 -0
- package/src/__tests__/tools.test.ts +248 -0
- package/src/index.ts +1 -0
- package/src/server.ts +46 -0
- package/src/tools/generateImage.ts +34 -0
- package/src/tools/screenshotUrl.ts +33 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
> @rendshot/mcp@0.1.0 test /Users/wuzhiqiang/Documents/WWW/MySelf/rendshot/packages/mcp
|
|
3
|
+
> vitest run --passWithNoTests
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
RUN v3.2.4 /Users/wuzhiqiang/Documents/WWW/MySelf/rendshot/packages/mcp
|
|
7
|
+
|
|
8
|
+
✓ src/__tests__/tools.test.ts (21 tests) 7ms
|
|
9
|
+
|
|
10
|
+
Test Files 1 passed (1)
|
|
11
|
+
Tests 21 passed (21)
|
|
12
|
+
Start at 16:13:37
|
|
13
|
+
Duration 1.29s (transform 152ms, setup 0ms, collect 163ms, tests 7ms, environment 0ms, prepare 526ms)
|
|
14
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tools.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { generateImageSchema, generateImageToolMeta, handleGenerateImage, } from '../tools/generateImage.js';
|
|
4
|
+
import { screenshotUrlSchema, screenshotUrlToolMeta, handleScreenshotUrl, } from '../tools/screenshotUrl.js';
|
|
5
|
+
import { ApiClient } from '../api-client.js';
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
function makeFetchMock(status, body) {
|
|
10
|
+
return vi.fn().mockResolvedValue({
|
|
11
|
+
ok: status >= 200 && status < 300,
|
|
12
|
+
status,
|
|
13
|
+
json: async () => body,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
vi.unstubAllGlobals();
|
|
18
|
+
vi.restoreAllMocks();
|
|
19
|
+
});
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Tool meta / definitions
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
describe('generate_image tool meta', () => {
|
|
24
|
+
it('has the correct name', () => {
|
|
25
|
+
expect(generateImageToolMeta.name).toBe('generate_image');
|
|
26
|
+
});
|
|
27
|
+
it('has a non-empty description', () => {
|
|
28
|
+
expect(typeof generateImageToolMeta.description).toBe('string');
|
|
29
|
+
expect(generateImageToolMeta.description.length).toBeGreaterThan(0);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('screenshot_url tool meta', () => {
|
|
33
|
+
it('has the correct name', () => {
|
|
34
|
+
expect(screenshotUrlToolMeta.name).toBe('screenshot_url');
|
|
35
|
+
});
|
|
36
|
+
it('has a non-empty description', () => {
|
|
37
|
+
expect(typeof screenshotUrlToolMeta.description).toBe('string');
|
|
38
|
+
expect(screenshotUrlToolMeta.description.length).toBeGreaterThan(0);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// generate_image schema validation
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
describe('generateImageSchema validation', () => {
|
|
45
|
+
const schema = z.object(generateImageSchema);
|
|
46
|
+
it('accepts valid input with only the required html field', () => {
|
|
47
|
+
const result = schema.safeParse({ html: '<h1>Hello</h1>' });
|
|
48
|
+
expect(result.success).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
it('accepts all optional fields when provided with correct types', () => {
|
|
51
|
+
const result = schema.safeParse({
|
|
52
|
+
html: '<p>test</p>',
|
|
53
|
+
css: 'body { color: red; }',
|
|
54
|
+
width: 800,
|
|
55
|
+
height: 600,
|
|
56
|
+
format: 'png',
|
|
57
|
+
quality: 90,
|
|
58
|
+
deviceScale: 2,
|
|
59
|
+
timeout: 10000,
|
|
60
|
+
});
|
|
61
|
+
expect(result.success).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
it('rejects input missing the required html field', () => {
|
|
64
|
+
const result = schema.safeParse({ width: 800 });
|
|
65
|
+
expect(result.success).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
it('rejects invalid format values', () => {
|
|
68
|
+
const result = schema.safeParse({ html: '<p>x</p>', format: 'gif' });
|
|
69
|
+
expect(result.success).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
it('rejects invalid deviceScale values', () => {
|
|
72
|
+
const result = schema.safeParse({ html: '<p>x</p>', deviceScale: 4 });
|
|
73
|
+
expect(result.success).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
it('accepts deviceScale of 1, 2, or 3', () => {
|
|
76
|
+
for (const scale of [1, 2, 3]) {
|
|
77
|
+
const result = schema.safeParse({ html: '<p>x</p>', deviceScale: scale });
|
|
78
|
+
expect(result.success).toBe(true);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// screenshot_url schema validation
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
describe('screenshotUrlSchema validation', () => {
|
|
86
|
+
const schema = z.object(screenshotUrlSchema);
|
|
87
|
+
it('accepts valid input with only the required url field', () => {
|
|
88
|
+
const result = schema.safeParse({ url: 'https://example.com' });
|
|
89
|
+
expect(result.success).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
it('accepts all optional fields when provided with correct types', () => {
|
|
92
|
+
const result = schema.safeParse({
|
|
93
|
+
url: 'https://example.com',
|
|
94
|
+
width: 1280,
|
|
95
|
+
height: 800,
|
|
96
|
+
format: 'webp',
|
|
97
|
+
quality: 85,
|
|
98
|
+
fullPage: true,
|
|
99
|
+
deviceScale: 1,
|
|
100
|
+
timeout: 5000,
|
|
101
|
+
});
|
|
102
|
+
expect(result.success).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
it('rejects input missing the required url field', () => {
|
|
105
|
+
const result = schema.safeParse({ width: 1280 });
|
|
106
|
+
expect(result.success).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
it('rejects invalid format values', () => {
|
|
109
|
+
const result = schema.safeParse({ url: 'https://example.com', format: 'bmp' });
|
|
110
|
+
expect(result.success).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
it('rejects invalid deviceScale values', () => {
|
|
113
|
+
const result = schema.safeParse({ url: 'https://example.com', deviceScale: 0 });
|
|
114
|
+
expect(result.success).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
it('accepts valid format values: png, jpg, webp', () => {
|
|
117
|
+
for (const format of ['png', 'jpg', 'webp']) {
|
|
118
|
+
const result = schema.safeParse({ url: 'https://example.com', format });
|
|
119
|
+
expect(result.success).toBe(true);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// handleGenerateImage — calls ApiClient correctly
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
describe('handleGenerateImage', () => {
|
|
127
|
+
it('calls client.generateImage with the provided args', async () => {
|
|
128
|
+
const mockFetch = makeFetchMock(200, {
|
|
129
|
+
url: 'https://cdn.example.com/img.png',
|
|
130
|
+
width: 1080,
|
|
131
|
+
height: 1080,
|
|
132
|
+
format: 'png',
|
|
133
|
+
size: 8192,
|
|
134
|
+
});
|
|
135
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
136
|
+
const client = new ApiClient({ apiUrl: 'https://api.rendshot.com', apiKey: 'rs_test_key' });
|
|
137
|
+
const args = { html: '<h1>Hello</h1>', width: 1080, height: 1080 };
|
|
138
|
+
const response = await handleGenerateImage(client, args);
|
|
139
|
+
// Verify fetch was called to POST /v1/image
|
|
140
|
+
expect(mockFetch).toHaveBeenCalledOnce();
|
|
141
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
142
|
+
expect(url).toBe('https://api.rendshot.com/v1/image');
|
|
143
|
+
expect(init.method).toBe('POST');
|
|
144
|
+
const body = JSON.parse(init.body);
|
|
145
|
+
expect(body.html).toBe('<h1>Hello</h1>');
|
|
146
|
+
// Verify the MCP response shape
|
|
147
|
+
expect(response.content).toHaveLength(1);
|
|
148
|
+
expect(response.content[0].type).toBe('text');
|
|
149
|
+
expect(response.content[0].text).toContain('https://cdn.example.com/img.png');
|
|
150
|
+
expect(response.content[0].text).toContain('Image generated successfully');
|
|
151
|
+
});
|
|
152
|
+
it('propagates errors from the API client', async () => {
|
|
153
|
+
const mockFetch = makeFetchMock(400, { error: { message: 'HTML is required' } });
|
|
154
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
155
|
+
const client = new ApiClient({ apiUrl: 'https://api.rendshot.com', apiKey: 'rs_test_key' });
|
|
156
|
+
await expect(handleGenerateImage(client, { html: '' })).rejects.toThrow('HTML is required');
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// handleScreenshotUrl — calls ApiClient correctly
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
describe('handleScreenshotUrl', () => {
|
|
163
|
+
it('calls client.screenshotUrl with the provided args', async () => {
|
|
164
|
+
const mockFetch = makeFetchMock(200, {
|
|
165
|
+
url: 'https://cdn.example.com/shot.jpg',
|
|
166
|
+
width: 1280,
|
|
167
|
+
height: 800,
|
|
168
|
+
format: 'jpg',
|
|
169
|
+
size: 20480,
|
|
170
|
+
});
|
|
171
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
172
|
+
const client = new ApiClient({ apiUrl: 'https://api.rendshot.com', apiKey: 'rs_test_key' });
|
|
173
|
+
const args = { url: 'https://example.com', fullPage: true, format: 'jpg' };
|
|
174
|
+
const response = await handleScreenshotUrl(client, args);
|
|
175
|
+
expect(mockFetch).toHaveBeenCalledOnce();
|
|
176
|
+
const [url, init] = mockFetch.mock.calls[0];
|
|
177
|
+
expect(url).toBe('https://api.rendshot.com/v1/screenshot');
|
|
178
|
+
expect(init.method).toBe('POST');
|
|
179
|
+
const body = JSON.parse(init.body);
|
|
180
|
+
expect(body.url).toBe('https://example.com');
|
|
181
|
+
expect(body.fullPage).toBe(true);
|
|
182
|
+
expect(response.content).toHaveLength(1);
|
|
183
|
+
expect(response.content[0].type).toBe('text');
|
|
184
|
+
expect(response.content[0].text).toContain('https://cdn.example.com/shot.jpg');
|
|
185
|
+
expect(response.content[0].text).toContain('Screenshot taken');
|
|
186
|
+
});
|
|
187
|
+
it('propagates errors from the API client', async () => {
|
|
188
|
+
const mockFetch = makeFetchMock(422, { error: { message: 'Invalid URL' } });
|
|
189
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
190
|
+
const client = new ApiClient({ apiUrl: 'https://api.rendshot.com', apiKey: 'rs_test_key' });
|
|
191
|
+
await expect(handleScreenshotUrl(client, { url: 'not-a-url' })).rejects.toThrow('Invalid URL');
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// ApiClient (MCP) — HTTP layer
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
describe('ApiClient (MCP package)', () => {
|
|
198
|
+
it('sends correct Authorization and Content-Type headers on POST', async () => {
|
|
199
|
+
const mockFetch = makeFetchMock(200, { url: 'https://cdn.example.com/img.png', width: 1080, height: 1080, format: 'png', size: 0 });
|
|
200
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
201
|
+
const client = new ApiClient({ apiUrl: 'https://api.rendshot.com', apiKey: 'rs_mcp_key' });
|
|
202
|
+
await client.generateImage({ html: '<p>test</p>' });
|
|
203
|
+
const [, init] = mockFetch.mock.calls[0];
|
|
204
|
+
const headers = init.headers;
|
|
205
|
+
expect(headers['Authorization']).toBe('Bearer rs_mcp_key');
|
|
206
|
+
expect(headers['Content-Type']).toBe('application/json');
|
|
207
|
+
});
|
|
208
|
+
it('throws on non-ok response with error message from body', async () => {
|
|
209
|
+
const mockFetch = makeFetchMock(403, { error: { message: 'Forbidden' } });
|
|
210
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
211
|
+
const client = new ApiClient({ apiUrl: 'https://api.rendshot.com', apiKey: 'rs_key' });
|
|
212
|
+
await expect(client.screenshotUrl({ url: 'https://example.com' })).rejects.toThrow('Forbidden');
|
|
213
|
+
});
|
|
214
|
+
it('throws fallback error message when body has no error field', async () => {
|
|
215
|
+
const mockFetch = makeFetchMock(503, {});
|
|
216
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
217
|
+
const client = new ApiClient({ apiUrl: 'https://api.rendshot.com', apiKey: 'rs_key' });
|
|
218
|
+
await expect(client.screenshotUrl({ url: 'https://example.com' })).rejects.toThrow('API error: 503');
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
//# sourceMappingURL=tools.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.test.js","sourceRoot":"","sources":["../../src/__tests__/tools.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAc,SAAS,EAAE,MAAM,QAAQ,CAAA;AACxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAE5C,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,SAAS,aAAa,CAAC,MAAc,EAAE,IAAa;IAClD,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC;QAC/B,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG;QACjC,MAAM;QACN,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI;KACvB,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,CAAC,GAAG,EAAE;IACb,EAAE,CAAC,gBAAgB,EAAE,CAAA;IACrB,EAAE,CAAC,eAAe,EAAE,CAAA;AACtB,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,0BAA0B;AAC1B,8EAA8E;AAE9E,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,OAAO,qBAAqB,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC/D,MAAM,CAAC,qBAAqB,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAA;IACrE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,OAAO,qBAAqB,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC/D,MAAM,CAAC,qBAAqB,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAA;IACrE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,mCAAmC;AACnC,8EAA8E;AAE9E,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAA;IAE5C,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAA;QAC3D,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACnC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACtE,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC;YAC9B,IAAI,EAAE,aAAa;YACnB,GAAG,EAAE,sBAAsB;YAC3B,KAAK,EAAE,GAAG;YACV,MAAM,EAAE,GAAG;YACX,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,EAAE;YACX,WAAW,EAAE,CAAC;YACd,OAAO,EAAE,KAAK;SACf,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACnC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;QAC/C,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACpC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;QACpE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACpC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,CAAA;QACrE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACpC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,KAAK,MAAM,KAAK,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAU,EAAE,CAAC;YACvC,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAA;YACzE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACnC,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,mCAAmC;AACnC,8EAA8E;AAE9E,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAA;IAE5C,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,qBAAqB,EAAE,CAAC,CAAA;QAC/D,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACnC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACtE,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC;YAC9B,GAAG,EAAE,qBAAqB;YAC1B,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,GAAG;YACX,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE;YACX,QAAQ,EAAE,IAAI;YACd,WAAW,EAAE,CAAC;YACd,OAAO,EAAE,IAAI;SACd,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACnC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QAChD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACpC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,qBAAqB,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;QAC9E,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACpC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,qBAAqB,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,CAAA;QAC/E,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACpC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,KAAK,MAAM,MAAM,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAU,EAAE,CAAC;YACrD,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,qBAAqB,EAAE,MAAM,EAAE,CAAC,CAAA;YACvE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACnC,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,kDAAkD;AAClD,8EAA8E;AAE9E,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,EAAE;YACnC,GAAG,EAAE,iCAAiC;YACtC,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,IAAI;SACX,CAAC,CAAA;QACF,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QAEjC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,0BAA0B,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAA;QAC3F,MAAM,IAAI,GAAG,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;QAElE,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,MAAM,EAAE,IAAW,CAAC,CAAA;QAE/D,4CAA4C;QAC5C,MAAM,CAAC,SAAS,CAAC,CAAC,oBAAoB,EAAE,CAAA;QACxC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAA0B,CAAA;QACpE,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAA;QACrD,MAAM,CAAE,IAAY,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACzC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAc,CAAC,CAAA;QAC5C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;QAExC,gCAAgC;QAChC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;QACxC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC7C,MAAM,CAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAS,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,iCAAiC,CAAC,CAAA;QACtF,MAAM,CAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAS,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,8BAA8B,CAAC,CAAA;IACrF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAA;QAChF,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QAEjC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,0BAA0B,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAA;QAC3F,MAAM,MAAM,CAAC,mBAAmB,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,EAAS,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAA;IACpG,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,kDAAkD;AAClD,8EAA8E;AAE9E,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,EAAE;YACnC,GAAG,EAAE,kCAAkC;YACvC,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,GAAG;YACX,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,KAAK;SACZ,CAAC,CAAA;QACF,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QAEjC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,0BAA0B,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAA;QAC3F,MAAM,IAAI,GAAG,EAAE,GAAG,EAAE,qBAAqB,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;QAE1E,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,MAAM,EAAE,IAAW,CAAC,CAAA;QAE/D,MAAM,CAAC,SAAS,CAAC,CAAC,oBAAoB,EAAE,CAAA;QACxC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAA0B,CAAA;QACpE,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAA;QAC1D,MAAM,CAAE,IAAY,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACzC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAc,CAAC,CAAA;QAC5C,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAA;QAC5C,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAEhC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;QACxC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC7C,MAAM,CAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAS,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,kCAAkC,CAAC,CAAA;QACvF,MAAM,CAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAS,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAA;IACzE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,CAAC,CAAA;QAC3E,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QAEjC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,0BAA0B,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAA;QAC3F,MAAM,MAAM,CACV,mBAAmB,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,WAAW,EAAS,CAAC,CACzD,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAA;IAClC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,+BAA+B;AAC/B,8EAA8E;AAE9E,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,iCAAiC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAA;QACnI,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QAEjC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,0BAA0B,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAA;QAC1F,MAAM,MAAM,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAA;QAEnD,MAAM,CAAC,EAAE,IAAI,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAA0B,CAAA;QACjE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAiC,CAAA;QACtD,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAA;QAC1D,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;IAC1D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,CAAC,CAAA;QACzE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QAEjC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,0BAA0B,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;QACtF,MAAM,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,GAAG,EAAE,qBAAqB,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IACjG,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC1E,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;QACxC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QAEjC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,0BAA0B,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;QACtF,MAAM,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,GAAG,EAAE,qBAAqB,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAA;IACtG,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface ApiClientConfig {
|
|
2
|
+
apiUrl: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
}
|
|
5
|
+
export declare class ApiClient {
|
|
6
|
+
private baseUrl;
|
|
7
|
+
private apiKey;
|
|
8
|
+
constructor(config: ApiClientConfig);
|
|
9
|
+
generateImage(params: {
|
|
10
|
+
html: string;
|
|
11
|
+
css?: string;
|
|
12
|
+
width?: number;
|
|
13
|
+
height?: number;
|
|
14
|
+
format?: 'png' | 'jpg';
|
|
15
|
+
quality?: number;
|
|
16
|
+
deviceScale?: 1 | 2 | 3;
|
|
17
|
+
timeout?: number;
|
|
18
|
+
}): Promise<any>;
|
|
19
|
+
screenshotUrl(params: {
|
|
20
|
+
url: string;
|
|
21
|
+
width?: number;
|
|
22
|
+
height?: number;
|
|
23
|
+
format?: 'png' | 'jpg';
|
|
24
|
+
quality?: number;
|
|
25
|
+
fullPage?: boolean;
|
|
26
|
+
deviceScale?: 1 | 2 | 3;
|
|
27
|
+
timeout?: number;
|
|
28
|
+
}): Promise<any>;
|
|
29
|
+
private post;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=api-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,MAAM,CAAQ;gBAEV,MAAM,EAAE,eAAe;IAK7B,aAAa,CAAC,MAAM,EAAE;QAC1B,IAAI,EAAE,MAAM,CAAA;QACZ,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAA;QACtB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,WAAW,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACvB,OAAO,CAAC,EAAE,MAAM,CAAA;KACjB;IAIK,aAAa,CAAC,MAAM,EAAE;QAC1B,GAAG,EAAE,MAAM,CAAA;QACX,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAA;QACtB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;QAClB,WAAW,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACvB,OAAO,CAAC,EAAE,MAAM,CAAA;KACjB;YAIa,IAAI;CAiBnB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export class ApiClient {
|
|
2
|
+
baseUrl;
|
|
3
|
+
apiKey;
|
|
4
|
+
constructor(config) {
|
|
5
|
+
this.baseUrl = config.apiUrl.replace(/\/$/, '');
|
|
6
|
+
this.apiKey = config.apiKey;
|
|
7
|
+
}
|
|
8
|
+
async generateImage(params) {
|
|
9
|
+
return this.post('/v1/image', params);
|
|
10
|
+
}
|
|
11
|
+
async screenshotUrl(params) {
|
|
12
|
+
return this.post('/v1/screenshot', params);
|
|
13
|
+
}
|
|
14
|
+
async post(path, body) {
|
|
15
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: {
|
|
18
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
19
|
+
'Content-Type': 'application/json',
|
|
20
|
+
},
|
|
21
|
+
body: JSON.stringify(body),
|
|
22
|
+
});
|
|
23
|
+
const data = await res.json();
|
|
24
|
+
if (!res.ok) {
|
|
25
|
+
const err = data;
|
|
26
|
+
throw new Error(err.error?.message || `API error: ${res.status}`);
|
|
27
|
+
}
|
|
28
|
+
return data;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAKA,MAAM,OAAO,SAAS;IACZ,OAAO,CAAQ;IACf,MAAM,CAAQ;IAEtB,YAAY,MAAuB;QACjC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;QAC/C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;IAC7B,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,MASnB;QACC,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAA;IACvC,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,MASnB;QACC,OAAO,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAA;IAC5C,CAAC;IAEO,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,IAAa;QAC5C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,EAAE;YAChD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,eAAe,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE;gBACxC,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC3B,CAAC,CAAA;QAEF,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;QAC7B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,GAAG,GAAG,IAAwC,CAAA;YACpD,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,IAAI,cAAc,GAAG,CAAC,MAAM,EAAE,CAAC,CAAA;QACnE,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,sCAAsC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":""}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { RendshotClient } from '@rendshot/sdk';
|
|
5
|
+
import { generateImageSchema, generateImageToolMeta, handleGenerateImage, } from './tools/generateImage.js';
|
|
6
|
+
import { screenshotUrlSchema, screenshotUrlToolMeta, handleScreenshotUrl, } from './tools/screenshotUrl.js';
|
|
7
|
+
const apiKey = process.env.RENDSHOT_API_KEY;
|
|
8
|
+
const apiUrl = process.env.RENDSHOT_API_URL || 'https://api.rendshot.ai';
|
|
9
|
+
if (!apiKey) {
|
|
10
|
+
console.error('RENDSHOT_API_KEY environment variable is required');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const client = new RendshotClient({ apiKey, baseUrl: apiUrl });
|
|
14
|
+
const server = new McpServer({
|
|
15
|
+
name: 'rendshot',
|
|
16
|
+
version: '0.1.0',
|
|
17
|
+
});
|
|
18
|
+
server.tool(generateImageToolMeta.name, generateImageToolMeta.description, generateImageSchema, async (args) => handleGenerateImage(client, args));
|
|
19
|
+
server.tool(screenshotUrlToolMeta.name, screenshotUrlToolMeta.description, screenshotUrlSchema, async (args) => handleScreenshotUrl(client, args));
|
|
20
|
+
const transport = new StdioServerTransport();
|
|
21
|
+
await server.connect(transport);
|
|
22
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,0BAA0B,CAAA;AAEjC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAA;AAC3C,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,yBAAyB,CAAA;AAExE,IAAI,CAAC,MAAM,EAAE,CAAC;IACZ,OAAO,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAA;IAClE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAA;AAE9D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAA;AAEF,MAAM,CAAC,IAAI,CACT,qBAAqB,CAAC,IAAI,EAC1B,qBAAqB,CAAC,WAAW,EACjC,mBAAmB,EACnB,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,mBAAmB,CAAC,MAAM,EAAE,IAAI,CAAC,CAClD,CAAA;AAED,MAAM,CAAC,IAAI,CACT,qBAAqB,CAAC,IAAI,EAC1B,qBAAqB,CAAC,WAAW,EACjC,mBAAmB,EACnB,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,mBAAmB,CAAC,MAAM,EAAE,IAAI,CAAC,CAClD,CAAA;AAED,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;AAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { RendshotClient } from '@rendshot/sdk';
|
|
3
|
+
export declare const generateImageSchema: {
|
|
4
|
+
html: z.ZodString;
|
|
5
|
+
css: z.ZodOptional<z.ZodString>;
|
|
6
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
8
|
+
format: z.ZodOptional<z.ZodEnum<["png", "jpg"]>>;
|
|
9
|
+
quality: z.ZodOptional<z.ZodNumber>;
|
|
10
|
+
deviceScale: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<3>]>>;
|
|
11
|
+
fonts: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
12
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
};
|
|
14
|
+
export declare const generateImageToolMeta: {
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function handleGenerateImage(client: RendshotClient, args: z.objectOutputType<typeof generateImageSchema, z.ZodTypeAny>): Promise<{
|
|
19
|
+
content: {
|
|
20
|
+
type: "text";
|
|
21
|
+
text: string;
|
|
22
|
+
}[];
|
|
23
|
+
}>;
|
|
24
|
+
//# sourceMappingURL=generateImage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generateImage.d.ts","sourceRoot":"","sources":["../../src/tools/generateImage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,eAAe,CAAA;AAEhE,eAAO,MAAM,mBAAmB;;;;;;;;;;CAU/B,CAAA;AAED,eAAO,MAAM,qBAAqB;;;CAGjC,CAAA;AAED,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,CAAC,CAAC,gBAAgB,CAAC,OAAO,mBAAmB,EAAE,CAAC,CAAC,UAAU,CAAC;;;;;GAWnE"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const generateImageSchema = {
|
|
3
|
+
html: z.string().describe('HTML content to render'),
|
|
4
|
+
css: z.string().optional().describe('Optional CSS styles'),
|
|
5
|
+
width: z.number().optional().describe('Image width in pixels (default 1080)'),
|
|
6
|
+
height: z.number().optional().describe('Image height in pixels (default 1080)'),
|
|
7
|
+
format: z.enum(['png', 'jpg']).optional().describe('Output format (default png)'),
|
|
8
|
+
quality: z.number().optional().describe('Quality 1-100 for jpg (default 90)'),
|
|
9
|
+
deviceScale: z.union([z.literal(1), z.literal(2), z.literal(3)]).optional().describe('Device scale factor (default 1)'),
|
|
10
|
+
fonts: z.array(z.string()).optional().describe('Custom fonts to load (e.g. ["Inter", "Roboto"])'),
|
|
11
|
+
timeout: z.number().optional().describe('Render timeout in ms (default 10000)'),
|
|
12
|
+
};
|
|
13
|
+
export const generateImageToolMeta = {
|
|
14
|
+
name: 'generate_image',
|
|
15
|
+
description: 'Render HTML/CSS to an image and return the URL',
|
|
16
|
+
};
|
|
17
|
+
export async function handleGenerateImage(client, args) {
|
|
18
|
+
const result = await client.renderImage(args);
|
|
19
|
+
return {
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
type: 'text',
|
|
23
|
+
text: `Image generated successfully!\nURL: ${result.url}\nSize: ${result.width}x${result.height} ${result.format}\nFile size: ${result.size} bytes`,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=generateImage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generateImage.js","sourceRoot":"","sources":["../../src/tools/generateImage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,wBAAwB,CAAC;IACnD,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qBAAqB,CAAC;IAC1D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;IAC7E,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;IAC/E,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6BAA6B,CAAC;IACjF,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oCAAoC,CAAC;IAC7E,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;IACvH,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iDAAiD,CAAC;IACjG,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;CAChF,CAAA;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,gBAAgB;IACtB,WAAW,EAAE,gDAAgD;CAC9D,CAAA;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAsB,EACtB,IAAkE;IAElE,MAAM,MAAM,GAAgB,MAAM,MAAM,CAAC,WAAW,CAAC,IAAW,CAAC,CAAA;IACjE,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAe;gBACrB,IAAI,EAAE,uCAAuC,MAAM,CAAC,GAAG,WAAW,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,gBAAgB,MAAM,CAAC,IAAI,QAAQ;aACpJ;SACF;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { RendshotClient } from '@rendshot/sdk';
|
|
3
|
+
export declare const screenshotUrlSchema: {
|
|
4
|
+
url: z.ZodString;
|
|
5
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
6
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
format: z.ZodOptional<z.ZodEnum<["png", "jpg"]>>;
|
|
8
|
+
quality: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
fullPage: z.ZodOptional<z.ZodBoolean>;
|
|
10
|
+
deviceScale: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<3>]>>;
|
|
11
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
12
|
+
};
|
|
13
|
+
export declare const screenshotUrlToolMeta: {
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function handleScreenshotUrl(client: RendshotClient, args: z.objectOutputType<typeof screenshotUrlSchema, z.ZodTypeAny>): Promise<{
|
|
18
|
+
content: {
|
|
19
|
+
type: "text";
|
|
20
|
+
text: string;
|
|
21
|
+
}[];
|
|
22
|
+
}>;
|
|
23
|
+
//# sourceMappingURL=screenshotUrl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshotUrl.d.ts","sourceRoot":"","sources":["../../src/tools/screenshotUrl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,eAAe,CAAA;AAEhE,eAAO,MAAM,mBAAmB;;;;;;;;;CAS/B,CAAA;AAED,eAAO,MAAM,qBAAqB;;;CAGjC,CAAA;AAED,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,CAAC,CAAC,gBAAgB,CAAC,OAAO,mBAAmB,EAAE,CAAC,CAAC,UAAU,CAAC;;;;;GAWnE"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const screenshotUrlSchema = {
|
|
3
|
+
url: z.string().describe('URL to screenshot'),
|
|
4
|
+
width: z.number().optional().describe('Viewport width (default 1280)'),
|
|
5
|
+
height: z.number().optional().describe('Viewport height (default 800)'),
|
|
6
|
+
format: z.enum(['png', 'jpg']).optional().describe('Output format (default png)'),
|
|
7
|
+
quality: z.number().optional().describe('Quality 1-100 for jpg (default 90)'),
|
|
8
|
+
fullPage: z.boolean().optional().describe('Capture full page (default false)'),
|
|
9
|
+
deviceScale: z.union([z.literal(1), z.literal(2), z.literal(3)]).optional().describe('Device scale factor (default 1)'),
|
|
10
|
+
timeout: z.number().optional().describe('Timeout in ms (default 10000)'),
|
|
11
|
+
};
|
|
12
|
+
export const screenshotUrlToolMeta = {
|
|
13
|
+
name: 'screenshot_url',
|
|
14
|
+
description: 'Take a screenshot of a URL and return the image URL',
|
|
15
|
+
};
|
|
16
|
+
export async function handleScreenshotUrl(client, args) {
|
|
17
|
+
const result = await client.screenshotUrl(args);
|
|
18
|
+
return {
|
|
19
|
+
content: [
|
|
20
|
+
{
|
|
21
|
+
type: 'text',
|
|
22
|
+
text: `Screenshot taken!\nURL: ${result.url}\nSize: ${result.width}x${result.height} ${result.format}\nFile size: ${result.size} bytes`,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=screenshotUrl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshotUrl.js","sourceRoot":"","sources":["../../src/tools/screenshotUrl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC;IAC7C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+BAA+B,CAAC;IACtE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+BAA+B,CAAC;IACvE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6BAA6B,CAAC;IACjF,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oCAAoC,CAAC;IAC7E,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;IAC9E,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;IACvH,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+BAA+B,CAAC;CACzE,CAAA;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,gBAAgB;IACtB,WAAW,EAAE,qDAAqD;CACnE,CAAA;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAsB,EACtB,IAAkE;IAElE,MAAM,MAAM,GAAgB,MAAM,MAAM,CAAC,aAAa,CAAC,IAAW,CAAC,CAAA;IACnE,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAe;gBACrB,IAAI,EAAE,2BAA2B,MAAM,CAAC,GAAG,WAAW,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,gBAAgB,MAAM,CAAC,IAAI,QAAQ;aACxI;SACF;KACF,CAAA;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rendshot/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"rendshot-mcp": "./dist/server.js"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@modelcontextprotocol/sdk": "^1",
|
|
10
|
+
"zod": "^3",
|
|
11
|
+
"@rendshot/sdk": "0.1.0"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"tsx": "^4",
|
|
15
|
+
"vitest": "^3"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"dev": "tsx watch src/server.ts",
|
|
20
|
+
"test": "vitest run --passWithNoTests",
|
|
21
|
+
"clean": "rm -rf dist"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import {
|
|
4
|
+
generateImageSchema,
|
|
5
|
+
generateImageToolMeta,
|
|
6
|
+
handleGenerateImage,
|
|
7
|
+
} from '../tools/generateImage.js'
|
|
8
|
+
import {
|
|
9
|
+
screenshotUrlSchema,
|
|
10
|
+
screenshotUrlToolMeta,
|
|
11
|
+
handleScreenshotUrl,
|
|
12
|
+
} from '../tools/screenshotUrl.js'
|
|
13
|
+
import { RendshotClient } from '@rendshot/sdk'
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Helpers
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
function makeFetchMock(status: number, body: unknown) {
|
|
20
|
+
return vi.fn().mockResolvedValue({
|
|
21
|
+
ok: status >= 200 && status < 300,
|
|
22
|
+
status,
|
|
23
|
+
json: async () => body,
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
vi.unstubAllGlobals()
|
|
29
|
+
vi.restoreAllMocks()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Tool meta / definitions
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
describe('generate_image tool meta', () => {
|
|
37
|
+
it('has the correct name', () => {
|
|
38
|
+
expect(generateImageToolMeta.name).toBe('generate_image')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('has a non-empty description', () => {
|
|
42
|
+
expect(typeof generateImageToolMeta.description).toBe('string')
|
|
43
|
+
expect(generateImageToolMeta.description.length).toBeGreaterThan(0)
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('screenshot_url tool meta', () => {
|
|
48
|
+
it('has the correct name', () => {
|
|
49
|
+
expect(screenshotUrlToolMeta.name).toBe('screenshot_url')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('has a non-empty description', () => {
|
|
53
|
+
expect(typeof screenshotUrlToolMeta.description).toBe('string')
|
|
54
|
+
expect(screenshotUrlToolMeta.description.length).toBeGreaterThan(0)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// generate_image schema validation
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
describe('generateImageSchema validation', () => {
|
|
63
|
+
const schema = z.object(generateImageSchema)
|
|
64
|
+
|
|
65
|
+
it('accepts valid input with only the required html field', () => {
|
|
66
|
+
const result = schema.safeParse({ html: '<h1>Hello</h1>' })
|
|
67
|
+
expect(result.success).toBe(true)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('accepts all optional fields when provided with correct types', () => {
|
|
71
|
+
const result = schema.safeParse({
|
|
72
|
+
html: '<p>test</p>',
|
|
73
|
+
css: 'body { color: red; }',
|
|
74
|
+
width: 800,
|
|
75
|
+
height: 600,
|
|
76
|
+
format: 'png',
|
|
77
|
+
quality: 90,
|
|
78
|
+
deviceScale: 2,
|
|
79
|
+
timeout: 10000,
|
|
80
|
+
})
|
|
81
|
+
expect(result.success).toBe(true)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('rejects input missing the required html field', () => {
|
|
85
|
+
const result = schema.safeParse({ width: 800 })
|
|
86
|
+
expect(result.success).toBe(false)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('rejects invalid format values', () => {
|
|
90
|
+
const result = schema.safeParse({ html: '<p>x</p>', format: 'gif' })
|
|
91
|
+
expect(result.success).toBe(false)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('rejects invalid deviceScale values', () => {
|
|
95
|
+
const result = schema.safeParse({ html: '<p>x</p>', deviceScale: 4 })
|
|
96
|
+
expect(result.success).toBe(false)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('accepts deviceScale of 1, 2, or 3', () => {
|
|
100
|
+
for (const scale of [1, 2, 3] as const) {
|
|
101
|
+
const result = schema.safeParse({ html: '<p>x</p>', deviceScale: scale })
|
|
102
|
+
expect(result.success).toBe(true)
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('accepts fonts array', () => {
|
|
107
|
+
const result = schema.safeParse({ html: '<p>x</p>', fonts: ['Inter', 'Roboto'] })
|
|
108
|
+
expect(result.success).toBe(true)
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// screenshot_url schema validation
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
describe('screenshotUrlSchema validation', () => {
|
|
117
|
+
const schema = z.object(screenshotUrlSchema)
|
|
118
|
+
|
|
119
|
+
it('accepts valid input with only the required url field', () => {
|
|
120
|
+
const result = schema.safeParse({ url: 'https://example.com' })
|
|
121
|
+
expect(result.success).toBe(true)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('accepts all optional fields when provided with correct types', () => {
|
|
125
|
+
const result = schema.safeParse({
|
|
126
|
+
url: 'https://example.com',
|
|
127
|
+
width: 1280,
|
|
128
|
+
height: 800,
|
|
129
|
+
format: 'jpg',
|
|
130
|
+
quality: 85,
|
|
131
|
+
fullPage: true,
|
|
132
|
+
deviceScale: 1,
|
|
133
|
+
timeout: 5000,
|
|
134
|
+
})
|
|
135
|
+
expect(result.success).toBe(true)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('rejects input missing the required url field', () => {
|
|
139
|
+
const result = schema.safeParse({ width: 1280 })
|
|
140
|
+
expect(result.success).toBe(false)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('rejects invalid format values', () => {
|
|
144
|
+
const result = schema.safeParse({ url: 'https://example.com', format: 'bmp' })
|
|
145
|
+
expect(result.success).toBe(false)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('rejects invalid deviceScale values', () => {
|
|
149
|
+
const result = schema.safeParse({ url: 'https://example.com', deviceScale: 0 })
|
|
150
|
+
expect(result.success).toBe(false)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('accepts valid format values: png, jpg', () => {
|
|
154
|
+
for (const format of ['png', 'jpg'] as const) {
|
|
155
|
+
const result = schema.safeParse({ url: 'https://example.com', format })
|
|
156
|
+
expect(result.success).toBe(true)
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// handleGenerateImage — calls ApiClient correctly
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
describe('handleGenerateImage', () => {
|
|
166
|
+
it('calls client.generateImage with the provided args', async () => {
|
|
167
|
+
const mockFetch = makeFetchMock(200, {
|
|
168
|
+
url: 'https://cdn.example.com/img.png',
|
|
169
|
+
width: 1080,
|
|
170
|
+
height: 1080,
|
|
171
|
+
format: 'png',
|
|
172
|
+
size: 8192,
|
|
173
|
+
})
|
|
174
|
+
vi.stubGlobal('fetch', mockFetch)
|
|
175
|
+
|
|
176
|
+
const client = new RendshotClient({ apiKey: 'rs_test_key', baseUrl: 'https://api.rendshot.com' })
|
|
177
|
+
const args = { html: '<h1>Hello</h1>', width: 1080, height: 1080 }
|
|
178
|
+
|
|
179
|
+
const response = await handleGenerateImage(client, args as any)
|
|
180
|
+
|
|
181
|
+
// Verify fetch was called to POST /v1/image
|
|
182
|
+
expect(mockFetch).toHaveBeenCalledOnce()
|
|
183
|
+
const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit]
|
|
184
|
+
expect(url).toBe('https://api.rendshot.com/v1/image')
|
|
185
|
+
expect((init as any).method).toBe('POST')
|
|
186
|
+
const body = JSON.parse(init.body as string)
|
|
187
|
+
expect(body.html).toBe('<h1>Hello</h1>')
|
|
188
|
+
|
|
189
|
+
// Verify the MCP response shape
|
|
190
|
+
expect(response.content).toHaveLength(1)
|
|
191
|
+
expect(response.content[0].type).toBe('text')
|
|
192
|
+
expect((response.content[0] as any).text).toContain('https://cdn.example.com/img.png')
|
|
193
|
+
expect((response.content[0] as any).text).toContain('Image generated successfully')
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('propagates errors from the API client', async () => {
|
|
197
|
+
const mockFetch = makeFetchMock(400, { error: { message: 'HTML is required' } })
|
|
198
|
+
vi.stubGlobal('fetch', mockFetch)
|
|
199
|
+
|
|
200
|
+
const client = new RendshotClient({ apiKey: 'rs_test_key', baseUrl: 'https://api.rendshot.com' })
|
|
201
|
+
await expect(handleGenerateImage(client, { html: '' } as any)).rejects.toThrow('HTML is required')
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
// handleScreenshotUrl — calls ApiClient correctly
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
describe('handleScreenshotUrl', () => {
|
|
210
|
+
it('calls client.screenshotUrl with the provided args', async () => {
|
|
211
|
+
const mockFetch = makeFetchMock(200, {
|
|
212
|
+
url: 'https://cdn.example.com/shot.jpg',
|
|
213
|
+
width: 1280,
|
|
214
|
+
height: 800,
|
|
215
|
+
format: 'jpg',
|
|
216
|
+
size: 20480,
|
|
217
|
+
})
|
|
218
|
+
vi.stubGlobal('fetch', mockFetch)
|
|
219
|
+
|
|
220
|
+
const client = new RendshotClient({ apiKey: 'rs_test_key', baseUrl: 'https://api.rendshot.com' })
|
|
221
|
+
const args = { url: 'https://example.com', fullPage: true, format: 'jpg' }
|
|
222
|
+
|
|
223
|
+
const response = await handleScreenshotUrl(client, args as any)
|
|
224
|
+
|
|
225
|
+
expect(mockFetch).toHaveBeenCalledOnce()
|
|
226
|
+
const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit]
|
|
227
|
+
expect(url).toBe('https://api.rendshot.com/v1/screenshot')
|
|
228
|
+
expect((init as any).method).toBe('POST')
|
|
229
|
+
const body = JSON.parse(init.body as string)
|
|
230
|
+
expect(body.url).toBe('https://example.com')
|
|
231
|
+
expect(body.fullPage).toBe(true)
|
|
232
|
+
|
|
233
|
+
expect(response.content).toHaveLength(1)
|
|
234
|
+
expect(response.content[0].type).toBe('text')
|
|
235
|
+
expect((response.content[0] as any).text).toContain('https://cdn.example.com/shot.jpg')
|
|
236
|
+
expect((response.content[0] as any).text).toContain('Screenshot taken')
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('propagates errors from the API client', async () => {
|
|
240
|
+
const mockFetch = makeFetchMock(422, { error: { message: 'Invalid URL' } })
|
|
241
|
+
vi.stubGlobal('fetch', mockFetch)
|
|
242
|
+
|
|
243
|
+
const client = new RendshotClient({ apiKey: 'rs_test_key', baseUrl: 'https://api.rendshot.com' })
|
|
244
|
+
await expect(
|
|
245
|
+
handleScreenshotUrl(client, { url: 'not-a-url' } as any)
|
|
246
|
+
).rejects.toThrow('Invalid URL')
|
|
247
|
+
})
|
|
248
|
+
})
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// MCP server entry point is server.ts
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
4
|
+
import { RendshotClient } from '@rendshot/sdk'
|
|
5
|
+
import {
|
|
6
|
+
generateImageSchema,
|
|
7
|
+
generateImageToolMeta,
|
|
8
|
+
handleGenerateImage,
|
|
9
|
+
} from './tools/generateImage.js'
|
|
10
|
+
import {
|
|
11
|
+
screenshotUrlSchema,
|
|
12
|
+
screenshotUrlToolMeta,
|
|
13
|
+
handleScreenshotUrl,
|
|
14
|
+
} from './tools/screenshotUrl.js'
|
|
15
|
+
|
|
16
|
+
const apiKey = process.env.RENDSHOT_API_KEY
|
|
17
|
+
const apiUrl = process.env.RENDSHOT_API_URL || 'https://api.rendshot.ai'
|
|
18
|
+
|
|
19
|
+
if (!apiKey) {
|
|
20
|
+
console.error('RENDSHOT_API_KEY environment variable is required')
|
|
21
|
+
process.exit(1)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const client = new RendshotClient({ apiKey, baseUrl: apiUrl })
|
|
25
|
+
|
|
26
|
+
const server = new McpServer({
|
|
27
|
+
name: 'rendshot',
|
|
28
|
+
version: '0.1.0',
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
server.tool(
|
|
32
|
+
generateImageToolMeta.name,
|
|
33
|
+
generateImageToolMeta.description,
|
|
34
|
+
generateImageSchema,
|
|
35
|
+
async (args) => handleGenerateImage(client, args),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
server.tool(
|
|
39
|
+
screenshotUrlToolMeta.name,
|
|
40
|
+
screenshotUrlToolMeta.description,
|
|
41
|
+
screenshotUrlSchema,
|
|
42
|
+
async (args) => handleScreenshotUrl(client, args),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const transport = new StdioServerTransport()
|
|
46
|
+
await server.connect(transport)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { RendshotClient, ImageResult } from '@rendshot/sdk'
|
|
3
|
+
|
|
4
|
+
export const generateImageSchema = {
|
|
5
|
+
html: z.string().describe('HTML content to render'),
|
|
6
|
+
css: z.string().optional().describe('Optional CSS styles'),
|
|
7
|
+
width: z.number().optional().describe('Image width in pixels (default 1080)'),
|
|
8
|
+
height: z.number().optional().describe('Image height in pixels (default 1080)'),
|
|
9
|
+
format: z.enum(['png', 'jpg']).optional().describe('Output format (default png)'),
|
|
10
|
+
quality: z.number().optional().describe('Quality 1-100 for jpg (default 90)'),
|
|
11
|
+
deviceScale: z.union([z.literal(1), z.literal(2), z.literal(3)]).optional().describe('Device scale factor (default 1)'),
|
|
12
|
+
fonts: z.array(z.string()).optional().describe('Custom fonts to load (e.g. ["Inter", "Roboto"])'),
|
|
13
|
+
timeout: z.number().optional().describe('Render timeout in ms (default 10000)'),
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const generateImageToolMeta = {
|
|
17
|
+
name: 'generate_image',
|
|
18
|
+
description: 'Render HTML/CSS to an image and return the URL',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function handleGenerateImage(
|
|
22
|
+
client: RendshotClient,
|
|
23
|
+
args: z.objectOutputType<typeof generateImageSchema, z.ZodTypeAny>,
|
|
24
|
+
) {
|
|
25
|
+
const result: ImageResult = await client.renderImage(args as any)
|
|
26
|
+
return {
|
|
27
|
+
content: [
|
|
28
|
+
{
|
|
29
|
+
type: 'text' as const,
|
|
30
|
+
text: `Image generated successfully!\nURL: ${result.url}\nSize: ${result.width}x${result.height} ${result.format}\nFile size: ${result.size} bytes`,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import type { RendshotClient, ImageResult } from '@rendshot/sdk'
|
|
3
|
+
|
|
4
|
+
export const screenshotUrlSchema = {
|
|
5
|
+
url: z.string().describe('URL to screenshot'),
|
|
6
|
+
width: z.number().optional().describe('Viewport width (default 1280)'),
|
|
7
|
+
height: z.number().optional().describe('Viewport height (default 800)'),
|
|
8
|
+
format: z.enum(['png', 'jpg']).optional().describe('Output format (default png)'),
|
|
9
|
+
quality: z.number().optional().describe('Quality 1-100 for jpg (default 90)'),
|
|
10
|
+
fullPage: z.boolean().optional().describe('Capture full page (default false)'),
|
|
11
|
+
deviceScale: z.union([z.literal(1), z.literal(2), z.literal(3)]).optional().describe('Device scale factor (default 1)'),
|
|
12
|
+
timeout: z.number().optional().describe('Timeout in ms (default 10000)'),
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const screenshotUrlToolMeta = {
|
|
16
|
+
name: 'screenshot_url',
|
|
17
|
+
description: 'Take a screenshot of a URL and return the image URL',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function handleScreenshotUrl(
|
|
21
|
+
client: RendshotClient,
|
|
22
|
+
args: z.objectOutputType<typeof screenshotUrlSchema, z.ZodTypeAny>,
|
|
23
|
+
) {
|
|
24
|
+
const result: ImageResult = await client.screenshotUrl(args as any)
|
|
25
|
+
return {
|
|
26
|
+
content: [
|
|
27
|
+
{
|
|
28
|
+
type: 'text' as const,
|
|
29
|
+
text: `Screenshot taken!\nURL: ${result.url}\nSize: ${result.width}x${result.height} ${result.format}\nFile size: ${result.size} bytes`,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
}
|
|
33
|
+
}
|