Skip to content

Commit 710b226

Browse files
nturinskiNathan TurinskiCopilotCopilot
authored
Open the sample file and the mcp.json in a split editor when creating self-hosted MCP projects (#4946)
* Open the sample file and the mcp.json in a split editor when creating self-hosted MCP projects * Update src/commands/createNewProject/mcpServerSteps/MCPOpenFileStep.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Use deterministic known-path selection for sampleToolFilePath Agent-Logs-Url: https://github.com/microsoft/vscode-azurefunctions/sessions/e0865c26-ca93-495e-8072-8412eb458e26 Co-authored-by: nturinski <5290572+nturinski@users.noreply.github.com> * {} linter --------- Co-authored-by: Nathan Turinski <naturins@microsoft.comm> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: nturinski <5290572+nturinski@users.noreply.github.com>
1 parent a4df109 commit 710b226

File tree

3 files changed

+67
-7
lines changed

3 files changed

+67
-7
lines changed

src/commands/createNewProject/IProjectWizardContext.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export interface MCPProjectWizardContext extends IProjectWizardContext {
3939
serverLanguage?: ProjectLanguage;
4040
includeSnippets?: boolean;
4141
sampleMcpRepoUrl?: string;
42+
sampleToolFilePath?: string;
4243
}
4344

4445
export type OpenBehavior = 'AddToWorkspace' | 'OpenInNewWindow' | 'OpenInCurrentWindow' | 'AlreadyOpen' | 'DontOpen';

src/commands/createNewProject/mcpServerSteps/MCPDownloadSnippetsExecuteStep.ts

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,68 @@ export class MCPDownloadSnippetsExecuteStep extends AzureWizardExecuteStepWithAc
3535
progress.report({ message: localize('downloadingSampleCodeExecute', 'Downloading sample server code...') });
3636
const sampleMcpRepoUrl: string = nonNullProp(context, 'sampleMcpRepoUrl');
3737
const sampleFiles: GitHubFileMetadata[] = await feedUtils.getJsonFeed(context, sampleMcpRepoUrl);
38-
await this.downloadFilesRecursively(context, sampleFiles, context.projectPath);
38+
const downloadedFiles = await this.downloadFilesRecursively(context, sampleFiles, context.projectPath);
39+
context.sampleToolFilePath = MCPDownloadSnippetsExecuteStep.selectSampleToolFile(context.serverLanguage, downloadedFiles);
3940
}
4041

41-
private async downloadFilesRecursively(context: MCPProjectWizardContext, items: GitHubFileMetadata[], basePath: string): Promise<void> {
42+
// Known entrypoint paths relative to the root of each language's sample GitHub repository
43+
private static readonly knownToolPaths: Partial<Record<ProjectLanguage, string>> = {
44+
[ProjectLanguage.TypeScript]: 'src/index.ts',
45+
[ProjectLanguage.Python]: 'hello.py',
46+
[ProjectLanguage.CSharp]: 'Tools/HelloTool.cs',
47+
};
48+
49+
private static readonly languageExtensions: Partial<Record<ProjectLanguage, string>> = {
50+
[ProjectLanguage.TypeScript]: '.ts',
51+
[ProjectLanguage.Python]: '.py',
52+
[ProjectLanguage.CSharp]: '.cs',
53+
};
54+
55+
private static selectSampleToolFile(
56+
language: ProjectLanguage | undefined,
57+
files: { itemPath: string; destPath: string }[]
58+
): string | undefined {
59+
if (!language) {
60+
return undefined;
61+
}
62+
63+
// Prefer the known entrypoint path for the language
64+
const knownPath = MCPDownloadSnippetsExecuteStep.knownToolPaths[language];
65+
if (knownPath) {
66+
const match = files.find(f => f.itemPath === knownPath);
67+
if (match) {
68+
return match.destPath;
69+
}
70+
}
71+
72+
// Fallback: any file with the right extension, sorted for determinism
73+
const sourceExt = MCPDownloadSnippetsExecuteStep.languageExtensions[language];
74+
if (sourceExt) {
75+
const candidates = files
76+
.filter(f => f.destPath.endsWith(sourceExt))
77+
.sort((a, b) => a.itemPath.localeCompare(b.itemPath));
78+
return candidates[0]?.destPath;
79+
}
80+
81+
return undefined;
82+
}
83+
84+
private async downloadFilesRecursively(
85+
context: MCPProjectWizardContext,
86+
items: GitHubFileMetadata[],
87+
basePath: string
88+
): Promise<{ itemPath: string; destPath: string }[]> {
89+
const downloadedFiles: { itemPath: string; destPath: string }[] = [];
4290
// Download all files and directories at this level in parallel
4391
await Promise.all(items.map(async (item) => {
4492
if (item.type === 'file') {
45-
await MCPDownloadSnippetsExecuteStep.downloadSingleFile({
93+
const destPath = await MCPDownloadSnippetsExecuteStep.downloadSingleFile({
4694
context, item,
4795
destDirPath: basePath,
4896
serverLanguage: context.serverLanguage,
4997
projectName: path.basename(context.projectPath)
5098
});
99+
downloadedFiles.push({ itemPath: item.path, destPath });
51100
} else if (item.type === 'dir') {
52101
// Create directory
53102
const dirPath: string = path.join(basePath, item.name);
@@ -58,9 +107,11 @@ export class MCPDownloadSnippetsExecuteStep extends AzureWizardExecuteStepWithAc
58107
const dirContents: GitHubFileMetadata[] = parseJson<GitHubFileMetadata[]>(nonNullProp(response, 'bodyAsText'));
59108

60109
// Recursively download directory contents
61-
await this.downloadFilesRecursively(context, dirContents, dirPath);
110+
const subFiles = await this.downloadFilesRecursively(context, dirContents, dirPath);
111+
downloadedFiles.push(...subFiles);
62112
}
63113
}));
114+
return downloadedFiles;
64115
}
65116

66117
public shouldExecute(context: MCPProjectWizardContext): boolean {
@@ -73,7 +124,7 @@ export class MCPDownloadSnippetsExecuteStep extends AzureWizardExecuteStepWithAc
73124
destDirPath: string,
74125
projectName: string
75126
serverLanguage?: ProjectLanguage,
76-
}): Promise<void> {
127+
}): Promise<string> {
77128
const { context, item, destDirPath, serverLanguage, projectName } = options;
78129
const fileUrl: string = item.download_url;
79130
let destinationPath: string = path.join(destDirPath, item.name);
@@ -92,5 +143,6 @@ export class MCPDownloadSnippetsExecuteStep extends AzureWizardExecuteStepWithAc
92143
}
93144
}
94145
await AzExtFsExtra.writeFile(destinationPath, fileContent ?? '');
146+
return destinationPath;
95147
}
96148
}

src/commands/createNewProject/mcpServerSteps/MCPOpenFileStep.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,26 @@
55

66
import { AzExtFsExtra, AzureWizardExecuteStep } from '@microsoft/vscode-azext-utils';
77
import * as path from 'path';
8-
import { Uri, window, workspace } from 'vscode';
8+
import { Uri, ViewColumn, window, workspace } from 'vscode';
99
import { type MCPProjectWizardContext } from '../IProjectWizardContext';
1010

1111
export class MCPOpenFileStep extends AzureWizardExecuteStep<MCPProjectWizardContext> {
1212
public priority: number = 240; // Execute before OpenFolderStep (priority 250) but after other project setup
1313

1414
public async execute(context: MCPProjectWizardContext): Promise<void> {
1515
const mcpJsonFilePath: string = path.join(context.projectPath, '.vscode', 'mcp.json');
16-
1716
if (await AzExtFsExtra.pathExists(mcpJsonFilePath)) {
1817
const mcpJsonFile = await workspace.openTextDocument(Uri.file(mcpJsonFilePath));
1918
await window.showTextDocument(mcpJsonFile, { preview: false });
2019
}
20+
21+
// Open sample tool file in a side-by-side editor
22+
if (context.sampleToolFilePath) {
23+
if (await AzExtFsExtra.pathExists(context.sampleToolFilePath)) {
24+
const doc = await workspace.openTextDocument(Uri.file(context.sampleToolFilePath));
25+
await window.showTextDocument(doc, { preview: false, viewColumn: ViewColumn.Beside });
26+
}
27+
}
2128
}
2229

2330
public shouldExecute(context: MCPProjectWizardContext): boolean {

0 commit comments

Comments
 (0)