Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/registry/src/routes/packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ import { triggerSecurityScan } from '../services/scanner.js';
import type { MCPBManifest } from '../types.js';

// Package name validation
const UNSCOPED_REGEX = /^[a-z0-9][a-z0-9-]{0,213}$/;
const SCOPED_REGEX = /^@[a-z0-9][a-z0-9-]{0,38}\/[a-z0-9][a-z0-9-]{0,213}$/;
const UNSCOPED_REGEX = /^[a-z0-9][a-z0-9-]{0,213}$/i;
const SCOPED_REGEX = /^@[a-z0-9][a-z0-9-]{0,38}\/[a-z0-9][a-z0-9-]{0,213}$/i;

function parsePackageName(name: string): { scope: string | null; packageName: string; isScoped: boolean } {
if (name.startsWith('@')) {
Expand Down
4 changes: 2 additions & 2 deletions apps/registry/src/routes/v1/bundles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function getPlatformString(os: string, arch: string): string {
}

// Package name validation (scoped only for v1 API)
const SCOPED_REGEX = /^@[a-z0-9][a-z0-9-]{0,38}\/[a-z0-9][a-z0-9-]{0,213}$/;
const SCOPED_REGEX = /^@[a-z0-9][a-z0-9-]{0,38}\/[a-z0-9][a-z0-9-]{0,213}$/i;

function isValidScopedPackageName(name: string): boolean {
return SCOPED_REGEX.test(name);
Expand Down Expand Up @@ -769,7 +769,7 @@ export const bundleRoutes: FastifyPluginAsync = async (fastify) => {
// Validate package name
if (!isValidScopedPackageName(name)) {
throw new BadRequestError(
`Invalid package name: "${name}". Must be scoped (@scope/name) with lowercase alphanumeric characters and hyphens.`
`Invalid package name: "${name}". Must be scoped (@scope/name) with alphanumeric characters and hyphens.`
);
}

Expand Down
4 changes: 2 additions & 2 deletions apps/registry/src/routes/v1/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ interface GitHubReleaseAsset {
}

// Scoped name validation
const SCOPED_REGEX = /^@[a-z0-9][a-z0-9-]{0,38}\/[a-z0-9][a-z0-9-]*$/;
const SCOPED_REGEX = /^@[a-z0-9][a-z0-9-]{0,38}\/[a-z0-9][a-z0-9-]*$/i;

function isValidScopedName(name: string): boolean {
return SCOPED_REGEX.test(name);
Expand Down Expand Up @@ -505,7 +505,7 @@ export const skillRoutes: FastifyPluginAsync = async (fastify) => {
// Validate name
if (!isValidScopedName(name)) {
throw new BadRequestError(
`Invalid skill name: "${name}". Must be scoped (@scope/name) with lowercase alphanumeric characters and hyphens.`
`Invalid skill name: "${name}". Must be scoped (@scope/name) with alphanumeric characters and hyphens.`
);
}

Expand Down
40 changes: 40 additions & 0 deletions apps/registry/tests/bundles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,46 @@ describe('Bundle Routes', () => {
expect(res.statusCode).toBe(400);
});

it('accepts package name with mixed-case scope', async () => {
(verifyGitHubOIDC as Mock).mockResolvedValue({
...validOIDCClaims,
repository: 'Test-Org/mcp-server',
repository_owner: 'Test-Org',
});

const res = await app.inject({
method: 'POST',
url: '/announce',
headers: { authorization: 'Bearer valid-token' },
payload: {
...validPayload,
name: '@Test-Org/mcp-server',
},
});

// The request should pass name validation (not get rejected for invalid name)
const body = JSON.parse(res.payload);
if (res.statusCode === 400) {
expect(body.error.message).not.toContain('Invalid package name');
}
});

it('rejects package name with invalid characters', async () => {
(verifyGitHubOIDC as Mock).mockResolvedValue(validOIDCClaims);

const res = await app.inject({
method: 'POST',
url: '/announce',
headers: { authorization: 'Bearer valid-token' },
payload: {
...validPayload,
name: '@test-org/mcp server!',
},
});

expect(res.statusCode).toBe(400);
});

it('rejects manifest without server type', async () => {
(verifyGitHubOIDC as Mock).mockResolvedValue(validOIDCClaims);

Expand Down
Loading