Untappd MCP Server using Azure Functions
Okay, here's an example of a minimal, complete, and practical (MCP) Azure Function written in F# that demonstrates a simple HTTP trigger. I'll break it down with explanations. ```fsharp namespace MyAzureFunction open Microsoft.AspNetCore.Mvc open Microsoft.Azure.WebJobs open Microsoft.Azure.WebJobs.Extensions.Http open Microsoft.AspNetCore.Http open Microsoft.Extensions.Logging module HelloFunction = [<FunctionName("Hello")>] let Run ( [<HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)>] req: HttpRequest, log: ILogger ) = log.LogInformation "C# HTTP trigger function processed a request." let name = match req.Query.["name"] with | ValueSome n -> n | ValueNone -> match System.IO.StreamReader(req.Body).ReadToEndAsync().Result with | body when not (String.IsNullOrEmpty body) -> body | _ -> "Azure Function" let responseMessage = sprintf "Hello, %s. This HTTP triggered function executed successfully." name ActionResult(OkObjectResult responseMessage) ``` **Explanation:** 1. **Namespace:** `namespace MyAzureFunction` This organizes your code. Choose a meaningful name. 2. **`open` Statements:** These bring in the necessary libraries. Crucially: * `Microsoft.AspNetCore.Mvc`: For `IActionResult` (the return type) and `OkObjectResult`. * `Microsoft.Azure.WebJobs`: The core Azure Functions library. * `Microsoft.Azure.WebJobs.Extensions.Http`: For HTTP trigger functionality. * `Microsoft.AspNetCore.Http`: For the `HttpRequest` object. * `Microsoft.Extensions.Logging`: For logging. 3. **Module:** `module HelloFunction` F# code is typically organized into modules. 4. **`[<FunctionName("Hello")>]`:** This *attribute* is **essential**. It tells Azure Functions the name of your function. This is the name you'll use in the Azure portal or when deploying. Change `"Hello"` to whatever you want to call your function. 5. **`let Run (...) = ...`:** This defines the function itself. `Run` is the standard name for the entry point. 6. **Function Parameters:** * `[<HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)>] req: HttpRequest`: This is the HTTP trigger. * `[<HttpTrigger(...)]`: This attribute configures the trigger. * `AuthorizationLevel.Anonymous`: Means anyone can call the function without authentication. Other options are `Function` (requires a function key) and `Admin` (requires an admin key). * `"get", "post"`: Specifies that the function will respond to both GET and POST requests. You can add other HTTP methods like `"put"`, `"delete"`, etc. * `Route = null`: This means the function is triggered directly by the function name. If you set `Route = "myroute/{id}"`, the function would be triggered by a URL like `/api/myroute/123`. `null` is the simplest option for a basic example. * `req: HttpRequest`: This is the HTTP request object. It contains information about the request, such as headers, query parameters, and the request body. * `log: ILogger`: This is the logger. Use it to write information to the Azure Functions logs. Very helpful for debugging. 7. **Function Body:** * `log.LogInformation "C# HTTP trigger function processed a request."`: Logs a message to the console. This will appear in the Azure Functions logs. * **Name Extraction:** This part tries to get the `name` parameter from either the query string or the request body. * `req.Query.["name"]`: Tries to get the `name` parameter from the query string (e.g., `?name=John`). `req.Query` is a dictionary-like structure. * `ValueSome n -> n`: If the `name` parameter is found in the query string, its value is assigned to `n`. * `ValueNone -> ...`: If the `name` parameter is *not* found in the query string, the code proceeds to read the request body. * `System.IO.StreamReader(req.Body).ReadToEndAsync().Result`: Reads the entire request body as a string. **Important:** Using `.Result` is generally discouraged in asynchronous code because it can lead to deadlocks. However, in the context of an Azure Function, it's often acceptable for simplicity, *especially* in F#. For more complex scenarios, you'd want to use `async` and `await` properly. * `body when not (String.IsNullOrEmpty body) -> body`: If the request body is not empty, its content is used as the `name`. * `_ -> "Azure Function"`: If neither the query string nor the request body contains a `name`, the default value "Azure Function" is used. * `let responseMessage = sprintf "Hello, %s. This HTTP triggered function executed successfully." name`: Creates the response message using `sprintf` (string formatting). * `ActionResult(OkObjectResult responseMessage)`: Creates an `OkObjectResult` (an HTTP 200 OK response) with the response message as the body. The `ActionResult` type is used to wrap the result. This is how you return a response from an Azure Function. **How to Use It:** 1. **Create an Azure Functions Project:** In Visual Studio or VS Code with the Azure Functions extension, create a new F# Azure Functions project. Choose the "HTTP trigger" template. 2. **Replace the Code:** Replace the contents of the generated `.fs` file with the code above. 3. **Run Locally:** Run the function locally. The Azure Functions runtime will start and tell you the URL to access your function. 4. **Test:** * **GET Request (with query parameter):** Open a browser and go to `http://localhost:7071/api/Hello?name=YourName` (replace `7071` with the port your function is running on). You should see "Hello, YourName. This HTTP triggered function executed successfully." * **GET Request (without query parameter):** Go to `http://localhost:7071/api/Hello`. You should see "Hello, Azure Function. This HTTP triggered function executed successfully." * **POST Request (with body):** Use a tool like `curl` or Postman to send a POST request to `http://localhost:7071/api/Hello` with a request body containing the name (e.g., `"MyName"`). The response should be "Hello, MyName. This HTTP triggered function executed successfully." 5. **Deploy to Azure:** Deploy your function to Azure using Visual Studio, VS Code, or the Azure CLI. **Key Improvements and Considerations:** * **Error Handling:** This example lacks proper error handling. In a real-world function, you should handle exceptions and return appropriate error responses (e.g., `BadRequestObjectResult`, `InternalServerErrorResult`). * **Asynchronous Operations:** For more complex operations (especially I/O), use `async` and `await` to avoid blocking the thread. This is crucial for scalability. The `.Result` call is a shortcut for this example, but should be avoided in production code where possible. * **Dependency Injection:** For larger functions, use dependency injection to manage dependencies (e.g., configuration, database connections). Azure Functions supports dependency injection. * **Configuration:** Use the Azure Functions configuration system to store settings (e.g., connection strings, API keys). * **Logging:** Use the `ILogger` interface extensively to log information, warnings, and errors. This is essential for debugging and monitoring. * **Idempotency:** If your function performs operations that should only be executed once (e.g., processing payments), ensure that it's idempotent. * **Testing:** Write unit tests to verify the logic of your function. **Chinese Translation of the Explanation:** **F# 中的一个最小、完整、实用的 (MCP) Azure 函数示例** 这是一个用 F# 编写的最小、完整、实用的 (MCP) Azure 函数示例,它演示了一个简单的 HTTP 触发器。 我将分解并解释它。 ```fsharp namespace MyAzureFunction open Microsoft.AspNetCore.Mvc open Microsoft.Azure.WebJobs open Microsoft.Azure.WebJobs.Extensions.Http open Microsoft.AspNetCore.Http open Microsoft.Extensions.Logging module HelloFunction = [<FunctionName("Hello")>] let Run ( [<HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)>] req: HttpRequest, log: ILogger ) = log.LogInformation "C# HTTP 触发器函数处理了一个请求。" let name = match req.Query.["name"] with | ValueSome n -> n | ValueNone -> match System.IO.StreamReader(req.Body).ReadToEndAsync().Result with | body when not (String.IsNullOrEmpty body) -> body | _ -> "Azure Function" let responseMessage = sprintf "你好,%s。 此 HTTP 触发函数已成功执行。" name ActionResult(OkObjectResult responseMessage) ``` **解释:** 1. **命名空间:** `namespace MyAzureFunction` 这组织你的代码。 选择一个有意义的名称。 2. **`open` 语句:** 这些引入了必要的库。 至关重要的是: * `Microsoft.AspNetCore.Mvc`:用于 `IActionResult`(返回类型)和 `OkObjectResult`。 * `Microsoft.Azure.WebJobs`:核心 Azure Functions 库。 * `Microsoft.Azure.WebJobs.Extensions.Http`:用于 HTTP 触发器功能。 * `Microsoft.AspNetCore.Http`:用于 `HttpRequest` 对象。 * `Microsoft.Extensions.Logging`:用于日志记录。 3. **模块:** `module HelloFunction` F# 代码通常组织成模块。 4. **`[<FunctionName("Hello")>]`:** 这个*属性*是**必不可少的**。 它告诉 Azure Functions 你的函数的名称。 这是你将在 Azure 门户中使用或部署时使用的名称。 将 `"Hello"` 更改为你想要调用的任何名称。 5. **`let Run (...) = ...`:** 这定义了函数本身。 `Run` 是入口点的标准名称。 6. **函数参数:** * `[<HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)>] req: HttpRequest`:这是 HTTP 触发器。 * `[<HttpTrigger(...)]`:此属性配置触发器。 * `AuthorizationLevel.Anonymous`:表示任何人都可以调用该函数而无需身份验证。 其他选项是 `Function`(需要函数密钥)和 `Admin`(需要管理员密钥)。 * `“get”, “post”`:指定该函数将响应 GET 和 POST 请求。 你可以添加其他 HTTP 方法,例如 `"put"`、`"delete"` 等。 * `Route = null`:这意味着该函数由函数名称直接触发。 如果你设置 `Route = "myroute/{id}"`,则该函数将由类似 `/api/myroute/123` 的 URL 触发。 `null` 是基本示例的最简单选项。 * `req: HttpRequest`:这是 HTTP 请求对象。 它包含有关请求的信息,例如标头、查询参数和请求正文。 * `log: ILogger`:这是记录器。 使用它将信息写入 Azure Functions 日志。 对调试非常有帮助。 7. **函数体:** * `log.LogInformation "C# HTTP 触发器函数处理了一个请求。"`:将消息记录到控制台。 这将出现在 Azure Functions 日志中。 * **名称提取:** 这部分尝试从查询字符串或请求正文中获取 `name` 参数。 * `req.Query.["name"]`:尝试从查询字符串中获取 `name` 参数(例如,`?name=John`)。 `req.Query` 是一个类似字典的结构。 * `ValueSome n -> n`:如果在查询字符串中找到 `name` 参数,则将其值分配给 `n`。 * `ValueNone -> ...`:如果在查询字符串中*未*找到 `name` 参数,则代码继续读取请求正文。 * `System.IO.StreamReader(req.Body).ReadToEndAsync().Result`:将整个请求正文读取为字符串。 **重要提示:** 通常不鼓励在异步代码中使用 `.Result`,因为它可能导致死锁。 但是,在 Azure Function 的上下文中,为了简单起见,通常是可以接受的,*尤其是在 F# 中*。 对于更复杂的场景,你将需要正确使用 `async` 和 `await`。 * `body when not (String.IsNullOrEmpty body) -> body`:如果请求正文不为空,则其内容将用作 `name`。 * `_ -> "Azure Function"`:如果查询字符串和请求正文都不包含 `name`,则使用默认值 "Azure Function"。 * `let responseMessage = sprintf "你好,%s。 此 HTTP 触发函数已成功执行。" name`:使用 `sprintf`(字符串格式化)创建响应消息。 * `ActionResult(OkObjectResult responseMessage)`:创建一个 `OkObjectResult`(HTTP 200 OK 响应),并将响应消息作为正文。 `ActionResult` 类型用于包装结果。 这是你从 Azure Function 返回响应的方式。 **如何使用它:** 1. **创建 Azure Functions 项目:** 在 Visual Studio 或带有 Azure Functions 扩展的 VS Code 中,创建一个新的 F# Azure Functions 项目。 选择“HTTP 触发器”模板。 2. **替换代码:** 将生成的 `.fs` 文件的内容替换为上面的代码。 3. **在本地运行:** 在本地运行该函数。 Azure Functions 运行时将启动并告诉你访问你的函数的 URL。 4. **测试:** * **GET 请求(带查询参数):** 打开浏览器并转到 `http://localhost:7071/api/Hello?name=YourName`(将 `7071` 替换为你的函数正在运行的端口)。 你应该看到“你好,YourName。 此 HTTP 触发函数已成功执行。” * **GET 请求(不带查询参数):** 转到 `http://localhost:7071/api/Hello`。 你应该看到“你好,Azure Function。 此 HTTP 触发函数已成功执行。” * **POST 请求(带正文):** 使用 `curl` 或 Postman 等工具向 `http://localhost:7071/api/Hello` 发送 POST 请求,请求正文包含名称(例如,`"MyName"`)。 响应应为“你好,MyName。 此 HTTP 触发函数已成功执行。” 5. **部署到 Azure:** 使用 Visual Studio、VS Code 或 Azure CLI 将你的函数部署到 Azure。 **关键改进和注意事项:** * **错误处理:** 此示例缺少适当的错误处理。 在实际函数中,你应该处理异常并返回适当的错误响应(例如,`BadRequestObjectResult`、`InternalServerErrorResult`)。 * **异步操作:** 对于更复杂的操作(尤其是 I/O),请使用 `async` 和 `await` 以避免阻塞线程。 这对于可伸缩性至关重要。 `.Result` 调用是此示例的快捷方式,但在可能的情况下应避免在生产代码中使用。 * **依赖注入:** 对于更大的函数,请使用依赖注入来管理依赖项(例如,配置、数据库连接)。 Azure Functions 支持依赖注入。 * **配置:** 使用 Azure Functions 配置系统来存储设置(例如,连接字符串、API 密钥)。 * **日志记录:** 广泛使用 `ILogger` 接口来记录信息、警告和错误。 这对于调试和监控至关重要。 * **幂等性:** 如果你的函数执行应仅执行一次的操作(例如,处理付款),请确保它是幂等的。 * **测试:** 编写单元测试以验证你的函数的逻辑。 This should give you a solid starting point for building Azure Functions in F#. Let me know if you have any more questions.
jtucker
README
使用 Azure Functions 的 Untappd MCP 服务器
一个使用 Azure Functions 和服务器发送事件 (Server Sent Events) 的 Untappd MCP 服务器版本。
相关博文即将发布。
配置 Claude Desktop
由于 Claude Desktop 目前不支持 SSE 配置,您需要包含一个中间组件来调用 SSE 服务器。
编辑您的 claude_desktop_config.json:
{
"mcpServers": {
"untappddotnet": {
"command": "npx",
"args": [
"mcp-remote",
"http://localhost:7071/runtime/webhooks/mcp/sse"
]
}
}
}
构建
前提条件:
- dotnet 9.0
- Docker Desktop
local.settings.json 设置
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "<CONNECTION_STRING>",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"Untappd:ClientId": "",
"Untappd:ClientSecret": ""
}
}
推荐服务器
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
MCP Package Docs Server
促进大型语言模型高效访问和获取 Go、Python 和 NPM 包的结构化文档,通过多语言支持和性能优化来增强软件开发。
Claude Code MCP
一个实现了 Claude Code 作为模型上下文协议(Model Context Protocol, MCP)服务器的方案,它可以通过标准化的 MCP 接口来使用 Claude 的软件工程能力(代码生成、编辑、审查和文件操作)。
@kazuph/mcp-taskmanager
用于任务管理的模型上下文协议服务器。它允许 Claude Desktop(或任何 MCP 客户端)在基于队列的系统中管理和执行任务。
mermaid-mcp-server
一个模型上下文协议 (MCP) 服务器,用于将 Mermaid 图表转换为 PNG 图像。
Jira-Context-MCP
MCP 服务器向 AI 编码助手(如 Cursor)提供 Jira 工单信息。
Linear MCP Server
一个模型上下文协议(Model Context Protocol)服务器,它与 Linear 的问题跟踪系统集成,允许大型语言模型(LLM)通过自然语言交互来创建、更新、搜索和评论 Linear 问题。
Sequential Thinking MCP Server
这个服务器通过将复杂问题分解为顺序步骤来促进结构化的问题解决,支持修订,并通过完整的 MCP 集成来实现多条解决方案路径。
Curri MCP Server
通过管理文本笔记、提供笔记创建工具以及使用结构化提示生成摘要,从而实现与 Curri API 的交互。