diff --git a/src/commands/createNewProject/IProjectWizardContext.ts b/src/commands/createNewProject/IProjectWizardContext.ts index 793a58a5f..84cb712f1 100644 --- a/src/commands/createNewProject/IProjectWizardContext.ts +++ b/src/commands/createNewProject/IProjectWizardContext.ts @@ -39,6 +39,7 @@ export interface MCPProjectWizardContext extends IProjectWizardContext { serverLanguage?: ProjectLanguage; includeSnippets?: boolean; sampleMcpRepoUrl?: string; + sampleToolFilePath?: string; } export type OpenBehavior = 'AddToWorkspace' | 'OpenInNewWindow' | 'OpenInCurrentWindow' | 'AlreadyOpen' | 'DontOpen'; diff --git a/src/commands/createNewProject/mcpServerSteps/MCPDownloadSnippetsExecuteStep.ts b/src/commands/createNewProject/mcpServerSteps/MCPDownloadSnippetsExecuteStep.ts index 9eb435cf1..f65eecc65 100644 --- a/src/commands/createNewProject/mcpServerSteps/MCPDownloadSnippetsExecuteStep.ts +++ b/src/commands/createNewProject/mcpServerSteps/MCPDownloadSnippetsExecuteStep.ts @@ -35,19 +35,68 @@ export class MCPDownloadSnippetsExecuteStep extends AzureWizardExecuteStepWithAc progress.report({ message: localize('downloadingSampleCodeExecute', 'Downloading sample server code...') }); const sampleMcpRepoUrl: string = nonNullProp(context, 'sampleMcpRepoUrl'); const sampleFiles: GitHubFileMetadata[] = await feedUtils.getJsonFeed(context, sampleMcpRepoUrl); - await this.downloadFilesRecursively(context, sampleFiles, context.projectPath); + const downloadedFiles = await this.downloadFilesRecursively(context, sampleFiles, context.projectPath); + context.sampleToolFilePath = MCPDownloadSnippetsExecuteStep.selectSampleToolFile(context.serverLanguage, downloadedFiles); } - private async downloadFilesRecursively(context: MCPProjectWizardContext, items: GitHubFileMetadata[], basePath: string): Promise { + // Known entrypoint paths relative to the root of each language's sample GitHub repository + private static readonly knownToolPaths: Partial> = { + [ProjectLanguage.TypeScript]: 'src/index.ts', + [ProjectLanguage.Python]: 'hello.py', + [ProjectLanguage.CSharp]: 'Tools/HelloTool.cs', + }; + + private static readonly languageExtensions: Partial> = { + [ProjectLanguage.TypeScript]: '.ts', + [ProjectLanguage.Python]: '.py', + [ProjectLanguage.CSharp]: '.cs', + }; + + private static selectSampleToolFile( + language: ProjectLanguage | undefined, + files: { itemPath: string; destPath: string }[] + ): string | undefined { + if (!language) { + return undefined; + } + + // Prefer the known entrypoint path for the language + const knownPath = MCPDownloadSnippetsExecuteStep.knownToolPaths[language]; + if (knownPath) { + const match = files.find(f => f.itemPath === knownPath); + if (match) { + return match.destPath; + } + } + + // Fallback: any file with the right extension, sorted for determinism + const sourceExt = MCPDownloadSnippetsExecuteStep.languageExtensions[language]; + if (sourceExt) { + const candidates = files + .filter(f => f.destPath.endsWith(sourceExt)) + .sort((a, b) => a.itemPath.localeCompare(b.itemPath)); + return candidates[0]?.destPath; + } + + return undefined; + } + + private async downloadFilesRecursively( + context: MCPProjectWizardContext, + items: GitHubFileMetadata[], + basePath: string + ): Promise<{ itemPath: string; destPath: string }[]> { + const downloadedFiles: { itemPath: string; destPath: string }[] = []; // Download all files and directories at this level in parallel await Promise.all(items.map(async (item) => { if (item.type === 'file') { - await MCPDownloadSnippetsExecuteStep.downloadSingleFile({ + const destPath = await MCPDownloadSnippetsExecuteStep.downloadSingleFile({ context, item, destDirPath: basePath, serverLanguage: context.serverLanguage, projectName: path.basename(context.projectPath) }); + downloadedFiles.push({ itemPath: item.path, destPath }); } else if (item.type === 'dir') { // Create directory const dirPath: string = path.join(basePath, item.name); @@ -58,9 +107,11 @@ export class MCPDownloadSnippetsExecuteStep extends AzureWizardExecuteStepWithAc const dirContents: GitHubFileMetadata[] = parseJson(nonNullProp(response, 'bodyAsText')); // Recursively download directory contents - await this.downloadFilesRecursively(context, dirContents, dirPath); + const subFiles = await this.downloadFilesRecursively(context, dirContents, dirPath); + downloadedFiles.push(...subFiles); } })); + return downloadedFiles; } public shouldExecute(context: MCPProjectWizardContext): boolean { @@ -73,7 +124,7 @@ export class MCPDownloadSnippetsExecuteStep extends AzureWizardExecuteStepWithAc destDirPath: string, projectName: string serverLanguage?: ProjectLanguage, - }): Promise { + }): Promise { const { context, item, destDirPath, serverLanguage, projectName } = options; const fileUrl: string = item.download_url; let destinationPath: string = path.join(destDirPath, item.name); @@ -92,5 +143,6 @@ export class MCPDownloadSnippetsExecuteStep extends AzureWizardExecuteStepWithAc } } await AzExtFsExtra.writeFile(destinationPath, fileContent ?? ''); + return destinationPath; } } diff --git a/src/commands/createNewProject/mcpServerSteps/MCPOpenFileStep.ts b/src/commands/createNewProject/mcpServerSteps/MCPOpenFileStep.ts index d580b79ee..ca62a68a9 100644 --- a/src/commands/createNewProject/mcpServerSteps/MCPOpenFileStep.ts +++ b/src/commands/createNewProject/mcpServerSteps/MCPOpenFileStep.ts @@ -5,7 +5,7 @@ import { AzExtFsExtra, AzureWizardExecuteStep } from '@microsoft/vscode-azext-utils'; import * as path from 'path'; -import { Uri, window, workspace } from 'vscode'; +import { Uri, ViewColumn, window, workspace } from 'vscode'; import { type MCPProjectWizardContext } from '../IProjectWizardContext'; export class MCPOpenFileStep extends AzureWizardExecuteStep { @@ -13,11 +13,18 @@ export class MCPOpenFileStep extends AzureWizardExecuteStep { const mcpJsonFilePath: string = path.join(context.projectPath, '.vscode', 'mcp.json'); - if (await AzExtFsExtra.pathExists(mcpJsonFilePath)) { const mcpJsonFile = await workspace.openTextDocument(Uri.file(mcpJsonFilePath)); await window.showTextDocument(mcpJsonFile, { preview: false }); } + + // Open sample tool file in a side-by-side editor + if (context.sampleToolFilePath) { + if (await AzExtFsExtra.pathExists(context.sampleToolFilePath)) { + const doc = await workspace.openTextDocument(Uri.file(context.sampleToolFilePath)); + await window.showTextDocument(doc, { preview: false, viewColumn: ViewColumn.Beside }); + } + } } public shouldExecute(context: MCPProjectWizardContext): boolean {