@rbillon59/vinted-mcp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +232 -0
  2. package/dist/api/browser-utils.d.ts +21 -0
  3. package/dist/api/browser-utils.d.ts.map +1 -0
  4. package/dist/api/browser-utils.js +64 -0
  5. package/dist/api/browser-utils.js.map +1 -0
  6. package/dist/api/client.d.ts +45 -0
  7. package/dist/api/client.d.ts.map +1 -0
  8. package/dist/api/client.js +220 -0
  9. package/dist/api/client.js.map +1 -0
  10. package/dist/api/session-provider.d.ts +30 -0
  11. package/dist/api/session-provider.d.ts.map +1 -0
  12. package/dist/api/session-provider.js +69 -0
  13. package/dist/api/session-provider.js.map +1 -0
  14. package/dist/api/types.d.ts +83 -0
  15. package/dist/api/types.d.ts.map +1 -0
  16. package/dist/api/types.js +12 -0
  17. package/dist/api/types.js.map +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +21 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/server.d.ts +3 -0
  23. package/dist/server.d.ts.map +1 -0
  24. package/dist/server.js +22 -0
  25. package/dist/server.js.map +1 -0
  26. package/dist/tools/brands.d.ts +3 -0
  27. package/dist/tools/brands.d.ts.map +1 -0
  28. package/dist/tools/brands.js +34 -0
  29. package/dist/tools/brands.js.map +1 -0
  30. package/dist/tools/categories.d.ts +3 -0
  31. package/dist/tools/categories.d.ts.map +1 -0
  32. package/dist/tools/categories.js +28 -0
  33. package/dist/tools/categories.js.map +1 -0
  34. package/dist/tools/item.d.ts +3 -0
  35. package/dist/tools/item.d.ts.map +1 -0
  36. package/dist/tools/item.js +155 -0
  37. package/dist/tools/item.js.map +1 -0
  38. package/dist/tools/search.d.ts +3 -0
  39. package/dist/tools/search.d.ts.map +1 -0
  40. package/dist/tools/search.js +73 -0
  41. package/dist/tools/search.js.map +1 -0
  42. package/dist/tools/user-items.d.ts +3 -0
  43. package/dist/tools/user-items.d.ts.map +1 -0
  44. package/dist/tools/user-items.js +50 -0
  45. package/dist/tools/user-items.js.map +1 -0
  46. package/dist/tools/user.d.ts +3 -0
  47. package/dist/tools/user.d.ts.map +1 -0
  48. package/dist/tools/user.js +38 -0
  49. package/dist/tools/user.js.map +1 -0
  50. package/dist/utils/cache.d.ts +17 -0
  51. package/dist/utils/cache.d.ts.map +1 -0
  52. package/dist/utils/cache.js +65 -0
  53. package/dist/utils/cache.js.map +1 -0
  54. package/dist/utils/mcp-error.d.ts +11 -0
  55. package/dist/utils/mcp-error.d.ts.map +1 -0
  56. package/dist/utils/mcp-error.js +11 -0
  57. package/dist/utils/mcp-error.js.map +1 -0
  58. package/dist/utils/rate-limiter.d.ts +22 -0
  59. package/dist/utils/rate-limiter.d.ts.map +1 -0
  60. package/dist/utils/rate-limiter.js +47 -0
  61. package/dist/utils/rate-limiter.js.map +1 -0
  62. package/package.json +45 -0
@@ -0,0 +1,50 @@
1
+ import { z } from "zod";
2
+ import { getClient } from "../api/client.js";
3
+ import { formatPrice } from "../api/types.js";
4
+ import { mcpError } from "../utils/mcp-error.js";
5
+ function formatUserItems(data, userId) {
6
+ if (data.items.length === 0) {
7
+ return `User ${userId} has no items listed.`;
8
+ }
9
+ const { pagination } = data;
10
+ const header = `User ${userId} — ${pagination.total_entries} items (page ${pagination.current_page}/${pagination.total_pages}):\n`;
11
+ const items = data.items.map((item) => {
12
+ const price = formatPrice(item.price, item.currency);
13
+ const brand = item.brand_title ?? item.brand ?? "";
14
+ const size = item.size_title ?? item.size ?? "";
15
+ const parts = [
16
+ `- **${item.title}** — ${price}`,
17
+ ];
18
+ if (brand)
19
+ parts[0] += ` | ${brand}`;
20
+ if (size)
21
+ parts[0] += ` | Size: ${size}`;
22
+ parts.push(` ID: ${item.id} | ${item.url}`);
23
+ return parts.join("\n");
24
+ });
25
+ return header + items.join("\n");
26
+ }
27
+ export function registerUserItemsTool(server) {
28
+ server.tool("get_user_items", "List items currently for sale by a specific Vinted user/seller.", {
29
+ user_id: z.number().int().positive().describe("The Vinted user ID"),
30
+ page: z.number().int().min(1).max(100).default(1).describe("Page number (default: 1)"),
31
+ per_page: z.number().int().min(1).max(96).default(20).describe("Results per page (default: 20, max: 96)"),
32
+ order: z.enum(["relevance", "price_low_to_high", "price_high_to_low", "newest_first"]).default("newest_first").describe("Sort order"),
33
+ }, async (params) => {
34
+ try {
35
+ const data = await getClient().get(`/wardrobe/${params.user_id}/items`, {
36
+ page: String(params.page),
37
+ per_page: String(params.per_page),
38
+ order: params.order,
39
+ });
40
+ const text = formatUserItems(data, params.user_id);
41
+ return {
42
+ content: [{ type: "text", text }],
43
+ };
44
+ }
45
+ catch (error) {
46
+ return mcpError("Failed to get user items", error);
47
+ }
48
+ });
49
+ }
50
+ //# sourceMappingURL=user-items.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-items.js","sourceRoot":"","sources":["../../src/tools/user-items.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerUserTool(server: McpServer): void;
3
+ //# sourceMappingURL=user.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/tools/user.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA4BpE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAoBxD"}
@@ -0,0 +1,38 @@
1
+ import { z } from "zod";
2
+ import { getClient } from "../api/client.js";
3
+ import { mcpError } from "../utils/mcp-error.js";
4
+ function formatUserProfile(data) {
5
+ const { user } = data;
6
+ const lines = [
7
+ `# ${user.login}`,
8
+ "",
9
+ `## Profile`,
10
+ `- **Rating:** ${user.feedback_reputation}/5`,
11
+ `- **Reviews:** ${user.positive_feedback_count} positive, ${user.negative_feedback_count} negative (${user.feedback_count} total)`,
12
+ `- **Items listed:** ${user.item_count}`,
13
+ `- **Items sold:** ${user.given_item_count}`,
14
+ ];
15
+ if (user.city || user.country_title) {
16
+ lines.push(`- **Location:** ${[user.city, user.country_title].filter(Boolean).join(", ")}`);
17
+ }
18
+ lines.push(`- **Member since:** ${user.created_at}`);
19
+ lines.push(`- **Last active:** ${user.last_loged_on_ts}`);
20
+ return lines.join("\n");
21
+ }
22
+ export function registerUserTool(server) {
23
+ server.tool("get_user_profile", "Get a Vinted user's profile including ratings, reviews, number of items, and activity.", {
24
+ user_id: z.number().int().positive().describe("The Vinted user ID (from search results or item details)"),
25
+ }, async (params) => {
26
+ try {
27
+ const data = await getClient().get(`/users/${params.user_id}`);
28
+ const text = formatUserProfile(data);
29
+ return {
30
+ content: [{ type: "text", text }],
31
+ };
32
+ }
33
+ catch (error) {
34
+ return mcpError("Failed to get user profile", error);
35
+ }
36
+ });
37
+ }
38
+ //# sourceMappingURL=user.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user.js","sourceRoot":"","sources":["../../src/tools/user.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,SAAS,iBAAiB,CAAC,IAAuB;IAChD,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;IACtB,MAAM,KAAK,GAAG;QACZ,KAAK,IAAI,CAAC,KAAK,EAAE;QACjB,EAAE;QACF,YAAY;QACZ,iBAAiB,IAAI,CAAC,mBAAmB,IAAI;QAC7C,kBAAkB,IAAI,CAAC,uBAAuB,cAAc,IAAI,CAAC,uBAAuB,cAAc,IAAI,CAAC,cAAc,SAAS;QAClI,uBAAuB,IAAI,CAAC,UAAU,EAAE;QACxC,qBAAqB,IAAI,CAAC,gBAAgB,EAAE;KAC7C,CAAC;IAEF,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;QACpC,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC9F,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,uBAAuB,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IACrD,KAAK,CAAC,IAAI,CAAC,sBAAsB,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAE1D,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAiB;IAChD,MAAM,CAAC,IAAI,CACT,kBAAkB,EAClB,wFAAwF,EACxF;QACE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0DAA0D,CAAC;KAC1G,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,SAAS,EAAE,CAAC,GAAG,CAAoB,UAAU,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;YAClF,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;YAErC,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC;aAC3C,CAAC;QACJ,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,OAAO,QAAQ,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Simple TTL cache with automatic cleanup.
3
+ * Memory-efficient: entries are evicted on access and periodically swept.
4
+ */
5
+ export declare class TtlCache<T> {
6
+ private readonly store;
7
+ private readonly ttlMs;
8
+ private readonly maxEntries;
9
+ private sweepTimer;
10
+ constructor(ttlMs: number, maxEntries?: number);
11
+ get(key: string): T | undefined;
12
+ set(key: string, value: T): void;
13
+ private sweep;
14
+ get size(): number;
15
+ destroy(): void;
16
+ }
17
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/utils/cache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,qBAAa,QAAQ,CAAC,CAAC;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;IAC1D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,UAAU,CAA+C;gBAErD,KAAK,EAAE,MAAM,EAAE,UAAU,SAAM;IAY3C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAgB/B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAehC,OAAO,CAAC,KAAK;IASb,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,OAAO,IAAI,IAAI;CAOhB"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Simple TTL cache with automatic cleanup.
3
+ * Memory-efficient: entries are evicted on access and periodically swept.
4
+ */
5
+ export class TtlCache {
6
+ store = new Map();
7
+ ttlMs;
8
+ maxEntries;
9
+ sweepTimer = null;
10
+ constructor(ttlMs, maxEntries = 200) {
11
+ this.ttlMs = ttlMs;
12
+ this.maxEntries = maxEntries;
13
+ // Sweep expired entries every TTL period
14
+ this.sweepTimer = setInterval(() => this.sweep(), this.ttlMs);
15
+ // Allow process to exit even if timer is active
16
+ if (this.sweepTimer.unref) {
17
+ this.sweepTimer.unref();
18
+ }
19
+ }
20
+ get(key) {
21
+ const entry = this.store.get(key);
22
+ if (!entry)
23
+ return undefined;
24
+ if (Date.now() > entry.expiresAt) {
25
+ this.store.delete(key);
26
+ return undefined;
27
+ }
28
+ // Move to end for LRU eviction order
29
+ this.store.delete(key);
30
+ this.store.set(key, entry);
31
+ return entry.value;
32
+ }
33
+ set(key, value) {
34
+ // Evict oldest entries if at capacity
35
+ if (this.store.size >= this.maxEntries) {
36
+ const firstKey = this.store.keys().next().value;
37
+ if (firstKey !== undefined) {
38
+ this.store.delete(firstKey);
39
+ }
40
+ }
41
+ this.store.set(key, {
42
+ value,
43
+ expiresAt: Date.now() + this.ttlMs,
44
+ });
45
+ }
46
+ sweep() {
47
+ const now = Date.now();
48
+ for (const [key, entry] of this.store) {
49
+ if (now > entry.expiresAt) {
50
+ this.store.delete(key);
51
+ }
52
+ }
53
+ }
54
+ get size() {
55
+ return this.store.size;
56
+ }
57
+ destroy() {
58
+ if (this.sweepTimer) {
59
+ clearInterval(this.sweepTimer);
60
+ this.sweepTimer = null;
61
+ }
62
+ this.store.clear();
63
+ }
64
+ }
65
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/utils/cache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,OAAO,QAAQ;IACF,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAC;IACzC,KAAK,CAAS;IACd,UAAU,CAAS;IAC5B,UAAU,GAA0C,IAAI,CAAC;IAEjE,YAAY,KAAa,EAAE,UAAU,GAAG,GAAG;QACzC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAE7B,yCAAyC;QACzC,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9D,gDAAgD;QAChD,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;YAC1B,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,GAAG,CAAC,GAAW;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAE7B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,qCAAqC;QACrC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAE3B,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,KAAQ;QACvB,sCAAsC;QACtC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YAChD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;YAClB,KAAK;YACL,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK;SACnC,CAAC,CAAC;IACL,CAAC;IAEO,KAAK;QACX,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACtC,IAAI,GAAG,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;gBAC1B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;IAED,OAAO;QACL,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACzB,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;CACF"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Shared MCP error response builder for tool handlers.
3
+ */
4
+ export declare function mcpError(prefix: string, error: unknown): {
5
+ content: [{
6
+ type: "text";
7
+ text: string;
8
+ }];
9
+ isError: true;
10
+ };
11
+ //# sourceMappingURL=mcp-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-error.d.ts","sourceRoot":"","sources":["../../src/utils/mcp-error.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG;IACxD,OAAO,EAAE,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1C,OAAO,EAAE,IAAI,CAAC;CACf,CAMA"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Shared MCP error response builder for tool handlers.
3
+ */
4
+ export function mcpError(prefix, error) {
5
+ const message = error instanceof Error ? error.message : String(error);
6
+ return {
7
+ content: [{ type: "text", text: `${prefix}: ${message}` }],
8
+ isError: true,
9
+ };
10
+ }
11
+ //# sourceMappingURL=mcp-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-error.js","sourceRoot":"","sources":["../../src/utils/mcp-error.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAc,EAAE,KAAc;IAIrD,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,GAAG,MAAM,KAAK,OAAO,EAAE,EAAE,CAAC;QACnE,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Token bucket rate limiter.
3
+ * Prevents overwhelming the Vinted API with too many requests.
4
+ */
5
+ export declare class RateLimiter {
6
+ private tokens;
7
+ private readonly maxTokens;
8
+ private readonly refillRate;
9
+ private lastRefill;
10
+ /**
11
+ * @param maxRequests Maximum burst size
12
+ * @param windowMs Time window in milliseconds for the max requests
13
+ */
14
+ constructor(maxRequests: number, windowMs: number);
15
+ private refill;
16
+ /**
17
+ * Wait until a token is available, then consume it.
18
+ * Returns immediately if tokens are available.
19
+ */
20
+ acquire(): Promise<void>;
21
+ }
22
+ //# sourceMappingURL=rate-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,UAAU,CAAS;IAE3B;;;OAGG;gBACS,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAOjD,OAAO,CAAC,MAAM;IAOd;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAmB/B"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Token bucket rate limiter.
3
+ * Prevents overwhelming the Vinted API with too many requests.
4
+ */
5
+ export class RateLimiter {
6
+ tokens;
7
+ maxTokens;
8
+ refillRate; // tokens per ms
9
+ lastRefill;
10
+ /**
11
+ * @param maxRequests Maximum burst size
12
+ * @param windowMs Time window in milliseconds for the max requests
13
+ */
14
+ constructor(maxRequests, windowMs) {
15
+ this.maxTokens = maxRequests;
16
+ this.tokens = maxRequests;
17
+ this.refillRate = maxRequests / windowMs;
18
+ this.lastRefill = Date.now();
19
+ }
20
+ refill() {
21
+ const now = Date.now();
22
+ const elapsed = now - this.lastRefill;
23
+ this.tokens = Math.min(this.maxTokens, this.tokens + elapsed * this.refillRate);
24
+ this.lastRefill = now;
25
+ }
26
+ /**
27
+ * Wait until a token is available, then consume it.
28
+ * Returns immediately if tokens are available.
29
+ */
30
+ async acquire() {
31
+ this.refill();
32
+ if (this.tokens >= 1) {
33
+ this.tokens -= 1;
34
+ return;
35
+ }
36
+ // Calculate wait time for next token
37
+ const waitMs = Math.ceil((1 - this.tokens) / this.refillRate);
38
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
39
+ this.refill();
40
+ // Guard against timer imprecision
41
+ if (this.tokens < 1) {
42
+ return this.acquire();
43
+ }
44
+ this.tokens -= 1;
45
+ }
46
+ }
47
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,OAAO,WAAW;IACd,MAAM,CAAS;IACN,SAAS,CAAS;IAClB,UAAU,CAAS,CAAC,gBAAgB;IAC7C,UAAU,CAAS;IAE3B;;;OAGG;IACH,YAAY,WAAmB,EAAE,QAAgB;QAC/C,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC;QAC1B,IAAI,CAAC,UAAU,GAAG,WAAW,GAAG,QAAQ,CAAC;QACzC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC/B,CAAC;IAEO,MAAM;QACZ,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC;QACtC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;QAChF,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC;IACxB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,MAAM,EAAE,CAAC;QAEd,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YACrB,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;YACjB,OAAO;QACT,CAAC;QAED,qCAAqC;QACrC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9D,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;QAC5D,IAAI,CAAC,MAAM,EAAE,CAAC;QAEd,kCAAkC;QAClC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;QACxB,CAAC;QACD,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;IACnB,CAAC;CACF"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@rbillon59/vinted-mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for interacting with the Vinted marketplace",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "vinted-mcp-server": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "dev": "tsc --watch",
14
+ "lint": "eslint src/",
15
+ "lint:fix": "eslint src/ --fix",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "keywords": [
19
+ "mcp",
20
+ "vinted",
21
+ "model-context-protocol",
22
+ "ai"
23
+ ],
24
+ "license": "MIT",
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "engines": {
29
+ "node": ">=20.0.0"
30
+ },
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "^1.12.1",
33
+ "puppeteer": "^24.39.1",
34
+ "puppeteer-extra": "^3.3.6",
35
+ "puppeteer-extra-plugin-stealth": "^2.11.2",
36
+ "zod": "^3.24.4"
37
+ },
38
+ "devDependencies": {
39
+ "@eslint/js": "^9.27.0",
40
+ "@types/node": "^22.15.3",
41
+ "eslint": "^9.27.0",
42
+ "typescript": "^5.8.3",
43
+ "typescript-eslint": "^8.32.1"
44
+ }
45
+ }