octo-mcp-server
octo-mcp-server
A Model Context Protocol (MCP) server built with Python and FastMCP, hosted on Azure Functions using the custom handler pattern. It exposes NWS weather tools over streamable-http transport and is designed for stateless, serverless deployment on the Flex Consumption plan.
Available tools
Tool | Description |
| Returns a message unchanged — useful for testing connectivity |
| 7-day or hourly NWS forecast for any US lat/lon |
| Live conditions from the nearest NWS observation station |
| Active NWS alerts for a US state (two-letter abbreviation) |
All tools call the public National Weather Service API — no API key required. US locations only.
Related MCP server: Simple MCP Server
Project structure
server.py # FastMCP server — all tools defined here
host.json # Azure Functions custom handler config (required)
local.settings.example.json # Template — copy to local.settings.json for local dev
pyproject.toml # Python project metadata and dependencies (uv)
azure.yaml # Azure Developer CLI (azd) project config
Dockerfile # Container image (optional; azd deploy preferred)Local development
1. Install prerequisites
All four tools below must be installed before continuing.
Python 3.11+
Verify: python --version should print 3.11.x or higher.
Windows: download from python.org or
winget install Python.Python.3.11macOS:
brew install python@3.11
uv (Python package manager)
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"Verify: uv --version
Azure Functions Core Tools v4
# macOS
brew tap azure/functions && brew install azure-functions-core-tools@4
# Windows (winget)
winget install Microsoft.AzureFunctionsCoreTools
# npm (any platform)
npm install -g azure-functions-core-tools@4 --unsafe-perm trueVerify: func --version should print 4.x.x
Azurite (local Azure Storage emulator)
npm install -g azuriteVerify: azurite --version
Node.js 18+ is required for both npx and Azurite. Download from nodejs.org if needed.
2. Clone and set up the project
git clone <repo-url>
cd octo_mcpCopy the settings template:
# macOS / Linux
cp local.settings.example.json local.settings.json
# Windows (Command Prompt)
copy local.settings.example.json local.settings.json
# Windows (PowerShell)
Copy-Item local.settings.example.json local.settings.jsonlocal.settings.json is git-ignored and never committed. It holds secrets and local environment configuration.
If you add tools that require API keys, add them under Values in local.settings.json:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "custom",
"CUSTOM_HANDLER_PORT": "8000",
"MY_API_KEY": "your_key_here"
}
}3. Start Azurite (storage emulator)
The Functions host requires a storage backend, even locally. Open a separate terminal and run:
azurite --silent --location .azurite --debug .azurite/debug.logLeave this terminal running while you develop. The .azurite/ directory is git-ignored.
4. Start the server
uv run func startThis command:
Creates/updates the
.venvvirtual environment automaticallyInstalls all dependencies from
pyproject.tomlStarts the Azure Functions host on port 7071
Launches
server.pyas the custom handler on port 8000
Expected startup output:
Azure Functions Core Tools
Core Tools Version: 4.x.x
...
[2024-...] Host initialized (XXXms)
[2024-...] Host started (XXXms)
[2024-...] Job host startedYou will also see red-colored log lines from the MCP SDK and a 404 on the root path — both are expected and harmless:
Red log lines: the MCP SDK writes to
stderr, which Functions renders in red. Cosmetic only.404on/: the Functions host pings/on startup. FastMCP doesn't implement/, so it returns 404. This is not an error.
The MCP endpoint is now live at: http://localhost:7071/mcp
5. Test the server
Option A — MCP Inspector (browser UI)
npx @modelcontextprotocol/inspector@latest http://localhost:7071/mcpOpen the URL printed by the inspector, click Connect, then List Tools to verify all four tools appear.
Option B — Claude Code (CLI)
claude mcp add --transport http octo-mcp-local http://localhost:7071/mcpThis registers the server at the project scope. Run it from inside the octo_mcp directory. Tools will be available in any Claude Code session started from this project.
Option C — Claude Desktop
Claude Desktop only supports stdio transport natively. Use mcp-remote (requires Node.js) as a bridge — it runs as a local stdio proxy that forwards to the HTTP server.
Edit the Claude Desktop config file:
Windows:
%APPDATA%\Claude\claude_desktop_config.jsonmacOS:
~/Library/Application Support/Claude/claude_desktop_config.json
Add the server under mcpServers:
{
"mcpServers": {
"octo-mcp-local": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://localhost:7071/mcp"]
}
}
}Restart Claude Desktop after saving. The tools will appear in the tool picker in any conversation. The MCP server must be running (uv run func start) before Claude Desktop can connect.
Option D — VS Code Copilot
Create or update .vscode/mcp.json in the repository root:
{
"servers": {
"local-octo-mcp": {
"type": "http",
"url": "http://localhost:7071/mcp"
}
}
}In VS Code, open the Copilot chat panel, switch to Agent mode, and the tools will appear in the tool picker.
Option E — curl (quick connectivity check)
curl -X POST http://localhost:7071/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'6. Add a new tool
All tools live in server.py. Add a decorated async function:
@mcp.tool()
async def my_tool(input: str) -> str:
"""
Description shown to the MCP client.
Args:
input: Description of the parameter.
"""
result = await call_some_api(input)
return str(result)Rules:
Always include a docstring with an
Args:section — this becomes the tool's schema.Type-hint all parameters — they generate the JSON schema.
Use
async deffor any I/O calls.Catch exceptions and return error strings — never let tools raise unhandled exceptions.
Return
stror a JSON-serializable type.
Restart uv run func start after adding tools.
Deploy to Azure
Prerequisites
# macOS brew tap azure/azd && brew install azd # Windows (winget) winget install Microsoft.Azd # Script curl -fsSL https://aka.ms/install-azd.sh | bash # macOS/Linux powershell -ex AllSigned -c "Invoke-RestMethod 'https://aka.ms/install-azd.ps1' | Invoke-Expression" # WindowsVerify:
azd versionAn Azure subscription with permission to create resources (Contributor or Owner role)
Azure CLI (optional but useful for post-deploy config):
winget install Microsoft.AzureCLI # Windows brew install azure-cli # macOS
Deploy
azd auth login
azd upazd up will:
Prompt you to select an Azure subscription and region
Provision a Flex Consumption Function App, Storage Account, and App Service Plan
Deploy the application code
This takes 3–5 minutes on first run. You will see the deployed Function App URL at the end.
For code-only updates (after infrastructure is already provisioned):
azd deployRequired App Setting after deploy
The custom handler pattern requires FUNCTIONS_WORKER_RUNTIME=custom in Azure, not python. This is correct — it tells the Functions host to proxy HTTP to your process rather than use the built-in Python worker. Your Python code still runs; the host just doesn't manage it through the language worker.
Validate this setting immediately after azd up:
az functionapp config appsettings list \
--name <function-app-name> \
--resource-group <resource-group> \
--query "[?name=='FUNCTIONS_WORKER_RUNTIME'].value" -o tsvIf the value is not custom, set it:
az functionapp config appsettings set \
--name <function-app-name> \
--resource-group <resource-group> \
--settings FUNCTIONS_WORKER_RUNTIME=customSet additional environment variables in Azure
For tools that require API keys or secrets, set them as App Settings:
az functionapp config appsettings set \
--name <function-app-name> \
--resource-group <resource-group> \
--settings MY_API_KEY=your_valueYou can find <function-app-name> and <resource-group> in the azd output or in the Azure Portal.
Access them in server.py via os.environ:
import os
MY_API_KEY = os.environ.get("MY_API_KEY", "")MCP endpoint URLs
Context | URL |
Local |
|
Azure |
|
Connect MCP clients to the deployed server
Replace <funcappname> with your actual Function App name from the azd up output.
Claude Code (CLI)
claude mcp add --transport http octo-mcp-azure https://<funcappname>.azurewebsites.net/mcpTo switch between local and Azure within the same project, use different names (octo-mcp-local vs octo-mcp-azure) so both can coexist. List registered servers with:
claude mcp listRemove a server with:
claude mcp remove octo-mcp-azureClaude Desktop
Same bridge approach as local — mcp-remote proxies stdio to the Azure HTTP endpoint:
{
"mcpServers": {
"octo-mcp-azure": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://<funcappname>.azurewebsites.net/mcp"]
}
}
}Restart Claude Desktop after saving. No local server needs to be running — mcp-remote connects directly to Azure.
VS Code Copilot
Update .vscode/mcp.json:
{
"servers": {
"octo-mcp-azure": {
"type": "http",
"url": "https://<funcappname>.azurewebsites.net/mcp"
}
}
}Architecture
MCP Client (VS Code Copilot / Claude Desktop / MCP Inspector)
│
▼ HTTP POST (streamable-http)
Azure Functions Host (port 7071 locally)
│ custom handler proxy
▼
FastMCP Server Process (server.py on port 8000)
│
▼
National Weather Service API (api.weather.gov)Key design decisions:
Custom handler pattern: Azure Functions acts as a managed HTTP host and proxies all requests to the FastMCP process. No Azure Functions triggers or bindings are used in
server.py.configurationProfile: "mcp-custom-handler"inhost.json: strips the/apiprefix, enables full HTTP proxying, and sets auth to anonymous (EasyAuth handles auth at the platform layer in production).stateless_http=True: required for Flex Consumption plan scale-out. Do not remove this.transport="streamable-http": required for Azure Functions hosting. Do not switch tostdioorsse.
Troubleshooting
Symptom | Cause | Fix |
| Functions pings | Expected — not an error |
Red log output on startup | MCP SDK logs to | Expected — cosmetic only |
| Azure Functions Core Tools not installed | Install v4 (see prerequisites) |
| uv not installed | Install uv (see prerequisites) |
Port already in use (8000 or 7071) | Another process is running on those ports | Kill the process or change ports in |
|
| Set to |
| Azurite not running | Start Azurite in a separate terminal |
Claude Desktop: "not valid MCP server configurations" | Claude Desktop doesn't support | Use |
Tools not appearing in client | Server not initialized or wrong URL | Check MCP Inspector → List Tools; verify URL ends in |
| Default | Ensure |
| Missing permissions or subscription not set | Run |
Cold start timeouts (Azure) | Flex Consumption cold start | Keep |
Environment variables
Variable | Default | Description |
|
| Port the FastMCP server binds to — must match |
|
| Storage connection string (Azurite locally; real account in Azure) |
|
| Required for Azure Functions custom handler routing |
Add tool-specific secrets (API keys, connection strings) to local.settings.json under Values for local dev, and as Azure App Settings for production. Never commit secrets to source control.
Deploy from a source repository (GitHub or Azure DevOps)
This project is already azd-ready (azure.yaml is present), so the most efficient and recommended path is:
One-time bootstrap from your workstation to provision Azure and configure CI/CD trust.
Commit/push only for all future app updates.
Let pipeline runs handle
azd provision/azd deployas needed.
Option A (recommended): GitHub + azd pipeline config
Use this when your code is hosted in GitHub and you want least-maintenance CI/CD with OpenID Connect (OIDC).
1) Push this repo to GitHub
git init
git add .
git commit -m "Initial commit"
git branch -M main
git remote add origin https://github.com/<org-or-user>/<repo>.git
git push -u origin main2) Log in and initialize environment metadata
azd auth login
azd env new <env-name>3) Provision once (creates Azure resources)
azd up4) Configure GitHub Actions pipeline via azd
azd pipeline configWhen prompted:
Provider: GitHub
Auth: OIDC/Federated credentials (recommended default)
Repository: choose existing repo or let
azdcreate one
azd generates/updates workflow files under .github/workflows/ and configures required Azure/GitHub trust.
5) Confirm runtime app setting once
az functionapp config appsettings set \
--name <function-app-name> \
--resource-group <resource-group> \
--settings FUNCTIONS_WORKER_RUNTIME=custom6) Day-2 workflow
For future changes:
git add .
git commit -m "Describe change"
git pushPush triggers GitHub Actions deployment automatically.
Option B: Azure DevOps Repos + Azure Pipelines via azd pipeline config
Use this when your code is in Azure DevOps and you want the same azd-managed deployment model.
1) Import/push the repo to Azure Repos
Use Azure DevOps UI (Repos → Import) or standard git remote push:
git remote add azdo https://dev.azure.com/<org>/<project>/_git/<repo>
git push -u azdo main2) Authenticate and provision (if not already done)
azd auth login
azd env new <env-name>
azd up3) Configure Azure Pipelines with azd
azd pipeline configWhen prompted:
Provider: Azure DevOps
Select your organization/project/repository
azd wires the service connection and pipeline definition for this project.
4) Confirm runtime app setting once
az functionapp config appsettings set \
--name <function-app-name> \
--resource-group <resource-group> \
--settings FUNCTIONS_WORKER_RUNTIME=custom5) Day-2 workflow
git add .
git commit -m "Describe change"
git pushPush triggers Azure Pipelines deployment automatically.
Validation checklist (dev → deploy)
Before enabling CI/CD:
Local run succeeds:
uv run func startTool discovery succeeds: MCP Inspector →
List ToolsOne-time cloud deploy succeeds:
azd upCloud endpoint responds:
https://<funcappname>.azurewebsites.net/mcpApp setting is correct in Azure:
FUNCTIONS_WORKER_RUNTIME=custom
This sequence is the shortest reliable path from local development to repeatable production deployment for this repo.
This server cannot be deployed
Maintenance
Related MCP Connectors
MCP server with quote and live cryptocurrency price tools, local and cloud-deployed transports.
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
MCP server for progressive tool usage at any scale (see https://klavis.ai)
Related MCP Servers
- AlicenseNot gradedqualityFmaintenanceA configurable server implementation that provides MCP (Model-Controller-Protocol) functionality, supporting both Node.js and Docker environments with automated setup and configuration options.318 npmMIT
- AlicenseNot gradedqualityDmaintenanceA self-contained, dependency-free MCP server that provides utility tools for time, date, mathematical calculations, and shell command execution. It supports remote connectivity through SSE and is designed for easy deployment via Docker.GPL 3.0
- AlicenseNot gradedqualityDmaintenanceA lightweight Node.js-based MCP server that exposes custom tools via HTTP and Server-Sent Events (SSE) for clients like Postman. It allows users to register tools with type-safe validation to establish bidirectional communication with MCP clients.2,153 npm1MIT
- FlicenseNot gradedqualityBmaintenanceA Node.js MCP server that loads plugins, registers tools via the MCP SDK, and exposes functionality over stdio with built-in persistence and a Next.js web UI. It provides a management stack for MCP tools with plugin lifecycle management and security controls.-