Skip to content

Commit d5da341

Browse files
authored
studio: Fixes for exposed MCP server (#853)
- **add, export base logger interface** - **accept logger w/ ctor, log requests** - **construct cli's mcp server w/ console logger** - **add leveldown as direct dep in cli...** - **fix: rm examples dir from main build entry...**
2 parents 5f1ddb8 + 651f452 commit d5da341

File tree

8 files changed

+148
-78
lines changed

8 files changed

+148
-78
lines changed

packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"commander": "^11.0.0",
5050
"fs-extra": "^11.2.0",
5151
"inquirer": "^9.2.0",
52+
"leveldown": "^6.1.1",
5253
"pinia": "^2.3.0",
5354
"pouchdb": "^9.0.0",
5455
"serve-static": "^1.15.0",

packages/cli/src/commands/studio.ts

Lines changed: 20 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,6 @@ async function launchStudio(coursePath: string, options: StudioOptions) {
185185
// Phase 9.5: Launch Express backend
186186
const expressResult = await startExpressBackend(
187187
couchDBManager.getConnectionDetails(),
188-
resolvedPath,
189188
unpackResult.databaseName
190189
);
191190
expressServer = expressResult.server;
@@ -210,7 +209,7 @@ async function launchStudio(coursePath: string, options: StudioOptions) {
210209
console.log(chalk.gray(` Express API: ${expressResult.url}`));
211210

212211
// Display MCP connection information
213-
const mcpInfo = getMCPConnectionInfo(unpackResult, couchDBManager, resolvedPath);
212+
const mcpInfo = getMCPConnectionInfo(unpackResult, couchDBManager);
214213
console.log(chalk.blue(`🔗 MCP Server: ${mcpInfo.command}`));
215214
console.log(chalk.gray(` Connect MCP clients using the command above`));
216215
console.log(chalk.gray(` Environment variables for MCP:`));
@@ -219,7 +218,7 @@ async function launchStudio(coursePath: string, options: StudioOptions) {
219218
});
220219

221220
// Display .mcp.json content for Claude Code integration
222-
const mcpJsonContent = generateMCPJson(unpackResult, couchDBManager, resolvedPath);
221+
const mcpJsonContent = generateMCPJson(unpackResult, couchDBManager);
223222
console.log(chalk.blue(`📋 .mcp.json content:`));
224223
console.log(chalk.gray(mcpJsonContent));
225224

@@ -569,7 +568,6 @@ async function openBrowser(url: string): Promise<void> {
569568
*/
570569
async function startExpressBackend(
571570
couchDbConnectionDetails: ConnectionDetails,
572-
_projectPath: string,
573571
databaseName: string
574572
): Promise<{ server: http.Server; port: number; url: string }> {
575573
console.log(chalk.blue('⚡ Starting Express backend server...'));
@@ -603,7 +601,7 @@ async function startExpressBackend(
603601
try {
604602
// Set NODE_ENV for studio mode authentication bypass
605603
process.env.NODE_ENV = 'studio';
606-
604+
607605
// Create Express app using factory
608606
const app = createExpressApp(config);
609607

@@ -1317,7 +1315,7 @@ export default defineConfig({
13171315
},
13181316
dedupe: [
13191317
'vue',
1320-
'vuetify',
1318+
'vuetify',
13211319
'vue-router',
13221320
'pinia',
13231321
'@vue-skuilder/db',
@@ -1334,45 +1332,18 @@ export default defineConfig({
13341332
}
13351333

13361334
/**
1337-
* Determine the correct MCP server executable path/command based on project context
1335+
* Determine the correct MCP server executable path.
1336+
* This is now greatly simplified because the bundled server is always
1337+
* located relative to this `studio.ts` file.
13381338
*/
1339-
function resolveMCPExecutable(projectPath: string): {
1340-
command: string;
1341-
args: string[];
1342-
isNpx: boolean;
1343-
} {
1344-
// Check if we're in the monorepo (packages/cli exists)
1345-
const monorepoCliPath = path.join(projectPath, 'packages', 'cli', 'dist', 'mcp-server.js');
1346-
if (fs.existsSync(monorepoCliPath)) {
1347-
return {
1348-
command: './packages/cli/dist/mcp-server.js',
1349-
args: [],
1350-
isNpx: false,
1351-
};
1352-
}
1339+
function resolveMCPExecutable(): { command: string; args: string[] } {
1340+
// Resolve the path to the bundled server relative to the current file.
1341+
// __dirname is the `dist/commands` directory.
1342+
const serverPath = path.resolve(__dirname, '..', 'mcp-server.js');
13531343

1354-
// Check if @vue-skuilder/cli is installed as a dependency
1355-
const scaffoldedCliPath = path.join(
1356-
projectPath,
1357-
'node_modules',
1358-
'@vue-skuilder',
1359-
'cli',
1360-
'dist',
1361-
'mcp-server.js'
1362-
);
1363-
if (fs.existsSync(scaffoldedCliPath)) {
1364-
return {
1365-
command: './node_modules/@vue-skuilder/cli/dist/mcp-server.js',
1366-
args: [],
1367-
isNpx: false,
1368-
};
1369-
}
1370-
1371-
// Fallback to npx approach
13721344
return {
1373-
command: 'npx',
1374-
args: ['@vue-skuilder/cli', 'mcp-server'],
1375-
isNpx: true,
1345+
command: 'node',
1346+
args: [serverPath],
13761347
};
13771348
}
13781349

@@ -1381,19 +1352,16 @@ function resolveMCPExecutable(projectPath: string): {
13811352
*/
13821353
function getMCPConnectionInfo(
13831354
unpackResult: UnpackResult,
1384-
couchDBManager: CouchDBManager,
1385-
projectPath: string
1355+
couchDBManager: CouchDBManager
13861356
): { command: string; env: Record<string, string> } {
13871357
const couchDetails = couchDBManager.getConnectionDetails();
1388-
const executable = resolveMCPExecutable(projectPath);
1358+
const executable = resolveMCPExecutable();
1359+
const port = couchDetails.port || 5985;
13891360

13901361
// Build command string for display
1391-
let commandStr: string;
1392-
if (executable.isNpx) {
1393-
commandStr = `${executable.command} ${executable.args.join(' ')} ${unpackResult.databaseName} ${couchDetails.port}`;
1394-
} else {
1395-
commandStr = `node ${executable.command} ${unpackResult.databaseName} ${couchDetails.port}`;
1396-
}
1362+
const commandStr = `${executable.command} ${executable.args.join(' ')} ${
1363+
unpackResult.databaseName
1364+
} ${port}`;
13971365

13981366
return {
13991367
command: commandStr,
@@ -1412,12 +1380,11 @@ function getMCPConnectionInfo(
14121380
function generateMCPJson(
14131381
unpackResult: UnpackResult,
14141382
couchDBManager: CouchDBManager,
1415-
projectPath: string,
14161383
serverName: string = 'vue-skuilder-studio'
14171384
): string {
14181385
const couchDetails = couchDBManager.getConnectionDetails();
14191386
const port = couchDetails.port || 5985;
1420-
const executable = resolveMCPExecutable(projectPath);
1387+
const executable = resolveMCPExecutable();
14211388

14221389
const mcpConfig = {
14231390
mcpServers: {

packages/cli/src/mcp-server.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
#!/usr/bin/env node
1+
// MCP Server for Vue-Skuilder courses
2+
// This file is bundled into a self-contained executable
23

34
import { initializeDataLayer, getDataLayer, initializeTuiLogging } from '@vue-skuilder/db';
45
import { MCPServer } from '@vue-skuilder/mcp';
6+
import { consoleLogger } from '@vue-skuilder/common';
57
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
68

79
initializeTuiLogging();
@@ -42,10 +44,11 @@ async function main() {
4244
await initializeDataLayer(couchdbConfig);
4345
const courseDB = getDataLayer().getCourseDB(courseId);
4446

45-
// Create and start MCP server
47+
// Create and start MCP server with console logger
4648
const server = new MCPServer(courseDB, {
4749
enableSourceLinking: true,
4850
maxCardsPerQuery: 50,
51+
logger: consoleLogger,
4952
});
5053

5154
const transport = new StdioServerTransport();

packages/common/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export * from './logshim.js';
66
export * from './validators.js';
77
export * from './fieldConverters.js';
88
export * from './db.js';
9+
export * from './logger.js';
910

1011
export * from './bulkImport/cardParser.js';
1112
export * from './bulkImport/types.js';

packages/common/src/logger.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Standard logger interface for vue-skuilder packages
3+
*
4+
* This interface enables dependency injection of logging functionality,
5+
* allowing different runtime contexts to provide appropriate logger implementations:
6+
* - Node.js contexts can use Winston
7+
* - Browser contexts can use console wrappers
8+
* - Test contexts can use mock loggers
9+
*/
10+
export interface SkLogger {
11+
debug(message: string, ...args: any[]): void;
12+
info(message: string, ...args: any[]): void;
13+
warn(message: string, ...args: any[]): void;
14+
error(message: string, ...args: any[]): void;
15+
}
16+
17+
/**
18+
* No-op logger implementation for contexts where logging is not needed
19+
*/
20+
export const noOpLogger: SkLogger = {
21+
debug: () => {},
22+
info: () => {},
23+
warn: () => {},
24+
error: () => {},
25+
};
26+
27+
/**
28+
* Console-based logger for browser/development contexts
29+
* Uses console methods with appropriate ESLint suppressions
30+
*/
31+
export const consoleLogger: SkLogger = {
32+
debug: (message: string, ...args: any[]) => console.debug(message, ...args), // eslint-disable-line no-console
33+
info: (message: string, ...args: any[]) => console.info(message, ...args), // eslint-disable-line no-console
34+
warn: (message: string, ...args: any[]) => console.warn(message, ...args), // eslint-disable-line no-console
35+
error: (message: string, ...args: any[]) => console.error(message, ...args), // eslint-disable-line no-console
36+
};

packages/mcp/src/index.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,4 @@ export * from './types/index.js';
88
export * from './resources/index.js';
99

1010
// Tools
11-
export * from './tools/index.js';
12-
13-
// Examples
14-
export * from './examples/index.js';
11+
export * from './tools/index.js';

0 commit comments

Comments
 (0)