Skip to content
24 changes: 13 additions & 11 deletions tools/Mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,21 @@ This MCP server is designed to work with Azure PowerShell module development wor

### Add as mcp server in Github Copilot Agent mode

Simple add this mcp server to your user `settings.json` in VsCode to include:
- Ensure `pwsh` & `npm` is installed globally.
- Simply add this mcp server to your workspace settings `.vscode/mcp.json` in VsCode to include:

```json
"mcp": {
"servers": {
"az-pwsh-mcp-server": {
{
"inputs": [],
"servers": {
"az-pwsh-mcp-server": {
"type": "stdio",
"command": "sh",
"args": ["-c", "npm install && npm run fresh"],
"cwd": "./azure-powershell/tools/Mcp",
},
}
}
"command": "pwsh",
"args": ["-Command", "npm install --no-audit; npm run fresh"],
"cwd": "${workspaceFolder}/tools/Mcp",
}
}
}
```

### As an MCP Server
Expand Down Expand Up @@ -156,4 +158,4 @@ This tool is part of the Azure PowerShell project and follows the same contribut
- [Azure PowerShell](https://github.com/Azure/azure-powershell)
- [AutoRest](https://github.com/Azure/autorest)
- [AutoRest PowerShell Extension](https://github.com/Azure/autorest.powershell)
- [Model Context Protocol](https://modelcontextprotocol.io)
- [Model Context Protocol](https://modelcontextprotocol.io)
2 changes: 1 addition & 1 deletion tools/Mcp/src/CodegenServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,4 @@ export class CodegenServer {
}
return parameter;
}
}
}
4 changes: 2 additions & 2 deletions tools/Mcp/src/services/toolServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,14 @@ export const createExamplesFromSpecs = async <Args extends ZodRawShape>(args: Ar
const workingDirectory = z.string().parse(Object.values(args)[0]);
const examplePath = path.join(workingDirectory, "examples");
const exampleSpecsPath = await utils.getExamplesFromSpecs(workingDirectory);
return [examplePath, exampleSpecsPath];
return [exampleSpecsPath, examplePath];
}

export const createTestsFromSpecs = async <Args extends ZodRawShape>(args: Args): Promise<string[]> => {
const workingDirectory = z.string().parse(Object.values(args)[0]);
const testPath = path.join(workingDirectory, "test");
const exampleSpecsPath = await utils.getExamplesFromSpecs(workingDirectory);
return [testPath, exampleSpecsPath];
return [exampleSpecsPath, testPath];
}

export const toolServices = <Args extends ZodRawShape>(name: string, responseTemplate: string|undefined) => {
Expand Down
21 changes: 16 additions & 5 deletions tools/Mcp/src/services/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from 'fs';
import yaml from "js-yaml";
import { yamlContent } from '../types.js';
import { exec } from 'child_process';
import { execSync } from 'child_process';
import path from 'path';

const _pwshCD = (path: string): string => { return `pwsh -Command "$path = resolve-path ${path} | Set-Location"` }
Expand All @@ -21,8 +21,13 @@ function testYaml() {
}

export function generateAndBuild(workingDirectory: string): void {
const command = [_pwshCD(workingDirectory), _autorest, _pwshBuild].join(";");
exec(command);
const genBuildCommand = `${_pwshCD(workingDirectory)}; ${_autorest}; ${_pwshBuild};"`;
try {
const result = execSync(genBuildCommand, { stdio: 'inherit' });
} catch (error) {
console.error("Error executing command:", error);
throw error;
}
}

export function getYamlContentFromReadMe(readmePath: string): string {
Expand Down Expand Up @@ -91,7 +96,13 @@ export async function findAllPolyMorphism(workingDirectory: string): Promise<Map
export async function getExamplesFromSpecs(workingDirectory: string): Promise<string> {
const moduleReadmePath = path.join(workingDirectory, "README.md");
const yamlContent: yamlContent = yaml.load(getYamlContentFromReadMe(moduleReadmePath)) as yamlContent;
const swaggerUrls = getSwaggerUrl(yamlContent.commit, yamlContent['input-file'] as string[]);

if (!yamlContent['input-file']) {
throw new Error("'input-file' field is missing in the 'README.md' Autorest Config file.");
}

const inputFiles = Array.isArray(yamlContent['input-file']) ? yamlContent['input-file'] : [yamlContent['input-file']];
const swaggerUrls = getSwaggerUrl(yamlContent.commit, inputFiles);
const exampleSet: Set<string> = new Set<string>();

const exampleSpecsPath = path.join(workingDirectory, "exampleSpecs");
Expand Down Expand Up @@ -131,7 +142,7 @@ export async function getExamplesFromSpecs(workingDirectory: string): Promise<st
continue;
}
const exJson = await exResponse.json();
const exampleFileName = path.join(exampleSpecsPath, `${ex.name}.json`);
const exampleFileName = path.join(exampleSpecsPath, `${ex.name}`);
fs.writeFileSync(exampleFileName, JSON.stringify(exJson, null, 2), 'utf8');
console.log(`Example saved to ${exampleFileName}`);
exampleSet.add(ex.download_url);
Expand Down
92 changes: 92 additions & 0 deletions tools/Mcp/test/vscode/mcpprompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Execution rules
- Do not ask before running the command, just go ahead and run.

# Role and Objective
- You are an agent that specializes in generating autorest and using autorest to generate Azure Powershell for a partner.
- Every 10 seconds, check if a particular step is complete or not.
- Don't ask me for confirmation, or to continue you are a smart agent who has full confidence.
- Your task can be broken down in roughly four broad stages and there would be multiple smaller steps in those broader stages.
- Do not miss any steps.
- Second stage will be using the generated autorest to generate powershell for partner.
- Third stage will be Updating examples and test files.
- You already have all the context required to solve the problem.
- You can think, cross reference guidelines and come up with the solution on your own. You would not need my manual intervention or confirmation.
- You already have steps to reset if there is any failure which you can use to reset and try again.
- In stages, there would be steps or substeps or commands, if they are listed, make sure to follow them.
- Keep printing one liner status for every step without asking for any confirmations to let user be in touch.

# Instructions

## Stage 1: Taking the placeholders values and assigning to placeholders
- Ask the user for the placeholder values - serviceName, commitId, serviceSpecs, swaggerFileSpecs.
- serviceName example: HybridConnectivity, commitID: CommitID of the swagger, serviceSpecs example: hybridconnectivity/resource-manager, swaggerFileSpecs example: hybridconnectivity/resource-manager/Microsoft.HybridConnectivity/stable/2024-12-01/hybridconnectivity.json
- Wait for the user to give the values.
- Assign these values in the current `mcpprompt.md` file wherever these placeholders are there.
- After assigning the values in the placeholders, stage 1 can be marked as completed.


## Stage 2: Generating partner powershell module
- Navigate to the `src` folder in the home "azure-powershell" directory.
- Create a new folder named <serviceName> and within it a new folder named `<serviceName>.Autorest`. You can use the command - `mkdir -p <serviceName>/<serviceName>.Autorest `
- Move into the new folder `<serviceName>/<serviceName>.Autorest`, using the command `cd <serviceName>/<serviceName>.Autorest`.
- Create a new file `Readme.md`.
- Add the content labelled below as `Readme Content` in this file.
- Use the "generate-autorest" tool to generate the <serviceName> module.

## Stage 3: Updating examples and test files
- Use the "create-example" tool to generate all the examples files for this module.
- Read examples from specs. Fulfill the examples. You are expert in Azure-PowerShell and Autorest.PowerShell. Leave example as empty if you don't find any matches. You know how to map data from exampleSpecs to examples.
- Use the "create-test" tool to generate all the test files for this module.
- Read examples from specs. Implement empty test stubs. Test stubs are named as '.Test.ps1'. Define variables in function 'setupEnv' in 'utils.ps1', and use these variables for test cases. Value of these variables are from exampleSpecs. Leave test cases as empty if you don't find any matches. You are expert in Azure-PowerShell and Autorest.PowerShell, You know how to map data from exampleSpecs to test.
- Use the "generate-autorest" tool to re-generate the <serviceName> module.

# Reset steps

- Go to the `Powershell` directory we created in Stage 1, and then move to src directory using command - `cd src`.
- Delete the <serviceName> folder and all the content inside it as well.
- Move back to `ProjectHome` and you are all reset to start again.

# Readme Content

### AutoRest Configuration

> see https://aka.ms/autorest

```yaml

# pin the swagger version by using the commit id instead of branch name

commit: <commitId>

require:
- $(this-folder)/../../readme.azure.noprofile.md
- $(repo)/specification/<serviceSpecs>/resource-manager/readme.md

try-require:
- $(repo)/specification/<serviceSpecs>/resource-manager/readme.powershell.md

input-file:
- $(repo)/<specification/<swaggerFileSpecs>

module-version: 0.1.0

title: <serviceName>
service-name: <serviceName>
subject-prefix: $(service-name)

directive:

- where:
variant: ^(Create|Update)(?!.*?(Expanded|JsonFilePath|JsonString))
remove: true

- where:
variant: ^CreateViaIdentity$|^CreateViaIdentityExpanded$
remove: true

- where:
verb: Set
remove: true
```