Skip to content

Commit c2e092e

Browse files
notyashhhCopilot
andauthored
[MCP] Added Support for MCP Prompts (#28408)
Co-authored-by: Copilot <[email protected]>
1 parent 5f4391b commit c2e092e

File tree

10 files changed

+346
-28
lines changed

10 files changed

+346
-28
lines changed

tools/Mcp/package-lock.json

Lines changed: 60 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/Mcp/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"license": "ISC",
2020
"description": "",
2121
"dependencies": {
22-
"@modelcontextprotocol/sdk": "^1.9.0",
22+
"@modelcontextprotocol/sdk": "^1.17.3",
2323
"js-yaml": "^4.1.0",
2424
"zod": "^3.24.2"
2525
},

tools/Mcp/src/CodegenServer.ts

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
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 } from "./types.js";
4+
import { responseSchema, toolParameterSchema, toolSchema, promptSchema } from "./types.js";
55
import { ToolsService } from "./services/toolsService.js";
6+
import { PromptsService } from "./services/promptsService.js";
67
import { readFileSync } from "fs";
78
import path from "path";
89
import { fileURLToPath } from "url";
@@ -59,7 +60,6 @@ export class CodegenServer {
5960
params: ElicitRequest["params"],
6061
options?: RequestOptions
6162
): Promise<ElicitResult> {
62-
//TODO: add log
6363
return this._mcp.server.elicitInput(params, options);
6464
}
6565

@@ -91,28 +91,33 @@ export class CodegenServer {
9191
}
9292

9393
initPrompts() {
94-
this._mcp.prompt(
95-
"create-greeting",
96-
"Generate a customized greeting message",
97-
{ name: z.string().describe("Name of the person to greet"), style: z.string().describe("The style of greeting, such a formal, excited, or casual. If not specified casual will be used")},
98-
({ name, style = "casual" }: { name: string, style?: string }) => {
99-
return {
100-
messages: [
101-
{
102-
role: "user",
103-
content: {
104-
type: "text",
105-
text: `Please generate a greeting in ${style} style to ${name}.`,
106-
},
107-
},
108-
],
109-
};
110-
});
94+
const promptsService = PromptsService.getInstance().setServer(this);
95+
const promptsSchemas = (specs.prompts || []) as promptSchema[];
96+
for (const schema of promptsSchemas) {
97+
const parameter = promptsService.createPromptParametersFromSchema(schema.parameters);
98+
const callback = promptsService.getPrompts(schema.callbackName, this._responses.get(schema.name));
99+
this._mcp.prompt(
100+
schema.name,
101+
schema.description,
102+
parameter,
103+
(args: any) => callback(args)
104+
);
105+
}
111106
}
112107

113108
initResponses() {
114109
(responses as responseSchema[])?.forEach((response: responseSchema) => {
115-
this._responses.set(response.name, response.text);
110+
let text = response.text;
111+
if (text.startsWith("@file:")) {
112+
const relPath = text.replace("@file:", "");
113+
const absPath = path.join(srcPath, "specs", relPath);
114+
try {
115+
text = readFileSync(absPath, "utf-8");
116+
} catch (e) {
117+
console.error(`Failed to load prompt file ${absPath}:`, e);
118+
}
119+
}
120+
this._responses.set(response.name, text);
116121
});
117122
}
118123
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { z, ZodRawShape } from "zod";
2+
import { promptSchema, promptParameterSchema } from "../types.js";
3+
import { CodegenServer } from "../CodegenServer.js";
4+
5+
6+
export class PromptsService {
7+
private static _instance: PromptsService;
8+
private _server: CodegenServer | null = null;
9+
private constructor() {}
10+
11+
static getInstance(): PromptsService {
12+
if (!PromptsService._instance) {
13+
PromptsService._instance = new PromptsService();
14+
}
15+
return PromptsService._instance;
16+
}
17+
18+
setServer(server: CodegenServer): PromptsService {
19+
this._server = server;
20+
return this;
21+
}
22+
23+
getPrompts<Args extends ZodRawShape>(name: string, responseTemplate: string | undefined) {
24+
let func;
25+
switch (name) {
26+
case "createGreetingPrompt":
27+
func = this.createGreetingPrompt<Args>;
28+
break;
29+
case "createPartnerModuleWorkflow":
30+
func = this.createPartnerModuleWorkflow<Args>;
31+
break;
32+
default:
33+
throw new Error(`Prompt ${name} not found`);
34+
}
35+
return this.constructCallback<Args>(func, responseTemplate);
36+
}
37+
38+
constructCallback<Args extends ZodRawShape>(fn: (arr: Args) => Promise<string[]>, responseTemplate: string | undefined) {
39+
return async (args: Args) => {
40+
const argsArray = await fn(args);
41+
const response = this.getResponseString(argsArray, responseTemplate) ?? "";
42+
return {
43+
messages: [
44+
{
45+
role: "user" as const,
46+
content: {
47+
type: "text" as const,
48+
text: response
49+
}
50+
}
51+
]
52+
};
53+
};
54+
}
55+
56+
getResponseString(args: string[], responseTemplate: string | undefined): string | undefined {
57+
if (!args || args.length === 0) {
58+
return responseTemplate;
59+
}
60+
let response = responseTemplate;
61+
for (let i = 0; i < args.length; i++) {
62+
response = response?.replaceAll(`{${i}}`, args[i]);
63+
}
64+
return response;
65+
}
66+
67+
createPromptParametersFromSchema(schemas: promptParameterSchema[]) {
68+
const parameter: { [k: string]: any } = {};
69+
for (const schema of schemas) {
70+
const base = schema.optional ? z.any().optional() : z.any();
71+
switch (schema.type) {
72+
case "string":
73+
parameter[schema.name] = (schema.optional ? z.string().optional() : z.string()).describe(schema.description);
74+
break;
75+
case "number":
76+
parameter[schema.name] = (schema.optional ? z.number().optional() : z.number()).describe(schema.description);
77+
break;
78+
case "boolean":
79+
parameter[schema.name] = (schema.optional ? z.boolean().optional() : z.boolean()).describe(schema.description);
80+
break;
81+
case "array":
82+
parameter[schema.name] = (schema.optional ? z.array(z.string()).optional() : z.array(z.string())).describe(schema.description);
83+
break;
84+
default:
85+
throw new Error(`Unsupported parameter type: ${schema.type}`);
86+
}
87+
}
88+
return parameter;
89+
}
90+
91+
// prompt implementations
92+
createGreetingPrompt = async <Args extends ZodRawShape>(args: Args): Promise<string[]> => {
93+
const values = Object.values(args);
94+
const name = values[0] as unknown as string; // required
95+
const style = (values[1] as unknown as string) || "casual"; // optional fallback
96+
return [name, style];
97+
};
98+
99+
100+
createPartnerModuleWorkflow = async <Args extends ZodRawShape>(args: Args): Promise<string[]> => {
101+
const { } = args as any;
102+
return [];
103+
};
104+
}
105+
106+
107+
// Some Testing Specs:
108+
109+
// {
110+
// "name": "partner-module-workflow",
111+
// "description": "Full autonomous workflow instructions to generate a partner Azure PowerShell module via Autorest.",
112+
// "parameters": [
113+
// {"name": "serviceName", "description": "Service name placeholder. This also often corresponds with the Name of the Powershell Module.", "type": "string", "optional": true},
114+
// {"name": "commitId", "description": "Commit id of the swagger from azure-rest-api-specs", "type": "string", "optional": true},
115+
// {"name": "serviceSpecs", "description": "Service specs path under specification. Path of a swagger upto the resource-manager.", "type": "string", "optional": true},
116+
// {"name": "swaggerFileSpecs", "description": "Swagger JSON relative path. Entire path of the swagger down to the openapi file.", "type": "string", "optional": true}
117+
// ],
118+
// "callbackName": "createPartnerModuleWorkflow"
119+
// }

tools/Mcp/src/services/toolsService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CallToolResult, ElicitRequest, ElicitResult } from '@modelcontextprotocol/sdk/types.js';
1+
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
22
import { z, ZodRawShape, ZodType, ZodTypeAny } from "zod";
33
import * as utils from "./utils.js";
44
import path from 'path';

0 commit comments

Comments
 (0)