@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 +6 -0
- package/generated/motion-lab-baseline.json +1 -1
- package/package.json +1 -1
- package/public/product-facts.json +2 -2
- package/recommend-icons.js +163 -33
- package/runtime/supericons-ai-taxonomy.js +2 -2
- package/server.json +2 -2
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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supericons/mcp",
|
|
3
|
-
"version": "0.4.
|
|
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",
|
package/recommend-icons.js
CHANGED
|
@@ -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
|
|
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
|
|
981
|
-
|
|
982
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
+
"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.
|
|
21
|
+
"version": "0.4.11",
|
|
22
22
|
"transport": {
|
|
23
23
|
"type": "stdio"
|
|
24
24
|
}
|