shutterstock-search-mcp 1.0.0 → 1.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 (2) hide show
  1. package/index.mjs +50 -14
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
- // MCP mínimo de busca no Shutterstock: devolve o link da página de cada imagem.
2
+ // MCP de busca no Shutterstock: devolve o link da página de cada imagem, priorizando fotos
3
+ // com cara de feed de rede social (lifestyle, luz natural, emoção real) e descartando
4
+ // fundo branco recortado, 3D, ilustração e cenas tristes ou assustadoras.
3
5
  // Autenticação: Basic com SHUTTERSTOCK_CONSUMER_KEY e SHUTTERSTOCK_CONSUMER_SECRET (só busca, não licencia).
4
6
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -9,27 +11,54 @@ const KEY = process.env.SHUTTERSTOCK_CONSUMER_KEY;
9
11
  const SECRET = process.env.SHUTTERSTOCK_CONSUMER_SECRET;
10
12
  const API = process.env.SHUTTERSTOCK_API_URL || "https://api.shutterstock.com/v2/images/search";
11
13
 
12
- const server = new McpServer({ name: "shutterstock-search", version: "1.0.0" });
14
+ // Estilo "feed": termos que indicam foto bonita, natural e alegre
15
+ const POSITIVOS = [
16
+ "happy", "smiling", "smile", "laughing", "joy", "joyful", "fun", "playing", "play", "cheerful",
17
+ "candid", "lifestyle", "authentic", "real", "natural light", "sunlight", "sunny", "sun", "summer",
18
+ "golden hour", "sunset", "backlight", "bokeh", "warm", "cozy", "tender", "love", "loving", "hug",
19
+ "cuddle", "together", "family", "garden", "outdoor", "park", "home", "beach", "sprinkler", "splash",
20
+ ];
21
+ // Termos que indicam foto artificial, de estúdio ou com clima negativo
22
+ const NEGATIVOS = [
23
+ "isolated", "white background", "cut out", "cutout", "3d", "render", "illustration", "vector",
24
+ "cartoon", "mockup", "template", "concept", "studio shot", "crying", "cry", "sad", "sick", "ill",
25
+ "fever", "disease", "virus", "injection", "syringe", "needle", "hospital", "doctor", "scared",
26
+ "fear", "angry", "tired", "exhausted", "stress", "dirty", "dark", "danger",
27
+ ];
28
+ const EXCLUSOES = ["isolated", "illustration", "vector", "3d", "crying", "sick"];
29
+
30
+ function pontuar(texto) {
31
+ const t = ` ${texto.toLowerCase()} `;
32
+ let p = 0;
33
+ for (const w of POSITIVOS) if (t.includes(` ${w}`) || t.includes(`${w} `)) p += 1;
34
+ for (const w of NEGATIVOS) if (t.includes(` ${w}`) || t.includes(`${w} `)) p -= 4;
35
+ return p;
36
+ }
37
+
38
+ const server = new McpServer({ name: "shutterstock-search", version: "1.1.0" });
13
39
 
14
40
  server.registerTool(
15
41
  "buscar_imagem",
16
42
  {
17
43
  title: "Buscar imagem no Shutterstock",
18
44
  description:
19
- "Busca fotos no Shutterstock e devolve, para cada resultado, o link da página da imagem (url), a descrição e o id. " +
20
- "Use palavras-chave em inglês (3 a 5) descrevendo a cena. Não licencia nem baixa imagens.",
45
+ "Busca fotos no Shutterstock com estilo de feed de rede social (lifestyle, luz natural, emoção real) e devolve, " +
46
+ "do melhor para o pior, o link da página da imagem (url), a descrição e uma nota de estilo. Já exclui fundo branco, " +
47
+ "3D, ilustração e cenas tristes ou assustadoras. Use palavras-chave em inglês (3 a 6) descrevendo a cena. " +
48
+ "Não licencia nem baixa imagens.",
21
49
  inputSchema: {
22
- query: z.string().min(1).describe("Palavras-chave em inglês, ex.: 'baby summer heat water splash'"),
23
- quantidade: z.number().int().min(1).max(10).default(3).describe("Quantos resultados devolver (1 a 10)"),
24
- orientacao: z.enum(["vertical", "horizontal", "qualquer"]).default("vertical"),
50
+ query: z.string().min(1).describe("Cena em inglês, ex.: 'toddler playing garden sprinkler summer'"),
51
+ quantidade: z.number().int().min(1).max(10).default(5).describe("Quantos resultados devolver, já ordenados (1 a 10)"),
52
+ orientacao: z.enum(["vertical", "horizontal", "qualquer"]).default("qualquer"),
25
53
  },
26
54
  },
27
55
  async ({ query, quantidade, orientacao }) => {
28
56
  if (!KEY || !SECRET) {
29
57
  return { isError: true, content: [{ type: "text", text: "ERRO: SHUTTERSTOCK_CONSUMER_KEY/SECRET não configurados no MCP." }] };
30
58
  }
59
+ const q = [query, ...EXCLUSOES.filter((w) => !query.toLowerCase().includes(`not ${w}`)).map((w) => `NOT ${w}`)].join(" ");
31
60
  const params = new URLSearchParams({
32
- query, per_page: String(quantidade), sort: "relevance", image_type: "photo", safe: "true", view: "minimal",
61
+ query: q, per_page: "30", sort: "relevance", image_type: "photo", safe: "true", view: "full",
33
62
  });
34
63
  if (orientacao !== "qualquer") params.set("orientation", orientacao);
35
64
  const auth = Buffer.from(`${KEY}:${SECRET}`).toString("base64");
@@ -40,13 +69,20 @@ server.registerTool(
40
69
  return { isError: true, content: [{ type: "text", text: `ERRO HTTP ${res.status}: ${body}` }] };
41
70
  }
42
71
  const data = await res.json();
43
- const resultados = (data.data || []).map((it) => ({
44
- url: it.url || `https://www.shutterstock.com/image-photo/${it.id}`,
45
- descricao: it.description || "",
46
- id: String(it.id),
47
- }));
72
+ const itens = (data.data || []).map((it, pos) => {
73
+ const desc = it.description || "";
74
+ const kws = Array.isArray(it.keywords) ? it.keywords.join(" ") : "";
75
+ const nota = pontuar(desc) * 2 + pontuar(kws) + (it.has_model_release ? 1 : 0) - pos * 0.15;
76
+ return {
77
+ url: it.url || `https://www.shutterstock.com/image-photo/${it.id}`,
78
+ descricao: desc,
79
+ id: String(it.id),
80
+ nota_estilo: Math.round(nota * 10) / 10,
81
+ };
82
+ });
83
+ const resultados = itens.sort((a, b) => b.nota_estilo - a.nota_estilo).slice(0, quantidade);
48
84
  return {
49
- content: [{ type: "text", text: JSON.stringify({ query, total: data.total_count ?? resultados.length, resultados }, null, 1) }],
85
+ content: [{ type: "text", text: JSON.stringify({ query: q, total: data.total_count ?? itens.length, resultados }, null, 1) }],
50
86
  };
51
87
  } catch (e) {
52
88
  return { isError: true, content: [{ type: "text", text: `ERRO de conexão: ${e.message}` }] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shutterstock-search-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "MCP mínimo de busca no Shutterstock que devolve o link da página de cada imagem",
5
5
  "type": "module",
6
6
  "bin": { "shutterstock-search-mcp": "index.mjs" },