Skip to content

Commit ced79f1

Browse files
QuantGeekDevclaude
andcommitted
fix: improve path resolution for npx and non-standard entry points
Closes #82 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent dde5519 commit ced79f1

2 files changed

Lines changed: 76 additions & 1 deletion

File tree

src/loaders/BaseLoader.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export abstract class BaseLoader<T> {
2626
return dir;
2727
}
2828

29+
// 1. Check project root dist/ directory (most common case)
2930
const projectRoot = process.cwd();
3031
const distPath = join(projectRoot, 'dist', subdirectory);
3132

@@ -34,9 +35,27 @@ export abstract class BaseLoader<T> {
3435
return distPath;
3536
}
3637

38+
// 2. Walk up from the main module (process.argv[1]) to find the subdirectory.
39+
// This handles npx where argv[1] is deep inside a temp/cache directory
40+
// but the package's dist/tools etc. are siblings to the entry point.
3741
const mainModulePath = process.argv[1];
38-
const moduleDir = dirname(mainModulePath);
42+
if (mainModulePath) {
43+
let searchDir = dirname(mainModulePath);
44+
// Search up to 5 levels up from the entry point
45+
for (let i = 0; i < 5; i++) {
46+
const candidate = join(searchDir, subdirectory);
47+
if (existsSync(candidate)) {
48+
logger.debug(`Found ${subdirectory} by walking up from argv[1]: ${candidate}`);
49+
return candidate;
50+
}
51+
const parent = dirname(searchDir);
52+
if (parent === searchDir) break; // reached filesystem root
53+
searchDir = parent;
54+
}
55+
}
3956

57+
// 3. Original fallback: derive from module path
58+
const moduleDir = mainModulePath ? dirname(mainModulePath) : projectRoot;
4059
const dir = moduleDir.endsWith('dist')
4160
? join(moduleDir, subdirectory)
4261
: join(moduleDir, 'dist', subdirectory);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
2+
import { mkdirSync, rmSync, existsSync } from 'fs';
3+
import { join } from 'path';
4+
import { tmpdir } from 'os';
5+
6+
describe('BaseLoader path resolution', () => {
7+
const testDir = join(tmpdir(), 'mcp-path-test-' + Date.now());
8+
9+
beforeEach(() => {
10+
mkdirSync(testDir, { recursive: true });
11+
});
12+
13+
afterEach(() => {
14+
rmSync(testDir, { recursive: true, force: true });
15+
});
16+
17+
it('should find tools directory in dist/ relative to cwd', async () => {
18+
// Create a dist/tools directory structure
19+
const distToolsDir = join(testDir, 'dist', 'tools');
20+
mkdirSync(distToolsDir, { recursive: true });
21+
22+
// Import ToolLoader dynamically
23+
const { ToolLoader } = await import('../../src/loaders/toolLoader.js');
24+
25+
// Save and override cwd
26+
const originalCwd = process.cwd;
27+
process.cwd = () => testDir;
28+
29+
try {
30+
const loader = new ToolLoader();
31+
// The loader should find the dist/tools directory
32+
const hasTools = await loader.hasTools();
33+
// No actual tool files, so false, but it shouldn't throw
34+
expect(hasTools).toBe(false);
35+
} finally {
36+
process.cwd = originalCwd;
37+
}
38+
});
39+
40+
it('should find tools directory when basePath is explicitly provided', async () => {
41+
const toolsDir = join(testDir, 'tools');
42+
mkdirSync(toolsDir, { recursive: true });
43+
44+
const { ToolLoader } = await import('../../src/loaders/toolLoader.js');
45+
const loader = new ToolLoader(testDir);
46+
const hasTools = await loader.hasTools();
47+
expect(hasTools).toBe(false); // no files, but directory resolved correctly
48+
});
49+
50+
it('should handle non-existent directories gracefully', async () => {
51+
const { ToolLoader } = await import('../../src/loaders/toolLoader.js');
52+
const loader = new ToolLoader('/non/existent/path');
53+
const hasTools = await loader.hasTools();
54+
expect(hasTools).toBe(false);
55+
});
56+
});

0 commit comments

Comments
 (0)