Skip to content

Commit 94c8b5c

Browse files
notyashhhCopilot
andauthored
[MCP] Modular inputs + Enhanced Quality Tests (#28460)
Co-authored-by: Copilot <[email protected]>
1 parent 423e124 commit 94c8b5c

37 files changed

+5666
-313
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ launchSettings.json
233233
/tools/Modules/tmp
234234
/tools/Az/Az.psm1
235235
/tools/AzPreview/AzPreview.psm1
236+
/tools/Mcp/.logs
236237
/Azure.PowerShell.sln
237238

238239
# Added due to scan

tools/Mcp/README.md

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,64 @@
11
# Azure PowerShell Codegen MCP Server
22

3-
A Model Context Protocol (MCP) server that provides tools for generating and managing Azure PowerShell modules using AutoRest. This server helps automate common tasks in the Azure PowerShell code generation process, including handling polymorphism, model directives, and code generation.
3+
A Model Context Protocol (MCP) server that provides tools for generating and managing Azure PowerShell modules using AutoRest. It now also orchestrates help‑driven example generation, CRUD test scaffolding, and an opinionated partner workflow to keep outputs deterministic and consistent.
44

55
## Overview
66

77
This MCP server is designed to work with Azure PowerShell module development workflows. It provides specialized tools for:
88

9-
- **AutoRest Code Generation**: Generate PowerShell modules from OpenAPI specifications
9+
- **Module Scaffolding**: Interactive selection of service → provider → API version and creation of the `<ModuleName>.Autorest` structure
10+
- **AutoRest Code Generation**: Generate PowerShell modules from OpenAPI specifications (reset/generate/build sequence)
11+
- **Example Generation**: Create example scripts from swagger example JSON while filtering strictly to parameters documented in help markdown
12+
- **Test Generation**: Produce per‑resource CRUD test files (idempotent, includes negative test) using the same help‑driven parameter filtering
13+
- **Help‑Driven Parameter Filtering**: Only parameters present in the generated help (`/src/<Module>/help/*.md`) are allowed in examples/tests
1014
- **Model Management**: Handle model directives like `no-inline` and `model-cmdlet`
11-
- **Polymorphism Support**: Automatically detect and configure polymorphic types
12-
- **YAML Configuration**: Parse and manipulate AutoRest configuration files
15+
- **Polymorphism Support**: Automatically detect and configure parent/child discriminator relationships
16+
- **YAML Configuration Utilities**: Parse and manipulate AutoRest configuration blocks
17+
- **Partner Workflow Prompt**: A single prompt that encodes the end‑to‑end deterministic workflow
1318

1419
## Features
1520

1621
### Available Tools
1722

18-
1. **generate-autorest**
19-
- Generates PowerShell code using AutoRest
20-
- Parameters: `workingDirectory` (absolute path to README.md)
21-
22-
2. **no-inline**
23-
- Converts flattened models to non-inline parameters
24-
- Parameters: `modelNames` (array of model names to make non-inline)
25-
- Useful for complex nested models that shouldn't be flattened
26-
27-
3. **model-cmdlet**
28-
- Creates `New-` cmdlets for specified models
29-
- Parameters: `modelNames` (array of model names)
30-
- Generates cmdlets with naming pattern: `New-Az{SubjectPrefix}{ModelName}Object`
31-
32-
4. **polymorphism**
33-
- Handles polymorphic type detection and configuration
34-
- Parameters: `workingDirectory` (absolute path to README.md)
35-
- Automatically identifies parent-child type relationships
23+
1. **setup-module-structure**
24+
- Interactive service → provider → API version selection and module name capture
25+
- Scaffolds `src/<Module>/<Module>.Autorest/` plus initial `README.md`
26+
- Output placeholder `{0}` = module name
27+
28+
2. **generate-autorest**
29+
- Executes Autorest reset, generate, and PowerShell build steps within the given working directory
30+
- Parameters: `workingDirectory` (absolute path to the Autorest folder containing README.md)
31+
- Output placeholder `{0}` = working directory
32+
33+
3. **create-example**
34+
- Downloads swagger example JSON, filters parameters to those documented in help markdown (`/src/<Module>/help/<Cmdlet>.md`), and writes example scripts under `examples/`
35+
- Parameters: `workingDirectory`
36+
- Output placeholders: `{0}` = harvested specs path, `{1}` = examples dir, `{2}` = reference ideal example dirs
37+
38+
4. **create-test**
39+
- Generates new `<ResourceName>.Crud.Tests.ps1` files (does not modify stubs) with Create/Get/List/Update/Delete/Negative blocks, using help‑filtered parameters
40+
- Parameters: `workingDirectory`
41+
- Output placeholders: `{0}` = harvested specs path, `{1}` = test dir, `{2}` = reference ideal test dirs
42+
43+
5. **polymorphism**
44+
- Detects discriminator parents and child model names to aid directive insertion
45+
- Parameters: `workingDirectory`
46+
- Output placeholders: `{0}` = parents, `{1}` = children, `{2}` = working directory
47+
48+
6. **no-inline**
49+
- Lists models to be marked `no-inline` (caller inserts directive into README Autorest YAML)
50+
- Parameters: `modelNames` (array)
51+
- Output `{0}` = comma-separated model list
52+
53+
7. **model-cmdlet**
54+
- Lists models for which `New-` object construction cmdlets should be added via directives
55+
- Parameters: `modelNames` (array)
56+
- Output `{0}` = comma-separated model list
3657

3758
### Available Prompts
3859

39-
- **create-greeting**: Generate customized greeting messages (example prompt)
60+
- **partner-module-workflow**: Canonical end‑to‑end instruction set (module structure → generation → examples → tests → regeneration)
61+
- **create-greeting**: Sample/demo greeting prompt
4062

4163
## Installation
4264

tools/Mcp/src/CodegenServer.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
33
import { z } from "zod";
4-
import { responseSchema, toolParameterSchema, toolSchema, promptSchema } from "./types.js";
4+
import { responseSchema, toolParameterSchema, toolSchema, promptSchema, resourceSchema } from "./types.js";
55
import { ToolsService } from "./services/toolsService.js";
66
import { PromptsService } from "./services/promptsService.js";
7+
import { ResourcesService } from "./services/resourcesService.js";
78
import { readFileSync } from "fs";
89
import path from "path";
910
import { fileURLToPath } from "url";
1011
import { RequestOptions } from "https";
1112
import { ElicitRequest, ElicitResult } from "@modelcontextprotocol/sdk/types.js";
13+
import { logger } from "./services/logger.js";
1214

1315
const __dirname = path.dirname(fileURLToPath(import.meta.url));
1416
const srcPath = path.resolve(__dirname, "..", "src");
@@ -37,6 +39,7 @@ export class CodegenServer {
3739
this.initResponses();
3840
this.initTools();
3941
this.initPrompts();
42+
this.initResources();
4043
}
4144

4245
// dummy method for sending sampling request
@@ -74,6 +77,9 @@ export class CodegenServer {
7477
await this._mcp.connect(transport);
7578
}
7679

80+
public getResponseTemplate(name: string): string | undefined {
81+
return this._responses.get(name);
82+
}
7783

7884
initTools() {
7985
const toolsService = ToolsService.getInstance().setServer(this);
@@ -100,7 +106,44 @@ export class CodegenServer {
100106
schema.name,
101107
schema.description,
102108
parameter,
103-
(args: any) => callback(args)
109+
async (args: any) => {
110+
const correlationId = `${schema.name}-${Date.now()}-${Math.random().toString(16).slice(2,8)}`;
111+
logger.debug('Prompt started', { prompt: schema.name, correlationId });
112+
try {
113+
const result = await callback(args);
114+
logger.info('Prompt completed', { prompt: schema.name, correlationId });
115+
return result;
116+
} catch (err: any) {
117+
logger.error('Prompt failed', { prompt: schema.name, correlationId }, err);
118+
throw err;
119+
}
120+
}
121+
);
122+
}
123+
}
124+
125+
initResources() {
126+
const resourcesService = ResourcesService.getInstance().setServer(this);
127+
const resourcesSchemas = (specs.resources || []) as resourceSchema[];
128+
for (const schema of resourcesSchemas) {
129+
const parameter = resourcesService.createResourceParametersFromSchema(schema.parameters || []);
130+
const callback = resourcesService.getResources(schema.callbackName, this._responses.get(schema.name));
131+
this._mcp.resource(
132+
schema.name,
133+
schema.description,
134+
parameter,
135+
async (args: any) => {
136+
const correlationId = `${schema.name}-${Date.now()}-${Math.random().toString(16).slice(2,8)}`;
137+
logger.debug('Resource requested', { resource: schema.name, correlationId });
138+
try {
139+
const result = await callback(args);
140+
logger.info('Resource provided', { resource: schema.name, correlationId });
141+
return result;
142+
} catch (err: any) {
143+
logger.error('Resource failed', { resource: schema.name, correlationId }, err);
144+
throw err;
145+
}
146+
}
104147
);
105148
}
106149
}
@@ -110,14 +153,16 @@ export class CodegenServer {
110153
let text = response.text;
111154
if (text.startsWith("@file:")) {
112155
const relPath = text.replace("@file:", "");
113-
const absPath = path.join(srcPath, "specs", relPath);
156+
const absPath = path.join(srcPath, relPath);
114157
try {
115158
text = readFileSync(absPath, "utf-8");
116-
} catch (e) {
117-
console.error(`Failed to load prompt file ${absPath}:`, e);
159+
} catch (e: any) {
160+
logger.error(`Failed to load prompt file`, { absPath }, e as Error);
118161
}
119162
}
120163
this._responses.set(response.name, text);
121164
});
122165
}
166+
167+
123168
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<!-- region Generated -->
2+
# Az.{moduleName}
3+
This directory contains the PowerShell module for the {moduleName} service.
4+
5+
---
6+
## Info
7+
- Modifiable: yes
8+
- Generated: all
9+
- Committed: yes
10+
- Packaged: yes
11+
12+
---
13+
## Detail
14+
This module was primarily generated via [AutoRest](https://github.com/Azure/autorest) using the [PowerShell](https://github.com/Azure/autorest.powershell) extension.
15+
16+
## Module Requirements
17+
- [Az.Accounts module](https://www.powershellgallery.com/packages/Az.Accounts/), version 2.7.5 or greater
18+
19+
## Authentication
20+
AutoRest does not generate authentication code for the module. Authentication is handled via Az.Accounts by altering the HTTP payload before it is sent.
21+
22+
## Development
23+
For information on how to develop for `Az.{moduleName}`, see [how-to.md](how-to.md).
24+
<!-- endregion -->
25+
26+
---
27+
### AutoRest Configuration
28+
> see https://aka.ms/autorest
29+
30+
```yaml
31+
32+
commit: {commitId}
33+
34+
require:
35+
- $(this-folder)/../../readme.azure.noprofile.md
36+
- $(repo)/specification/{serviceSpecs}/readme.md
37+
38+
try-require:
39+
- $(repo)/specification/{serviceSpecs}/readme.powershell.md
40+
41+
input-file:
42+
- $(repo)/{swaggerFileSpecs}
43+
44+
module-version: 0.1.0
45+
46+
title: {moduleName}
47+
service-name: {moduleName}
48+
subject-prefix: $(service-name)
49+
50+
directive:
51+
52+
- where:
53+
variant: ^(Create|Update)(?!.*?(Expanded|JsonFilePath|JsonString))
54+
remove: true
55+
56+
- where:
57+
variant: ^CreateViaIdentity$|^CreateViaIdentityExpanded$
58+
remove: true
59+
60+
- where:
61+
verb: Set
62+
remove: true
63+
```
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
## LLM Example Generation Directions
2+
3+
You have just called tool `create-example` for a freshly generated module.
4+
5+
Inputs:
6+
- `{0}` = source swagger example JSON directory (read only)
7+
- `{1}` = target examples directory (write here only)
8+
- `{2}` = reference example dirs (style cues; may be empty)
9+
- helpDir = parentOf({1}) with `.Autorest` removed + `/help` (read only)
10+
11+
Goal: Produce minimal, runnable PowerShell example scripts for each relevant cmdlet using ONLY parameters documented in help.
12+
13+
Algorithm (repeat per cmdlet needed):
14+
1. Open `helpDir/<CmdletName>.md`.
15+
2. Collect allowed params = (a) params in first syntax line(s) in code fences + (b) every `### -ParamName` heading. Exclude `CommonParameters`.
16+
3. For each swagger JSON in `{0}` referencing this cmdlet, map its fields to allowed params; drop non‑allowed silently.
17+
4. Order parameters: required (in the order of the first syntax signature) then optional alphabetical.
18+
5. Build one minimal example. Add a second variant ONLY if it demonstrates distinct optional parameters.
19+
20+
Rules:
21+
* Never invent or rename parameters; casing must match help.
22+
* Value selection precedence (per allowed parameter):
23+
1. If the swagger example JSON (source `{0}`) contains a concrete value for that parameter (after mapping), use that value directly.
24+
2. If the swagger value is obviously redacted (e.g. `"string"`, `"<string>"`, `"XXXX"`, empty, or null) then fall back to a stable placeholder instead of using the dummy.
25+
3. Otherwise (no concrete usable value) use a stable placeholder: `<ResourceGroupName>`, `<Location>`, `<SubscriptionId>`, `<Name>`, etc.
26+
* Do not substitute placeholders where a good swagger value exists.
27+
* If no allowed params remain after filtering, create/leave an empty file or a single comment line.
28+
* Do not copy help prose; output only script lines (and brief inline comments if helpful).
29+
* Mirror formatting style hints (indentation, spacing) from reference dirs `{2}` without copying their literal values.
30+
31+
Output Handling:
32+
- Modify/create files ONLY under `{1}`; no other directories.
33+
- Preserve existing example files, updating parameter sets/order as needed.
34+
35+
Quick Validation Checklist (stop if any fail):
36+
1. All parameters exist in help.
37+
2. Required parameters present & ordered first.
38+
3. No swagger‑only or duplicate parameters.
39+
4. Placeholders consistent.
40+
5. No redundant variant scripts.
41+
42+
Produce the final example script contents now; do not restate these instructions.
43+

0 commit comments

Comments
 (0)