33
44using Azure . Functions . Cli . Common ;
55using Azure . Functions . Cli . Helpers ;
6- using Colors . Net ;
76using Newtonsoft . Json ;
87using Newtonsoft . Json . Linq ;
98
109namespace Azure . Functions . Cli . ConfigurationProfiles
1110{
1211 internal class McpCustomHandlerConfigurationProfile : IConfigurationProfile
1312 {
14- private static readonly WorkerRuntime [ ] _supportedRuntimes = new [ ]
15- {
16- WorkerRuntime . DotnetIsolated ,
17- WorkerRuntime . Python ,
18- WorkerRuntime . Node
19- } ;
13+ // This feature flag enables MCP (Multi-Container Platform) support for custom handlers
14+ // This flag is not required locally, but is required when deploying to Azure environments.
15+ private const string McpFeatureFlag = "EnableMcpCustomHandlerPreview" ;
2016
21- public string Name { get ; set ; } = "mcp-custom-handler" ;
17+ public string Name { get ; } = "mcp-custom-handler" ;
2218
2319 public async Task ApplyAsync ( WorkerRuntime workerRuntime , bool shouldForce = false )
2420 {
25- ValidateWorkerRuntime ( workerRuntime ) ;
2621 await ApplyHostJsonAsync ( shouldForce ) ;
2722 await ApplyLocalSettingsAsync ( workerRuntime , shouldForce ) ;
2823 }
2924
30- private static void ValidateWorkerRuntime ( WorkerRuntime workerRuntime )
25+ public async Task ApplyHostJsonAsync ( bool force )
3126 {
32- if ( ! _supportedRuntimes . Contains ( workerRuntime ) )
33- {
34- var supportedRuntimesList = string . Join ( ", " , _supportedRuntimes . Select ( r => WorkerRuntimeLanguageHelper . GetRuntimeMoniker ( r ) ) ) ;
35- throw new CliException ( $ "The MCP custom handler configuration profile only supports the following runtimes: { supportedRuntimesList } . " +
36- $ "The current runtime '{ WorkerRuntimeLanguageHelper . GetRuntimeMoniker ( workerRuntime ) } ' is not supported.") ;
37- }
38- }
27+ bool changed = false ;
28+ string baseHostJson ;
3929
40- public async Task ApplyHostJsonAsync ( bool shouldForce )
41- {
4230 // Check if host.json exists and read it, otherwise use the default template
43- var hostJsonPath = Path . Combine ( Environment . CurrentDirectory , Constants . HostJsonFileName ) ;
44- var hostExists = FileSystemHelpers . FileExists ( hostJsonPath ) ;
45- string baseHostJson ;
31+ string hostJsonPath = Path . Combine ( Environment . CurrentDirectory , Constants . HostJsonFileName ) ;
4632
47- JObject hostJsonObj ;
48- if ( hostExists )
33+ if ( FileSystemHelpers . FileExists ( hostJsonPath ) )
4934 {
50- ColoredConsole . WriteLine ( $ "Applying MCP custom handler configuration profile to existing { hostJsonPath } ..." ) ;
35+ SetupProgressLogger . FileFound ( "host.json" , hostJsonPath ) ;
5136 baseHostJson = await FileSystemHelpers . ReadAllTextFromFileAsync ( hostJsonPath ) ;
5237 }
5338 else
5439 {
40+ SetupProgressLogger . FileCreated ( "host.json" , hostJsonPath ) ;
5541 baseHostJson = await StaticResources . HostJson ;
5642 }
5743
58- hostJsonObj = JsonConvert . DeserializeObject < JObject > ( baseHostJson ) ;
59-
60- var changed = false ;
44+ JObject hostJsonObj = JsonConvert . DeserializeObject < JObject > ( baseHostJson ) ;
6145
62- // Add configurationProfile if missing or if shouldForce is true
63- if ( ! hostJsonObj . TryGetValue ( "configurationProfile" , StringComparison . OrdinalIgnoreCase , out _ ) || shouldForce )
46+ // Add configurationProfile if missing or if force is true
47+ if ( ! hostJsonObj . TryGetValue ( "configurationProfile" , StringComparison . OrdinalIgnoreCase , out _ ) || force )
6448 {
65- hostJsonObj [ "configurationProfile" ] = "mcp-custom-handler" ;
49+ hostJsonObj [ "configurationProfile" ] = Name ;
6650 changed = true ;
6751 }
6852
69- // Add customHandler section if missing or if shouldForce is true
70- if ( ! hostJsonObj . TryGetValue ( "customHandler" , StringComparison . OrdinalIgnoreCase , out _ ) || shouldForce )
53+ // Add customHandler section if missing or if force is true
54+ if ( ! hostJsonObj . TryGetValue ( "customHandler" , StringComparison . OrdinalIgnoreCase , out _ ) || force )
7155 {
7256 hostJsonObj [ "customHandler" ] = new JObject
7357 {
@@ -82,58 +66,55 @@ public async Task ApplyHostJsonAsync(bool shouldForce)
8266
8367 if ( changed )
8468 {
85- var hostJsonContent = JsonConvert . SerializeObject ( hostJsonObj , Formatting . Indented ) ;
69+ string hostJsonContent = JsonConvert . SerializeObject ( hostJsonObj , Formatting . Indented ) ;
8670 await FileSystemHelpers . WriteAllTextToFileAsync ( hostJsonPath , hostJsonContent ) ;
71+ SetupProgressLogger . Ok ( "host.json" , "Updated with MCP configuration profile" ) ;
72+ }
73+ else
74+ {
75+ SetupProgressLogger . Warn ( "host.json" , "Already configured (use --force to overwrite)" ) ;
8776 }
88-
89- ColoredConsole . WriteLine ( changed
90- ? "Updated host.json with MCP configuration profile."
91- : "host.json already contains MCP configuration profile. Please pass in `--force` to overwrite.\n " ) ;
9277 }
9378
94- public async Task ApplyLocalSettingsAsync ( WorkerRuntime workerRuntime , bool shouldForce )
79+ public async Task ApplyLocalSettingsAsync ( WorkerRuntime workerRuntime , bool force )
9580 {
96- // Check if local.settings.json exists and read it, otherwise use the default template
97- var localSettingsPath = Path . Combine ( Environment . CurrentDirectory , "local.settings.json" ) ;
98- var localExists = FileSystemHelpers . FileExists ( localSettingsPath ) ;
81+ bool changed = false ;
9982 string baseLocalSettings ;
10083
101- JObject localObj ;
102- if ( localExists )
84+ // Check if local.settings.json exists and read it, otherwise use the default template
85+ string localSettingsPath = Path . Combine ( Environment . CurrentDirectory , "local.settings.json" ) ;
86+
87+ if ( FileSystemHelpers . FileExists ( localSettingsPath ) )
10388 {
104- ColoredConsole . WriteLine ( $ "Applying MCP custom handler configuration profile to existing { localSettingsPath } ..." ) ;
89+ SetupProgressLogger . FileFound ( "local.settings.json" , localSettingsPath ) ;
10590 baseLocalSettings = await FileSystemHelpers . ReadAllTextFromFileAsync ( localSettingsPath ) ;
10691 }
10792 else
10893 {
94+ SetupProgressLogger . FileCreated ( "local.settings.json" , localSettingsPath ) ;
10995 baseLocalSettings = await StaticResources . LocalSettingsJson ;
11096
11197 // Replace placeholders in the template
11298 baseLocalSettings = baseLocalSettings . Replace ( $ "{{{Constants.FunctionsWorkerRuntime}}}", WorkerRuntimeLanguageHelper . GetRuntimeMoniker ( workerRuntime ) ) ;
11399 baseLocalSettings = baseLocalSettings . Replace ( $ "{{{Constants.AzureWebJobsStorage}}}", Constants . StorageEmulatorConnectionString ) ;
114100 }
115101
116- localObj = JsonConvert . DeserializeObject < JObject > ( baseLocalSettings ) ;
117-
118- var changed = false ;
119- var values = localObj [ "Values" ] as JObject ?? new JObject ( ) ;
120- var runtimeKey = Constants . FunctionsWorkerRuntime ;
102+ JObject localObj = JsonConvert . DeserializeObject < JObject > ( baseLocalSettings ) ;
103+ JObject values = localObj [ "Values" ] as JObject ?? [ ] ;
121104
122- // Determine moniker for default; if existing runtime present, do not overwrite unless shouldForce is true
123- if ( ! values . TryGetValue ( runtimeKey , StringComparison . OrdinalIgnoreCase , out _ ) || shouldForce )
105+ // Determine moniker for default; if existing runtime present, do not overwrite unless force is true
106+ if ( ! values . TryGetValue ( Constants . FunctionsWorkerRuntime , StringComparison . OrdinalIgnoreCase , out _ ) || force )
124107 {
125- var runtimeMoniker = WorkerRuntimeLanguageHelper . GetRuntimeMoniker ( workerRuntime ) ;
126-
127- values [ runtimeKey ] = runtimeMoniker ;
108+ string runtimeMoniker = WorkerRuntimeLanguageHelper . GetRuntimeMoniker ( workerRuntime ) ;
109+ values [ Constants . FunctionsWorkerRuntime ] = runtimeMoniker ;
128110 changed = true ;
129111 }
130112
131- // Handle AzureWebJobsFeatureFlags - append if exists and shouldForce is enabled, create if not
132- const string mcpFeatureFlag = "EnableMcpCustomHandlerPreview" ;
133- var azureWebJobsFeatureFlagsExists = values . TryGetValue ( "AzureWebJobsFeatureFlags" , StringComparison . OrdinalIgnoreCase , out var existingFlagsToken ) ;
134- if ( azureWebJobsFeatureFlagsExists && shouldForce )
113+ // Handle AzureWebJobsFeatureFlags - append if exists and force is enabled, create if not
114+ bool azureWebJobsFeatureFlagsExists = values . TryGetValue ( Constants . AzureWebJobsFeatureFlags , StringComparison . OrdinalIgnoreCase , out var existingFlagsToken ) ;
115+ if ( azureWebJobsFeatureFlagsExists && force )
135116 {
136- var existingFlags = existingFlagsToken ? . ToString ( ) ?? string . Empty ;
117+ string existingFlags = existingFlagsToken ? . ToString ( ) ?? string . Empty ;
137118
138119 // Split by comma and trim whitespace
139120 var flagsList = existingFlags
@@ -143,9 +124,9 @@ public async Task ApplyLocalSettingsAsync(WorkerRuntime workerRuntime, bool shou
143124 . ToList ( ) ;
144125
145126 // Add the MCP feature flag if it's not already present
146- if ( ! flagsList . Contains ( mcpFeatureFlag , StringComparer . OrdinalIgnoreCase ) )
127+ if ( ! flagsList . Contains ( McpFeatureFlag , StringComparer . OrdinalIgnoreCase ) )
147128 {
148- flagsList . Add ( mcpFeatureFlag ) ;
129+ flagsList . Add ( McpFeatureFlag ) ;
149130
150131 // Rejoin with comma and space
151132 values [ "AzureWebJobsFeatureFlags" ] = string . Join ( "," , flagsList ) ;
@@ -155,20 +136,22 @@ public async Task ApplyLocalSettingsAsync(WorkerRuntime workerRuntime, bool shou
155136 else if ( ! azureWebJobsFeatureFlagsExists )
156137 {
157138 // No existing feature flags, create with just our flag
158- values [ "AzureWebJobsFeatureFlags" ] = mcpFeatureFlag ;
139+ values [ "AzureWebJobsFeatureFlags" ] = McpFeatureFlag ;
159140 changed = true ;
141+ SetupProgressLogger . Warn ( "local.settings.json" , $ "Added feature flag '{ McpFeatureFlag } '") ;
160142 }
161143
162144 if ( changed )
163145 {
164146 localObj [ "Values" ] = values ;
165- var localContent = JsonConvert . SerializeObject ( localObj , Formatting . Indented ) ;
147+ string localContent = JsonConvert . SerializeObject ( localObj , Formatting . Indented ) ;
166148 await FileSystemHelpers . WriteAllTextToFileAsync ( localSettingsPath , localContent ) ;
149+ SetupProgressLogger . Ok ( "local.settings.json" , "Updated settings" ) ;
150+ }
151+ else
152+ {
153+ SetupProgressLogger . Warn ( "local.settings.json" , "Already configured (use --force to overwrite)" ) ;
167154 }
168-
169- ColoredConsole . WriteLine ( changed
170- ? "Updated local.settings.json with MCP configuration profile."
171- : "local.settings.json already contains MCP configuration profile. Please pass in `--force` to overwrite.\n " ) ;
172155 }
173156 }
174157}
0 commit comments