Skip to content
Merged
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
76 changes: 76 additions & 0 deletions __tests__/resolve-mcp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { describe, it, expect } from 'vitest';
import { resolveMcpShorthand, resolveAndLogMcpShorthand } from '../src/resolve-mcp';

describe('resolveMcpShorthand', () => {
it('passes through scoped packages unchanged', () => {
expect(resolveMcpShorthand('@modelcontextprotocol/server-filesystem')).toBe(
'@modelcontextprotocol/server-filesystem'
);
expect(resolveMcpShorthand('@anthropic/claude-mcp')).toBe('@anthropic/claude-mcp');
});

it('resolves server-* shorthand', () => {
expect(resolveMcpShorthand('server-filesystem')).toBe(
'@modelcontextprotocol/server-filesystem'
);
expect(resolveMcpShorthand('server-fetch')).toBe(
'@modelcontextprotocol/server-fetch'
);
});

it('resolves mcp/server-* shorthand', () => {
expect(resolveMcpShorthand('mcp/server-filesystem')).toBe(
'@modelcontextprotocol/server-filesystem'
);
});

it('resolves mcp-server-* shorthand', () => {
expect(resolveMcpShorthand('mcp-server-fetch')).toBe(
'@modelcontextprotocol/server-fetch'
);
expect(resolveMcpShorthand('mcp-server-slack')).toBe(
'@modelcontextprotocol/server-slack'
);
});

it('passes through regular package names unchanged', () => {
expect(resolveMcpShorthand('express')).toBe('express');
expect(resolveMcpShorthand('langchain')).toBe('langchain');
expect(resolveMcpShorthand('my-mcp-tool')).toBe('my-mcp-tool');
});
});

describe('resolveAndLogMcpShorthand', () => {
it('writes to stderr when resolution changes the name', () => {
const chunks: string[] = [];
const origWrite = process.stderr.write;
process.stderr.write = ((chunk: string) => {
chunks.push(chunk);
return true;
}) as typeof process.stderr.write;

const result = resolveAndLogMcpShorthand('server-filesystem');

process.stderr.write = origWrite;

expect(result).toBe('@modelcontextprotocol/server-filesystem');
expect(chunks).toHaveLength(1);
expect(chunks[0]).toContain('Resolved: server-filesystem -> @modelcontextprotocol/server-filesystem');
});

it('does not write to stderr when name is unchanged', () => {
const chunks: string[] = [];
const origWrite = process.stderr.write;
process.stderr.write = ((chunk: string) => {
chunks.push(chunk);
return true;
}) as typeof process.stderr.write;

const result = resolveAndLogMcpShorthand('express');

process.stderr.write = origWrite;

expect(result).toBe('express');
expect(chunks).toHaveLength(0);
});
});
12 changes: 10 additions & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
type DomainResult,
type SoulLevel,
} from './index';
import { resolveAndLogMcpShorthand } from './resolve-mcp';

const program = new Command();

Expand Down Expand Up @@ -4897,6 +4898,8 @@ Modes:

Examples:
$ ${CLI_PREFIX} trust @anthropic/claude-mcp
$ ${CLI_PREFIX} trust server-filesystem (resolves to @modelcontextprotocol/server-filesystem)
$ ${CLI_PREFIX} trust mcp-server-fetch (resolves to @modelcontextprotocol/server-fetch)
$ ${CLI_PREFIX} trust my-mcp-server --type mcp_server
$ ${CLI_PREFIX} trust --audit package.json
$ ${CLI_PREFIX} trust --audit requirements.txt --min-trust 3
Expand Down Expand Up @@ -4929,7 +4932,11 @@ Examples:
try {
// Mode: audit a dependency file
if (opts.audit) {
const packages = await parseDepsFile(opts.audit);
const rawPackages = await parseDepsFile(opts.audit);
const packages = rawPackages.map((pkg) => ({
...pkg,
name: resolveAndLogMcpShorthand(pkg.name),
}));
if (packages.length === 0) {
process.stdout.write('No dependencies found in the specified file.\n');
return;
Expand All @@ -4956,7 +4963,7 @@ Examples:
process.exit(1);
}
const packages = opts.batch.map((name) => ({
name,
name: resolveAndLogMcpShorthand(name),
...(opts.type ? { type: opts.type } : {}),
}));
const response = await trustBatch(packages, registryUrl);
Expand All @@ -4977,6 +4984,7 @@ Examples:
process.exit(1);
}

packageName = resolveAndLogMcpShorthand(packageName);
const result = await trustCheck(packageName, registryUrl, opts.type);
if (opts.json) {
writeJsonStdout(result);
Expand Down
36 changes: 36 additions & 0 deletions src/resolve-mcp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* MCP package name shorthand resolution.
*
* Converts short forms like "server-filesystem" or "mcp-server-fetch"
* into the full scoped name "@modelcontextprotocol/server-*".
*/

const MCP_SCOPE = '@modelcontextprotocol';

/**
* Resolve a package name, expanding MCP shorthand if applicable.
*
* Rules (applied in order):
* 1. Starts with `@` -- use as-is (already scoped).
* 2. Starts with `server-` -- prefix with @modelcontextprotocol/.
* 3. Starts with `mcp/server-` or `mcp-server-` -- convert to @modelcontextprotocol/server-*.
* 4. Otherwise -- use as-is (regular npm package).
*/
export function resolveMcpShorthand(name: string): string {
if (name.startsWith('@')) return name;
if (name.startsWith('server-')) return `${MCP_SCOPE}/${name}`;
if (name.startsWith('mcp/server-')) return `${MCP_SCOPE}/${name.slice('mcp/'.length)}`;
if (name.startsWith('mcp-server-')) return `${MCP_SCOPE}/${name.slice('mcp-'.length)}`;
return name;
}

/**
* Resolve and log to stderr if the name changed.
*/
export function resolveAndLogMcpShorthand(name: string): string {
const resolved = resolveMcpShorthand(name);
if (resolved !== name) {
process.stderr.write(`Resolved: ${name} -> ${resolved}\n`);
}
return resolved;
}
Loading