Skip to content

Adding the MCP Resource Trigger Templates#4952

Closed
swapnil-nagar wants to merge 0 commit intomicrosoft:mainfrom
swapnil-nagar:main
Closed

Adding the MCP Resource Trigger Templates#4952
swapnil-nagar wants to merge 0 commit intomicrosoft:mainfrom
swapnil-nagar:main

Conversation

@swapnil-nagar
Copy link
Copy Markdown
Contributor

No description provided.

Copilot AI review requested due to automatic review settings March 31, 2026 19:53
swapnil-nagar added a commit to swapnil-nagar/vscode-azurefunctions that referenced this pull request Mar 31, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds MCP Resource trigger templates to the Node.js v4 (4.x) bundled template set and updates the “verified templates” allowlist and corresponding template count tests to account for the new template.

Changes:

  • Add McpResourceTrigger to the verified template ID set for script languages (v2+).
  • Update template-count tests to expect one additional verified template for JavaScript/TypeScript v4.
  • Update MCP Tool trigger templates and add new MCP Resource trigger templates to the nodejs-4.x backup templates.json.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.

File Description
test/templateCount.test.ts Bumps expected verified template counts for JS/TS v4 from 18 → 19.
src/templates/script/getScriptVerifiedTemplateIds.ts Adds McpResourceTrigger to the verified template ID list (v2+ regex set).
resources/backupTemplates/nodejs-4.x/templates/templates.json Updates MCP Tool trigger templates and introduces new MCP Resource trigger templates (JS/TS).
Comments suppressed due to low confidence (4)

resources/backupTemplates/nodejs-4.x/templates/templates.json:622

  • This template string uses \n newlines, but the rest of this templates file consistently uses \r\n (e.g., TimerTrigger-TypeScript-4.x at line 537). Using a different newline style will generate files with inconsistent line endings; consider switching this template to \r\n for consistency.
            "%functionName%.js": "const { app, arg } = require('@azure/functions');\n\nfunction mcpToolHello(_toolArguments, context) {\n    const mcptoolargs = context.triggerMetadata.mcptoolargs || {};\n    const name = mcptoolargs.name;\n    console.info(`Hello ${name}, I am MCP Tool!`);\n    return `Hello ${name}, I am MCP Tool!`;\n}\n\napp.mcpTool('hello', {\n    toolName: 'hello',\n    description: 'Simple hello world MCP Tool that responses with a hello message.',\n    toolProperties:{\n        name: arg.string().describe('Name to greet'),\n    },\n    handler: mcpToolHello\n});"

resources/backupTemplates/nodejs-4.x/templates/templates.json:622

  • This template logs via console.info, but other Node v4 templates in this file use context.log (e.g., TimerTrigger-TypeScript-4.x at line 537). Using context.log keeps logging consistent with the Azure Functions host output and makes it easier for users to follow the established pattern.
            "%functionName%.js": "const { app, arg } = require('@azure/functions');\n\nfunction mcpToolHello(_toolArguments, context) {\n    const mcptoolargs = context.triggerMetadata.mcptoolargs || {};\n    const name = mcptoolargs.name;\n    console.info(`Hello ${name}, I am MCP Tool!`);\n    return `Hello ${name}, I am MCP Tool!`;\n}\n\napp.mcpTool('hello', {\n    toolName: 'hello',\n    description: 'Simple hello world MCP Tool that responses with a hello message.',\n    toolProperties:{\n        name: arg.string().describe('Name to greet'),\n    },\n    handler: mcpToolHello\n});"

resources/backupTemplates/nodejs-4.x/templates/templates.json:643

  • This template string uses \n newlines, but the rest of this templates file consistently uses \r\n (e.g., TimerTrigger-TypeScript-4.x at line 537). Consider switching this template to \r\n for consistent generated file line endings.
            "%functionName%.ts": "import { app, InvocationContext, arg } from \"@azure/functions\";\n\nexport async function mcpToolHello(_toolArguments: any, context: InvocationContext): Promise<string> {\n    const mcptoolargs = _toolArguments.arguments as {\n        name?: string;\n    };\n    const name = mcptoolargs?.name;\n\n    console.info(`Hello ${name}, I am MCP Tool!`);\n\n    return `Hello ${name}, I am MCP Tool!`;\n}\n\napp.mcpTool('hello', {\n    toolName: 'hello',\n    description: 'Simple hello world MCP Tool that responses with a hello message.',\n    toolProperties: {\n      name: arg.string().describe('Name to greet'),\n    },\n    handler: mcpToolHello\n});"

resources/backupTemplates/nodejs-4.x/templates/templates.json:643

  • This template logs via console.info, but other Node v4 templates in this file use context.log (e.g., TimerTrigger-TypeScript-4.x at line 537). Consider using context.log here as well to align with the rest of the templates and Azure Functions logging conventions.
            "%functionName%.ts": "import { app, InvocationContext, arg } from \"@azure/functions\";\n\nexport async function mcpToolHello(_toolArguments: any, context: InvocationContext): Promise<string> {\n    const mcptoolargs = _toolArguments.arguments as {\n        name?: string;\n    };\n    const name = mcptoolargs?.name;\n\n    console.info(`Hello ${name}, I am MCP Tool!`);\n\n    return `Hello ${name}, I am MCP Tool!`;\n}\n\napp.mcpTool('hello', {\n    toolName: 'hello',\n    description: 'Simple hello world MCP Tool that responses with a hello message.',\n    toolProperties: {\n      name: arg.string().describe('Name to greet'),\n    },\n    handler: mcpToolHello\n});"

"id": "McpResourceTrigger-JavaScript-4.x",
"runtime": "2",
"files": {
"%functionName%.js": "const { app, arg } = require('@azure/functions');\nconst fs = require('fs');\nconst path = require('path');\n\nconst WEATHER_WIDGET_URI = 'ui://weather/index.html';\nconst WEATHER_WIDGET_NAME = 'Weather Widget';\nconst WEATHER_WIDGET_DESCRIPTION = 'Interactive weather display for MCP Apps';\nconst WEATHER_WIDGET_MIME_TYPE = 'text/html;profile=mcp-app';\n\nconst TOOL_METADATA = JSON.stringify({\n ui: {\n resourceUri: WEATHER_WIDGET_URI\n }\n});\n\nconst RESOURCE_METADATA = JSON.stringify({\n ui: {\n prefersBorder: true\n }\n});\n\nasync function getWeatherWidget(_resourceContext, context) {\n try {\n const filePath = path.join(process.cwd(), 'app', 'dist', 'index.html');\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n context.log(`Error reading weather widget file: ${error}`);\n return '<!doctype html><html><body><h1>Weather Widget</h1><p>Widget content not found.</p></body></html>';\n }\n}\n\nasync function getWeather(_toolArguments, context) {\n const mcptoolargs = context.triggerMetadata.mcptoolargs || {};\n const location = mcptoolargs.location || 'Seattle';\n\n return {\n Location: location,\n TemperatureC: 20,\n Condition: 'Partly cloudy',\n Source: 'template-sample'\n };\n}\n\napp.mcpResource('getWeatherWidget', {\n uri: WEATHER_WIDGET_URI,\n resourceName: WEATHER_WIDGET_NAME,\n description: WEATHER_WIDGET_DESCRIPTION,\n mimeType: WEATHER_WIDGET_MIME_TYPE,\n metadata: RESOURCE_METADATA,\n handler: getWeatherWidget,\n});\n\napp.mcpTool('getWeather', {\n toolName: 'GetWeather',\n description: 'Returns current weather for a location.',\n toolProperties: {\n location: arg.string().describe('City name to check weather for (e.g., Seattle, New York, Miami)')\n },\n metadata: TOOL_METADATA,\n handler: getWeather,\n});",
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This template uses \n newlines, but other templates in this file use \r\n. Consider switching to \r\n escapes so generated files have consistent line endings with the rest of the nodejs-4.x templates.

Copilot uses AI. Check for mistakes.
"id": "McpResourceTrigger-JavaScript-4.x",
"runtime": "2",
"files": {
"%functionName%.js": "const { app, arg } = require('@azure/functions');\nconst fs = require('fs');\nconst path = require('path');\n\nconst WEATHER_WIDGET_URI = 'ui://weather/index.html';\nconst WEATHER_WIDGET_NAME = 'Weather Widget';\nconst WEATHER_WIDGET_DESCRIPTION = 'Interactive weather display for MCP Apps';\nconst WEATHER_WIDGET_MIME_TYPE = 'text/html;profile=mcp-app';\n\nconst TOOL_METADATA = JSON.stringify({\n ui: {\n resourceUri: WEATHER_WIDGET_URI\n }\n});\n\nconst RESOURCE_METADATA = JSON.stringify({\n ui: {\n prefersBorder: true\n }\n});\n\nasync function getWeatherWidget(_resourceContext, context) {\n try {\n const filePath = path.join(process.cwd(), 'app', 'dist', 'index.html');\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n context.log(`Error reading weather widget file: ${error}`);\n return '<!doctype html><html><body><h1>Weather Widget</h1><p>Widget content not found.</p></body></html>';\n }\n}\n\nasync function getWeather(_toolArguments, context) {\n const mcptoolargs = context.triggerMetadata.mcptoolargs || {};\n const location = mcptoolargs.location || 'Seattle';\n\n return {\n Location: location,\n TemperatureC: 20,\n Condition: 'Partly cloudy',\n Source: 'template-sample'\n };\n}\n\napp.mcpResource('getWeatherWidget', {\n uri: WEATHER_WIDGET_URI,\n resourceName: WEATHER_WIDGET_NAME,\n description: WEATHER_WIDGET_DESCRIPTION,\n mimeType: WEATHER_WIDGET_MIME_TYPE,\n metadata: RESOURCE_METADATA,\n handler: getWeatherWidget,\n});\n\napp.mcpTool('getWeather', {\n toolName: 'GetWeather',\n description: 'Returns current weather for a location.',\n toolProperties: {\n location: arg.string().describe('City name to check weather for (e.g., Seattle, New York, Miami)')\n },\n metadata: TOOL_METADATA,\n handler: getWeather,\n});",
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resource handler reads the widget HTML with fs.readFileSync on every invocation. Synchronous disk I/O can block the Node event loop; consider using async I/O (e.g., fs.promises.readFile) and/or caching the file contents at module load to avoid repeated reads.

Suggested change
"%functionName%.js": "const { app, arg } = require('@azure/functions');\nconst fs = require('fs');\nconst path = require('path');\n\nconst WEATHER_WIDGET_URI = 'ui://weather/index.html';\nconst WEATHER_WIDGET_NAME = 'Weather Widget';\nconst WEATHER_WIDGET_DESCRIPTION = 'Interactive weather display for MCP Apps';\nconst WEATHER_WIDGET_MIME_TYPE = 'text/html;profile=mcp-app';\n\nconst TOOL_METADATA = JSON.stringify({\n ui: {\n resourceUri: WEATHER_WIDGET_URI\n }\n});\n\nconst RESOURCE_METADATA = JSON.stringify({\n ui: {\n prefersBorder: true\n }\n});\n\nasync function getWeatherWidget(_resourceContext, context) {\n try {\n const filePath = path.join(process.cwd(), 'app', 'dist', 'index.html');\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n context.log(`Error reading weather widget file: ${error}`);\n return '<!doctype html><html><body><h1>Weather Widget</h1><p>Widget content not found.</p></body></html>';\n }\n}\n\nasync function getWeather(_toolArguments, context) {\n const mcptoolargs = context.triggerMetadata.mcptoolargs || {};\n const location = mcptoolargs.location || 'Seattle';\n\n return {\n Location: location,\n TemperatureC: 20,\n Condition: 'Partly cloudy',\n Source: 'template-sample'\n };\n}\n\napp.mcpResource('getWeatherWidget', {\n uri: WEATHER_WIDGET_URI,\n resourceName: WEATHER_WIDGET_NAME,\n description: WEATHER_WIDGET_DESCRIPTION,\n mimeType: WEATHER_WIDGET_MIME_TYPE,\n metadata: RESOURCE_METADATA,\n handler: getWeatherWidget,\n});\n\napp.mcpTool('getWeather', {\n toolName: 'GetWeather',\n description: 'Returns current weather for a location.',\n toolProperties: {\n location: arg.string().describe('City name to check weather for (e.g., Seattle, New York, Miami)')\n },\n metadata: TOOL_METADATA,\n handler: getWeather,\n});",
"%functionName%.js": "const { app, arg } = require('@azure/functions');\nconst fs = require('fs').promises;\nconst path = require('path');\n\nconst WEATHER_WIDGET_URI = 'ui://weather/index.html';\nconst WEATHER_WIDGET_NAME = 'Weather Widget';\nconst WEATHER_WIDGET_DESCRIPTION = 'Interactive weather display for MCP Apps';\nconst WEATHER_WIDGET_MIME_TYPE = 'text/html;profile=mcp-app';\n\nconst TOOL_METADATA = JSON.stringify({\n ui: {\n resourceUri: WEATHER_WIDGET_URI\n }\n});\n\nconst RESOURCE_METADATA = JSON.stringify({\n ui: {\n prefersBorder: true\n }\n});\n\nlet cachedWeatherWidgetHtml = null;\n\nasync function getWeatherWidget(_resourceContext, context) {\n try {\n if (!cachedWeatherWidgetHtml) {\n const filePath = path.join(process.cwd(), 'app', 'dist', 'index.html');\n cachedWeatherWidgetHtml = await fs.readFile(filePath, 'utf-8');\n }\n return cachedWeatherWidgetHtml;\n } catch (error) {\n context.log(`Error reading weather widget file: ${error}`);\n return '<!doctype html><html><body><h1>Weather Widget</h1><p>Widget content not found.</p></body></html>';\n }\n}\n\nasync function getWeather(_toolArguments, context) {\n const mcptoolargs = context.triggerMetadata.mcptoolargs || {};\n const location = mcptoolargs.location || 'Seattle';\n\n return {\n Location: location,\n TemperatureC: 20,\n Condition: 'Partly cloudy',\n Source: 'template-sample'\n };\n}\n\napp.mcpResource('getWeatherWidget', {\n uri: WEATHER_WIDGET_URI,\n resourceName: WEATHER_WIDGET_NAME,\n description: WEATHER_WIDGET_DESCRIPTION,\n mimeType: WEATHER_WIDGET_MIME_TYPE,\n metadata: RESOURCE_METADATA,\n handler: getWeatherWidget,\n});\n\napp.mcpTool('getWeather', {\n toolName: 'GetWeather',\n description: 'Returns current weather for a location.',\n toolProperties: {\n location: arg.string().describe('City name to check weather for (e.g., Seattle, New York, Miami)')\n },\n metadata: TOOL_METADATA,\n handler: getWeather,\n});",

Copilot uses AI. Check for mistakes.
"id": "McpResourceTrigger-JavaScript-4.x",
"runtime": "2",
"files": {
"%functionName%.js": "const { app, arg } = require('@azure/functions');\nconst fs = require('fs');\nconst path = require('path');\n\nconst WEATHER_WIDGET_URI = 'ui://weather/index.html';\nconst WEATHER_WIDGET_NAME = 'Weather Widget';\nconst WEATHER_WIDGET_DESCRIPTION = 'Interactive weather display for MCP Apps';\nconst WEATHER_WIDGET_MIME_TYPE = 'text/html;profile=mcp-app';\n\nconst TOOL_METADATA = JSON.stringify({\n ui: {\n resourceUri: WEATHER_WIDGET_URI\n }\n});\n\nconst RESOURCE_METADATA = JSON.stringify({\n ui: {\n prefersBorder: true\n }\n});\n\nasync function getWeatherWidget(_resourceContext, context) {\n try {\n const filePath = path.join(process.cwd(), 'app', 'dist', 'index.html');\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n context.log(`Error reading weather widget file: ${error}`);\n return '<!doctype html><html><body><h1>Weather Widget</h1><p>Widget content not found.</p></body></html>';\n }\n}\n\nasync function getWeather(_toolArguments, context) {\n const mcptoolargs = context.triggerMetadata.mcptoolargs || {};\n const location = mcptoolargs.location || 'Seattle';\n\n return {\n Location: location,\n TemperatureC: 20,\n Condition: 'Partly cloudy',\n Source: 'template-sample'\n };\n}\n\napp.mcpResource('getWeatherWidget', {\n uri: WEATHER_WIDGET_URI,\n resourceName: WEATHER_WIDGET_NAME,\n description: WEATHER_WIDGET_DESCRIPTION,\n mimeType: WEATHER_WIDGET_MIME_TYPE,\n metadata: RESOURCE_METADATA,\n handler: getWeatherWidget,\n});\n\napp.mcpTool('getWeather', {\n toolName: 'GetWeather',\n description: 'Returns current weather for a location.',\n toolProperties: {\n location: arg.string().describe('City name to check weather for (e.g., Seattle, New York, Miami)')\n },\n metadata: TOOL_METADATA,\n handler: getWeather,\n});",
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error logging uses string interpolation (${error}), which often ends up as [object Object] and loses stack/message details. Consider logging error.message/error.stack (or String(error) plus structured details) so users can diagnose missing widget file issues more easily.

Suggested change
"%functionName%.js": "const { app, arg } = require('@azure/functions');\nconst fs = require('fs');\nconst path = require('path');\n\nconst WEATHER_WIDGET_URI = 'ui://weather/index.html';\nconst WEATHER_WIDGET_NAME = 'Weather Widget';\nconst WEATHER_WIDGET_DESCRIPTION = 'Interactive weather display for MCP Apps';\nconst WEATHER_WIDGET_MIME_TYPE = 'text/html;profile=mcp-app';\n\nconst TOOL_METADATA = JSON.stringify({\n ui: {\n resourceUri: WEATHER_WIDGET_URI\n }\n});\n\nconst RESOURCE_METADATA = JSON.stringify({\n ui: {\n prefersBorder: true\n }\n});\n\nasync function getWeatherWidget(_resourceContext, context) {\n try {\n const filePath = path.join(process.cwd(), 'app', 'dist', 'index.html');\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n context.log(`Error reading weather widget file: ${error}`);\n return '<!doctype html><html><body><h1>Weather Widget</h1><p>Widget content not found.</p></body></html>';\n }\n}\n\nasync function getWeather(_toolArguments, context) {\n const mcptoolargs = context.triggerMetadata.mcptoolargs || {};\n const location = mcptoolargs.location || 'Seattle';\n\n return {\n Location: location,\n TemperatureC: 20,\n Condition: 'Partly cloudy',\n Source: 'template-sample'\n };\n}\n\napp.mcpResource('getWeatherWidget', {\n uri: WEATHER_WIDGET_URI,\n resourceName: WEATHER_WIDGET_NAME,\n description: WEATHER_WIDGET_DESCRIPTION,\n mimeType: WEATHER_WIDGET_MIME_TYPE,\n metadata: RESOURCE_METADATA,\n handler: getWeatherWidget,\n});\n\napp.mcpTool('getWeather', {\n toolName: 'GetWeather',\n description: 'Returns current weather for a location.',\n toolProperties: {\n location: arg.string().describe('City name to check weather for (e.g., Seattle, New York, Miami)')\n },\n metadata: TOOL_METADATA,\n handler: getWeather,\n});",
"%functionName%.js": "const { app, arg } = require('@azure/functions');\nconst fs = require('fs');\nconst path = require('path');\n\nconst WEATHER_WIDGET_URI = 'ui://weather/index.html';\nconst WEATHER_WIDGET_NAME = 'Weather Widget';\nconst WEATHER_WIDGET_DESCRIPTION = 'Interactive weather display for MCP Apps';\nconst WEATHER_WIDGET_MIME_TYPE = 'text/html;profile=mcp-app';\n\nconst TOOL_METADATA = JSON.stringify({\n ui: {\n resourceUri: WEATHER_WIDGET_URI\n }\n});\n\nconst RESOURCE_METADATA = JSON.stringify({\n ui: {\n prefersBorder: true\n }\n});\n\nasync function getWeatherWidget(_resourceContext, context) {\n try {\n const filePath = path.join(process.cwd(), 'app', 'dist', 'index.html');\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n context.log(`Error reading weather widget file: ${error && (error.stack || error.message) || String(error)}`);\n return '<!doctype html><html><body><h1>Weather Widget</h1><p>Widget content not found.</p></body></html>';\n }\n}\n\nasync function getWeather(_toolArguments, context) {\n const mcptoolargs = context.triggerMetadata.mcptoolargs || {};\n const location = mcptoolargs.location || 'Seattle';\n\n return {\n Location: location,\n TemperatureC: 20,\n Condition: 'Partly cloudy',\n Source: 'template-sample'\n };\n}\n\napp.mcpResource('getWeatherWidget', {\n uri: WEATHER_WIDGET_URI,\n resourceName: WEATHER_WIDGET_NAME,\n description: WEATHER_WIDGET_DESCRIPTION,\n mimeType: WEATHER_WIDGET_MIME_TYPE,\n metadata: RESOURCE_METADATA,\n handler: getWeatherWidget,\n});\n\napp.mcpTool('getWeather', {\n toolName: 'GetWeather',\n description: 'Returns current weather for a location.',\n toolProperties: {\n location: arg.string().describe('City name to check weather for (e.g., Seattle, New York, Miami)')\n },\n metadata: TOOL_METADATA,\n handler: getWeather,\n});",

Copilot uses AI. Check for mistakes.
"id": "McpResourceTrigger-TypeScript-4.x",
"runtime": "2",
"files": {
"%functionName%.ts": "import { app, InvocationContext, arg } from \"@azure/functions\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\nconst WEATHER_WIDGET_URI = 'ui://weather/index.html';\nconst WEATHER_WIDGET_NAME = 'Weather Widget';\nconst WEATHER_WIDGET_DESCRIPTION = 'Interactive weather display for MCP Apps';\nconst WEATHER_WIDGET_MIME_TYPE = 'text/html;profile=mcp-app';\n\nconst TOOL_METADATA = JSON.stringify({\n ui: {\n resourceUri: WEATHER_WIDGET_URI\n }\n});\n\nconst RESOURCE_METADATA = JSON.stringify({\n ui: {\n prefersBorder: true\n }\n});\n\nexport async function getWeatherWidget(_resourceContext: unknown, context: InvocationContext): Promise<string> {\n try {\n const filePath = path.join(process.cwd(), 'app', 'dist', 'index.html');\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n context.log(`Error reading weather widget file: ${error}`);\n return '<!doctype html><html><body><h1>Weather Widget</h1><p>Widget content not found.</p></body></html>';\n }\n}\n\nexport async function getWeather(_toolArguments: unknown, context: InvocationContext): Promise<object> {\n const mcptoolargs = (_toolArguments as { arguments?: { location?: string } })?.arguments || (context.triggerMetadata.mcptoolargs as { location?: string } | undefined) || {};\n const location = mcptoolargs.location || 'Seattle';\n\n return {\n Location: location,\n TemperatureC: 20,\n Condition: 'Partly cloudy',\n Source: 'template-sample'\n };\n}\n\napp.mcpResource('getWeatherWidget', {\n uri: WEATHER_WIDGET_URI,\n resourceName: WEATHER_WIDGET_NAME,\n description: WEATHER_WIDGET_DESCRIPTION,\n mimeType: WEATHER_WIDGET_MIME_TYPE,\n metadata: RESOURCE_METADATA,\n handler: getWeatherWidget,\n});\n\napp.mcpTool('getWeather', {\n toolName: 'GetWeather',\n description: 'Returns current weather for a location.',\n toolProperties: {\n location: arg.string().describe('City name to check weather for (e.g., Seattle, New York, Miami)')\n },\n metadata: TOOL_METADATA,\n handler: getWeather,\n});",
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This template uses \n newlines, but other templates in this file use \r\n. Consider switching to \r\n escapes so generated files have consistent line endings with the rest of the nodejs-4.x templates.

Copilot uses AI. Check for mistakes.
"id": "McpResourceTrigger-TypeScript-4.x",
"runtime": "2",
"files": {
"%functionName%.ts": "import { app, InvocationContext, arg } from \"@azure/functions\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\nconst WEATHER_WIDGET_URI = 'ui://weather/index.html';\nconst WEATHER_WIDGET_NAME = 'Weather Widget';\nconst WEATHER_WIDGET_DESCRIPTION = 'Interactive weather display for MCP Apps';\nconst WEATHER_WIDGET_MIME_TYPE = 'text/html;profile=mcp-app';\n\nconst TOOL_METADATA = JSON.stringify({\n ui: {\n resourceUri: WEATHER_WIDGET_URI\n }\n});\n\nconst RESOURCE_METADATA = JSON.stringify({\n ui: {\n prefersBorder: true\n }\n});\n\nexport async function getWeatherWidget(_resourceContext: unknown, context: InvocationContext): Promise<string> {\n try {\n const filePath = path.join(process.cwd(), 'app', 'dist', 'index.html');\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n context.log(`Error reading weather widget file: ${error}`);\n return '<!doctype html><html><body><h1>Weather Widget</h1><p>Widget content not found.</p></body></html>';\n }\n}\n\nexport async function getWeather(_toolArguments: unknown, context: InvocationContext): Promise<object> {\n const mcptoolargs = (_toolArguments as { arguments?: { location?: string } })?.arguments || (context.triggerMetadata.mcptoolargs as { location?: string } | undefined) || {};\n const location = mcptoolargs.location || 'Seattle';\n\n return {\n Location: location,\n TemperatureC: 20,\n Condition: 'Partly cloudy',\n Source: 'template-sample'\n };\n}\n\napp.mcpResource('getWeatherWidget', {\n uri: WEATHER_WIDGET_URI,\n resourceName: WEATHER_WIDGET_NAME,\n description: WEATHER_WIDGET_DESCRIPTION,\n mimeType: WEATHER_WIDGET_MIME_TYPE,\n metadata: RESOURCE_METADATA,\n handler: getWeatherWidget,\n});\n\napp.mcpTool('getWeather', {\n toolName: 'GetWeather',\n description: 'Returns current weather for a location.',\n toolProperties: {\n location: arg.string().describe('City name to check weather for (e.g., Seattle, New York, Miami)')\n },\n metadata: TOOL_METADATA,\n handler: getWeather,\n});",
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resource handler reads the widget HTML with fs.readFileSync on every invocation. Synchronous disk I/O can block the Node event loop; consider using async I/O (e.g., fs.promises.readFile) and/or caching the file contents at module load to avoid repeated reads.

Suggested change
"%functionName%.ts": "import { app, InvocationContext, arg } from \"@azure/functions\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\nconst WEATHER_WIDGET_URI = 'ui://weather/index.html';\nconst WEATHER_WIDGET_NAME = 'Weather Widget';\nconst WEATHER_WIDGET_DESCRIPTION = 'Interactive weather display for MCP Apps';\nconst WEATHER_WIDGET_MIME_TYPE = 'text/html;profile=mcp-app';\n\nconst TOOL_METADATA = JSON.stringify({\n ui: {\n resourceUri: WEATHER_WIDGET_URI\n }\n});\n\nconst RESOURCE_METADATA = JSON.stringify({\n ui: {\n prefersBorder: true\n }\n});\n\nexport async function getWeatherWidget(_resourceContext: unknown, context: InvocationContext): Promise<string> {\n try {\n const filePath = path.join(process.cwd(), 'app', 'dist', 'index.html');\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n context.log(`Error reading weather widget file: ${error}`);\n return '<!doctype html><html><body><h1>Weather Widget</h1><p>Widget content not found.</p></body></html>';\n }\n}\n\nexport async function getWeather(_toolArguments: unknown, context: InvocationContext): Promise<object> {\n const mcptoolargs = (_toolArguments as { arguments?: { location?: string } })?.arguments || (context.triggerMetadata.mcptoolargs as { location?: string } | undefined) || {};\n const location = mcptoolargs.location || 'Seattle';\n\n return {\n Location: location,\n TemperatureC: 20,\n Condition: 'Partly cloudy',\n Source: 'template-sample'\n };\n}\n\napp.mcpResource('getWeatherWidget', {\n uri: WEATHER_WIDGET_URI,\n resourceName: WEATHER_WIDGET_NAME,\n description: WEATHER_WIDGET_DESCRIPTION,\n mimeType: WEATHER_WIDGET_MIME_TYPE,\n metadata: RESOURCE_METADATA,\n handler: getWeatherWidget,\n});\n\napp.mcpTool('getWeather', {\n toolName: 'GetWeather',\n description: 'Returns current weather for a location.',\n toolProperties: {\n location: arg.string().describe('City name to check weather for (e.g., Seattle, New York, Miami)')\n },\n metadata: TOOL_METADATA,\n handler: getWeather,\n});",
"%functionName%.ts": "import { app, InvocationContext, arg } from \"@azure/functions\";\nimport { promises as fs } from \"fs\";\nimport * as path from \"path\";\n\nconst WEATHER_WIDGET_URI = 'ui://weather/index.html';\nconst WEATHER_WIDGET_NAME = 'Weather Widget';\nconst WEATHER_WIDGET_DESCRIPTION = 'Interactive weather display for MCP Apps';\nconst WEATHER_WIDGET_MIME_TYPE = 'text/html;profile=mcp-app';\n\nconst TOOL_METADATA = JSON.stringify({\n ui: {\n resourceUri: WEATHER_WIDGET_URI\n }\n});\n\nconst RESOURCE_METADATA = JSON.stringify({\n ui: {\n prefersBorder: true\n }\n});\n\nlet cachedWeatherWidgetHtml: string | null = null;\n\nexport async function getWeatherWidget(_resourceContext: unknown, context: InvocationContext): Promise<string> {\n try {\n if (cachedWeatherWidgetHtml !== null) {\n return cachedWeatherWidgetHtml;\n }\n\n const filePath = path.join(process.cwd(), 'app', 'dist', 'index.html');\n const widgetHtml = await fs.readFile(filePath, 'utf-8');\n cachedWeatherWidgetHtml = widgetHtml;\n return widgetHtml;\n } catch (error) {\n context.log(`Error reading weather widget file: ${error}`);\n return '<!doctype html><html><body><h1>Weather Widget</h1><p>Widget content not found.</p></body></html>';\n }\n}\n\nexport async function getWeather(_toolArguments: unknown, context: InvocationContext): Promise<object> {\n const mcptoolargs = (_toolArguments as { arguments?: { location?: string } })?.arguments || (context.triggerMetadata.mcptoolargs as { location?: string } | undefined) || {};\n const location = mcptoolargs.location || 'Seattle';\n\n return {\n Location: location,\n TemperatureC: 20,\n Condition: 'Partly cloudy',\n Source: 'template-sample'\n };\n}\n\napp.mcpResource('getWeatherWidget', {\n uri: WEATHER_WIDGET_URI,\n resourceName: WEATHER_WIDGET_NAME,\n description: WEATHER_WIDGET_DESCRIPTION,\n mimeType: WEATHER_WIDGET_MIME_TYPE,\n metadata: RESOURCE_METADATA,\n handler: getWeatherWidget,\n});\n\napp.mcpTool('getWeather', {\n toolName: 'GetWeather',\n description: 'Returns current weather for a location.',\n toolProperties: {\n location: arg.string().describe('City name to check weather for (e.g., Seattle, New York, Miami)')\n },\n metadata: TOOL_METADATA,\n handler: getWeather,\n});",

Copilot uses AI. Check for mistakes.
"id": "McpResourceTrigger-TypeScript-4.x",
"runtime": "2",
"files": {
"%functionName%.ts": "import { app, InvocationContext, arg } from \"@azure/functions\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\nconst WEATHER_WIDGET_URI = 'ui://weather/index.html';\nconst WEATHER_WIDGET_NAME = 'Weather Widget';\nconst WEATHER_WIDGET_DESCRIPTION = 'Interactive weather display for MCP Apps';\nconst WEATHER_WIDGET_MIME_TYPE = 'text/html;profile=mcp-app';\n\nconst TOOL_METADATA = JSON.stringify({\n ui: {\n resourceUri: WEATHER_WIDGET_URI\n }\n});\n\nconst RESOURCE_METADATA = JSON.stringify({\n ui: {\n prefersBorder: true\n }\n});\n\nexport async function getWeatherWidget(_resourceContext: unknown, context: InvocationContext): Promise<string> {\n try {\n const filePath = path.join(process.cwd(), 'app', 'dist', 'index.html');\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n context.log(`Error reading weather widget file: ${error}`);\n return '<!doctype html><html><body><h1>Weather Widget</h1><p>Widget content not found.</p></body></html>';\n }\n}\n\nexport async function getWeather(_toolArguments: unknown, context: InvocationContext): Promise<object> {\n const mcptoolargs = (_toolArguments as { arguments?: { location?: string } })?.arguments || (context.triggerMetadata.mcptoolargs as { location?: string } | undefined) || {};\n const location = mcptoolargs.location || 'Seattle';\n\n return {\n Location: location,\n TemperatureC: 20,\n Condition: 'Partly cloudy',\n Source: 'template-sample'\n };\n}\n\napp.mcpResource('getWeatherWidget', {\n uri: WEATHER_WIDGET_URI,\n resourceName: WEATHER_WIDGET_NAME,\n description: WEATHER_WIDGET_DESCRIPTION,\n mimeType: WEATHER_WIDGET_MIME_TYPE,\n metadata: RESOURCE_METADATA,\n handler: getWeatherWidget,\n});\n\napp.mcpTool('getWeather', {\n toolName: 'GetWeather',\n description: 'Returns current weather for a location.',\n toolProperties: {\n location: arg.string().describe('City name to check weather for (e.g., Seattle, New York, Miami)')\n },\n metadata: TOOL_METADATA,\n handler: getWeather,\n});",
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error logging uses string interpolation (${error}), which often ends up as [object Object] and loses stack/message details. Consider logging error.message/error.stack (or String(error) plus structured details) so users can diagnose missing widget file issues more easily.

Suggested change
"%functionName%.ts": "import { app, InvocationContext, arg } from \"@azure/functions\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\nconst WEATHER_WIDGET_URI = 'ui://weather/index.html';\nconst WEATHER_WIDGET_NAME = 'Weather Widget';\nconst WEATHER_WIDGET_DESCRIPTION = 'Interactive weather display for MCP Apps';\nconst WEATHER_WIDGET_MIME_TYPE = 'text/html;profile=mcp-app';\n\nconst TOOL_METADATA = JSON.stringify({\n ui: {\n resourceUri: WEATHER_WIDGET_URI\n }\n});\n\nconst RESOURCE_METADATA = JSON.stringify({\n ui: {\n prefersBorder: true\n }\n});\n\nexport async function getWeatherWidget(_resourceContext: unknown, context: InvocationContext): Promise<string> {\n try {\n const filePath = path.join(process.cwd(), 'app', 'dist', 'index.html');\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n context.log(`Error reading weather widget file: ${error}`);\n return '<!doctype html><html><body><h1>Weather Widget</h1><p>Widget content not found.</p></body></html>';\n }\n}\n\nexport async function getWeather(_toolArguments: unknown, context: InvocationContext): Promise<object> {\n const mcptoolargs = (_toolArguments as { arguments?: { location?: string } })?.arguments || (context.triggerMetadata.mcptoolargs as { location?: string } | undefined) || {};\n const location = mcptoolargs.location || 'Seattle';\n\n return {\n Location: location,\n TemperatureC: 20,\n Condition: 'Partly cloudy',\n Source: 'template-sample'\n };\n}\n\napp.mcpResource('getWeatherWidget', {\n uri: WEATHER_WIDGET_URI,\n resourceName: WEATHER_WIDGET_NAME,\n description: WEATHER_WIDGET_DESCRIPTION,\n mimeType: WEATHER_WIDGET_MIME_TYPE,\n metadata: RESOURCE_METADATA,\n handler: getWeatherWidget,\n});\n\napp.mcpTool('getWeather', {\n toolName: 'GetWeather',\n description: 'Returns current weather for a location.',\n toolProperties: {\n location: arg.string().describe('City name to check weather for (e.g., Seattle, New York, Miami)')\n },\n metadata: TOOL_METADATA,\n handler: getWeather,\n});",
"%functionName%.ts": "import { app, InvocationContext, arg } from \"@azure/functions\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\nconst WEATHER_WIDGET_URI = 'ui://weather/index.html';\nconst WEATHER_WIDGET_NAME = 'Weather Widget';\nconst WEATHER_WIDGET_DESCRIPTION = 'Interactive weather display for MCP Apps';\nconst WEATHER_WIDGET_MIME_TYPE = 'text/html;profile=mcp-app';\n\nconst TOOL_METADATA = JSON.stringify({\n ui: {\n resourceUri: WEATHER_WIDGET_URI\n }\n});\n\nconst RESOURCE_METADATA = JSON.stringify({\n ui: {\n prefersBorder: true\n }\n});\n\nexport async function getWeatherWidget(_resourceContext: unknown, context: InvocationContext): Promise<string> {\n try {\n const filePath = path.join(process.cwd(), 'app', 'dist', 'index.html');\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n const message = error instanceof Error\n ? `${error.message}${error.stack ? '\\n' + error.stack : ''}`\n : String(error);\n context.log(`Error reading weather widget file: ${message}`);\n return '<!doctype html><html><body><h1>Weather Widget</h1><p>Widget content not found.</p></body></html>';\n }\n}\n\nexport async function getWeather(_toolArguments: unknown, context: InvocationContext): Promise<object> {\n const mcptoolargs = (_toolArguments as { arguments?: { location?: string } })?.arguments || (context.triggerMetadata.mcptoolargs as { location?: string } | undefined) || {};\n const location = mcptoolargs.location || 'Seattle';\n\n return {\n Location: location,\n TemperatureC: 20,\n Condition: 'Partly cloudy',\n Source: 'template-sample'\n };\n}\n\napp.mcpResource('getWeatherWidget', {\n uri: WEATHER_WIDGET_URI,\n resourceName: WEATHER_WIDGET_NAME,\n description: WEATHER_WIDGET_DESCRIPTION,\n mimeType: WEATHER_WIDGET_MIME_TYPE,\n metadata: RESOURCE_METADATA,\n handler: getWeatherWidget,\n});\n\napp.mcpTool('getWeather', {\n toolName: 'GetWeather',\n description: 'Returns current weather for a location.',\n toolProperties: {\n location: arg.string().describe('City name to check weather for (e.g., Seattle, New York, Miami)')\n },\n metadata: TOOL_METADATA,\n handler: getWeather,\n});",

Copilot uses AI. Check for mistakes.
"runtime": "2",
"files": {
"%functionName%.js": "const { app, arg } = require('@azure/functions');\nconst fs = require('fs');\nconst path = require('path');\n\nconst WEATHER_WIDGET_URI = 'ui://weather/index.html';\nconst WEATHER_WIDGET_NAME = 'Weather Widget';\nconst WEATHER_WIDGET_DESCRIPTION = 'Interactive weather display for MCP Apps';\nconst WEATHER_WIDGET_MIME_TYPE = 'text/html;profile=mcp-app';\n\nconst TOOL_METADATA = JSON.stringify({\n ui: {\n resourceUri: WEATHER_WIDGET_URI\n }\n});\n\nconst RESOURCE_METADATA = JSON.stringify({\n ui: {\n prefersBorder: true\n }\n});\n\nasync function getWeatherWidget(_resourceContext, context) {\n try {\n const filePath = path.join(process.cwd(), 'app', 'dist', 'index.html');\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n context.log(`Error reading weather widget file: ${error}`);\n return '<!doctype html><html><body><h1>Weather Widget</h1><p>Widget content not found.</p></body></html>';\n }\n}\n\nasync function getWeather(_toolArguments, context) {\n const mcptoolargs = context.triggerMetadata.mcptoolargs || {};\n const location = mcptoolargs.location || 'Seattle';\n\n return {\n Location: location,\n TemperatureC: 20,\n Condition: 'Partly cloudy',\n Source: 'template-sample'\n };\n}\n\napp.mcpResource('getWeatherWidget', {\n uri: WEATHER_WIDGET_URI,\n resourceName: WEATHER_WIDGET_NAME,\n description: WEATHER_WIDGET_DESCRIPTION,\n mimeType: WEATHER_WIDGET_MIME_TYPE,\n metadata: RESOURCE_METADATA,\n handler: getWeatherWidget,\n});\n\napp.mcpTool('getWeather', {\n toolName: 'GetWeather',\n description: 'Returns current weather for a location.',\n toolProperties: {\n location: arg.string().describe('City name to check weather for (e.g., Seattle, New York, Miami)')\n },\n metadata: TOOL_METADATA,\n handler: getWeather,\n});",
"app/dist/index.html": "<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>Weather Widget</title></head><body><h1>Weather Widget</h1><p>Select a city using the GetWeather MCP tool.</p></body></html>"
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Node v4 template defines an additional file (app/dist/index.html) alongside the function file. The current NodeV4FunctionCreateStep writes all templateFiles to the single ${functionName}${ext} path (it ignores the templateFiles key), so this extra entry will overwrite the generated function source and break the created function. Either remove the extra file from the template (embed the HTML in the function code) or update the Node v4 function creation logic to write each template file to its own path.

Suggested change
"app/dist/index.html": "<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>Weather Widget</title></head><body><h1>Weather Widget</h1><p>Select a city using the GetWeather MCP tool.</p></body></html>"

Copilot uses AI. Check for mistakes.
"runtime": "2",
"files": {
"%functionName%.ts": "import { app, InvocationContext, arg } from \"@azure/functions\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\nconst WEATHER_WIDGET_URI = 'ui://weather/index.html';\nconst WEATHER_WIDGET_NAME = 'Weather Widget';\nconst WEATHER_WIDGET_DESCRIPTION = 'Interactive weather display for MCP Apps';\nconst WEATHER_WIDGET_MIME_TYPE = 'text/html;profile=mcp-app';\n\nconst TOOL_METADATA = JSON.stringify({\n ui: {\n resourceUri: WEATHER_WIDGET_URI\n }\n});\n\nconst RESOURCE_METADATA = JSON.stringify({\n ui: {\n prefersBorder: true\n }\n});\n\nexport async function getWeatherWidget(_resourceContext: unknown, context: InvocationContext): Promise<string> {\n try {\n const filePath = path.join(process.cwd(), 'app', 'dist', 'index.html');\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n context.log(`Error reading weather widget file: ${error}`);\n return '<!doctype html><html><body><h1>Weather Widget</h1><p>Widget content not found.</p></body></html>';\n }\n}\n\nexport async function getWeather(_toolArguments: unknown, context: InvocationContext): Promise<object> {\n const mcptoolargs = (_toolArguments as { arguments?: { location?: string } })?.arguments || (context.triggerMetadata.mcptoolargs as { location?: string } | undefined) || {};\n const location = mcptoolargs.location || 'Seattle';\n\n return {\n Location: location,\n TemperatureC: 20,\n Condition: 'Partly cloudy',\n Source: 'template-sample'\n };\n}\n\napp.mcpResource('getWeatherWidget', {\n uri: WEATHER_WIDGET_URI,\n resourceName: WEATHER_WIDGET_NAME,\n description: WEATHER_WIDGET_DESCRIPTION,\n mimeType: WEATHER_WIDGET_MIME_TYPE,\n metadata: RESOURCE_METADATA,\n handler: getWeatherWidget,\n});\n\napp.mcpTool('getWeather', {\n toolName: 'GetWeather',\n description: 'Returns current weather for a location.',\n toolProperties: {\n location: arg.string().describe('City name to check weather for (e.g., Seattle, New York, Miami)')\n },\n metadata: TOOL_METADATA,\n handler: getWeather,\n});",
"app/dist/index.html": "<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>Weather Widget</title></head><body><h1>Weather Widget</h1><p>Select a city using the GetWeather MCP tool.</p></body></html>"
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Node v4 template defines an additional file (app/dist/index.html) alongside the function file. The current NodeV4FunctionCreateStep writes all templateFiles to the single ${functionName}${ext} path (it ignores the templateFiles key), so this extra entry will overwrite the generated function source and break the created function. Either remove the extra file from the template (embed the HTML in the function code) or update the Node v4 function creation logic to write each template file to its own path.

Suggested change
"app/dist/index.html": "<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>Weather Widget</title></head><body><h1>Weather Widget</h1><p>Select a city using the GetWeather MCP tool.</p></body></html>"

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants