Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions src/commands/createNewProject/IProjectWizardContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,27 @@ export class MCPDownloadSnippetsExecuteStep extends AzureWizardExecuteStepWithAc
await this.downloadFilesRecursively(context, sampleFiles, context.projectPath);
}

private static readonly languageExtensions: Partial<Record<ProjectLanguage, string>> = {
[ProjectLanguage.TypeScript]: '.ts',
[ProjectLanguage.Python]: '.py',
[ProjectLanguage.CSharp]: '.cs',
};

private async downloadFilesRecursively(context: MCPProjectWizardContext, items: GitHubFileMetadata[], basePath: string): Promise<void> {
const sourceExt = context.serverLanguage ? MCPDownloadSnippetsExecuteStep.languageExtensions[context.serverLanguage] : undefined;
// 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)
});
// Track the first source file matching the server language as the sample tool file
if (!context.sampleToolFilePath && sourceExt && destPath.endsWith(sourceExt)) {
context.sampleToolFilePath = destPath;
}
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sampleToolFilePath is set inside a Promise.all over downloads. Because the downloads resolve in nondeterministic order, the "first" matching file picked here is effectively whichever finishes first (and can even be overwritten if multiple resolves hit before the flag is observed). Consider collecting candidate paths during the traversal and selecting deterministically after the parallel work completes (e.g., prefer known tool paths per language using item.path, or pick by stable sort order) rather than mutating context from concurrent tasks.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using destPath.endsWith(sourceExt) to identify the tool file is fairly loose and can select the wrong file as soon as the sample repo contains more than one source file for that language (and it also ignores the known expected paths from the issue, e.g. src/index.ts, hello.py, Tools/HelloTool.cs). Prefer matching on item.path against those known relative paths (with a fallback strategy) to make the file that opens predictable and aligned with the intended entrypoint.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in commit 5856acf. The changes:

  • Refactored downloadFilesRecursively to collect {itemPath, destPath} tuples and return them instead of mutating context inside a concurrent Promise.all
  • Added a knownToolPaths map with the exact repo-relative entrypoints (src/index.ts, hello.py, Tools/HelloTool.cs)
  • Added a selectSampleToolFile static method that first matches on item.path against the known paths, then falls back to a lexicographically-sorted extension-based search for determinism
  • context.sampleToolFilePath is now assigned once after all downloads complete

} else if (item.type === 'dir') {
// Create directory
const dirPath: string = path.join(basePath, item.name);
Expand All @@ -73,7 +84,7 @@ export class MCPDownloadSnippetsExecuteStep extends AzureWizardExecuteStepWithAc
destDirPath: string,
projectName: string
serverLanguage?: ProjectLanguage,
}): Promise<void> {
}): Promise<string> {
const { context, item, destDirPath, serverLanguage, projectName } = options;
const fileUrl: string = item.download_url;
let destinationPath: string = path.join(destDirPath, item.name);
Expand All @@ -92,5 +103,6 @@ export class MCPDownloadSnippetsExecuteStep extends AzureWizardExecuteStepWithAc
}
}
await AzExtFsExtra.writeFile(destinationPath, fileContent ?? '');
return destinationPath;
}
}
13 changes: 10 additions & 3 deletions src/commands/createNewProject/mcpServerSteps/MCPOpenFileStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,25 @@

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<MCPProjectWizardContext> {
public priority: number = 240; // Execute before OpenFolderStep (priority 250) but after other project setup

public async execute(context: MCPProjectWizardContext): Promise<void> {
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 });
await window.showTextDocument(mcpJsonFile, { preview: false, viewColumn: ViewColumn.One });
}

// 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.Two });
Comment thread
nturinski marked this conversation as resolved.
Outdated
}
}
}

Expand Down
Loading