@supericons/mcp 0.4.10 → 0.4.11

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.11 - 2026-06-28
4
+
5
+ ### Fixed
6
+
7
+ - improved `recommend_icons` for Supericons (`si`) brand and logo slots so exact AI tool logos such as OpenAI Codex, Lovable, Kickbacks.ai, and xAI rank correctly
8
+
3
9
  ## 0.4.10 - 2026-06-27
4
10
 
5
11
  ### Added
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "generated_at": "2026-06-27T17:17:55.388Z",
3
+ "generated_at": "2026-06-28T08:12:34.465Z",
4
4
  "presets": [
5
5
  {
6
6
  "preset": "bounce",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supericons/mcp",
3
- "version": "0.4.10",
3
+ "version": "0.4.11",
4
4
  "mcpName": "io.github.curlymolelabs/supericons",
5
5
  "description": "MCP server for Supericons: multilingual semantic SVG icon search and recommendations for AI coding agents.",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
- "generatedAt": "2026-06-27T16:34:16.739Z",
3
- "freeIconCount": 21367,
2
+ "generatedAt": "2026-06-28T06:24:29.474Z",
3
+ "freeIconCount": 21371,
4
4
  "freeLibraryCount": 11,
5
5
  "premiumCollectionCount": 8,
6
6
  "premiumIconCount": 400,
@@ -29,9 +29,9 @@ const multilingualExpansionTerms = [...cjkSearchTerms, ...multilingualSearchAlia
29
29
  const SLOT_SEARCH_CONCURRENCY = 2;
30
30
  const SLOT_QUERY_CONCURRENCY = 1;
31
31
 
32
- const GENERIC_SLOT_WORDS = new Set([
33
- 'action',
34
- 'button',
32
+ const GENERIC_SLOT_WORDS = new Set([
33
+ 'action',
34
+ 'button',
35
35
  'buttons',
36
36
  'control',
37
37
  'controls',
@@ -45,10 +45,33 @@ const GENERIC_SLOT_WORDS = new Set([
45
45
  'tab',
46
46
  'tabs',
47
47
  'ui',
48
- 'view',
49
- ]);
50
-
51
- const VARIANT_PENALTIES = Object.freeze([
48
+ 'view',
49
+ ]);
50
+
51
+ const BRAND_LOGO_WORDS = new Set([
52
+ 'brand',
53
+ 'brands',
54
+ 'logo',
55
+ 'logos',
56
+ 'mark',
57
+ 'wordmark',
58
+ ]);
59
+
60
+ const BRAND_LOGO_GENERIC_WORDS = new Set([
61
+ ...GENERIC_SLOT_WORDS,
62
+ ...BRAND_LOGO_WORDS,
63
+ 'app',
64
+ 'application',
65
+ 'company',
66
+ 'hero',
67
+ 'page',
68
+ 'product',
69
+ 'site',
70
+ 'title',
71
+ 'website',
72
+ ]);
73
+
74
+ const VARIANT_PENALTIES = Object.freeze([
52
75
  { token: 'circle', pattern: /circle/i, penalty: 12 },
53
76
  { token: 'square', pattern: /square/i, penalty: 12 },
54
77
  { token: 'dash', pattern: /dash/i, penalty: 5 },
@@ -966,20 +989,55 @@ function normalizeToken(token) {
966
989
  return value;
967
990
  }
968
991
 
969
- function tokenizeText(value) {
970
- const normalized = normalizeText(value);
971
- if (!normalized) return [];
972
- const tokens = normalized.split(' ');
973
- return dedupe([...tokens, ...tokens.map(normalizeToken)]);
974
- }
975
-
976
- function dedupe(values) {
977
- return [...new Set(values.filter(Boolean))];
978
- }
979
-
980
- function buildDirectLocalizedIntentTerms(value) {
981
- const text = String(value || '');
982
- return DIRECT_LOCALIZED_INTENT_RULES
992
+ function tokenizeText(value) {
993
+ const normalized = normalizeText(value);
994
+ if (!normalized) return [];
995
+ const tokens = normalized.split(' ');
996
+ return dedupe([...tokens, ...tokens.map(normalizeToken)]);
997
+ }
998
+
999
+ function dedupe(values) {
1000
+ return [...new Set(values.filter(Boolean))];
1001
+ }
1002
+
1003
+ function stripBrandLogoWords(value) {
1004
+ return tokenizeText(value)
1005
+ .filter((token) => !BRAND_LOGO_WORDS.has(token))
1006
+ .join(' ');
1007
+ }
1008
+
1009
+ function hasSpecificBrandTerms(value) {
1010
+ return tokenizeText(stripBrandLogoWords(value))
1011
+ .some((token) => !BRAND_LOGO_GENERIC_WORDS.has(token));
1012
+ }
1013
+
1014
+ function isBrandLogoRecommendation(task, slot, library) {
1015
+ if (library === 'si') return true;
1016
+ const text = normalizeText(`${slot} ${task}`);
1017
+ const namesLogoOrBrand = /\b(logos?|brands?|wordmark|mark)\b/.test(text);
1018
+ return namesLogoOrBrand && hasSpecificBrandTerms(slot);
1019
+ }
1020
+
1021
+ function buildBrandLogoQueryVariants(task, slot, library) {
1022
+ if (!isBrandLogoRecommendation(task, slot, library)) return [];
1023
+
1024
+ const rawSlot = String(slot || '').trim();
1025
+ const normalizedSlot = normalizeText(slot);
1026
+ const strippedSlot = stripBrandLogoWords(slot);
1027
+ const usefulTokens = tokenizeText(strippedSlot)
1028
+ .filter((token) => !BRAND_LOGO_GENERIC_WORDS.has(token));
1029
+
1030
+ return dedupe([
1031
+ rawSlot,
1032
+ normalizedSlot,
1033
+ strippedSlot,
1034
+ ...usefulTokens,
1035
+ ]);
1036
+ }
1037
+
1038
+ function buildDirectLocalizedIntentTerms(value) {
1039
+ const text = String(value || '');
1040
+ return DIRECT_LOCALIZED_INTENT_RULES
983
1041
  .filter((rule) => rule.pattern.test(text))
984
1042
  .flatMap((rule) => rule.terms);
985
1043
  }
@@ -1108,7 +1166,7 @@ function buildSlotQueryVariants(task, slot, locale = null) {
1108
1166
  return dedupe(variants).slice(0, 12);
1109
1167
  }
1110
1168
 
1111
- function scoreLexicalFit(icon, intentTerms, slotLabel, taskLabel = '') {
1169
+ function scoreLexicalFit(icon, intentTerms, slotLabel, taskLabel = '') {
1112
1170
  const tokens = new Set([
1113
1171
  ...tokenizeText(icon.id),
1114
1172
  ...tokenizeText(icon.name),
@@ -1147,10 +1205,77 @@ function scoreLexicalFit(icon, intentTerms, slotLabel, taskLabel = '') {
1147
1205
  score += 26;
1148
1206
  }
1149
1207
 
1150
- return score;
1151
- }
1152
-
1153
- function getVariantPenalty(icon, intentTerms = []) {
1208
+ return score;
1209
+ }
1210
+
1211
+ function collectBrandCandidateTexts(icon, semanticRecord) {
1212
+ const semanticValues = semanticRecord ? [
1213
+ semanticRecord.label,
1214
+ semanticRecord.source_name,
1215
+ semanticRecord.slug,
1216
+ semanticRecord.name,
1217
+ semanticRecord.meaning,
1218
+ semanticRecord.purpose,
1219
+ ...(semanticRecord.aliases || []),
1220
+ ...(semanticRecord.synonyms || []),
1221
+ ...(semanticRecord.search_terms || []),
1222
+ ...(semanticRecord.semantic_tags || []),
1223
+ ] : [];
1224
+
1225
+ return dedupe([
1226
+ icon.id,
1227
+ icon.name,
1228
+ `${icon.lib}:${icon.id}`,
1229
+ ...(icon.aliases || []),
1230
+ ...(icon.synonyms || []),
1231
+ ...(icon.searchTerms || []),
1232
+ ...(icon.semanticTags || []),
1233
+ ...(icon.semantic?.aliases || []),
1234
+ ...(icon.semantic?.synonyms || []),
1235
+ ...(icon.semantic?.search_terms || []),
1236
+ ...(icon.semantic?.semantic_tags || []),
1237
+ ...semanticValues,
1238
+ ].map((value) => normalizeText(value)).filter(Boolean));
1239
+ }
1240
+
1241
+ function getBrandLogoMatchBonus(icon, semanticRecord, slotLabel, taskLabel, library) {
1242
+ if (!isBrandLogoRecommendation(taskLabel, slotLabel, library)) return 0;
1243
+
1244
+ const slotCandidates = buildBrandLogoQueryVariants(taskLabel, slotLabel, library)
1245
+ .map((value) => normalizeText(value))
1246
+ .filter((value) => value.length >= 2);
1247
+ const iconCandidates = collectBrandCandidateTexts(icon, semanticRecord);
1248
+ const iconTokens = new Set(iconCandidates.flatMap(tokenizeText));
1249
+ const meaningfulSlotTokens = dedupe(
1250
+ slotCandidates
1251
+ .flatMap(tokenizeText)
1252
+ .filter((token) => !BRAND_LOGO_GENERIC_WORDS.has(token))
1253
+ );
1254
+
1255
+ let best = 0;
1256
+ for (const slotCandidate of slotCandidates) {
1257
+ for (const iconCandidate of iconCandidates) {
1258
+ if (slotCandidate === iconCandidate) {
1259
+ best = Math.max(best, 360);
1260
+ } else if (slotCandidate.length >= 3 && iconCandidate.includes(slotCandidate)) {
1261
+ best = Math.max(best, 260);
1262
+ } else if (iconCandidate.length >= 3 && slotCandidate.includes(iconCandidate)) {
1263
+ best = Math.max(best, 180);
1264
+ }
1265
+ }
1266
+ }
1267
+
1268
+ const overlapCount = meaningfulSlotTokens.filter((token) => iconTokens.has(token)).length;
1269
+ if (meaningfulSlotTokens.length > 0 && overlapCount === meaningfulSlotTokens.length) {
1270
+ best = Math.max(best, 220 + overlapCount * 20);
1271
+ } else {
1272
+ best = Math.max(best, overlapCount * 30);
1273
+ }
1274
+
1275
+ return best;
1276
+ }
1277
+
1278
+ function getVariantPenalty(icon, intentTerms = []) {
1154
1279
  const normalizedId = normalizeText(icon.id);
1155
1280
  const requestedTerms = buildRequestedTermSet(intentTerms);
1156
1281
  let penalty = 0;
@@ -1327,7 +1452,10 @@ export async function recommendIconsForTask({
1327
1452
  ...buildLocalizedVariants(slotLabel, locale).flatMap(tokenizeText),
1328
1453
  ...buildDirectLocalizedIntentTerms(slotLabel),
1329
1454
  ]);
1330
- const queryVariants = buildSlotQueryVariants(task, slotLabel, locale).slice(0, locale ? 8 : 4);
1455
+ const queryVariants = dedupe([
1456
+ ...buildBrandLogoQueryVariants(task, slotLabel, library),
1457
+ ...buildSlotQueryVariants(task, slotLabel, locale),
1458
+ ]).slice(0, locale ? 8 : 4);
1331
1459
  const pooledIcons = [];
1332
1460
  const seen = new Set();
1333
1461
 
@@ -1358,9 +1486,10 @@ export async function recommendIconsForTask({
1358
1486
  .map((icon, index) => {
1359
1487
  const semanticRecord = getSemanticRecordForIcon(semanticMap, icon);
1360
1488
  const semanticQuery = queryVariants.join(' ');
1361
- const semanticScore = semanticRecord ? scoreSemanticAlignment(semanticQuery, semanticRecord) * 3 : 0;
1362
- const lexicalScore = scoreLexicalFit(icon, intentTerms, slotLabel, task);
1363
- const semanticBonus = semanticRecord ? 6 : 0;
1489
+ const semanticScore = semanticRecord ? scoreSemanticAlignment(semanticQuery, semanticRecord) * 3 : 0;
1490
+ const lexicalScore = scoreLexicalFit(icon, intentTerms, slotLabel, task);
1491
+ const brandLogoMatchBonus = getBrandLogoMatchBonus(icon, semanticRecord, slotLabel, task, library);
1492
+ const semanticBonus = semanticRecord ? 6 : 0;
1364
1493
  const variantPenalty = getVariantPenalty(icon, requestedVariantTerms);
1365
1494
  const brandPenalty = getBrandPenalty(icon, requestedVariantTerms);
1366
1495
  const slotPreferenceBonus = getSlotPreferenceBonus(icon, slotLabel, intentTerms, library, requestedVariantTerms);
@@ -1375,9 +1504,10 @@ export async function recommendIconsForTask({
1375
1504
  brandPenalty,
1376
1505
  slotPreferenceBonus,
1377
1506
  totalScore:
1378
- semanticScore +
1379
- lexicalScore +
1380
- semanticBonus +
1507
+ semanticScore +
1508
+ lexicalScore +
1509
+ brandLogoMatchBonus +
1510
+ semanticBonus +
1381
1511
  slotPreferenceBonus +
1382
1512
  intentAdjustment.boost -
1383
1513
  intentAdjustment.penalty -
@@ -317,8 +317,8 @@ export const SUPERICONS_AI_ICON_TAXONOMY = Object.freeze({
317
317
  ),
318
318
  'si:x-ai': taxonomy(
319
319
  'model-platforms-ai-labs',
320
- ['foundation-model', 'grok', 'ai-lab'],
321
- ['model-provider', 'llm', 'xai']
320
+ ['foundation-model', 'grok', 'grok-imagine', 'ai-lab'],
321
+ ['model-provider', 'llm', 'image-generation', 'video-generation', 'xai']
322
322
  ),
323
323
  'si:xiaomi-mimo': taxonomy(
324
324
  'model-platforms-ai-labs',
package/server.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "url": "https://github.com/curlymolelabs/supericons",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.4.10",
10
+ "version": "0.4.11",
11
11
  "remotes": [
12
12
  {
13
13
  "type": "streamable-http",
@@ -18,7 +18,7 @@
18
18
  {
19
19
  "registryType": "npm",
20
20
  "identifier": "@supericons/mcp",
21
- "version": "0.4.10",
21
+ "version": "0.4.11",
22
22
  "transport": {
23
23
  "type": "stdio"
24
24
  }