@supericons/mcp
Advanced tools
| function normalizePositiveInteger(value, fallback, { min = 1, max = Number.MAX_SAFE_INTEGER } = {}) { | ||
| const parsed = Number(value); | ||
| if (!Number.isFinite(parsed)) return fallback; | ||
| return Math.min(max, Math.max(min, Math.trunc(parsed))); | ||
| } | ||
| function failureCode(error) { | ||
| const value = String(error?.code || error?.name || 'unknown_error').trim().toLowerCase(); | ||
| return value.replace(/[^a-z0-9_:-]+/g, '_').slice(0, 80) || 'unknown_error'; | ||
| } | ||
| export async function deliverSupabaseUsageEvent({ | ||
| endpoint, | ||
| serviceRoleKey, | ||
| payload, | ||
| fetchImpl = fetch, | ||
| timeoutMs = 2500, | ||
| } = {}) { | ||
| try { | ||
| const response = await fetchImpl(endpoint, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| apikey: serviceRoleKey, | ||
| Authorization: `Bearer ${serviceRoleKey}`, | ||
| Prefer: 'return=minimal', | ||
| }, | ||
| body: JSON.stringify(payload), | ||
| signal: AbortSignal.timeout(timeoutMs), | ||
| }); | ||
| if (response.ok || response.status === 409) return; | ||
| const error = new Error(`usage_write_http_${response.status}`); | ||
| error.code = `http_${response.status}`; | ||
| error.retryable = response.status === 408 | ||
| || response.status === 425 | ||
| || response.status === 429 | ||
| || response.status >= 500; | ||
| throw error; | ||
| } catch (error) { | ||
| if (typeof error?.retryable !== 'boolean') error.retryable = true; | ||
| throw error; | ||
| } | ||
| } | ||
| export function createUsageEventDelivery({ | ||
| deliver, | ||
| concurrency = 4, | ||
| maxItems = 500, | ||
| retryDelaysMs = [250, 1000], | ||
| logger = console, | ||
| setTimer = setTimeout, | ||
| clearTimer = clearTimeout, | ||
| now = Date.now, | ||
| } = {}) { | ||
| if (typeof deliver !== 'function') throw new TypeError('deliver must be a function'); | ||
| const workerLimit = normalizePositiveInteger(concurrency, 4, { max: 32 }); | ||
| const itemLimit = normalizePositiveInteger(maxItems, 500, { max: 10_000 }); | ||
| const delays = Array.isArray(retryDelaysMs) | ||
| ? retryDelaysMs.map((value) => normalizePositiveInteger(value, 250, { min: 0, max: 30_000 })) | ||
| : [250, 1000]; | ||
| const queue = []; | ||
| const retryTimers = new Map(); | ||
| const idleWaiters = new Set(); | ||
| let inFlight = 0; | ||
| let outstanding = 0; | ||
| let scheduled = false; | ||
| const counters = { | ||
| enqueued: 0, | ||
| delivered: 0, | ||
| retried: 0, | ||
| dropped: 0, | ||
| failed: 0, | ||
| }; | ||
| let lastDeliveryAt = null; | ||
| let lastFailureAt = null; | ||
| let lastFailureCode = null; | ||
| function isIdle() { | ||
| return outstanding === 0 && inFlight === 0 && queue.length === 0 && retryTimers.size === 0; | ||
| } | ||
| function notifyIdle() { | ||
| if (!isIdle()) return; | ||
| for (const resolve of idleWaiters) resolve(true); | ||
| idleWaiters.clear(); | ||
| } | ||
| function recordFinalFailure(error) { | ||
| counters.failed += 1; | ||
| lastFailureAt = new Date(now()).toISOString(); | ||
| lastFailureCode = failureCode(error); | ||
| logger.warn?.(`[Supericons MCP] usage delivery failed: ${lastFailureCode}`); | ||
| } | ||
| function schedulePump() { | ||
| if (scheduled) return; | ||
| scheduled = true; | ||
| queueMicrotask(() => { | ||
| scheduled = false; | ||
| pump(); | ||
| }); | ||
| } | ||
| function finishItem() { | ||
| outstanding = Math.max(0, outstanding - 1); | ||
| schedulePump(); | ||
| notifyIdle(); | ||
| } | ||
| function scheduleRetry(item, delayMs) { | ||
| counters.retried += 1; | ||
| const timer = setTimer(() => { | ||
| retryTimers.delete(timer); | ||
| queue.push(item); | ||
| schedulePump(); | ||
| }, delayMs); | ||
| retryTimers.set(timer, item); | ||
| } | ||
| function runItem(item) { | ||
| inFlight += 1; | ||
| Promise.resolve() | ||
| .then(() => deliver(item.payload)) | ||
| .then(() => { | ||
| counters.delivered += 1; | ||
| lastDeliveryAt = new Date(now()).toISOString(); | ||
| finishItem(); | ||
| }) | ||
| .catch((error) => { | ||
| const retryDelay = delays[item.attempt]; | ||
| if (error?.retryable === true && retryDelay !== undefined) { | ||
| scheduleRetry({ ...item, attempt: item.attempt + 1 }, retryDelay); | ||
| } else { | ||
| recordFinalFailure(error); | ||
| finishItem(); | ||
| } | ||
| }) | ||
| .finally(() => { | ||
| inFlight = Math.max(0, inFlight - 1); | ||
| schedulePump(); | ||
| notifyIdle(); | ||
| }); | ||
| } | ||
| function pump() { | ||
| while (inFlight < workerLimit && queue.length > 0) { | ||
| runItem(queue.shift()); | ||
| } | ||
| notifyIdle(); | ||
| } | ||
| function enqueue(payload) { | ||
| if (outstanding >= itemLimit) { | ||
| counters.dropped += 1; | ||
| lastFailureAt = new Date(now()).toISOString(); | ||
| lastFailureCode = 'queue_full'; | ||
| logger.warn?.('[Supericons MCP] usage delivery queue full'); | ||
| return false; | ||
| } | ||
| outstanding += 1; | ||
| counters.enqueued += 1; | ||
| queue.push({ payload, attempt: 0 }); | ||
| schedulePump(); | ||
| return true; | ||
| } | ||
| function getStatus() { | ||
| return { | ||
| status: counters.dropped > 0 || counters.failed > 0 ? 'degraded' : 'ready', | ||
| queue_depth: queue.length, | ||
| in_flight: inFlight, | ||
| pending_retries: retryTimers.size, | ||
| outstanding, | ||
| concurrency: workerLimit, | ||
| max_items: itemLimit, | ||
| max_attempts: delays.length + 1, | ||
| enqueued_total: counters.enqueued, | ||
| delivered_total: counters.delivered, | ||
| retried_total: counters.retried, | ||
| dropped_total: counters.dropped, | ||
| failed_total: counters.failed, | ||
| last_delivery_at: lastDeliveryAt, | ||
| last_failure_at: lastFailureAt, | ||
| last_failure_code: lastFailureCode, | ||
| }; | ||
| } | ||
| function waitForIdle({ timeoutMs = 5000 } = {}) { | ||
| if (isIdle()) return Promise.resolve(true); | ||
| return new Promise((resolve) => { | ||
| let timer = null; | ||
| const done = (value) => { | ||
| if (timer) clearTimer(timer); | ||
| idleWaiters.delete(onIdle); | ||
| resolve(value); | ||
| }; | ||
| const onIdle = () => done(true); | ||
| idleWaiters.add(onIdle); | ||
| timer = setTimer(() => done(false), normalizePositiveInteger(timeoutMs, 5000, { max: 60_000 })); | ||
| }); | ||
| } | ||
| function drain({ timeoutMs = 5000, expediteRetries = true } = {}) { | ||
| if (expediteRetries && retryTimers.size > 0) { | ||
| for (const [timer, item] of retryTimers.entries()) { | ||
| clearTimer(timer); | ||
| retryTimers.delete(timer); | ||
| queue.push(item); | ||
| } | ||
| schedulePump(); | ||
| } | ||
| return waitForIdle({ timeoutMs }); | ||
| } | ||
| return { | ||
| enqueue, | ||
| getStatus, | ||
| waitForIdle, | ||
| drain, | ||
| }; | ||
| } |
+10
-0
| # Changelog | ||
| ## 0.4.33 - 2026-08-12 | ||
| ### Added | ||
| - Added five reviewed anime icons for Akatsuki, Dragon Ball, Konoha, One Piece, and Sharingan searches. | ||
| - Refreshed the Supericons catalog, registry records, and agent-facing taxonomy. | ||
| ### Verified | ||
| - Preserved the existing MCP tool schema, hosted-primary routing, strict library behavior, and established search behavior. | ||
| - Aligned website, hosted catalog, Railway runtime, and local npm package inputs in one release bundle. | ||
| ## 0.4.32 - 2026-08-11 | ||
@@ -5,0 +15,0 @@ |
@@ -184,6 +184,8 @@ import { appendFileSync, existsSync, readFileSync } from 'node:fs'; | ||
| async function postSearchRequest(url, headers, body, { | ||
| export async function postSearchRequest(url, headers, body, { | ||
| failureLabel, | ||
| failureCode, | ||
| resilience = hostedSearchResilience, | ||
| fetchImpl = fetch, | ||
| requestTimeoutMs = HOSTED_SEARCH_REQUEST_TIMEOUT_MS, | ||
| }) { | ||
@@ -193,7 +195,7 @@ return resilience.execute(async () => { | ||
| try { | ||
| response = await fetch(url, { | ||
| response = await fetchImpl(url, { | ||
| method: 'POST', | ||
| headers, | ||
| body: JSON.stringify(body), | ||
| signal: AbortSignal.timeout(HOSTED_SEARCH_REQUEST_TIMEOUT_MS), | ||
| signal: AbortSignal.timeout(requestTimeoutMs), | ||
| }); | ||
@@ -206,2 +208,4 @@ } catch (cause) { | ||
| error.hosted_search_dependency_failure = true; | ||
| error.failure_stage = 'dependency_connect'; | ||
| error.upstream_status = null; | ||
| throw error; | ||
@@ -219,2 +223,4 @@ } | ||
| error.hosted_search_dependency_failure = true; | ||
| error.failure_stage = 'dependency_decode'; | ||
| error.upstream_status = response.status; | ||
| throw error; | ||
@@ -230,2 +236,4 @@ } | ||
| error.hosted_search_dependency_failure = response.status >= 500; | ||
| error.failure_stage = 'dependency_response'; | ||
| error.upstream_status = response.status; | ||
| const retryAfter = Number(response.headers.get('retry-after')); | ||
@@ -232,0 +240,0 @@ if (Number.isFinite(retryAfter) && retryAfter > 0) { |
+2
-1
| { | ||
| "name": "@supericons/mcp", | ||
| "version": "0.4.32", | ||
| "version": "0.4.33", | ||
| "mcpName": "io.github.curlymolelabs/supericons", | ||
@@ -82,2 +82,3 @@ "description": "MCP server for Supericons: multilingual semantic SVG icon search and recommendations for AI coding agents.", | ||
| "usage-event-detail.js", | ||
| "usage-event-delivery.js", | ||
| "usage-dedupe.js", | ||
@@ -84,0 +85,0 @@ "usage-attribution.js", |
| { | ||
| "generatedAt": "2026-08-11T09:33:58.443Z", | ||
| "freeIconCount": 21542, | ||
| "generatedAt": "2026-08-11T16:58:37.250Z", | ||
| "freeIconCount": 21547, | ||
| "freeLibraryCount": 11, | ||
@@ -9,3 +9,3 @@ "premiumCollectionCount": 9, | ||
| "mcpFreeToolCount": 5, | ||
| "mcpPackageVersion": "0.4.32", | ||
| "mcpPackageVersion": "0.4.33", | ||
| "display": { | ||
@@ -12,0 +12,0 @@ "freeIconsRounded": "20,000+", |
@@ -187,3 +187,3 @@ const DEFAULT_LOCAL_FIRST_VALUE = 'on'; | ||
| }); | ||
| return prioritizeExactIconMatches(params.query, merged, {}, reranked, { | ||
| const prioritized = prioritizeExactIconMatches(params.query, merged, {}, reranked, { | ||
| library: params.library, | ||
@@ -194,2 +194,3 @@ libraryMode: params.libraryMode, | ||
| }); | ||
| return prioritized.slice(0, limit); | ||
| } | ||
@@ -196,0 +197,0 @@ |
@@ -17,2 +17,18 @@ // Generated by scripts/build-search-ranking-policy.mjs | ||
| ], | ||
| "brand_identity_wrapper_terms": [ | ||
| "logo", | ||
| "logos", | ||
| "icon", | ||
| "icons", | ||
| "brand", | ||
| "brands", | ||
| "wordmark", | ||
| "mark", | ||
| "marks", | ||
| "symbol", | ||
| "symbols" | ||
| ], | ||
| "brand_identity_noise_terms": [ | ||
| "official" | ||
| ], | ||
| "candidate_strength_policy": { | ||
@@ -19,0 +35,0 @@ "expressive_fallback_tags": [ |
@@ -9,2 +9,3 @@ /** | ||
| import { | ||
| getBrandIdentityQueryVariants, | ||
| getBrandRankAdjustment, | ||
@@ -364,26 +365,28 @@ getSearchInterpretationPlan, | ||
| function getExactBrandIdentityResults(query, icons, synonyms, options = {}) { | ||
| const identityWords = getMeaningfulQueryWords(tokenizeSemanticText(query)); | ||
| const identityQuery = identityWords.join(' '); | ||
| if (!identityQuery) return []; | ||
| for (const identityQuery of getBrandIdentityQueryVariants(query)) { | ||
| if (!identityQuery) continue; | ||
| const identityResults = searchIconsForSingleQuery(identityQuery, icons, synonyms, { | ||
| ...options, | ||
| limit: Math.max(Number(options.limit || 20) * 2, 20), | ||
| applyExpressiveFallback: false, | ||
| candidatePool: getIndexedCandidatePool(icons, identityQuery, synonyms), | ||
| }); | ||
| const identityResults = searchIconsForSingleQuery(identityQuery, icons, synonyms, { | ||
| ...options, | ||
| limit: Math.max(Number(options.limit || 20) * 2, 20), | ||
| applyExpressiveFallback: false, | ||
| candidatePool: getIndexedCandidatePool(icons, identityQuery, synonyms), | ||
| }); | ||
| const exactResults = identityResults.filter((icon) => { | ||
| const directScore = getDirectSearchScore( | ||
| icon, | ||
| normalizeSemanticText(identityQuery), | ||
| tokenizeSemanticText(identityQuery), | ||
| ); | ||
| const brandAdjustment = getBrandRankAdjustment(identityQuery, icon); | ||
| return ( | ||
| directScore >= 300 && | ||
| isLikelyBrandIdentityIcon(icon) && | ||
| brandAdjustment.penalty === 0 | ||
| ); | ||
| }); | ||
| if (exactResults.length > 0) return exactResults; | ||
| } | ||
| return identityResults.filter((icon) => { | ||
| const directScore = getDirectSearchScore( | ||
| icon, | ||
| normalizeSemanticText(identityQuery), | ||
| tokenizeSemanticText(identityQuery), | ||
| ); | ||
| const brandAdjustment = getBrandRankAdjustment(query, icon); | ||
| return ( | ||
| directScore >= 300 && | ||
| isLikelyBrandIdentityIcon(icon) && | ||
| brandAdjustment.penalty === 0 | ||
| ); | ||
| }); | ||
| return []; | ||
| } | ||
@@ -390,0 +393,0 @@ |
@@ -6,2 +6,4 @@ import { GENERATED_SEARCH_RANKING_POLICY } from './generated-search-ranking-policy.js'; | ||
| const brandIntentTerms = new Set(policy.brand_intent_terms || []); | ||
| const brandIdentityWrapperTerms = new Set(policy.brand_identity_wrapper_terms || []); | ||
| const brandIdentityNoiseTerms = new Set(policy.brand_identity_noise_terms || []); | ||
| const expressiveFallbackTags = new Set( | ||
@@ -176,3 +178,3 @@ (policy.candidate_strength_policy?.expressive_fallback_tags || []).map(normalizeSearchRankingText), | ||
| function getCandidateText(candidate = {}) { | ||
| function getCandidateText(candidate = {}, options = {}) { | ||
| return normalizeSearchRankingText( | ||
@@ -186,3 +188,3 @@ [ | ||
| candidate.meaning, | ||
| candidate.query_variant, | ||
| options.includeQueryVariant !== false ? candidate.query_variant : null, | ||
| ...(candidate.semanticTags || []), | ||
@@ -236,2 +238,14 @@ ...(candidate.synonyms || []), | ||
| export function getBrandIdentityQueryVariants(query) { | ||
| const tokens = tokenize(query); | ||
| const exactCandidateTokens = tokens.filter((token) => !brandIdentityWrapperTerms.has(token)); | ||
| const fallbackCandidateTokens = exactCandidateTokens.filter( | ||
| (token) => !brandIdentityNoiseTerms.has(token), | ||
| ); | ||
| return unique([ | ||
| exactCandidateTokens.join(' '), | ||
| fallbackCandidateTokens.join(' '), | ||
| ]); | ||
| } | ||
| function hasBrandIntent(query) { | ||
@@ -427,3 +441,7 @@ return tokenize(query).some((token) => brandIntentTerms.has(token)); | ||
| if (libraryMode === 'prefer' && requestedLibrary && scored.length > 1 && scored[0].policyScore <= 0) { | ||
| if (libraryMode === 'prefer' && requestedLibrary && scored.length > 1) { | ||
| const shouldPreserveExistingPreferenceFlow = scored[0].policyScore <= 0; | ||
| const leadingFamilyIds = new Set( | ||
| getCandidateInterpretationFamilyIds(query, scored[0].candidate, { includeQueryVariant: false }), | ||
| ); | ||
| const preferredIndex = scored.findIndex((entry) => { | ||
@@ -433,3 +451,9 @@ const library = String( | ||
| ).toLowerCase(); | ||
| return library === requestedLibrary; | ||
| if (library !== requestedLibrary || entry.policyScore < 0) return false; | ||
| if (entry.policyScore > 0 || shouldPreserveExistingPreferenceFlow) return true; | ||
| const candidateFamilyIds = getCandidateInterpretationFamilyIds(query, entry.candidate, { | ||
| includeQueryVariant: false, | ||
| }); | ||
| return candidateFamilyIds.some((familyId) => leadingFamilyIds.has(familyId)); | ||
| }); | ||
@@ -440,11 +464,13 @@ if (preferredIndex > 0) { | ||
| const alternativeIndex = scored.findIndex((entry, index) => { | ||
| if (index === 0) return false; | ||
| const library = String( | ||
| entry.candidate.lib || entry.candidate.library || entry.candidate.source_library || '', | ||
| ).toLowerCase(); | ||
| return library !== requestedLibrary; | ||
| }); | ||
| if (alternativeIndex > 1) { | ||
| scored.splice(1, 0, ...scored.splice(alternativeIndex, 1)); | ||
| if (shouldPreserveExistingPreferenceFlow) { | ||
| const alternativeIndex = scored.findIndex((entry, index) => { | ||
| if (index === 0) return false; | ||
| const library = String( | ||
| entry.candidate.lib || entry.candidate.library || entry.candidate.source_library || '', | ||
| ).toLowerCase(); | ||
| return library !== requestedLibrary; | ||
| }); | ||
| if (alternativeIndex > 1) { | ||
| scored.splice(1, 0, ...scored.splice(alternativeIndex, 1)); | ||
| } | ||
| } | ||
@@ -456,3 +482,3 @@ } | ||
| export function getCandidateInterpretationFamilyIds(query, candidate = {}) { | ||
| export function getCandidateInterpretationFamilyIds(query, candidate = {}, options = {}) { | ||
| const plan = getSearchInterpretationPlan(query); | ||
@@ -462,3 +488,4 @@ if (!plan) return []; | ||
| const queryVariant = normalizeSearchRankingText(candidate.query_variant); | ||
| const candidateText = getCandidateText(candidate); | ||
| const includeQueryVariant = options.includeQueryVariant !== false; | ||
| const candidateText = getCandidateText(candidate, { includeQueryVariant }); | ||
| const iconRef = getCandidateIconRef(candidate); | ||
@@ -471,3 +498,3 @@ const familyIds = []; | ||
| const candidateIconRefs = (family.candidate_icon_refs || []).map((value) => String(value).toLowerCase()); | ||
| const matchedVariant = queryVariant && retrievalQueries.includes(queryVariant); | ||
| const matchedVariant = includeQueryVariant && queryVariant && retrievalQueries.includes(queryVariant); | ||
| const matchedCandidate = candidateTerms.some((term) => includesPhrase(candidateText, term)); | ||
@@ -474,0 +501,0 @@ const matchedIconRef = candidateIconRefs.includes(iconRef); |
@@ -175,2 +175,8 @@ const BASE_FILTER_TAGS = Object.freeze(['agentic-ai-tools-pack', 'brand-logo']); | ||
| }, | ||
| { | ||
| id: 'anime-manga', | ||
| label: 'Anime & Manga', | ||
| description: 'Iconic symbols from anime and manga: village emblems, crew flags, and pop culture marks.', | ||
| sidebarGlyph: 'auto_awesome', | ||
| }, | ||
| ]); | ||
@@ -1297,2 +1303,27 @@ | ||
| ), | ||
| 'si:anime-konoha-leaf': taxonomy( | ||
| 'anime-manga', | ||
| ['japanese-animation', 'media-entertainment', 'pop-culture'], | ||
| ['naruto', 'konoha', 'ninja', 'shonen'] | ||
| ), | ||
| 'si:anime-akatsuki-cloud': taxonomy( | ||
| 'anime-manga', | ||
| ['japanese-animation', 'media-entertainment', 'pop-culture'], | ||
| ['naruto', 'akatsuki', 'villain', 'shonen'] | ||
| ), | ||
| 'si:anime-sharingan': taxonomy( | ||
| 'anime-manga', | ||
| ['japanese-animation', 'media-entertainment', 'pop-culture'], | ||
| ['naruto', 'sharingan', 'uchiha', 'shonen'] | ||
| ), | ||
| 'si:anime-dragonball-fourstar': taxonomy( | ||
| 'anime-manga', | ||
| ['japanese-animation', 'media-entertainment', 'pop-culture'], | ||
| ['dragon-ball', 'goku', 'collectible', 'shonen'] | ||
| ), | ||
| 'si:anime-onepiece-jolly-roger': taxonomy( | ||
| 'anime-manga', | ||
| ['japanese-animation', 'media-entertainment', 'pop-culture'], | ||
| ['one-piece', 'luffy', 'pirate', 'shonen'] | ||
| ), | ||
| }); | ||
@@ -1299,0 +1330,0 @@ |
+2
-2
@@ -10,3 +10,3 @@ { | ||
| }, | ||
| "version": "0.4.32", | ||
| "version": "0.4.33", | ||
| "remotes": [ | ||
@@ -22,3 +22,3 @@ { | ||
| "identifier": "@supericons/mcp", | ||
| "version": "0.4.32", | ||
| "version": "0.4.33", | ||
| "transport": { | ||
@@ -25,0 +25,0 @@ "type": "stdio" |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
26335702
0.25%75
1.35%123836
0.28%74
2.78%