Skip to content

Commit dfd2c87

Browse files
author
Lasim
committed
feat(backend): add utility to convert empty strings to undefined for mcp registry sync
1 parent 9370072 commit dfd2c87

File tree

2 files changed

+37
-10
lines changed

2 files changed

+37
-10
lines changed

services/backend/src/services/transforms/officialRegistryTransforms.ts

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,21 @@ import type {
2222
} from '../../routes/mcp/servers/schemas';
2323
import { GitHubService } from '../githubService';
2424

25+
// =============================================================================
26+
// UTILITY FUNCTIONS
27+
// =============================================================================
28+
29+
/**
30+
* Convert empty strings to undefined for optional fields
31+
* Official registry sometimes returns empty strings instead of omitting fields
32+
*
33+
* @param value - Value to check and convert
34+
* @returns undefined if value is empty string, otherwise the original value
35+
*/
36+
function emptyToUndefined(value: string | undefined): string | undefined {
37+
return value === '' ? undefined : value;
38+
}
39+
2540
// =============================================================================
2641
// OFFICIAL REGISTRY TYPE DEFINITIONS
2742
// =============================================================================
@@ -123,13 +138,16 @@ export interface OfficialServer {
123138
* - "io.github.upstash/context7" → "Context7"
124139
* - "io.github.vfarcic/dot-ai" → "Dot AI"
125140
* - "com.apple-rag/mcp-server" → "Apple RAG"
141+
* - "ai.tickettailor/mcp" → "Tickettailor" (fallback to domain when server part becomes empty)
126142
*
127143
* @param officialName - Official reverse-DNS name
128144
* @returns User-friendly display name
129145
*/
130146
export function createFriendlyName(officialName: string): string {
131147
// Extract the server part after the last slash
132-
const serverPart = officialName.split('/').pop() || officialName;
148+
const parts = officialName.split('/');
149+
const serverPart = parts[parts.length - 1] || officialName;
150+
const domainPart = parts.length > 1 ? parts[0] : '';
133151

134152
// Remove common prefixes
135153
let friendlyName = serverPart
@@ -138,6 +156,13 @@ export function createFriendlyName(officialName: string): string {
138156
.replace(/-server$/i, '')
139157
.replace(/-mcp$/i, '');
140158

159+
// If friendly name is empty after removing prefixes, use domain part
160+
if (!friendlyName || friendlyName.trim() === '') {
161+
// Extract company/service name from domain (e.g., "ai.tickettailor" → "tickettailor")
162+
const domainParts = domainPart.split('.');
163+
friendlyName = domainParts[domainParts.length - 1] || officialName;
164+
}
165+
141166
// Convert kebab-case and snake_case to Title Case
142167
friendlyName = friendlyName
143168
.replace(/[-_]/g, ' ')
@@ -554,14 +579,14 @@ export async function transformOfficialToDeployStack(
554579
// Version information
555580
version: officialServer.version,
556581

557-
// Repository information
558-
repository_url: repository?.url,
559-
repository_source: repository?.source,
560-
repository_id: repository?.id,
561-
repository_subfolder: repository?.subfolder,
582+
// Repository information - convert empty strings to undefined
583+
repository_url: emptyToUndefined(repository?.url),
584+
repository_source: emptyToUndefined(repository?.source),
585+
repository_id: emptyToUndefined(repository?.id),
586+
repository_subfolder: emptyToUndefined(repository?.subfolder),
562587

563-
// Website
564-
website_url: officialServer.websiteUrl,
588+
// Website - convert empty strings to undefined
589+
website_url: emptyToUndefined(officialServer.websiteUrl),
565590

566591
// Official format storage (will be JSON stringified by create-global.ts)
567592
// Use packagesCopy which now has inferred command/args
@@ -580,7 +605,8 @@ export async function transformOfficialToDeployStack(
580605
};
581606

582607
// Optionally enhance with GitHub metadata
583-
if (options?.fetchGitHubMetadata && options.logger && repository?.url) {
608+
// Skip if repository URL is empty or undefined
609+
if (options?.fetchGitHubMetadata && options.logger && repository?.url && repository.url !== '') {
584610
try {
585611
const githubData = await enhanceWithGitHubMetadata(
586612
repository.url,

services/backend/src/workers/mcpServerSyncWorker.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ export class McpServerSyncWorker implements Worker {
168168
operation: 'mcp_server_sync_complete'
169169
}, 'Successfully synced MCP server');
170170

171-
// Fetch and save GitHub README if this is a GitHub repository
171+
// Fetch and save GitHub README if this is a GitHub repository with a valid URL
172+
// Skip if repository_url is undefined (empty string from registry was converted to undefined)
172173
if (transformedData.repository_url && transformedData.repository_url.includes('github.com')) {
173174
this.logger.debug({
174175
jobId,

0 commit comments

Comments
 (0)