Adding the MCP Resource Trigger Templates#4952
Adding the MCP Resource Trigger Templates#4952swapnil-nagar wants to merge 0 commit intomicrosoft:mainfrom
Conversation
There was a problem hiding this comment.
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
McpResourceTriggerto 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.xbackuptemplates.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
\nnewlines, 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\nfor 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 usecontext.log(e.g., TimerTrigger-TypeScript-4.x at line 537). Usingcontext.logkeeps 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
\nnewlines, 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\nfor 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 usecontext.log(e.g., TimerTrigger-TypeScript-4.x at line 537). Consider usingcontext.loghere 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});", |
There was a problem hiding this comment.
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.
| "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});", |
There was a problem hiding this comment.
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.
| "%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});", |
| "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});", |
There was a problem hiding this comment.
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.
| "%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});", |
| "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});", |
There was a problem hiding this comment.
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.
| "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});", |
There was a problem hiding this comment.
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.
| "%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});", |
| "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});", |
There was a problem hiding this comment.
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.
| "%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});", |
| "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>" |
There was a problem hiding this comment.
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.
| "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>" |
| "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>" |
There was a problem hiding this comment.
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.
| "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>" |
No description provided.