Skip to content

Commit 95e4f50

Browse files
committed
refactor(@angular/cli): add MCP test support for custom code examples
The experimental MCP server now supports an additional environment variable for test purposes that allows custom markdown code examples to be used with the `find_examples` tool. This uses the `NG_MCP_EXAMPLES_DIR` environment variable.
1 parent 5eeebf9 commit 95e4f50

File tree

2 files changed

+41
-5
lines changed

2 files changed

+41
-5
lines changed

packages/angular/cli/src/commands/mcp/mcp-server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export async function createMcpServer(
7474
' Registration of this tool has been skipped.',
7575
);
7676
} else {
77-
registerFindExampleTool(server, path.join(__dirname, '../../../lib/code-examples.db'));
77+
await registerFindExampleTool(server, path.join(__dirname, '../../../lib/code-examples.db'));
7878
}
7979
}
8080

packages/angular/cli/src/commands/mcp/tools/examples.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
*/
88

99
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10+
import { glob, readFile } from 'node:fs/promises';
11+
import path from 'node:path';
1012
import { z } from 'zod';
1113

1214
/**
@@ -18,10 +20,20 @@ import { z } from 'zod';
1820
* @param server The MCP server instance.
1921
* @param exampleDatabasePath The path to the SQLite database file containing the examples.
2022
*/
21-
export function registerFindExampleTool(server: McpServer, exampleDatabasePath: string): void {
23+
export async function registerFindExampleTool(
24+
server: McpServer,
25+
exampleDatabasePath: string,
26+
): Promise<void> {
2227
let db: import('node:sqlite').DatabaseSync | undefined;
2328
let queryStatement: import('node:sqlite').StatementSync | undefined;
2429

30+
// Runtime directory of examples uses an in-memory database
31+
if (process.env['NG_MCP_EXAMPLES_DIR']) {
32+
db = await setupRuntimeExamples(process.env['NG_MCP_EXAMPLES_DIR']);
33+
}
34+
35+
suppressSqliteWarning();
36+
2537
server.registerTool(
2638
'find_examples',
2739
{
@@ -67,11 +79,11 @@ Examples of queries:
6779
},
6880
},
6981
async ({ query }) => {
70-
if (!db || !queryStatement) {
71-
suppressSqliteWarning();
72-
82+
if (!db) {
7383
const { DatabaseSync } = await import('node:sqlite');
7484
db = new DatabaseSync(exampleDatabasePath, { readOnly: true });
85+
}
86+
if (!queryStatement) {
7587
queryStatement = db.prepare('SELECT * from examples WHERE examples MATCH ? ORDER BY rank;');
7688
}
7789

@@ -172,3 +184,27 @@ function suppressSqliteWarning() {
172184
return originalProcessEmit.apply(process, arguments as any);
173185
};
174186
}
187+
188+
async function setupRuntimeExamples(
189+
examplesPath: string,
190+
): Promise<import('node:sqlite').DatabaseSync> {
191+
const { DatabaseSync } = await import('node:sqlite');
192+
const db = new DatabaseSync(':memory:');
193+
194+
db.exec(`CREATE VIRTUAL TABLE examples USING fts5(content, tokenize = 'porter ascii');`);
195+
196+
const insertStatement = db.prepare('INSERT INTO examples(content) VALUES(?);');
197+
198+
db.exec('BEGIN TRANSACTION');
199+
for await (const entry of glob('*.md', { cwd: examplesPath, withFileTypes: true })) {
200+
if (!entry.isFile()) {
201+
continue;
202+
}
203+
204+
const example = await readFile(path.join(entry.parentPath, entry.name), 'utf-8');
205+
insertStatement.run(example);
206+
}
207+
db.exec('END TRANSACTION');
208+
209+
return db;
210+
}

0 commit comments

Comments
 (0)