@iflow-mcp/dynamicendpoints-etsy-mcp 1.2.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/src/index.ts ADDED
@@ -0,0 +1,2667 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import {
6
+ CallToolRequestSchema,
7
+ ListToolsRequestSchema,
8
+ ListPromptsRequestSchema,
9
+ GetPromptRequestSchema,
10
+ ListResourcesRequestSchema,
11
+ ReadResourceRequestSchema,
12
+ Tool,
13
+ Prompt,
14
+ Resource,
15
+ GetPromptResult,
16
+ ReadResourceResult,
17
+ } from '@modelcontextprotocol/sdk/types.js';
18
+ import axios, { AxiosInstance } from 'axios';
19
+ import { z } from 'zod';
20
+
21
+ // Etsy API Configuration
22
+ const ETSY_API_BASE_URL = 'https://openapi.etsy.com/v3';
23
+
24
+ interface EtsyConfig {
25
+ apiKey: string;
26
+ shopId?: string;
27
+ accessToken?: string;
28
+ }
29
+
30
+ // Configuration schema for Smithery - All fields optional
31
+ export const configSchema = z.object({
32
+ apiKey: z.string().optional().describe('Your Etsy API key (Keystring) from the Etsy Developer Portal. Falls back to ETSY_API_KEY environment variable.'),
33
+ shopId: z.string().optional().describe('Your Etsy shop ID (optional, for faster shop operations). Falls back to ETSY_SHOP_ID environment variable.'),
34
+ accessToken: z.string().optional().describe('OAuth access token for shop management features (optional, required for write operations). Falls back to ETSY_ACCESS_TOKEN environment variable.'),
35
+ });
36
+
37
+ class EtsyMCPServer {
38
+ private server: Server;
39
+ private axiosInstance: AxiosInstance;
40
+ private config: EtsyConfig;
41
+
42
+ constructor(config: EtsyConfig) {
43
+ this.config = config;
44
+
45
+ const headers: any = {
46
+ 'x-api-key': this.config.apiKey,
47
+ };
48
+
49
+ // Add OAuth token if available for authenticated requests
50
+ if (this.config.accessToken) {
51
+ headers['Authorization'] = `Bearer ${this.config.accessToken}`;
52
+ }
53
+
54
+ this.axiosInstance = axios.create({
55
+ baseURL: ETSY_API_BASE_URL,
56
+ headers,
57
+ });
58
+
59
+ this.server = new Server(
60
+ {
61
+ name: 'etsy-mcp-server',
62
+ version: '1.0.0',
63
+ },
64
+ {
65
+ capabilities: {
66
+ tools: {},
67
+ prompts: {},
68
+ resources: {},
69
+ },
70
+ }
71
+ );
72
+
73
+ this.setupHandlers();
74
+ }
75
+
76
+ private setupHandlers(): void {
77
+ // List available tools
78
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
79
+ tools: this.getTools(),
80
+ }));
81
+
82
+ // Handle tool calls
83
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
84
+ const { name, arguments: args } = request.params;
85
+
86
+ try {
87
+ switch (name) {
88
+ case 'search_listings':
89
+ return await this.searchListings(args);
90
+ case 'get_listing':
91
+ return await this.getListing(args);
92
+ case 'get_shop':
93
+ return await this.getShop(args);
94
+ case 'get_shop_listings':
95
+ return await this.getShopListings(args);
96
+ case 'search_shops':
97
+ return await this.searchShops(args);
98
+ case 'get_listing_inventory':
99
+ return await this.getListingInventory(args);
100
+ case 'get_listing_images':
101
+ return await this.getListingImages(args);
102
+ case 'get_shop_sections':
103
+ return await this.getShopSections(args);
104
+ case 'get_trending_listings':
105
+ return await this.getTrendingListings(args);
106
+ case 'find_shops':
107
+ return await this.findShops(args);
108
+ // Shop Management Tools
109
+ case 'create_listing':
110
+ return await this.createListing(args);
111
+ case 'update_listing':
112
+ return await this.updateListing(args);
113
+ case 'delete_listing':
114
+ return await this.deleteListing(args);
115
+ case 'update_listing_inventory':
116
+ return await this.updateListingInventory(args);
117
+ case 'upload_listing_image':
118
+ return await this.uploadListingImage(args);
119
+ case 'create_shop_section':
120
+ return await this.createShopSection(args);
121
+ case 'update_shop_section':
122
+ return await this.updateShopSection(args);
123
+ case 'delete_shop_section':
124
+ return await this.deleteShopSection(args);
125
+ case 'update_shop':
126
+ return await this.updateShop(args);
127
+ default:
128
+ throw new Error(`Unknown tool: ${name}`);
129
+ }
130
+ } catch (error) {
131
+ if (axios.isAxiosError(error)) {
132
+ return {
133
+ content: [
134
+ {
135
+ type: 'text',
136
+ text: JSON.stringify(
137
+ {
138
+ error: error.response?.data || error.message,
139
+ status: error.response?.status,
140
+ },
141
+ null,
142
+ 2
143
+ ),
144
+ },
145
+ ],
146
+ };
147
+ }
148
+ throw error;
149
+ }
150
+ });
151
+
152
+ // List available prompts
153
+ this.server.setRequestHandler(ListPromptsRequestSchema, async () => ({
154
+ prompts: this.getPrompts(),
155
+ }));
156
+
157
+ // Handle prompt requests
158
+ this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
159
+ const { name, arguments: args } = request.params;
160
+ return await this.getPrompt(name, args);
161
+ });
162
+
163
+ // List available resources
164
+ this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
165
+ resources: this.getResources(),
166
+ }));
167
+
168
+ // Handle resource reads
169
+ this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
170
+ const { uri } = request.params;
171
+ return await this.readResource(uri);
172
+ });
173
+ }
174
+
175
+ private getTools(): Tool[] {
176
+ return [
177
+ {
178
+ name: 'search_listings',
179
+ description:
180
+ 'Search for active Etsy listings. Supports keyword search with various filters.',
181
+ inputSchema: {
182
+ type: 'object',
183
+ properties: {
184
+ keywords: {
185
+ type: 'string',
186
+ description: 'Search keywords for finding listings',
187
+ },
188
+ limit: {
189
+ type: 'number',
190
+ description: 'Maximum number of results (default: 25, max: 100)',
191
+ default: 25,
192
+ },
193
+ offset: {
194
+ type: 'number',
195
+ description: 'Number of results to skip (for pagination)',
196
+ default: 0,
197
+ },
198
+ min_price: {
199
+ type: 'number',
200
+ description: 'Minimum price in the shop currency',
201
+ },
202
+ max_price: {
203
+ type: 'number',
204
+ description: 'Maximum price in the shop currency',
205
+ },
206
+ sort_on: {
207
+ type: 'string',
208
+ enum: ['created', 'price', 'updated', 'score'],
209
+ description: 'Field to sort results on',
210
+ },
211
+ sort_order: {
212
+ type: 'string',
213
+ enum: ['asc', 'desc', 'ascending', 'descending'],
214
+ description: 'Sort order',
215
+ },
216
+ },
217
+ required: ['keywords'],
218
+ },
219
+ annotations: {
220
+ readOnlyHint: true,
221
+ destructiveHint: false,
222
+ idempotentHint: true,
223
+ },
224
+ },
225
+ {
226
+ name: 'get_listing',
227
+ description: 'Get detailed information about a specific Etsy listing by its ID.',
228
+ inputSchema: {
229
+ type: 'object',
230
+ properties: {
231
+ listing_id: {
232
+ type: 'number',
233
+ description: 'The numeric ID of the listing',
234
+ },
235
+ includes: {
236
+ type: 'array',
237
+ items: {
238
+ type: 'string',
239
+ enum: ['Shop', 'Images', 'User', 'Translations', 'Inventory'],
240
+ },
241
+ description: 'Additional data to include in the response',
242
+ },
243
+ },
244
+ required: ['listing_id'],
245
+ },
246
+ annotations: {
247
+ readOnlyHint: true,
248
+ destructiveHint: false,
249
+ idempotentHint: true,
250
+ },
251
+ },
252
+ {
253
+ name: 'get_shop',
254
+ description: 'Get information about an Etsy shop by shop ID.',
255
+ inputSchema: {
256
+ type: 'object',
257
+ properties: {
258
+ shop_id: {
259
+ type: 'number',
260
+ description: 'The numeric ID of the shop',
261
+ },
262
+ },
263
+ required: ['shop_id'],
264
+ },
265
+ annotations: {
266
+ readOnlyHint: true,
267
+ destructiveHint: false,
268
+ idempotentHint: true,
269
+ },
270
+ },
271
+ {
272
+ name: 'get_shop_listings',
273
+ description: 'Get all active listings from a specific shop.',
274
+ inputSchema: {
275
+ type: 'object',
276
+ properties: {
277
+ shop_id: {
278
+ type: 'number',
279
+ description: 'The numeric ID of the shop',
280
+ },
281
+ limit: {
282
+ type: 'number',
283
+ description: 'Maximum number of results (default: 25, max: 100)',
284
+ default: 25,
285
+ },
286
+ offset: {
287
+ type: 'number',
288
+ description: 'Number of results to skip (for pagination)',
289
+ default: 0,
290
+ },
291
+ state: {
292
+ type: 'string',
293
+ enum: ['active', 'inactive', 'sold_out', 'draft', 'expired'],
294
+ description: 'Filter by listing state (default: active)',
295
+ default: 'active',
296
+ },
297
+ },
298
+ required: ['shop_id'],
299
+ },
300
+ annotations: {
301
+ readOnlyHint: true,
302
+ destructiveHint: false,
303
+ idempotentHint: true,
304
+ },
305
+ },
306
+ {
307
+ name: 'search_shops',
308
+ description: 'Search for Etsy shops by shop name.',
309
+ inputSchema: {
310
+ type: 'object',
311
+ properties: {
312
+ shop_name: {
313
+ type: 'string',
314
+ description: 'The shop name to search for',
315
+ },
316
+ limit: {
317
+ type: 'number',
318
+ description: 'Maximum number of results (default: 25, max: 100)',
319
+ default: 25,
320
+ },
321
+ offset: {
322
+ type: 'number',
323
+ description: 'Number of results to skip (for pagination)',
324
+ default: 0,
325
+ },
326
+ },
327
+ required: ['shop_name'],
328
+ },
329
+ annotations: {
330
+ readOnlyHint: true,
331
+ destructiveHint: false,
332
+ idempotentHint: true,
333
+ },
334
+ },
335
+ {
336
+ name: 'get_listing_inventory',
337
+ description:
338
+ 'Get inventory information for a listing, including available quantities and variations.',
339
+ inputSchema: {
340
+ type: 'object',
341
+ properties: {
342
+ listing_id: {
343
+ type: 'number',
344
+ description: 'The numeric ID of the listing',
345
+ },
346
+ },
347
+ required: ['listing_id'],
348
+ },
349
+ annotations: {
350
+ readOnlyHint: true,
351
+ destructiveHint: false,
352
+ idempotentHint: true,
353
+ },
354
+ },
355
+ {
356
+ name: 'get_listing_images',
357
+ description: 'Get all images associated with a specific listing.',
358
+ inputSchema: {
359
+ type: 'object',
360
+ properties: {
361
+ listing_id: {
362
+ type: 'number',
363
+ description: 'The numeric ID of the listing',
364
+ },
365
+ },
366
+ required: ['listing_id'],
367
+ },
368
+ annotations: {
369
+ readOnlyHint: true,
370
+ destructiveHint: false,
371
+ idempotentHint: true,
372
+ },
373
+ },
374
+ {
375
+ name: 'get_shop_sections',
376
+ description: 'Get all sections/categories for a specific shop.',
377
+ inputSchema: {
378
+ type: 'object',
379
+ properties: {
380
+ shop_id: {
381
+ type: 'number',
382
+ description: 'The numeric ID of the shop',
383
+ },
384
+ },
385
+ required: ['shop_id'],
386
+ },
387
+ annotations: {
388
+ readOnlyHint: true,
389
+ destructiveHint: false,
390
+ idempotentHint: true,
391
+ },
392
+ },
393
+ {
394
+ name: 'get_trending_listings',
395
+ description: 'Get current trending listings on Etsy.',
396
+ inputSchema: {
397
+ type: 'object',
398
+ properties: {
399
+ limit: {
400
+ type: 'number',
401
+ description: 'Maximum number of results (default: 25, max: 100)',
402
+ default: 25,
403
+ },
404
+ offset: {
405
+ type: 'number',
406
+ description: 'Number of results to skip (for pagination)',
407
+ default: 0,
408
+ },
409
+ },
410
+ },
411
+ annotations: {
412
+ readOnlyHint: true,
413
+ destructiveHint: false,
414
+ idempotentHint: true,
415
+ },
416
+ },
417
+ {
418
+ name: 'find_shops',
419
+ description: 'Find shops by location or other criteria.',
420
+ inputSchema: {
421
+ type: 'object',
422
+ properties: {
423
+ location: {
424
+ type: 'string',
425
+ description: 'Location to search for shops',
426
+ },
427
+ limit: {
428
+ type: 'number',
429
+ description: 'Maximum number of results (default: 25, max: 100)',
430
+ default: 25,
431
+ },
432
+ offset: {
433
+ type: 'number',
434
+ description: 'Number of results to skip (for pagination)',
435
+ default: 0,
436
+ },
437
+ },
438
+ },
439
+ annotations: {
440
+ readOnlyHint: true,
441
+ destructiveHint: false,
442
+ idempotentHint: true,
443
+ },
444
+ },
445
+ // Shop Management Tools (require OAuth access token)
446
+ {
447
+ name: 'create_listing',
448
+ description:
449
+ 'Create a new listing in your Etsy shop. Requires OAuth access token.',
450
+ inputSchema: {
451
+ type: 'object',
452
+ properties: {
453
+ shop_id: {
454
+ type: 'number',
455
+ description: 'Your shop ID',
456
+ },
457
+ quantity: {
458
+ type: 'number',
459
+ description: 'Available quantity',
460
+ },
461
+ title: {
462
+ type: 'string',
463
+ description: 'Listing title (max 140 characters)',
464
+ },
465
+ description: {
466
+ type: 'string',
467
+ description: 'Item description',
468
+ },
469
+ price: {
470
+ type: 'number',
471
+ description: 'Price in shop currency',
472
+ },
473
+ who_made: {
474
+ type: 'string',
475
+ enum: ['i_did', 'someone_else', 'collective'],
476
+ description: 'Who made this item',
477
+ },
478
+ when_made: {
479
+ type: 'string',
480
+ description:
481
+ 'When was it made (e.g., made_to_order, 2020_2023, 2010_2019)',
482
+ },
483
+ taxonomy_id: {
484
+ type: 'number',
485
+ description: 'Category taxonomy ID',
486
+ },
487
+ shipping_profile_id: {
488
+ type: 'number',
489
+ description: 'Shipping profile ID (optional)',
490
+ },
491
+ shop_section_id: {
492
+ type: 'number',
493
+ description: 'Shop section ID (optional)',
494
+ },
495
+ tags: {
496
+ type: 'array',
497
+ items: { type: 'string' },
498
+ description: 'Array of tags (max 13)',
499
+ },
500
+ },
501
+ required: [
502
+ 'shop_id',
503
+ 'quantity',
504
+ 'title',
505
+ 'description',
506
+ 'price',
507
+ 'who_made',
508
+ 'when_made',
509
+ 'taxonomy_id',
510
+ ],
511
+ },
512
+ annotations: {
513
+ readOnlyHint: false,
514
+ destructiveHint: false,
515
+ idempotentHint: false,
516
+ },
517
+ },
518
+ {
519
+ name: 'update_listing',
520
+ description: 'Update an existing listing. Requires OAuth access token.',
521
+ inputSchema: {
522
+ type: 'object',
523
+ properties: {
524
+ shop_id: {
525
+ type: 'number',
526
+ description: 'Your shop ID',
527
+ },
528
+ listing_id: {
529
+ type: 'number',
530
+ description: 'Listing ID to update',
531
+ },
532
+ title: {
533
+ type: 'string',
534
+ description: 'New title',
535
+ },
536
+ description: {
537
+ type: 'string',
538
+ description: 'New description',
539
+ },
540
+ price: {
541
+ type: 'number',
542
+ description: 'New price',
543
+ },
544
+ quantity: {
545
+ type: 'number',
546
+ description: 'New quantity',
547
+ },
548
+ tags: {
549
+ type: 'array',
550
+ items: { type: 'string' },
551
+ description: 'New tags',
552
+ },
553
+ shop_section_id: {
554
+ type: 'number',
555
+ description: 'Shop section ID',
556
+ },
557
+ },
558
+ required: ['shop_id', 'listing_id'],
559
+ },
560
+ annotations: {
561
+ readOnlyHint: false,
562
+ destructiveHint: false,
563
+ idempotentHint: true,
564
+ },
565
+ },
566
+ {
567
+ name: 'delete_listing',
568
+ description:
569
+ 'Delete a listing from your shop. Requires OAuth access token.',
570
+ inputSchema: {
571
+ type: 'object',
572
+ properties: {
573
+ listing_id: {
574
+ type: 'number',
575
+ description: 'Listing ID to delete',
576
+ },
577
+ },
578
+ required: ['listing_id'],
579
+ },
580
+ annotations: {
581
+ readOnlyHint: false,
582
+ destructiveHint: true,
583
+ idempotentHint: true,
584
+ },
585
+ },
586
+ {
587
+ name: 'update_listing_inventory',
588
+ description:
589
+ 'Update inventory for a listing (quantities, prices, SKUs). Requires OAuth access token.',
590
+ inputSchema: {
591
+ type: 'object',
592
+ properties: {
593
+ listing_id: {
594
+ type: 'number',
595
+ description: 'Listing ID',
596
+ },
597
+ products: {
598
+ type: 'array',
599
+ description: 'Array of product variations with offerings',
600
+ items: {
601
+ type: 'object',
602
+ properties: {
603
+ sku: { type: 'string' },
604
+ property_values: {
605
+ type: 'array',
606
+ items: {
607
+ type: 'object',
608
+ properties: {
609
+ property_id: { type: 'number' },
610
+ property_name: { type: 'string' },
611
+ scale_id: { type: 'number' },
612
+ value_ids: {
613
+ type: 'array',
614
+ items: { type: 'number' },
615
+ },
616
+ values: {
617
+ type: 'array',
618
+ items: { type: 'string' },
619
+ },
620
+ },
621
+ },
622
+ },
623
+ offerings: {
624
+ type: 'array',
625
+ items: {
626
+ type: 'object',
627
+ properties: {
628
+ price: { type: 'number' },
629
+ quantity: { type: 'number' },
630
+ is_enabled: { type: 'boolean' },
631
+ },
632
+ },
633
+ },
634
+ },
635
+ },
636
+ },
637
+ price_on_property: {
638
+ type: 'array',
639
+ items: { type: 'number' },
640
+ description: 'Property IDs that affect price',
641
+ },
642
+ quantity_on_property: {
643
+ type: 'array',
644
+ items: { type: 'number' },
645
+ description: 'Property IDs that affect quantity',
646
+ },
647
+ sku_on_property: {
648
+ type: 'array',
649
+ items: { type: 'number' },
650
+ description: 'Property IDs that affect SKU',
651
+ },
652
+ },
653
+ required: ['listing_id', 'products'],
654
+ },
655
+ annotations: {
656
+ readOnlyHint: false,
657
+ destructiveHint: false,
658
+ idempotentHint: true,
659
+ },
660
+ },
661
+ {
662
+ name: 'upload_listing_image',
663
+ description:
664
+ 'Upload an image to a listing. Requires OAuth access token and image file.',
665
+ inputSchema: {
666
+ type: 'object',
667
+ properties: {
668
+ shop_id: {
669
+ type: 'number',
670
+ description: 'Your shop ID',
671
+ },
672
+ listing_id: {
673
+ type: 'number',
674
+ description: 'Listing ID',
675
+ },
676
+ image_url: {
677
+ type: 'string',
678
+ description: 'URL of the image to upload',
679
+ },
680
+ rank: {
681
+ type: 'number',
682
+ description: 'Display order (1 = primary image)',
683
+ },
684
+ alt_text: {
685
+ type: 'string',
686
+ description: 'Alternative text for accessibility',
687
+ },
688
+ },
689
+ required: ['shop_id', 'listing_id', 'image_url'],
690
+ },
691
+ annotations: {
692
+ readOnlyHint: false,
693
+ destructiveHint: false,
694
+ idempotentHint: false,
695
+ },
696
+ },
697
+ {
698
+ name: 'create_shop_section',
699
+ description:
700
+ 'Create a new shop section/category. Requires OAuth access token.',
701
+ inputSchema: {
702
+ type: 'object',
703
+ properties: {
704
+ shop_id: {
705
+ type: 'number',
706
+ description: 'Your shop ID',
707
+ },
708
+ title: {
709
+ type: 'string',
710
+ description: 'Section title',
711
+ },
712
+ },
713
+ required: ['shop_id', 'title'],
714
+ },
715
+ annotations: {
716
+ readOnlyHint: false,
717
+ destructiveHint: false,
718
+ idempotentHint: false,
719
+ },
720
+ },
721
+ {
722
+ name: 'update_shop_section',
723
+ description: 'Update a shop section. Requires OAuth access token.',
724
+ inputSchema: {
725
+ type: 'object',
726
+ properties: {
727
+ shop_id: {
728
+ type: 'number',
729
+ description: 'Your shop ID',
730
+ },
731
+ shop_section_id: {
732
+ type: 'number',
733
+ description: 'Section ID to update',
734
+ },
735
+ title: {
736
+ type: 'string',
737
+ description: 'New section title',
738
+ },
739
+ },
740
+ required: ['shop_id', 'shop_section_id', 'title'],
741
+ },
742
+ annotations: {
743
+ readOnlyHint: false,
744
+ destructiveHint: false,
745
+ idempotentHint: true,
746
+ },
747
+ },
748
+ {
749
+ name: 'delete_shop_section',
750
+ description: 'Delete a shop section. Requires OAuth access token.',
751
+ inputSchema: {
752
+ type: 'object',
753
+ properties: {
754
+ shop_id: {
755
+ type: 'number',
756
+ description: 'Your shop ID',
757
+ },
758
+ shop_section_id: {
759
+ type: 'number',
760
+ description: 'Section ID to delete',
761
+ },
762
+ },
763
+ required: ['shop_id', 'shop_section_id'],
764
+ },
765
+ annotations: {
766
+ readOnlyHint: false,
767
+ destructiveHint: true,
768
+ idempotentHint: true,
769
+ },
770
+ },
771
+ {
772
+ name: 'update_shop',
773
+ description:
774
+ 'Update shop information (title, announcement, etc.). Requires OAuth access token.',
775
+ inputSchema: {
776
+ type: 'object',
777
+ properties: {
778
+ shop_id: {
779
+ type: 'number',
780
+ description: 'Your shop ID',
781
+ },
782
+ title: {
783
+ type: 'string',
784
+ description: 'Shop title',
785
+ },
786
+ announcement: {
787
+ type: 'string',
788
+ description: 'Shop announcement message',
789
+ },
790
+ sale_message: {
791
+ type: 'string',
792
+ description: 'Message to buyers at checkout',
793
+ },
794
+ policy_welcome: {
795
+ type: 'string',
796
+ description: 'Shop policies welcome message',
797
+ },
798
+ },
799
+ required: ['shop_id'],
800
+ },
801
+ annotations: {
802
+ readOnlyHint: false,
803
+ destructiveHint: false,
804
+ idempotentHint: true,
805
+ },
806
+ },
807
+ ];
808
+ }
809
+
810
+ private async searchListings(args: any) {
811
+ const params: any = {
812
+ keywords: args.keywords,
813
+ limit: args.limit || 25,
814
+ offset: args.offset || 0,
815
+ };
816
+
817
+ if (args.min_price) params.min_price = args.min_price;
818
+ if (args.max_price) params.max_price = args.max_price;
819
+ if (args.sort_on) params.sort_on = args.sort_on;
820
+ if (args.sort_order) params.sort_order = args.sort_order;
821
+
822
+ const response = await this.axiosInstance.get('/application/listings/active', {
823
+ params,
824
+ });
825
+
826
+ return {
827
+ content: [
828
+ {
829
+ type: 'text',
830
+ text: JSON.stringify(response.data, null, 2),
831
+ },
832
+ ],
833
+ };
834
+ }
835
+
836
+ private async getListing(args: any) {
837
+ const includes = args.includes?.join(',');
838
+ const params = includes ? { includes } : {};
839
+
840
+ const response = await this.axiosInstance.get(
841
+ `/application/listings/${args.listing_id}`,
842
+ { params }
843
+ );
844
+
845
+ return {
846
+ content: [
847
+ {
848
+ type: 'text',
849
+ text: JSON.stringify(response.data, null, 2),
850
+ },
851
+ ],
852
+ };
853
+ }
854
+
855
+ private async getShop(args: any) {
856
+ const response = await this.axiosInstance.get(`/application/shops/${args.shop_id}`);
857
+
858
+ return {
859
+ content: [
860
+ {
861
+ type: 'text',
862
+ text: JSON.stringify(response.data, null, 2),
863
+ },
864
+ ],
865
+ };
866
+ }
867
+
868
+ private async getShopListings(args: any) {
869
+ const params = {
870
+ limit: args.limit || 25,
871
+ offset: args.offset || 0,
872
+ state: args.state || 'active',
873
+ };
874
+
875
+ const response = await this.axiosInstance.get(
876
+ `/application/shops/${args.shop_id}/listings`,
877
+ { params }
878
+ );
879
+
880
+ return {
881
+ content: [
882
+ {
883
+ type: 'text',
884
+ text: JSON.stringify(response.data, null, 2),
885
+ },
886
+ ],
887
+ };
888
+ }
889
+
890
+ private async searchShops(args: any) {
891
+ const params = {
892
+ shop_name: args.shop_name,
893
+ limit: args.limit || 25,
894
+ offset: args.offset || 0,
895
+ };
896
+
897
+ const response = await this.axiosInstance.get('/application/shops', { params });
898
+
899
+ return {
900
+ content: [
901
+ {
902
+ type: 'text',
903
+ text: JSON.stringify(response.data, null, 2),
904
+ },
905
+ ],
906
+ };
907
+ }
908
+
909
+ private async getListingInventory(args: any) {
910
+ const response = await this.axiosInstance.get(
911
+ `/application/listings/${args.listing_id}/inventory`
912
+ );
913
+
914
+ return {
915
+ content: [
916
+ {
917
+ type: 'text',
918
+ text: JSON.stringify(response.data, null, 2),
919
+ },
920
+ ],
921
+ };
922
+ }
923
+
924
+ private async getListingImages(args: any) {
925
+ const response = await this.axiosInstance.get(
926
+ `/application/listings/${args.listing_id}/images`
927
+ );
928
+
929
+ return {
930
+ content: [
931
+ {
932
+ type: 'text',
933
+ text: JSON.stringify(response.data, null, 2),
934
+ },
935
+ ],
936
+ };
937
+ }
938
+
939
+ private async getShopSections(args: any) {
940
+ const response = await this.axiosInstance.get(
941
+ `/application/shops/${args.shop_id}/sections`
942
+ );
943
+
944
+ return {
945
+ content: [
946
+ {
947
+ type: 'text',
948
+ text: JSON.stringify(response.data, null, 2),
949
+ },
950
+ ],
951
+ };
952
+ }
953
+
954
+ private async getTrendingListings(args: any) {
955
+ const params = {
956
+ limit: args.limit || 25,
957
+ offset: args.offset || 0,
958
+ };
959
+
960
+ const response = await this.axiosInstance.get('/application/listings/trending', {
961
+ params,
962
+ });
963
+
964
+ return {
965
+ content: [
966
+ {
967
+ type: 'text',
968
+ text: JSON.stringify(response.data, null, 2),
969
+ },
970
+ ],
971
+ };
972
+ }
973
+
974
+ private async findShops(args: any) {
975
+ const params: any = {
976
+ limit: args.limit || 25,
977
+ offset: args.offset || 0,
978
+ };
979
+
980
+ if (args.location) params.location = args.location;
981
+
982
+ const response = await this.axiosInstance.get('/application/shops', { params });
983
+
984
+ return {
985
+ content: [
986
+ {
987
+ type: 'text',
988
+ text: JSON.stringify(response.data, null, 2),
989
+ },
990
+ ],
991
+ };
992
+ }
993
+
994
+ // Shop Management Methods (require OAuth)
995
+ private async createListing(args: any) {
996
+ if (!this.config.accessToken) {
997
+ return {
998
+ content: [
999
+ {
1000
+ type: 'text',
1001
+ text: JSON.stringify(
1002
+ {
1003
+ error:
1004
+ 'OAuth access token required. Set ETSY_ACCESS_TOKEN environment variable.',
1005
+ },
1006
+ null,
1007
+ 2
1008
+ ),
1009
+ },
1010
+ ],
1011
+ };
1012
+ }
1013
+
1014
+ const data: any = {
1015
+ quantity: args.quantity,
1016
+ title: args.title,
1017
+ description: args.description,
1018
+ price: args.price,
1019
+ who_made: args.who_made,
1020
+ when_made: args.when_made,
1021
+ taxonomy_id: args.taxonomy_id,
1022
+ };
1023
+
1024
+ if (args.shipping_profile_id) data.shipping_profile_id = args.shipping_profile_id;
1025
+ if (args.shop_section_id) data.shop_section_id = args.shop_section_id;
1026
+ if (args.tags) data.tags = args.tags;
1027
+
1028
+ const response = await this.axiosInstance.post(
1029
+ `/application/shops/${args.shop_id}/listings`,
1030
+ data
1031
+ );
1032
+
1033
+ return {
1034
+ content: [
1035
+ {
1036
+ type: 'text',
1037
+ text: JSON.stringify(response.data, null, 2),
1038
+ },
1039
+ ],
1040
+ };
1041
+ }
1042
+
1043
+ private async updateListing(args: any) {
1044
+ if (!this.config.accessToken) {
1045
+ return {
1046
+ content: [
1047
+ {
1048
+ type: 'text',
1049
+ text: JSON.stringify(
1050
+ {
1051
+ error:
1052
+ 'OAuth access token required. Set ETSY_ACCESS_TOKEN environment variable.',
1053
+ },
1054
+ null,
1055
+ 2
1056
+ ),
1057
+ },
1058
+ ],
1059
+ };
1060
+ }
1061
+
1062
+ const data: any = {};
1063
+ if (args.title) data.title = args.title;
1064
+ if (args.description) data.description = args.description;
1065
+ if (args.price) data.price = args.price;
1066
+ if (args.quantity) data.quantity = args.quantity;
1067
+ if (args.tags) data.tags = args.tags;
1068
+ if (args.shop_section_id !== undefined) data.shop_section_id = args.shop_section_id;
1069
+
1070
+ const response = await this.axiosInstance.patch(
1071
+ `/application/shops/${args.shop_id}/listings/${args.listing_id}`,
1072
+ data
1073
+ );
1074
+
1075
+ return {
1076
+ content: [
1077
+ {
1078
+ type: 'text',
1079
+ text: JSON.stringify(response.data, null, 2),
1080
+ },
1081
+ ],
1082
+ };
1083
+ }
1084
+
1085
+ private async deleteListing(args: any) {
1086
+ if (!this.config.accessToken) {
1087
+ return {
1088
+ content: [
1089
+ {
1090
+ type: 'text',
1091
+ text: JSON.stringify(
1092
+ {
1093
+ error:
1094
+ 'OAuth access token required. Set ETSY_ACCESS_TOKEN environment variable.',
1095
+ },
1096
+ null,
1097
+ 2
1098
+ ),
1099
+ },
1100
+ ],
1101
+ };
1102
+ }
1103
+
1104
+ const response = await this.axiosInstance.delete(
1105
+ `/application/listings/${args.listing_id}`
1106
+ );
1107
+
1108
+ return {
1109
+ content: [
1110
+ {
1111
+ type: 'text',
1112
+ text: JSON.stringify(
1113
+ { success: true, message: 'Listing deleted successfully' },
1114
+ null,
1115
+ 2
1116
+ ),
1117
+ },
1118
+ ],
1119
+ };
1120
+ }
1121
+
1122
+ private async updateListingInventory(args: any) {
1123
+ if (!this.config.accessToken) {
1124
+ return {
1125
+ content: [
1126
+ {
1127
+ type: 'text',
1128
+ text: JSON.stringify(
1129
+ {
1130
+ error:
1131
+ 'OAuth access token required. Set ETSY_ACCESS_TOKEN environment variable.',
1132
+ },
1133
+ null,
1134
+ 2
1135
+ ),
1136
+ },
1137
+ ],
1138
+ };
1139
+ }
1140
+
1141
+ const data: any = {
1142
+ products: args.products,
1143
+ };
1144
+
1145
+ if (args.price_on_property) data.price_on_property = args.price_on_property;
1146
+ if (args.quantity_on_property) data.quantity_on_property = args.quantity_on_property;
1147
+ if (args.sku_on_property) data.sku_on_property = args.sku_on_property;
1148
+
1149
+ const response = await this.axiosInstance.put(
1150
+ `/application/listings/${args.listing_id}/inventory`,
1151
+ data
1152
+ );
1153
+
1154
+ return {
1155
+ content: [
1156
+ {
1157
+ type: 'text',
1158
+ text: JSON.stringify(response.data, null, 2),
1159
+ },
1160
+ ],
1161
+ };
1162
+ }
1163
+
1164
+ private async uploadListingImage(args: any) {
1165
+ if (!this.config.accessToken) {
1166
+ return {
1167
+ content: [
1168
+ {
1169
+ type: 'text',
1170
+ text: JSON.stringify(
1171
+ {
1172
+ error:
1173
+ 'OAuth access token required. Set ETSY_ACCESS_TOKEN environment variable.',
1174
+ },
1175
+ null,
1176
+ 2
1177
+ ),
1178
+ },
1179
+ ],
1180
+ };
1181
+ }
1182
+
1183
+ // Note: Etsy API requires multipart/form-data for image upload
1184
+ // This is a simplified version - actual implementation needs form-data handling
1185
+ const data: any = {
1186
+ image: args.image_url,
1187
+ };
1188
+
1189
+ if (args.rank) data.rank = args.rank;
1190
+ if (args.alt_text) data.alt_text = args.alt_text;
1191
+
1192
+ const response = await this.axiosInstance.post(
1193
+ `/application/shops/${args.shop_id}/listings/${args.listing_id}/images`,
1194
+ data
1195
+ );
1196
+
1197
+ return {
1198
+ content: [
1199
+ {
1200
+ type: 'text',
1201
+ text: JSON.stringify(response.data, null, 2),
1202
+ },
1203
+ ],
1204
+ };
1205
+ }
1206
+
1207
+ private async createShopSection(args: any) {
1208
+ if (!this.config.accessToken) {
1209
+ return {
1210
+ content: [
1211
+ {
1212
+ type: 'text',
1213
+ text: JSON.stringify(
1214
+ {
1215
+ error:
1216
+ 'OAuth access token required. Set ETSY_ACCESS_TOKEN environment variable.',
1217
+ },
1218
+ null,
1219
+ 2
1220
+ ),
1221
+ },
1222
+ ],
1223
+ };
1224
+ }
1225
+
1226
+ const response = await this.axiosInstance.post(
1227
+ `/application/shops/${args.shop_id}/sections`,
1228
+ { title: args.title }
1229
+ );
1230
+
1231
+ return {
1232
+ content: [
1233
+ {
1234
+ type: 'text',
1235
+ text: JSON.stringify(response.data, null, 2),
1236
+ },
1237
+ ],
1238
+ };
1239
+ }
1240
+
1241
+ private async updateShopSection(args: any) {
1242
+ if (!this.config.accessToken) {
1243
+ return {
1244
+ content: [
1245
+ {
1246
+ type: 'text',
1247
+ text: JSON.stringify(
1248
+ {
1249
+ error:
1250
+ 'OAuth access token required. Set ETSY_ACCESS_TOKEN environment variable.',
1251
+ },
1252
+ null,
1253
+ 2
1254
+ ),
1255
+ },
1256
+ ],
1257
+ };
1258
+ }
1259
+
1260
+ const response = await this.axiosInstance.put(
1261
+ `/application/shops/${args.shop_id}/sections/${args.shop_section_id}`,
1262
+ { title: args.title }
1263
+ );
1264
+
1265
+ return {
1266
+ content: [
1267
+ {
1268
+ type: 'text',
1269
+ text: JSON.stringify(response.data, null, 2),
1270
+ },
1271
+ ],
1272
+ };
1273
+ }
1274
+
1275
+ private async deleteShopSection(args: any) {
1276
+ if (!this.config.accessToken) {
1277
+ return {
1278
+ content: [
1279
+ {
1280
+ type: 'text',
1281
+ text: JSON.stringify(
1282
+ {
1283
+ error:
1284
+ 'OAuth access token required. Set ETSY_ACCESS_TOKEN environment variable.',
1285
+ },
1286
+ null,
1287
+ 2
1288
+ ),
1289
+ },
1290
+ ],
1291
+ };
1292
+ }
1293
+
1294
+ const response = await this.axiosInstance.delete(
1295
+ `/application/shops/${args.shop_id}/sections/${args.shop_section_id}`
1296
+ );
1297
+
1298
+ return {
1299
+ content: [
1300
+ {
1301
+ type: 'text',
1302
+ text: JSON.stringify(
1303
+ { success: true, message: 'Shop section deleted successfully' },
1304
+ null,
1305
+ 2
1306
+ ),
1307
+ },
1308
+ ],
1309
+ };
1310
+ }
1311
+
1312
+ private async updateShop(args: any) {
1313
+ if (!this.config.accessToken) {
1314
+ return {
1315
+ content: [
1316
+ {
1317
+ type: 'text',
1318
+ text: JSON.stringify(
1319
+ {
1320
+ error:
1321
+ 'OAuth access token required. Set ETSY_ACCESS_TOKEN environment variable.',
1322
+ },
1323
+ null,
1324
+ 2
1325
+ ),
1326
+ },
1327
+ ],
1328
+ };
1329
+ }
1330
+
1331
+ const data: any = {};
1332
+ if (args.title) data.title = args.title;
1333
+ if (args.announcement) data.announcement = args.announcement;
1334
+ if (args.sale_message) data.sale_message = args.sale_message;
1335
+ if (args.policy_welcome) data.policy_welcome = args.policy_welcome;
1336
+
1337
+ const response = await this.axiosInstance.put(
1338
+ `/application/shops/${args.shop_id}`,
1339
+ data
1340
+ );
1341
+
1342
+ return {
1343
+ content: [
1344
+ {
1345
+ type: 'text',
1346
+ text: JSON.stringify(response.data, null, 2),
1347
+ },
1348
+ ],
1349
+ };
1350
+ }
1351
+
1352
+ // Prompts functionality
1353
+ private getPrompts(): Prompt[] {
1354
+ return [
1355
+ {
1356
+ name: 'create-listing-guide',
1357
+ title: 'Create Listing Guide',
1358
+ description: 'Guide for creating a complete Etsy listing with best practices',
1359
+ arguments: [
1360
+ {
1361
+ name: 'product_type',
1362
+ description: 'Type of product to list (e.g., handmade, vintage, craft supply)',
1363
+ required: true,
1364
+ },
1365
+ ],
1366
+ },
1367
+ {
1368
+ name: 'optimize-listing',
1369
+ title: 'Optimize Listing',
1370
+ description: 'Generate SEO-optimized title, tags, and description for a listing',
1371
+ arguments: [
1372
+ {
1373
+ name: 'listing_id',
1374
+ description: 'The ID of the listing to optimize',
1375
+ required: true,
1376
+ },
1377
+ {
1378
+ name: 'focus_keywords',
1379
+ description: 'Keywords to focus on for SEO',
1380
+ required: false,
1381
+ },
1382
+ ],
1383
+ },
1384
+ {
1385
+ name: 'shop-analytics-review',
1386
+ title: 'Shop Analytics Review',
1387
+ description: 'Create a comprehensive shop performance review template',
1388
+ arguments: [
1389
+ {
1390
+ name: 'shop_id',
1391
+ description: 'The shop ID to analyze',
1392
+ required: true,
1393
+ },
1394
+ {
1395
+ name: 'time_period',
1396
+ description: 'Time period for analysis (e.g., last_month, last_quarter)',
1397
+ required: false,
1398
+ },
1399
+ ],
1400
+ },
1401
+ {
1402
+ name: 'product-photography-tips',
1403
+ title: 'Product Photography Tips',
1404
+ description: 'Get tailored product photography guidance for Etsy',
1405
+ arguments: [
1406
+ {
1407
+ name: 'product_category',
1408
+ description: 'Category of product (e.g., jewelry, home decor, clothing)',
1409
+ required: true,
1410
+ },
1411
+ ],
1412
+ },
1413
+ {
1414
+ name: 'pricing-strategy',
1415
+ title: 'Pricing Strategy',
1416
+ description: 'Calculate competitive pricing for Etsy listings',
1417
+ arguments: [
1418
+ {
1419
+ name: 'material_cost',
1420
+ description: 'Total cost of materials',
1421
+ required: true,
1422
+ },
1423
+ {
1424
+ name: 'time_hours',
1425
+ description: 'Hours spent creating the product',
1426
+ required: true,
1427
+ },
1428
+ {
1429
+ name: 'desired_hourly_rate',
1430
+ description: 'Desired hourly rate for labor',
1431
+ required: false,
1432
+ },
1433
+ ],
1434
+ },
1435
+ ];
1436
+ }
1437
+
1438
+ private async getPrompt(name: string, args?: Record<string, string>): Promise<GetPromptResult> {
1439
+ switch (name) {
1440
+ case 'create-listing-guide':
1441
+ return {
1442
+ messages: [
1443
+ {
1444
+ role: 'user',
1445
+ content: {
1446
+ type: 'text',
1447
+ text: `Help me create a compelling Etsy listing for a ${args?.product_type || 'handmade'} product. Please guide me through:
1448
+
1449
+ 1. **Title Creation** (max 140 characters):
1450
+ - Include key search terms
1451
+ - Be specific and descriptive
1452
+ - Follow format: [What it is] | [Key feature] | [Use case]
1453
+
1454
+ 2. **Description Structure**:
1455
+ - Opening hook (2-3 sentences)
1456
+ - Key features and benefits (bullet points)
1457
+ - Materials and dimensions
1458
+ - Care instructions
1459
+ - Shipping and policies
1460
+
1461
+ 3. **Tags Strategy** (13 tags):
1462
+ - Mix of broad and specific terms
1463
+ - Include long-tail keywords
1464
+ - Consider seasonal trends
1465
+
1466
+ 4. **Pricing Considerations**:
1467
+ - Material costs + labor + overhead
1468
+ - Competitive analysis
1469
+ - Etsy fees (6.5% transaction + 3% + $0.25 processing)
1470
+
1471
+ 5. **Photos Checklist**:
1472
+ - Main photo: white background, product centered
1473
+ - Lifestyle photos showing scale/use
1474
+ - Detail shots of craftsmanship
1475
+ - Size comparison images
1476
+
1477
+ Please help me optimize each section for maximum visibility and conversions.`,
1478
+ },
1479
+ },
1480
+ ],
1481
+ };
1482
+
1483
+ case 'optimize-listing':
1484
+ const listingId = args?.listing_id;
1485
+ const keywords = args?.focus_keywords || 'handmade, unique, quality';
1486
+
1487
+ return {
1488
+ messages: [
1489
+ {
1490
+ role: 'user',
1491
+ content: {
1492
+ type: 'text',
1493
+ text: `Analyze and optimize Etsy listing ${listingId} with focus on these keywords: ${keywords}
1494
+
1495
+ Please provide:
1496
+
1497
+ 1. **SEO-Optimized Title**:
1498
+ - Incorporate keywords: ${keywords}
1499
+ - Stay under 140 characters
1500
+ - Front-load important terms
1501
+
1502
+ 2. **Enhanced Description**:
1503
+ - Keyword-rich opening paragraph
1504
+ - Scannable bullet points
1505
+ - Answer common customer questions
1506
+ - Include size, material, and shipping info
1507
+
1508
+ 3. **Strategic Tags** (13 recommendations):
1509
+ - High-volume search terms
1510
+ - Niche-specific keywords
1511
+ - Long-tail variations
1512
+ - Seasonal opportunities
1513
+
1514
+ 4. **Competitive Analysis**:
1515
+ - Similar listings comparison
1516
+ - Price positioning
1517
+ - Unique selling points
1518
+
1519
+ 5. **Action Items**:
1520
+ - Quick wins for immediate improvement
1521
+ - Long-term optimization strategies
1522
+ - A/B testing suggestions`,
1523
+ },
1524
+ },
1525
+ ],
1526
+ };
1527
+
1528
+ case 'shop-analytics-review':
1529
+ const shopId = args?.shop_id;
1530
+ const period = args?.time_period || 'last_month';
1531
+
1532
+ return {
1533
+ messages: [
1534
+ {
1535
+ role: 'user',
1536
+ content: {
1537
+ type: 'text',
1538
+ text: `Create a comprehensive analytics review for Etsy shop ${shopId} covering ${period}.
1539
+
1540
+ Analysis Framework:
1541
+
1542
+ 1. **Traffic Metrics**:
1543
+ - Total visits and sources
1544
+ - Conversion rate trends
1545
+ - Bounce rate analysis
1546
+ - Top-performing listings
1547
+
1548
+ 2. **Sales Performance**:
1549
+ - Revenue and order volume
1550
+ - Average order value
1551
+ - Best-selling products
1552
+ - Seasonal patterns
1553
+
1554
+ 3. **SEO Performance**:
1555
+ - Top search terms driving traffic
1556
+ - Listing ranking improvements/declines
1557
+ - Click-through rates
1558
+ - Tag effectiveness
1559
+
1560
+ 4. **Customer Insights**:
1561
+ - Geographic distribution
1562
+ - Repeat customer rate
1563
+ - Review sentiment analysis
1564
+ - Cart abandonment rate
1565
+
1566
+ 5. **Recommendations**:
1567
+ - Underperforming listings to optimize
1568
+ - Inventory adjustments
1569
+ - Marketing opportunities
1570
+ - Pricing strategy review
1571
+
1572
+ Please use the available MCP tools to gather data and provide actionable insights.`,
1573
+ },
1574
+ },
1575
+ ],
1576
+ };
1577
+
1578
+ case 'product-photography-tips':
1579
+ const category = args?.product_category || 'general products';
1580
+
1581
+ return {
1582
+ messages: [
1583
+ {
1584
+ role: 'user',
1585
+ content: {
1586
+ type: 'text',
1587
+ text: `Provide comprehensive product photography guidance for ${category} on Etsy.
1588
+
1589
+ **Photography Essentials:**
1590
+
1591
+ 1. **Equipment Setup**:
1592
+ - Camera: DSLR, mirrorless, or modern smartphone
1593
+ - Lighting: Natural light or softbox setup
1594
+ - Background: White sweep or lifestyle setting
1595
+ - Tripod for consistency
1596
+
1597
+ 2. **Shot List for ${category}**:
1598
+ - Hero shot (main listing photo)
1599
+ - Scale/size reference shots
1600
+ - Detail and texture closeups
1601
+ - Lifestyle/in-use photos
1602
+ - Packaging presentation
1603
+ - Multiple angles (360° view)
1604
+
1605
+ 3. **Technical Requirements**:
1606
+ - Resolution: Minimum 2000px on longest side
1607
+ - Format: JPG for best compatibility
1608
+ - File size: Under 1MB for fast loading
1609
+ - Aspect ratio: Square (1:1) works best
1610
+
1611
+ 4. **Styling Tips for ${category}**:
1612
+ - Props that complement without distracting
1613
+ - Consistent brand aesthetic
1614
+ - Color harmony and contrast
1615
+ - Storytelling through composition
1616
+
1617
+ 5. **Post-Processing**:
1618
+ - Brightness and contrast adjustment
1619
+ - Color correction for accuracy
1620
+ - Background cleanup
1621
+ - Watermarking considerations
1622
+
1623
+ 6. **Etsy-Specific Best Practices**:
1624
+ - First photo determines thumbnail
1625
+ - Mobile optimization crucial (70% of traffic)
1626
+ - Use all 10 image slots
1627
+ - Video recommended for engagement`,
1628
+ },
1629
+ },
1630
+ ],
1631
+ };
1632
+
1633
+ case 'pricing-strategy':
1634
+ const materialCost = parseFloat(args?.material_cost || '0');
1635
+ const timeHours = parseFloat(args?.time_hours || '0');
1636
+ const hourlyRate = parseFloat(args?.desired_hourly_rate || '25');
1637
+
1638
+ const laborCost = timeHours * hourlyRate;
1639
+ const subtotal = materialCost + laborCost;
1640
+ const overhead = subtotal * 0.15; // 15% overhead
1641
+ const totalCost = subtotal + overhead;
1642
+ const etsyFees = totalCost * 0.095; // 6.5% transaction + ~3% processing
1643
+ const minimumPrice = totalCost + etsyFees;
1644
+ const suggestedRetail = minimumPrice * 1.5; // 50% profit margin
1645
+
1646
+ return {
1647
+ messages: [
1648
+ {
1649
+ role: 'user',
1650
+ content: {
1651
+ type: 'text',
1652
+ text: `Pricing Analysis for Your Etsy Product:
1653
+
1654
+ **Cost Breakdown:**
1655
+ - Materials: $${materialCost.toFixed(2)}
1656
+ - Labor (${timeHours}h × $${hourlyRate}/h): $${laborCost.toFixed(2)}
1657
+ - Overhead (15%): $${overhead.toFixed(2)}
1658
+ - **Total Cost: $${totalCost.toFixed(2)}**
1659
+
1660
+ **Etsy Fees:**
1661
+ - Transaction fee (6.5%): $${(totalCost * 0.065).toFixed(2)}
1662
+ - Processing fee (~3%): $${(totalCost * 0.03).toFixed(2)}
1663
+ - Listing fee: $0.20
1664
+ - **Total Fees: $${(etsyFees + 0.20).toFixed(2)}**
1665
+
1666
+ **Pricing Recommendations:**
1667
+
1668
+ 1. **Break-Even Price:** $${minimumPrice.toFixed(2)}
1669
+ - Covers all costs and fees
1670
+ - No profit margin
1671
+
1672
+ 2. **Suggested Retail Price:** $${suggestedRetail.toFixed(2)}
1673
+ - Includes 50% profit margin
1674
+ - Competitive positioning
1675
+ - Room for sales/promotions
1676
+
1677
+ 3. **Premium Positioning:** $${(suggestedRetail * 1.3).toFixed(2)}
1678
+ - Higher perceived value
1679
+ - Artisan/luxury market
1680
+ - Custom/made-to-order
1681
+
1682
+ **Pricing Strategy Tips:**
1683
+
1684
+ • **Psychological Pricing:** Consider ending prices in .95 or .99
1685
+ • **Competitor Analysis:** Research 10-15 similar listings
1686
+ • **Volume Discounts:** Offer tiered pricing for multiple purchases
1687
+ • **Seasonal Adjustments:** Premium during peak seasons
1688
+ • **Bundle Opportunities:** Create product sets for higher AOV
1689
+
1690
+ **Profitability Check:**
1691
+ - At $${suggestedRetail.toFixed(2)}: ${((suggestedRetail - minimumPrice) / suggestedRetail * 100).toFixed(1)}% profit margin
1692
+ - Monthly goal: Sell 20 units = $${(suggestedRetail * 20).toFixed(2)} revenue
1693
+ - Profit: $${((suggestedRetail - minimumPrice) * 20).toFixed(2)}
1694
+
1695
+ Would you like me to analyze competitor pricing or explore different pricing strategies?`,
1696
+ },
1697
+ },
1698
+ ],
1699
+ };
1700
+
1701
+ default:
1702
+ throw new Error(`Unknown prompt: ${name}`);
1703
+ }
1704
+ }
1705
+
1706
+ // Resources functionality
1707
+ private getResources(): Resource[] {
1708
+ return [
1709
+ {
1710
+ name: 'etsy-api-docs',
1711
+ uri: 'etsy://docs/api',
1712
+ title: 'Etsy API Documentation',
1713
+ description: 'Comprehensive Etsy Open API v3 documentation and reference',
1714
+ mimeType: 'text/plain',
1715
+ },
1716
+ {
1717
+ name: 'etsy-seller-handbook',
1718
+ uri: 'etsy://docs/seller-handbook',
1719
+ title: 'Etsy Seller Handbook',
1720
+ description: 'Best practices and guides for Etsy sellers',
1721
+ mimeType: 'text/plain',
1722
+ },
1723
+ {
1724
+ name: 'etsy-seo-guide',
1725
+ uri: 'etsy://docs/seo-guide',
1726
+ title: 'Etsy SEO Guide',
1727
+ description: 'Complete guide to Etsy search engine optimization',
1728
+ mimeType: 'text/plain',
1729
+ },
1730
+ {
1731
+ name: 'etsy-shipping-guide',
1732
+ uri: 'etsy://docs/shipping',
1733
+ title: 'Etsy Shipping Guide',
1734
+ description: 'Shipping policies, strategies, and best practices',
1735
+ mimeType: 'text/plain',
1736
+ },
1737
+ {
1738
+ name: 'etsy-photography-tips',
1739
+ uri: 'etsy://docs/photography',
1740
+ title: 'Product Photography Tips',
1741
+ description: 'Professional product photography guide for Etsy',
1742
+ mimeType: 'text/plain',
1743
+ },
1744
+ {
1745
+ name: 'etsy-fees-calculator',
1746
+ uri: 'etsy://tools/fees-calculator',
1747
+ title: 'Etsy Fees Calculator',
1748
+ description: 'Calculate all Etsy fees and pricing recommendations',
1749
+ mimeType: 'application/json',
1750
+ },
1751
+ ];
1752
+ }
1753
+
1754
+ private async readResource(uri: string): Promise<ReadResourceResult> {
1755
+ switch (uri) {
1756
+ case 'etsy://docs/api':
1757
+ return {
1758
+ contents: [
1759
+ {
1760
+ uri,
1761
+ mimeType: 'text/plain',
1762
+ text: `# Etsy Open API v3 Documentation
1763
+
1764
+ **Base URL:** https://openapi.etsy.com/v3
1765
+
1766
+ ## Authentication
1767
+ - **API Key:** Required for all requests (x-api-key header)
1768
+ - **OAuth 2.0:** Required for write operations and private data
1769
+
1770
+ ## Rate Limits
1771
+ - 10,000 requests per day per API key
1772
+ - Burst limit: 10 requests per second
1773
+
1774
+ ## Key Endpoints
1775
+
1776
+ ### Listings
1777
+ - GET /v3/application/listings/active - Search active listings
1778
+ - GET /v3/application/listings/{listing_id} - Get listing details
1779
+ - POST /v3/application/shops/{shop_id}/listings - Create listing (OAuth)
1780
+ - PUT /v3/application/shops/{shop_id}/listings/{listing_id} - Update listing (OAuth)
1781
+
1782
+ ### Shops
1783
+ - GET /v3/application/shops/{shop_id} - Get shop details
1784
+ - GET /v3/application/shops/{shop_id}/listings/active - Get shop listings
1785
+ - PATCH /v3/application/shops/{shop_id} - Update shop (OAuth)
1786
+
1787
+ ### Search
1788
+ - GET /v3/application/listings/active - Search with filters
1789
+ - GET /v3/application/shops - Search shops
1790
+
1791
+ ## Common Parameters
1792
+ - **limit:** Number of results (max 100)
1793
+ - **offset:** Pagination offset
1794
+ - **keywords:** Search terms
1795
+ - **sort_on:** created, price, score
1796
+ - **sort_order:** asc, desc
1797
+
1798
+ ## Response Format
1799
+ All responses return JSON with consistent structure:
1800
+ {
1801
+ "count": 10,
1802
+ "results": [...],
1803
+ "next_page": "..."
1804
+ }
1805
+
1806
+ ## Error Codes
1807
+ - 400: Bad Request
1808
+ - 401: Unauthorized
1809
+ - 403: Forbidden
1810
+ - 404: Not Found
1811
+ - 429: Rate Limit Exceeded
1812
+ - 500: Internal Server Error
1813
+
1814
+ For complete documentation, visit: https://developers.etsy.com/documentation`,
1815
+ },
1816
+ ],
1817
+ };
1818
+
1819
+ case 'etsy://docs/seller-handbook':
1820
+ return {
1821
+ contents: [
1822
+ {
1823
+ uri,
1824
+ mimeType: 'text/plain',
1825
+ text: `# Etsy Seller Handbook
1826
+
1827
+ ## Getting Started
1828
+
1829
+ ### Shop Setup
1830
+ 1. Create your shop name (unique, memorable, brandable)
1831
+ 2. Set shop policies (returns, exchanges, privacy)
1832
+ 3. Configure payment methods
1833
+ 4. Set up shipping profiles
1834
+ 5. Add shop sections for organization
1835
+
1836
+ ### Listing Optimization
1837
+ - **Title:** 140 characters, front-load keywords
1838
+ - **Tags:** 13 tags, mix broad and specific terms
1839
+ - **Description:** Rich details, answer questions
1840
+ - **Photos:** 10 images, high quality, multiple angles
1841
+ - **Price:** Competitive but profitable
1842
+
1843
+ ## Best Practices
1844
+
1845
+ ### Product Photography
1846
+ - Natural lighting or softbox setup
1847
+ - White/neutral background for main image
1848
+ - Lifestyle photos showing scale and use
1849
+ - Detail shots of craftsmanship
1850
+ - Consistent style across listings
1851
+
1852
+ ### SEO Strategy
1853
+ - Research keywords with Etsy search
1854
+ - Use all 13 tag slots
1855
+ - Update titles seasonally
1856
+ - Monitor search analytics
1857
+ - Optimize based on performance
1858
+
1859
+ ### Customer Service
1860
+ - Respond within 24 hours
1861
+ - Be professional and friendly
1862
+ - Address issues promptly
1863
+ - Request reviews politely
1864
+ - Build relationships
1865
+
1866
+ ### Marketing
1867
+ - Share listings on social media
1868
+ - Use Etsy Ads strategically
1869
+ - Offer promotions and sales
1870
+ - Build email list
1871
+ - Collaborate with other sellers
1872
+
1873
+ ### Shop Management
1874
+ - Update inventory regularly
1875
+ - Process orders promptly
1876
+ - Track metrics and analytics
1877
+ - Adjust strategy based on data
1878
+ - Stay current with Etsy policies
1879
+
1880
+ ## Growth Strategies
1881
+
1882
+ ### Scaling Your Business
1883
+ 1. Identify best-sellers
1884
+ 2. Create complementary products
1885
+ 3. Optimize production workflow
1886
+ 4. Consider help or automation
1887
+ 5. Expand product line strategically
1888
+
1889
+ ### Seasonal Planning
1890
+ - Plan inventory 3 months ahead
1891
+ - Create seasonal listings early
1892
+ - Adjust keywords for holidays
1893
+ - Run strategic promotions
1894
+ - Prepare for peak shipping times
1895
+
1896
+ ## Resources
1897
+ - Etsy Seller Handbook: https://www.etsy.com/seller-handbook
1898
+ - Etsy Forums: Community support
1899
+ - Etsy Teams: Local seller groups
1900
+ - Etsy Success Newsletter: Tips and updates`,
1901
+ },
1902
+ ],
1903
+ };
1904
+
1905
+ case 'etsy://docs/seo-guide':
1906
+ return {
1907
+ contents: [
1908
+ {
1909
+ uri,
1910
+ mimeType: 'text/plain',
1911
+ text: `# Complete Etsy SEO Guide
1912
+
1913
+ ## Understanding Etsy Search
1914
+
1915
+ Etsy search algorithm considers:
1916
+ 1. **Query Matching:** Titles, tags, categories, attributes
1917
+ 2. **Listing Quality Score:** Photos, description, shop policies
1918
+ 3. **Customer Experience:** Reviews, shipping, messages
1919
+ 4. **Shop History:** Sales, favorites, age
1920
+
1921
+ ## Keyword Research
1922
+
1923
+ ### Tools and Methods
1924
+ - Etsy search bar autocomplete
1925
+ - Competitor listing analysis
1926
+ - Google Trends for seasonal terms
1927
+ - Customer language from reviews
1928
+ - Long-tail keyword variations
1929
+
1930
+ ### Keyword Types
1931
+ - **Broad terms:** "handmade jewelry"
1932
+ - **Specific terms:** "sterling silver moon necklace"
1933
+ - **Long-tail:** "personalized graduation gift for daughter"
1934
+ - **Seasonal:** "Christmas stocking stuffer"
1935
+
1936
+ ## Title Optimization
1937
+
1938
+ ### Best Practices
1939
+ - **Front-load keywords:** Most important words first
1940
+ - **Be specific:** "Vintage 1960s Leather Crossbody Bag"
1941
+ - **Include key attributes:** Size, color, material, use
1942
+ - **Use natural language:** Readable, not keyword stuffed
1943
+ - **Stay under 140 characters**
1944
+
1945
+ ### Title Formula
1946
+ [What It Is] | [Key Features] | [Use Case/Benefit]
1947
+
1948
+ Example: "Handmade Ceramic Mug | 16oz Large Coffee Cup | Perfect Gift for Coffee Lovers"
1949
+
1950
+ ## Tag Strategy
1951
+
1952
+ ### Maximizing 13 Tags
1953
+ - 3-4 broad terms (handmade jewelry)
1954
+ - 4-5 specific terms (moonstone pendant necklace)
1955
+ - 3-4 long-tail keywords (boho wedding jewelry gift)
1956
+ - 1-2 seasonal/trending terms
1957
+
1958
+ ### Tag Tips
1959
+ - Use all 13 tags
1960
+ - Multi-word phrases (etsy treats as one tag)
1961
+ - Consider misspellings for common terms
1962
+ - Update seasonally
1963
+ - Test and adjust based on analytics
1964
+
1965
+ ## Description SEO
1966
+
1967
+ ### Structure
1968
+ 1. **Opening paragraph:** Keyword-rich summary (first 160 characters show in search)
1969
+ 2. **Key features:** Bullet points with natural keywords
1970
+ 3. **Details:** Size, materials, care instructions
1971
+ 4. **Story/Use cases:** Emotional connection
1972
+ 5. **Policies:** Shipping, returns (reassurance)
1973
+
1974
+ ### SEO Writing Tips
1975
+ - Natural keyword integration
1976
+ - Answer common questions
1977
+ - Use headers/formatting
1978
+ - Include relevant attributes
1979
+ - Cross-link related listings
1980
+
1981
+ ## Categories and Attributes
1982
+
1983
+ ### Importance
1984
+ - Required for category-specific searches
1985
+ - Helps Etsy understand your product
1986
+ - Affects search placement
1987
+
1988
+ ### Best Practices
1989
+ - Choose most specific category
1990
+ - Fill all relevant attributes
1991
+ - Be accurate (affects trust signals)
1992
+ - Update when Etsy adds new options
1993
+
1994
+ ## Shop-Wide SEO
1995
+
1996
+ ### Shop Title
1997
+ - Appears in external search (Google)
1998
+ - Include main product category
1999
+ - Keep under 55 characters
2000
+
2001
+ ### Shop Sections
2002
+ - Organize products logically
2003
+ - Use keyword-rich section names
2004
+ - Helps customers browse
2005
+ - Improves internal linking
2006
+
2007
+ ### About Section
2008
+ - Tell your brand story
2009
+ - Include relevant keywords naturally
2010
+ - Build trust and connection
2011
+
2012
+ ## Performance Tracking
2013
+
2014
+ ### Key Metrics
2015
+ - **Search views:** How often listings appear
2016
+ - **Click-through rate:** Views vs. visits
2017
+ - **Conversion rate:** Visits to sales
2018
+ - **Search terms:** What brings traffic
2019
+
2020
+ ### Tools
2021
+ - Etsy Stats (Shop Manager)
2022
+ - Google Analytics (external traffic)
2023
+ - Search Analytics (keyword performance)
2024
+
2025
+ ## Advanced Strategies
2026
+
2027
+ ### A/B Testing
2028
+ - Test title variations
2029
+ - Try different primary photos
2030
+ - Adjust pricing strategies
2031
+ - Monitor conversion impact
2032
+
2033
+ ### Seasonal Optimization
2034
+ - Update keywords 6-8 weeks before holidays
2035
+ - Create seasonal landing collections
2036
+ - Adjust inventory for demand
2037
+ - Plan content calendar
2038
+
2039
+ ### External SEO
2040
+ - Social media sharing
2041
+ - Blog content
2042
+ - Pinterest optimization
2043
+ - Backlink building
2044
+
2045
+ ## Common SEO Mistakes
2046
+
2047
+ ### Avoid These
2048
+ - ❌ Keyword stuffing (unreadable titles)
2049
+ - ❌ Irrelevant tags (misleading)
2050
+ - ❌ All caps titles (looks spammy)
2051
+ - ❌ Trademark violations
2052
+ - ❌ Duplicate listings (compete with yourself)
2053
+ - ❌ Ignoring analytics (data-driven decisions)
2054
+
2055
+ ## Quick Wins Checklist
2056
+
2057
+ 1. ✅ Use all 13 tags on every listing
2058
+ 2. ✅ Front-load titles with keywords
2059
+ 3. ✅ Fill all category attributes
2060
+ 4. ✅ Add 10 high-quality photos
2061
+ 5. ✅ Write detailed descriptions
2062
+ 6. ✅ Update seasonal listings early
2063
+ 7. ✅ Monitor and adjust based on stats
2064
+ 8. ✅ Respond to messages quickly
2065
+ 9. ✅ Encourage reviews
2066
+ 10. ✅ Keep inventory active
2067
+
2068
+ Remember: SEO is ongoing. Review and optimize regularly based on performance data.`,
2069
+ },
2070
+ ],
2071
+ };
2072
+
2073
+ case 'etsy://docs/shipping':
2074
+ return {
2075
+ contents: [
2076
+ {
2077
+ uri,
2078
+ mimeType: 'text/plain',
2079
+ text: `# Etsy Shipping Guide
2080
+
2081
+ ## Shipping Strategy
2082
+
2083
+ ### Setting Up Profiles
2084
+ - Create profiles for different product types
2085
+ - Include domestic and international options
2086
+ - Consider package dimensions and weight
2087
+ - Build in handling time realistically
2088
+
2089
+ ### Pricing Models
2090
+ 1. **Calculated shipping:** Real-time carrier rates
2091
+ 2. **Fixed shipping:** Set price per location
2092
+ 3. **Free shipping:** Built into product price
2093
+ 4. **Combined shipping:** Discounts for multiple items
2094
+
2095
+ ## Domestic Shipping (US)
2096
+
2097
+ ### Carrier Options
2098
+ - **USPS:** Most cost-effective for small items
2099
+ - First Class: Under 16oz, 2-5 days
2100
+ - Priority Mail: 1-3 days, includes tracking
2101
+ - Priority Express: Overnight
2102
+ - **UPS/FedEx:** Better for heavy items
2103
+ - **Regional carriers:** For local deliveries
2104
+
2105
+ ### Shipping Supplies
2106
+ - Proper boxes/mailers for protection
2107
+ - Bubble wrap or packing peanuts
2108
+ - Thank you cards/branding materials
2109
+ - Shipping labels (consider label printer)
2110
+ - Tape gun for efficiency
2111
+
2112
+ ## International Shipping
2113
+
2114
+ ### Considerations
2115
+ - Customs forms required
2116
+ - Longer delivery times (7-21+ days)
2117
+ - Higher costs
2118
+ - Customs fees (buyer responsibility)
2119
+ - Tracking may be limited
2120
+
2121
+ ### International Best Practices
2122
+ - Clearly state delivery times
2123
+ - Mention customs fees in listing
2124
+ - Use USPS International services
2125
+ - Consider restricted countries
2126
+ - Package securely for long transit
2127
+
2128
+ ## Free Shipping
2129
+
2130
+ ### Etsy Free Shipping Guarantee
2131
+ - US orders $35+
2132
+ - Boosts search placement
2133
+ - Competitive advantage
2134
+
2135
+ ### Implementation Strategies
2136
+ 1. **Built-in pricing:** Add shipping to product cost
2137
+ 2. **Threshold:** Free over $X
2138
+ 3. **Flat rate:** Same price all items
2139
+ 4. **Promotional:** Limited time offers
2140
+
2141
+ ### Making It Work
2142
+ - Calculate average shipping cost
2143
+ - Adjust product pricing accordingly
2144
+ - Monitor profit margins
2145
+ - Test different thresholds
2146
+
2147
+ ## Processing and Handling
2148
+
2149
+ ### Handling Time
2150
+ - Set realistic timeframes
2151
+ - Account for production time
2152
+ - Consider weekends/holidays
2153
+ - Update for busy seasons
2154
+ - Communicate delays promptly
2155
+
2156
+ ### Best Practices
2157
+ - Process orders within 24 hours
2158
+ - Print labels via Etsy (discounts)
2159
+ - Upload tracking immediately
2160
+ - Send shipped notification
2161
+ - Include tracking in message
2162
+
2163
+ ## Packaging Tips
2164
+
2165
+ ### Protection
2166
+ - Double box fragile items
2167
+ - Use adequate cushioning
2168
+ - Seal securely with quality tape
2169
+ - Waterproof if necessary
2170
+ - Test drop to ensure safety
2171
+
2172
+ ### Branding
2173
+ - Thank you card with logo
2174
+ - Branded tissue paper/tape
2175
+ - Business cards for reorders
2176
+ - Care instruction cards
2177
+ - Surprise bonus (sticker, sample)
2178
+
2179
+ ### Sustainability
2180
+ - Eco-friendly materials
2181
+ - Minimal packaging (reduce waste)
2182
+ - Recyclable/biodegradable options
2183
+ - Reuse shipping materials
2184
+ - Communicate green practices
2185
+
2186
+ ## Tracking and Insurance
2187
+
2188
+ ### Tracking
2189
+ - Always include for $20+ orders
2190
+ - Protects buyer and seller
2191
+ - Required for claims
2192
+ - Builds customer confidence
2193
+ - Enables delivery confirmation
2194
+
2195
+ ### Insurance
2196
+ - Recommend for $100+ items
2197
+ - Protects against loss/damage
2198
+ - Small additional cost
2199
+ - Peace of mind for both parties
2200
+ - Required for high-value claims
2201
+
2202
+ ## Handling Shipping Issues
2203
+
2204
+ ### Lost Packages
2205
+ 1. Check tracking for updates
2206
+ 2. Contact carrier after expected delivery
2207
+ 3. File claim if insured
2208
+ 4. Reship or refund customer
2209
+ 5. Document everything
2210
+
2211
+ ### Damaged Items
2212
+ 1. Request photos from customer
2213
+ 2. File carrier claim if insured
2214
+ 3. Offer replacement or refund
2215
+ 4. Improve packaging for future
2216
+ 5. Learn from issues
2217
+
2218
+ ### Delays
2219
+ - Communicate proactively
2220
+ - Provide tracking updates
2221
+ - Apologize sincerely
2222
+ - Offer solutions (expedited reship)
2223
+ - Learn from experience
2224
+
2225
+ ## International Customs
2226
+
2227
+ ### Required Information
2228
+ - Accurate item description
2229
+ - Value for customs
2230
+ - Country of origin
2231
+ - HS tariff code (when applicable)
2232
+ - Proper forms (CN22, CN23)
2233
+
2234
+ ### Common Issues
2235
+ - Held in customs (varies by country)
2236
+ - Duties/taxes (buyer pays)
2237
+ - Restricted items (varies by country)
2238
+ - Lost customs forms
2239
+
2240
+ ## Shipping Metrics to Track
2241
+
2242
+ ### Key Performance Indicators
2243
+ - Average shipping cost per order
2244
+ - Delivery time vs. promised time
2245
+ - Damage/loss rate
2246
+ - Customer satisfaction with shipping
2247
+ - Carrier performance
2248
+
2249
+ ### Optimization
2250
+ - Compare carrier rates regularly
2251
+ - Negotiate volume discounts
2252
+ - Optimize package sizes
2253
+ - Reduce handling time
2254
+ - Improve packaging efficiency
2255
+
2256
+ ## Seasonal Shipping
2257
+
2258
+ ### Holiday Preparation
2259
+ - Post cutoff dates clearly
2260
+ - Allow extra processing time
2261
+ - Recommend shipping upgrades
2262
+ - Communicate delays promptly
2263
+ - Set shop vacation if overwhelmed
2264
+
2265
+ ### Peak Season Tips
2266
+ - Order supplies early
2267
+ - Batch process orders
2268
+ - Use shipping software
2269
+ - Consider hired help
2270
+ - Maintain quality standards
2271
+
2272
+ ## Shipping Policies
2273
+
2274
+ ### What to Include
2275
+ - Processing/handling time
2276
+ - Shipping methods available
2277
+ - International shipping details
2278
+ - Customs/duties information
2279
+ - Lost package procedures
2280
+ - Upgrade options
2281
+
2282
+ ### Communication
2283
+ - Clear in listing descriptions
2284
+ - Shop policies page
2285
+ - Order messages
2286
+ - FAQ section
2287
+ - Proactive updates
2288
+
2289
+ Remember: Excellent shipping experience leads to 5-star reviews and repeat customers!`,
2290
+ },
2291
+ ],
2292
+ };
2293
+
2294
+ case 'etsy://docs/photography':
2295
+ return {
2296
+ contents: [
2297
+ {
2298
+ uri,
2299
+ mimeType: 'text/plain',
2300
+ text: `# Professional Product Photography for Etsy
2301
+
2302
+ ## Equipment Essentials
2303
+
2304
+ ### Camera Options
2305
+ - **DSLR/Mirrorless:** Best quality and control
2306
+ - **High-end smartphone:** Adequate for many products
2307
+ - **Point-and-shoot:** Budget-friendly option
2308
+
2309
+ ### Lighting
2310
+ - **Natural light:** Free, beautiful (near window)
2311
+ - **Softbox kit:** $50-200, consistent results
2312
+ - **Ring light:** Great for small items
2313
+ - **Reflectors:** Bounce and fill light ($20-40)
2314
+
2315
+ ### Support Equipment
2316
+ - **Tripod:** Essential for consistency ($30-100)
2317
+ - **Backdrop:** White seamless paper or sweep
2318
+ - **Light tent:** For small items ($20-50)
2319
+ - **Props:** Lifestyle and scale reference
2320
+
2321
+ ## Photo Requirements
2322
+
2323
+ ### Technical Specifications
2324
+ - **Resolution:** Minimum 2000px longest side
2325
+ - **Format:** JPG preferred
2326
+ - **File size:** Under 1MB for fast loading
2327
+ - **Aspect ratio:** Square (1:1) ideal
2328
+ - **Number:** Use all 10 slots
2329
+
2330
+ ### Etsy-Specific Considerations
2331
+ - First photo is thumbnail (most important)
2332
+ - Mobile optimization critical (70% traffic)
2333
+ - Photos appear in search results
2334
+ - Zoom feature requires high resolution
2335
+
2336
+ ## The Essential Shot List
2337
+
2338
+ ### 1. Hero Shot (Main Photo)
2339
+ - Clean white/neutral background
2340
+ - Product centered and well-lit
2341
+ - Shows full item clearly
2342
+ - No distractions
2343
+ - **This determines clicks!**
2344
+
2345
+ ### 2. Detail Shots (2-3 photos)
2346
+ - Close-ups of craftsmanship
2347
+ - Texture and materials
2348
+ - Quality indicators
2349
+ - Unique features
2350
+ - Proof of handmade quality
2351
+
2352
+ ### 3. Scale Reference (1-2 photos)
2353
+ - Product in hand
2354
+ - Next to common object (coin, ruler)
2355
+ - Lifestyle shot showing size
2356
+ - Multiple items together
2357
+
2358
+ ### 4. Lifestyle Photos (2-3 photos)
2359
+ - Product in use
2360
+ - Styled in environment
2361
+ - Emotional connection
2362
+ - Shows practical application
2363
+ - Aspirational yet relatable
2364
+
2365
+ ### 5. Variations (if applicable)
2366
+ - Color options
2367
+ - Size options
2368
+ - Customization examples
2369
+ - Before/after (if relevant)
2370
+
2371
+ ### 6. Packaging Shot (1 photo)
2372
+ - How it arrives
2373
+ - Gift-ready presentation
2374
+ - Professional impression
2375
+ - Sets expectations
2376
+
2377
+ ## Lighting Techniques
2378
+
2379
+ ### Natural Light Setup
2380
+ 1. **Location:** Near large window
2381
+ 2. **Time:** Overcast day or indirect sunlight
2382
+ 3. **Positioning:** Product 3-5 feet from window
2383
+ 4. **Fill light:** White reflector opposite window
2384
+ 5. **Background:** Window to side, not behind
2385
+
2386
+ ### Artificial Light Setup
2387
+ 1. **Main light:** 45° angle, above product
2388
+ 2. **Fill light:** Opposite side, lower intensity
2389
+ 3. **Background light:** Optional, separates product
2390
+ 4. **Diffusion:** Soften with umbrella or softbox
2391
+
2392
+ ### Common Lighting Mistakes
2393
+ - ❌ Direct harsh sunlight (hard shadows)
2394
+ - ❌ Yellow indoor bulbs (color cast)
2395
+ - ❌ Mixed light sources (inconsistent color)
2396
+ - ❌ Underexposure (dark, details lost)
2397
+ - ❌ Overexposure (blown highlights)
2398
+
2399
+ ## Styling and Composition
2400
+
2401
+ ### Rule of Thirds
2402
+ - Divide frame into 3x3 grid
2403
+ - Place subject at intersections
2404
+ - Creates visual interest
2405
+ - More dynamic than centered
2406
+
2407
+ ### Background Choices
2408
+ - **White:** Clean, professional, versatile
2409
+ - **Neutral (gray, beige):** Sophisticated
2410
+ - **Wood/texture:** Warm, handmade feel
2411
+ - **Lifestyle setting:** Context and story
2412
+ - **Avoid:** Busy patterns, competing colors
2413
+
2414
+ ### Props and Styling
2415
+ - **Purpose:** Complement, not compete
2416
+ - **Scale:** Show size and context
2417
+ - **Color:** Harmonize with product
2418
+ - **Relevance:** Support product story
2419
+ - **Minimalism:** Less is often more
2420
+
2421
+ ## Camera Settings
2422
+
2423
+ ### Smartphone Photography
2424
+ - **HDR Mode:** ON for balanced exposure
2425
+ - **Grid:** Enable for composition
2426
+ - **Focus:** Tap to focus on product
2427
+ - **Exposure:** Adjust with slider
2428
+ - **Portrait Mode:** Use for depth (carefully)
2429
+
2430
+ ### DSLR/Mirrorless Settings
2431
+ - **Aperture:** f/8-f/16 for sharpness
2432
+ - **ISO:** 100-400 (minimize noise)
2433
+ - **Shutter speed:** 1/125 or faster
2434
+ - **White balance:** Match light source
2435
+ - **Shooting mode:** Aperture priority or manual
2436
+
2437
+ ## Post-Processing
2438
+
2439
+ ### Essential Edits
2440
+ 1. **Crop:** Straighten and compose
2441
+ 2. **Exposure:** Brighten if needed
2442
+ 3. **White balance:** Correct color cast
2443
+ 4. **Contrast:** Add depth
2444
+ 5. **Sharpen:** Enhance details (subtle)
2445
+
2446
+ ### Editing Software
2447
+ - **Mobile:** Snapseed, VSCO, Lightroom Mobile
2448
+ - **Desktop:** Adobe Lightroom, Photoshop
2449
+ - **Free:** GIMP, Photopea
2450
+ - **Etsy App:** Basic editing built-in
2451
+
2452
+ ### Consistency is Key
2453
+ - Create presets for brand look
2454
+ - Edit entire shoot together
2455
+ - Match lighting across images
2456
+ - Maintain color accuracy
2457
+ - Same background style
2458
+
2459
+ ## Category-Specific Tips
2460
+
2461
+ ### Jewelry
2462
+ - Macro lens or smartphone macro mode
2463
+ - Neutral background or lifestyle
2464
+ - Show scale (worn on model)
2465
+ - Capture sparkle and detail
2466
+ - Multiple angles of stones
2467
+
2468
+ ### Clothing
2469
+ - On model or dress form
2470
+ - Show fit and drape
2471
+ - Detail shots of fabric/stitching
2472
+ - Size chart reference
2473
+ - Flat lay for patterns
2474
+
2475
+ ### Home Decor
2476
+ - Staged in room setting
2477
+ - Show scale with furniture
2478
+ - Multiple room applications
2479
+ - Detail shots of materials
2480
+ - Lighting effects if applicable
2481
+
2482
+ ### Art/Prints
2483
+ - Straight-on, no distortion
2484
+ - Frame it to show presentation
2485
+ - Include size reference
2486
+ - Detail shot if textured
2487
+ - Lifestyle shot on wall
2488
+
2489
+ ## Mobile Photography Tips
2490
+
2491
+ ### Smartphone Best Practices
2492
+ - Clean lens before shooting
2493
+ - Use built-in grid lines
2494
+ - Tap to focus on product
2495
+ - Shoot in natural light
2496
+ - Use HDR mode
2497
+ - Take multiple shots
2498
+ - Edit consistently
2499
+
2500
+ ### Mobile Limitations
2501
+ - Lower resolution than DSLR
2502
+ - Less control over depth of field
2503
+ - Challenging in low light
2504
+ - Limited zoom (use digital carefully)
2505
+
2506
+ ## Video Content
2507
+
2508
+ ### Why Add Video
2509
+ - 360° product view
2510
+ - Show size and scale
2511
+ - Demonstrate use
2512
+ - Increase engagement
2513
+ - Boost conversions
2514
+
2515
+ ### Video Tips
2516
+ - 5-15 seconds ideal
2517
+ - Stable (use tripod)
2518
+ - Good lighting
2519
+ - Slow smooth movements
2520
+ - No sound needed
2521
+ - Show key features
2522
+
2523
+ ## Photo Mistakes to Avoid
2524
+
2525
+ ### Common Errors
2526
+ - ❌ Cluttered background
2527
+ - ❌ Poor lighting (dark/yellow)
2528
+ - ❌ Blurry images
2529
+ - ❌ Incorrect colors
2530
+ - ❌ Can't see details
2531
+ - ❌ Inconsistent style
2532
+ - ❌ Filter overuse
2533
+ - ❌ Watermarks (reduces clicks)
2534
+
2535
+ ## Workflow Efficiency
2536
+
2537
+ ### Batch Photography
2538
+ 1. Set up once for multiple products
2539
+ 2. Same lighting for consistency
2540
+ 3. Shoot all variations
2541
+ 4. Edit together with presets
2542
+ 5. Upload in batch
2543
+
2544
+ ### Time-Saving Tips
2545
+ - Prepare all products beforehand
2546
+ - Set up permanent photo station
2547
+ - Use presets for editing
2548
+ - Create shot list template
2549
+ - Schedule regular photo days
2550
+
2551
+ ## Testing and Optimization
2552
+
2553
+ ### A/B Testing
2554
+ - Try different main photos
2555
+ - Test lifestyle vs. white background
2556
+ - Monitor click-through rates
2557
+ - Adjust based on performance
2558
+
2559
+ ### Analytics
2560
+ - Track which photos get clicked
2561
+ - Monitor conversion rates
2562
+ - Note customer questions about photos
2563
+ - Update based on data
2564
+
2565
+ Remember: Photos are your most important listing element. Invest time in getting them right!`,
2566
+ },
2567
+ ],
2568
+ };
2569
+
2570
+ case 'etsy://tools/fees-calculator':
2571
+ return {
2572
+ contents: [
2573
+ {
2574
+ uri,
2575
+ mimeType: 'application/json',
2576
+ text: JSON.stringify({
2577
+ title: 'Etsy Fees Calculator',
2578
+ description: 'Calculate all Etsy fees and determine optimal pricing',
2579
+ fees: {
2580
+ listing_fee: 0.20,
2581
+ transaction_fee_percent: 6.5,
2582
+ payment_processing_percent: 3.0,
2583
+ payment_processing_fixed: 0.25,
2584
+ offsite_ads_percent: 12.0, // for sales under $10k/year
2585
+ currency_conversion_percent: 2.5,
2586
+ },
2587
+ calculator: {
2588
+ example: {
2589
+ product_price: 50.00,
2590
+ calculation: {
2591
+ listing_fee: 0.20,
2592
+ transaction_fee: 3.25, // 6.5% of $50
2593
+ payment_processing: 1.75, // 3% of $50 + $0.25
2594
+ total_fees: 5.20,
2595
+ seller_receives: 44.80,
2596
+ fee_percentage: 10.4,
2597
+ },
2598
+ },
2599
+ },
2600
+ pricing_formula: {
2601
+ minimum_price: '(Material_Cost + Labor_Cost + Overhead) / (1 - Fee_Percentage)',
2602
+ suggested_retail: 'Minimum_Price * (1 + Desired_Profit_Margin)',
2603
+ profit_margin: 'Recommended 30-50% for sustainable business',
2604
+ },
2605
+ tips: [
2606
+ 'Build fees into your pricing, don\'t absorb them',
2607
+ 'Account for 10-15% in fees for domestic sales',
2608
+ 'Add 15-20% for international sales (currency conversion)',
2609
+ 'Remember offsite ads are 12% (15% if over $10k annual)',
2610
+ 'Free shipping? Add average shipping cost to price',
2611
+ 'Consider payment processor fees vary by country',
2612
+ ],
2613
+ }, null, 2),
2614
+ },
2615
+ ],
2616
+ };
2617
+
2618
+ default:
2619
+ throw new Error(`Unknown resource URI: ${uri}`);
2620
+ }
2621
+ }
2622
+
2623
+ getServer(): Server {
2624
+ return this.server;
2625
+ }
2626
+
2627
+ async run(): Promise<void> {
2628
+ const transport = new StdioServerTransport();
2629
+ await this.server.connect(transport);
2630
+ console.error('Etsy MCP Server running on stdio');
2631
+ }
2632
+ }
2633
+
2634
+ // Smithery createServer export (required for Smithery deployment)
2635
+ export default function createServer({ config }: { config?: z.infer<typeof configSchema> }) {
2636
+ const etsyConfig: EtsyConfig = {
2637
+ apiKey: config?.apiKey || process.env.ETSY_API_KEY || 'demo-key-readonly',
2638
+ shopId: config?.shopId || process.env.ETSY_SHOP_ID,
2639
+ accessToken: config?.accessToken || process.env.ETSY_ACCESS_TOKEN,
2640
+ };
2641
+
2642
+ // No validation - server can run without credentials for demo/documentation purposes
2643
+ // API calls will fail gracefully if credentials are missing
2644
+
2645
+ const mcpServer = new EtsyMCPServer(etsyConfig);
2646
+ return mcpServer.getServer();
2647
+ }
2648
+
2649
+ // CLI entry point (for direct execution with stdio)
2650
+ if (import.meta.url === `file://${process.argv[1]}`) {
2651
+ const config: EtsyConfig = {
2652
+ apiKey: process.env.ETSY_API_KEY || '',
2653
+ shopId: process.env.ETSY_SHOP_ID,
2654
+ accessToken: process.env.ETSY_ACCESS_TOKEN,
2655
+ };
2656
+
2657
+ if (!config.apiKey) {
2658
+ console.error('Error: ETSY_API_KEY environment variable is required');
2659
+ process.exit(1);
2660
+ }
2661
+
2662
+ const server = new EtsyMCPServer(config);
2663
+ server.run().catch((error) => {
2664
+ console.error('Fatal error:', error);
2665
+ process.exit(1);
2666
+ });
2667
+ }