diff --git a/package-lock.json b/package-lock.json index b620ce1..3183358 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,16 @@ { "name": "@azure/functions", - "version": "4.7.2", + "version": "4.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@azure/functions", - "version": "4.7.2", + "version": "4.8.0", "license": "MIT", "dependencies": { "cookie": "^0.7.0", - "long": "^4.0.0", - "undici": "^5.29.0" + "long": "^4.0.0" }, "devDependencies": { "@types/chai": "^4.2.22", @@ -239,15 +238,6 @@ "node": ">= 4" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", @@ -6447,18 +6437,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/undici": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", - "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", - "license": "MIT", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", diff --git a/package.json b/package.json index dabaf9c..9a32cde 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@azure/functions", - "version": "4.7.2", + "version": "4.8.0", "description": "Microsoft Azure Functions NodeJS Framework", "keywords": [ "azure", @@ -42,8 +42,7 @@ }, "dependencies": { "cookie": "^0.7.0", - "long": "^4.0.0", - "undici": "^5.29.0" + "long": "^4.0.0" }, "devDependencies": { "@types/chai": "^4.2.22", diff --git a/src/app.ts b/src/app.ts index 485dd35..84d4be7 100644 --- a/src/app.ts +++ b/src/app.ts @@ -11,6 +11,7 @@ import { HttpHandler, HttpMethod, HttpMethodFunctionOptions, + McpToolFunctionOptions, MySqlFunctionOptions, ServiceBusQueueFunctionOptions, ServiceBusTopicFunctionOptions, @@ -145,6 +146,17 @@ export function webPubSub(name: string, options: WebPubSubFunctionOptions): void generic(name, convertToGenericOptions(options, trigger.webPubSub)); } +/** + * Registers an MCP Tool function in your app. + * This function is triggered by MCP Tool events and allows you to define the behavior of the function. + * + * @param name - The name of the function. This must be unique within your app and is primarily used for tracking purposes. + * @param options - Configuration options for the MCP Tool function, including the handler and trigger-specific settings. + */ +export function mcpTool(name: string, options: McpToolFunctionOptions): void { + generic(name, convertToGenericOptions(options, trigger.mcpTool)); +} + export function generic(name: string, options: GenericFunctionOptions): void { if (!hasSetModel) { setProgrammingModel(); diff --git a/src/constants.ts b/src/constants.ts index c453818..3b05c2d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,6 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. -export const version = '4.7.2'; +export const version = '4.8.0'; export const returnBindingKey = '$return'; diff --git a/src/converters/toMcpToolTriggerOptionsToRpc.ts b/src/converters/toMcpToolTriggerOptionsToRpc.ts new file mode 100644 index 0000000..14d324e --- /dev/null +++ b/src/converters/toMcpToolTriggerOptionsToRpc.ts @@ -0,0 +1,146 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. + +import { McpToolProperty, McpToolTriggerOptions, McpToolTriggerOptionsToRpc } from '../../types'; + +/** + * Converts an McpToolTriggerOptions object to an McpToolTriggerOptionsToRpc object. + * + * @param mcpToolTriggerOptions - The input options to be converted. + * @returns The converted McpToolTriggerOptionsToRpc object. + */ +export function converToMcpToolTriggerOptionsToRpc( + mcpToolTriggerOptions: McpToolTriggerOptions +): McpToolTriggerOptionsToRpc { + // Base object for the return value + const baseResult = { + toolName: mcpToolTriggerOptions.toolName, + description: mcpToolTriggerOptions.description, + }; + + // Check for null or undefined toolProperties + if (!mcpToolTriggerOptions?.toolProperties) { + return { + ...baseResult, + toolProperties: JSON.stringify([]), // Default to an empty array + }; + } + + // Check if toolProperties is an array of McpToolProperty objects + if (Array.isArray(mcpToolTriggerOptions.toolProperties)) { + const isValid = mcpToolTriggerOptions.toolProperties.every(isMcpToolProperty); + if (isValid) { + return { + ...baseResult, + toolProperties: JSON.stringify(mcpToolTriggerOptions.toolProperties), + }; + } else { + throw new Error( + 'Invalid toolProperties: Array contains invalid McpToolProperty, please validate the parameters.' + ); + } + } + + // Handle cases where toolProperties is an object (e.g., Zod schema) + if (typeof mcpToolTriggerOptions.toolProperties === 'object') { + // Define the type of the ZodObject shape and ZodPropertyDef + type ZodPropertyDef = { + description?: string; + typeName: string; + }; + type ZodObjectShape = Record; + + // Define the type of the toolProperties object + type ToolProperties = + | { + _def?: { + typeName?: string; + }; + shape?: ZodObjectShape; + } + | Record; + + let isZodObject = false; + + const toolProperties = mcpToolTriggerOptions.toolProperties as ToolProperties; + + // Check if the object is a ZodObject + if ((toolProperties?._def as { typeName?: string })?.typeName === 'ZodObject') { + isZodObject = true; + } + + // Check if shape is a valid ZodObject shape + const shape: ZodObjectShape | Record = isZodObject + ? (toolProperties as { shape: ZodObjectShape }).shape + : toolProperties; + + // Extract properties from the ZodObject shape + const result = Object.keys(shape).map((propertyName) => { + const property = shape[propertyName] as { _def: ZodPropertyDef }; + const description = property?._def?.description || ''; + const propertyType = getPropertyType(property?._def?.typeName?.toLowerCase() || 'unknown'); // Extract type name or default to "unknown" + + return { + propertyName, + propertyType, + description, + }; + }); + + return { + ...baseResult, + toolProperties: JSON.stringify(result), + }; + } + // Handle cases where toolProperties is not an array + throw new Error('Invalid toolProperties: Expected an array of McpToolProperty objects or zod objects.'); +} + +// Helper function to infer property type from zod schema +function getPropertyType(zodType: string): string { + switch (zodType) { + case 'zodnumber': + return 'number'; + case 'zodstring': + return 'string'; + case 'zodboolean': + return 'boolean'; + case 'zodarray': + return 'array'; + case 'zodobject': + return 'object'; + case 'zodbigint': + return 'long'; + case 'zoddate': + return 'DateTime'; + case 'zodtuple': + return 'Tuple'; + default: + console.warn(`Unknown zod type: ${zodType}`); + return 'unknown'; + } +} + +/** + * Type guard to check if a given object is of type McpToolProperty. + * + * @param property - The object to check. + * @returns True if the object is of type McpToolProperty, otherwise false. + * + * This function ensures that the object: + * - Is not null and is of type 'object'. + * - Contains the required properties: 'propertyName', 'propertyValue', and 'description'. + * - Each of these properties is of the correct type (string). + */ +function isMcpToolProperty(property: unknown): property is McpToolProperty { + return ( + typeof property === 'object' && + property !== null && + 'propertyName' in property && + 'propertyType' in property && + 'description' in property && + typeof (property as McpToolProperty).propertyName === 'string' && + typeof (property as McpToolProperty).propertyType === 'string' && + typeof (property as McpToolProperty).description === 'string' + ); +} diff --git a/src/http/HttpRequest.ts b/src/http/HttpRequest.ts index 7e0ab8f..2865868 100644 --- a/src/http/HttpRequest.ts +++ b/src/http/HttpRequest.ts @@ -8,7 +8,6 @@ import { Blob } from 'buffer'; import { IncomingMessage } from 'http'; import * as stream from 'stream'; import { ReadableStream } from 'stream/web'; -import { FormData, Headers, HeadersInit, Request as uRequest } from 'undici'; import { URLSearchParams } from 'url'; import { fromNullableMapping } from '../converters/fromRpcNullable'; import { fromRpcTypedData } from '../converters/fromRpcTypedData'; @@ -17,7 +16,7 @@ import { isDefined, nonNullProp } from '../utils/nonNull'; import { extractHttpUserFromHeaders } from './extractHttpUserFromHeaders'; interface InternalHttpRequestInit extends RpcHttpData { - undiciRequest?: uRequest; + request?: Request; } export class HttpRequest implements types.HttpRequest { @@ -25,14 +24,14 @@ export class HttpRequest implements types.HttpRequest { readonly params: HttpRequestParams; #cachedUser?: HttpRequestUser | null; - #uReq: uRequest; + #req: Request; #init: InternalHttpRequestInit; constructor(init: InternalHttpRequestInit) { this.#init = init; - let uReq = init.undiciRequest; - if (!uReq) { + let req = init.request; + if (!req) { const url = nonNullProp(init, 'url'); let body: Buffer | string | undefined; @@ -42,33 +41,33 @@ export class HttpRequest implements types.HttpRequest { body = init.body.string; } - uReq = new uRequest(url, { + req = new Request(url, { body, method: nonNullProp(init, 'method'), headers: fromNullableMapping(init.nullableHeaders, init.headers), }); } - this.#uReq = uReq; + this.#req = req; if (init.nullableQuery || init.query) { this.query = new URLSearchParams(fromNullableMapping(init.nullableQuery, init.query)); } else { - this.query = new URL(this.#uReq.url).searchParams; + this.query = new URL(this.#req.url).searchParams; } this.params = fromNullableMapping(init.nullableParams, init.params); } get url(): string { - return this.#uReq.url; + return this.#req.url; } get method(): string { - return this.#uReq.method; + return this.#req.method; } get headers(): Headers { - return this.#uReq.headers; + return this.#req.headers; } get user(): HttpRequestUser | null { @@ -79,37 +78,40 @@ export class HttpRequest implements types.HttpRequest { return this.#cachedUser; } - get body(): ReadableStream | null { - return this.#uReq.body; + get body(): ReadableStream | null { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return this.#req.body as any; // Type compatibility between global and Node.js ReadableStream } get bodyUsed(): boolean { - return this.#uReq.bodyUsed; + return this.#req.bodyUsed; } async arrayBuffer(): Promise { - return this.#uReq.arrayBuffer(); + return this.#req.arrayBuffer(); } + // eslint-disable-next-line @typescript-eslint/require-await async blob(): Promise { - return this.#uReq.blob(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return this.#req.blob() as any; // Type compatibility with Node.js Blob } async formData(): Promise { - return this.#uReq.formData(); + return this.#req.formData(); } async json(): Promise { - return this.#uReq.json(); + return this.#req.json(); } async text(): Promise { - return this.#uReq.text(); + return this.#req.text(); } clone(): HttpRequest { const newInit = structuredClone(this.#init); - newInit.undiciRequest = this.#uReq.clone(); + newInit.request = this.#req.clone(); return new HttpRequest(newInit); } } @@ -144,12 +146,14 @@ export function createStreamRequest( headers = headersData; } - const uReq = new uRequest(url, { - body, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const req = new Request(url, { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + body: body as any, // Node.js Readable stream compatibility duplex: 'half', method: nonNullProp(proxyReq, 'method'), headers, - }); + } as any); // Global Request constructor compatibility const params: Record = {}; for (const [key, rpcValue] of Object.entries(rpcParams)) { @@ -159,7 +163,7 @@ export function createStreamRequest( } return new HttpRequest({ - undiciRequest: uReq, + request: req, params, }); } diff --git a/src/http/HttpResponse.ts b/src/http/HttpResponse.ts index 120dc1a..eeadaba 100644 --- a/src/http/HttpResponse.ts +++ b/src/http/HttpResponse.ts @@ -5,32 +5,39 @@ import * as types from '@azure/functions'; import { HttpResponseInit } from '@azure/functions'; import { Blob } from 'buffer'; import { ReadableStream } from 'stream/web'; -import { FormData, Headers, Response as uResponse, ResponseInit as uResponseInit } from 'undici'; import { isDefined } from '../utils/nonNull'; interface InternalHttpResponseInit extends HttpResponseInit { - undiciResponse?: uResponse; + response?: Response; } export class HttpResponse implements types.HttpResponse { readonly cookies: types.Cookie[]; readonly enableContentNegotiation: boolean; - #uRes: uResponse; + #res: Response; #init: InternalHttpResponseInit; constructor(init?: InternalHttpResponseInit) { init ??= {}; this.#init = init; - if (init.undiciResponse) { - this.#uRes = init.undiciResponse; + if (init.response) { + this.#res = init.response; } else { - const uResInit: uResponseInit = { status: init.status, headers: init.headers }; + const resInit: ResponseInit = { status: init.status, headers: init.headers }; if (isDefined(init.jsonBody)) { - this.#uRes = uResponse.json(init.jsonBody, uResInit); + // Create JSON response manually for compatibility + const jsonHeaders = new Headers(resInit.headers); + if (!jsonHeaders.has('content-type')) { + jsonHeaders.set('content-type', 'application/json'); + } + this.#res = new Response(JSON.stringify(init.jsonBody), { + ...resInit, + headers: jsonHeaders, + }); } else { - this.#uRes = new uResponse(init.body, uResInit); + this.#res = new Response(init.body, resInit); } } @@ -39,44 +46,47 @@ export class HttpResponse implements types.HttpResponse { } get status(): number { - return this.#uRes.status; + return this.#res.status; } get headers(): Headers { - return this.#uRes.headers; + return this.#res.headers; } - get body(): ReadableStream | null { - return this.#uRes.body; + get body(): ReadableStream | null { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return this.#res.body as any; // Type compatibility between global and Node.js ReadableStream } get bodyUsed(): boolean { - return this.#uRes.bodyUsed; + return this.#res.bodyUsed; } async arrayBuffer(): Promise { - return this.#uRes.arrayBuffer(); + return this.#res.arrayBuffer(); } + // eslint-disable-next-line @typescript-eslint/require-await async blob(): Promise { - return this.#uRes.blob(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return this.#res.blob() as any; // Type compatibility with Node.js Blob } async formData(): Promise { - return this.#uRes.formData(); + return this.#res.formData(); } async json(): Promise { - return this.#uRes.json(); + return this.#res.json(); } async text(): Promise { - return this.#uRes.text(); + return this.#res.text(); } clone(): HttpResponse { const newInit = structuredClone(this.#init); - newInit.undiciResponse = this.#uRes.clone(); + newInit.response = this.#res.clone(); return new HttpResponse(newInit); } } diff --git a/src/http/extractHttpUserFromHeaders.ts b/src/http/extractHttpUserFromHeaders.ts index a2b24a2..616cdac 100644 --- a/src/http/extractHttpUserFromHeaders.ts +++ b/src/http/extractHttpUserFromHeaders.ts @@ -2,7 +2,6 @@ // Licensed under the MIT License. import { HttpRequestUser } from '@azure/functions'; -import { Headers } from 'undici'; import { nonNullValue } from '../utils/nonNull'; /* grandfathered in. Should fix when possible */ diff --git a/src/trigger.ts b/src/trigger.ts index f709abb..1a25f69 100644 --- a/src/trigger.ts +++ b/src/trigger.ts @@ -12,8 +12,10 @@ import { GenericTriggerOptions, HttpTrigger, HttpTriggerOptions, + McpToolTrigger, + McpToolTriggerOptions, MySqlTrigger, - MySqlTriggerOptions, + MySqlTriggerOptions, ServiceBusQueueTrigger, ServiceBusQueueTriggerOptions, ServiceBusTopicTrigger, @@ -32,6 +34,7 @@ import { WebPubSubTriggerOptions, } from '@azure/functions'; import { addBindingName } from './addBindingName'; +import { converToMcpToolTriggerOptionsToRpc } from './converters/toMcpToolTriggerOptionsToRpc'; export function http(options: HttpTriggerOptions): HttpTrigger { return addTriggerBindingName({ @@ -126,6 +129,20 @@ export function webPubSub(options: WebPubSubTriggerOptions): WebPubSubTrigger { }); } +/** + * Creates an MCP Tool trigger configuration. + * This function is used to define an MCP Tool trigger for an Azure Function. + * + * @param options - The configuration options for the MCP Tool trigger, including tool-specific metadata. + * @returns An MCP Tool trigger object with the specified configuration. + */ +export function mcpTool(options: McpToolTriggerOptions): McpToolTrigger { + return addTriggerBindingName({ + ...converToMcpToolTriggerOptionsToRpc(options), + type: 'mcpToolTrigger', + }); +} + export function generic(options: GenericTriggerOptions): FunctionTrigger { return addTriggerBindingName(options); } diff --git a/test/converters/toRpcHttp.test.ts b/test/converters/toRpcHttp.test.ts index bbc056f..2de39ce 100644 --- a/test/converters/toRpcHttp.test.ts +++ b/test/converters/toRpcHttp.test.ts @@ -5,7 +5,6 @@ import 'mocha'; import * as chai from 'chai'; import { expect } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { Headers } from 'undici'; import { toRpcHttp } from '../../src/converters/toRpcHttp'; import { HttpResponse } from '../../src/http/HttpResponse'; diff --git a/test/http/HttpRequest.test.ts b/test/http/HttpRequest.test.ts index 23cddba..5de1908 100644 --- a/test/http/HttpRequest.test.ts +++ b/test/http/HttpRequest.test.ts @@ -5,7 +5,6 @@ import 'mocha'; import * as chai from 'chai'; import { expect } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { File } from 'undici'; import { HttpRequest } from '../../src/http/HttpRequest'; chai.use(chaiAsPromised); @@ -94,7 +93,7 @@ world const parsedForm = await req.formData(); expect(parsedForm.has('myfile')).to.equal(true); - const file = parsedForm.get('myfile'); + const file = parsedForm.get('myfile') as File; expect(file.name).to.equal('test.txt'); expect(file.type).to.equal('text/plain'); expect(await file.text()).to.equal(`hello\r\nworld`); @@ -132,10 +131,19 @@ value2 }); it('Unsupported content type', async () => { - const contentTypes = ['application/octet-stream', 'application/json', 'text/plain', 'invalid']; - for (const contentType of contentTypes) { - const req = createFormRequest('', contentType); - await expect(req.formData()).to.eventually.be.rejectedWith(/Could not parse content as FormData/i); + if (process.version.startsWith('v18.')) { + console.log('Here'); + const contentTypes = ['application/octet-stream', 'application/json', 'text/plain', 'invalid']; + for (const contentType of contentTypes) { + const req = createFormRequest('', contentType); + await expect(req.formData()).to.eventually.be.rejectedWith(/Request.formData: Could not/i); + } + } else { + const contentTypes = ['application/octet-stream', 'application/json', 'text/plain', 'invalid']; + for (const contentType of contentTypes) { + const req = createFormRequest('', contentType); + await expect(req.formData()).to.eventually.be.rejectedWith(/Content-Type was not one of/i); + } } }); }); diff --git a/test/http/extractHttpUserFromHeaders.test.ts b/test/http/extractHttpUserFromHeaders.test.ts index 3c5452b..4eff6e6 100644 --- a/test/http/extractHttpUserFromHeaders.test.ts +++ b/test/http/extractHttpUserFromHeaders.test.ts @@ -4,7 +4,6 @@ import 'mocha'; import { HttpRequestUser } from '@azure/functions'; import { expect } from 'chai'; -import { Headers } from 'undici'; import { extractHttpUserFromHeaders } from '../../src/http/extractHttpUserFromHeaders'; describe('Extract Http User Claims Principal from Headers', () => { diff --git a/types/app.d.ts b/types/app.d.ts index c06c9b1..db3e059 100644 --- a/types/app.d.ts +++ b/types/app.d.ts @@ -6,6 +6,7 @@ import { EventGridFunctionOptions } from './eventGrid'; import { EventHubFunctionOptions } from './eventHub'; import { GenericFunctionOptions } from './generic'; import { HttpFunctionOptions, HttpHandler, HttpMethodFunctionOptions } from './http'; +import { McpToolFunctionOptions } from './mcpTool'; import { MySqlFunctionOptions } from './mySql'; import { ServiceBusQueueFunctionOptions, ServiceBusTopicFunctionOptions } from './serviceBus'; import { SetupOptions } from './setup'; @@ -196,4 +197,6 @@ export function generic(name: string, options: GenericFunctionOptions): void; */ export function webPubSub(name: string, options: WebPubSubFunctionOptions): void; +export function mcpTool(name: string, options: McpToolFunctionOptions): void; + export * as hook from './hooks/registerHook'; diff --git a/types/http.d.ts b/types/http.d.ts index e918ef1..83693a9 100644 --- a/types/http.d.ts +++ b/types/http.d.ts @@ -3,7 +3,6 @@ import { Blob } from 'buffer'; import { ReadableStream } from 'stream/web'; -import { BodyInit, FormData, Headers, HeadersInit } from 'undici'; import { URLSearchParams } from 'url'; import { FunctionOptions, FunctionOutput, FunctionResult, FunctionTrigger } from './index'; import { InvocationContext } from './InvocationContext'; diff --git a/types/index.d.ts b/types/index.d.ts index 2676a89..314c4c1 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -17,6 +17,7 @@ export * from './hooks/logHooks'; export * from './http'; export * as input from './input'; export * from './InvocationContext'; +export * from './mcpTool'; export * from './mySql'; export * as output from './output'; export * from './serviceBus'; diff --git a/types/mcpTool.d.ts b/types/mcpTool.d.ts new file mode 100644 index 0000000..8f2d026 --- /dev/null +++ b/types/mcpTool.d.ts @@ -0,0 +1,107 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. + +import { FunctionOptions, FunctionResult, FunctionTrigger } from './index'; +import { InvocationContext } from './InvocationContext'; + +/** + * A handler function for MCP Tool triggers. + * + * @param messages - The messages or data received by the trigger. + * @param context - The invocation context for the function. + * @returns A result that can be a promise or a synchronous value. + */ +export type McpToolTriggerHandler = (messages: unknown, context: InvocationContext) => FunctionResult; + +/** + * Configuration options for an MCP Tool function. + * This includes trigger-specific options and general function options. + */ +export interface McpToolFunctionOptions extends McpToolTriggerOptions, Partial { + /** + * The handler function to execute when the trigger is invoked. + */ + handler: McpToolTriggerHandler; + + /** + * The trigger configuration for the MCP Tool. + */ + trigger?: McpToolTrigger; +} + +/** + * Configuration options for an MCP Tool trigger. + * These options define the behavior and metadata for the trigger. + */ +export interface McpToolTriggerOptions { + /** + * The name of the tool associated with the trigger. + * This is typically an app setting or environment variable. + */ + toolName: string; + + /** + * A description of the tool or trigger. + * This provides additional context about the trigger's purpose. + */ + description: string; + + /** + * Additional properties or metadata for the tool. + * This is a dictionary of key-value pairs that can be used to configure the trigger. + */ + toolProperties?: any | McpToolProperty[]; +} + +/** + * Configuration options for an MCP Tool trigger. + * These options define the behavior and metadata for the trigger. + */ +export interface McpToolTriggerOptionsToRpc { + /** + * The name of the tool associated with the trigger. + * This is typically an app setting or environment variable. + */ + toolName: string; + + /** + * A description of the tool or trigger. + * This provides additional context about the trigger's purpose. + */ + description: string; + + /** + * Additional properties or metadata for the tool. + * This is a dictionary of key-value pairs that can be used to configure the trigger. + */ + toolProperties?: string; +} + +/** + * Represents an MCP Tool trigger, combining base function trigger options + * with MCP Tool-specific trigger options. + */ +export type McpToolTrigger = FunctionTrigger & McpToolTriggerOptionsToRpc; + +export interface McpToolProperty { + /** + * The name of the property. + */ + propertyName: string; + + /** + * The type of the property. + */ + propertyType: string; + + /** + * A description of the property. + * This provides additional context about the purpose or usage of the property. + */ + description: string; + + /** + * Indicates whether the property is required. + */ + required?: boolean; +} diff --git a/types/trigger.d.ts b/types/trigger.d.ts index f54764b..e8105d2 100644 --- a/types/trigger.d.ts +++ b/types/trigger.d.ts @@ -7,6 +7,7 @@ import { EventHubTrigger, EventHubTriggerOptions } from './eventHub'; import { GenericTriggerOptions } from './generic'; import { HttpTrigger, HttpTriggerOptions } from './http'; import { FunctionTrigger } from './index'; +import { McpToolFunctionOptions, McpToolTrigger } from './mcpTool'; import { MySqlTrigger, MySqlTriggerOptions } from './mySql'; import { ServiceBusQueueTrigger, @@ -90,6 +91,11 @@ export function mySql(options: MySqlTriggerOptions): MySqlTrigger; */ export function webPubSub(options: WebPubSubTriggerOptions): WebPubSubTrigger; +/** + * [Link to docs and examples](//TODO Add link to docs and examples) + */ +export function mcpTool(options: McpToolFunctionOptions): McpToolTrigger; + /** * A generic option that can be used for any trigger type * Use this method if your desired trigger type does not already have its own method