<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>topic Create an MCP for Azure DevOps To Use With Genie Code in Community Articles</title>
    <link>https://community.databricks.com/t5/community-articles/create-an-mcp-for-azure-devops-to-use-with-genie-code/m-p/152041#M1114</link>
    <description>&lt;H2&gt;&lt;SPAN&gt;Overview&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Prompted by a customer question, I wanted to see what was possible in terms of MCP integration into Genie Code, in order to try this out I decided to look at Azure Dev Ops, as it's a common workflow to want to see your tickets alongside the work you're doing. When I dug into it I realised the Azure out of the box MCP did not have the right protocols to enable Genie code to use it. Given the common nature of this kind of request I thought I'd share my learnings here. After some testing I found the best way to achieve this was using a python udf in devops and then setting this up as a Genie Code MCP server.&amp;nbsp; If you want to give this a go, here are the instructions and code.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&amp;nbsp;What You'll Build&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Two Python UDFs in Unity Catalog that connect to Azure DevOps — search_work_items and list_project_backlog. No extra infrastructure. Add them to Genie Code via the UC Function browser and start asking natural language questions immediately.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;H2&gt;&lt;SPAN&gt;Prerequisites&lt;/SPAN&gt;&lt;/H2&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;A Databricks workspace with Apps enabled&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Databricks CLI installed and authenticated&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;An Azure DevOps organisation with a project&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;A Personal Access Token (PAT) for Azure DevOps with Work Items (Read) scope&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;H2&gt;&lt;SPAN&gt;Step 1: Create an Azure DevOps Project &amp;amp; Personal token&lt;/SPAN&gt;&lt;/H2&gt;
&lt;H3&gt;&lt;SPAN&gt;Create DevOps Project&lt;/SPAN&gt;&lt;/H3&gt;
&lt;OL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Go to &lt;A href="https://dev.azure.com" target="_blank" rel="noopener"&gt;https://dev.azure.com&lt;/A&gt; and sign in (or create a free account)&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Create a new organisation&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Create a project&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Add some work items (Epics, Issues, Tasks) to have data to query&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;H3&gt;&lt;SPAN&gt;Create a Personal Access Token&lt;/SPAN&gt;&lt;/H3&gt;
&lt;OL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Click the user settings icon (top right) → Personal access tokens&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Click New Token&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Name: &amp;lt;set-your-name&amp;gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Scopes: Full access (tighten for production)&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Copy the token — you'll need it in Step 3&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;H3&gt;&lt;SPAN&gt;Step 2: Enable Prerequisites&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;Before creating the UC Functions, enable outbound networking for UDFs:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;In your Databricks workspace, go to Settings &amp;gt; Preview features&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Enable "Enable networking for UDFs in Serverless SQL Warehouses"&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Restart your SQL warehouse after enabling. Changes can take up to 15 minutes to propagate.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3&gt;&lt;SPAN&gt;Step 3: Create the UC Functions&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;First, create a schema to hold the functions:&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;CREATE SCHEMA IF NOT EXISTS &amp;lt;your-catalog&amp;gt;.&amp;lt;your-schema&amp;gt;

COMMENT 'Azure DevOps MCP tools for Genie Code';&lt;/LI-CODE&gt;
&lt;P&gt;&lt;SPAN&gt;Note: Python UDFs do not support default parameter values — all parameters are required.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3&gt;&lt;SPAN&gt;Function 1: search_work_items&lt;/SPAN&gt;&lt;/H3&gt;
&lt;LI-CODE lang="python"&gt;CREATE OR REPLACE FUNCTION &amp;lt;your-catalog&amp;gt;.&amp;lt;your-schema&amp;gt;.search_work_items(

  query STRING COMMENT 'Text to search for in work item titles and descriptions',

  max_results INT COMMENT 'Maximum number of results to return, e.g. 20'

)

RETURNS STRING

LANGUAGE PYTHON

COMMENT 'Search Azure DevOps work items by text in title or description. Use this to find tasks, issues, and epics matching a keyword.'

AS $$

import urllib.request, json, base64

PAT = "&amp;lt;your-pat-token&amp;gt;"

ORG = "&amp;lt;your-org-name&amp;gt;"

PROJECT = "&amp;lt;your-project-name&amp;gt;"

BASE = f"https://dev.azure.com/{ORG}"

creds = base64.b64encode(f":{PAT}".encode()).decode()

def api_call(path, body=None, extra_params=''):

    url = f"{BASE}/{path}?api-version=7.0{extra_params}"

    headers = {"Authorization": f"Basic {creds}", "Content-Type": "application/json"}

    if body:

        req = urllib.request.Request(url, data=json.dumps(body).encode(), headers=headers)

    else:

        req = urllib.request.Request(url, headers=headers)

    return json.loads(urllib.request.urlopen(req, timeout=30).read())

wiql = f"SELECT [System.Id] FROM workitems WHERE [System.Title] CONTAINS '{query}' OR [System.Description] CONTAINS '{query}' ORDER BY [System.ChangedDate] DESC"

result = api_call(f"{PROJECT}/_apis/wit/wiql", {"query": wiql}, f"&amp;amp;$top={max_results}")

ids = [wi["id"] for wi in result.get("workItems", [])]

if not ids:

    return f"No work items found matching '{query}'."

fields = "System.Id,System.Title,System.State,System.AssignedTo,System.WorkItemType"

id_str = ",".join(str(x) for x in ids[:200])

data = api_call(f"{PROJECT}/_apis/wit/workitems", extra_params=f"&amp;amp;ids={id_str}&amp;amp;$fields={fields}")

lines = [f"Search results for '{query}' ({len(data.get('value', []))} items):", ""]

for item in data.get("value", []):

    f = item.get("fields", {})

    assigned = f.get("System.AssignedTo", {})

    if isinstance(assigned, dict):

        assigned = assigned.get("displayName", "Unassigned")

    lines.append(f"- #{item['id']} [{f.get('System.WorkItemType', '?')}] {f.get('System.Title', 'Untitled')} - {f.get('System.State', '?')} (Assigned: {assigned})")

return "\n".join(lines)

$$;&lt;/LI-CODE&gt;
&lt;H3&gt;&lt;SPAN&gt;Function 2: list_project_backlog&lt;/SPAN&gt;&lt;/H3&gt;
&lt;LI-CODE lang="python"&gt;CREATE OR REPLACE FUNCTION &amp;lt;your-catalog&amp;gt;.&amp;lt;your-schema&amp;gt;.list_project_backlog(
  max_results INT COMMENT 'Maximum number of backlog items to return, e.g. 50'
)
RETURNS STRING
LANGUAGE PYTHON
COMMENT 'List the active project backlog showing all open Epics, Issues, and Tasks grouped by type.'
AS $$
import urllib.request, json, base64
PAT = "&amp;lt;your-pat-token&amp;gt;"
ORG = "&amp;lt;your-org-name&amp;gt;"
PROJECT = "&amp;lt;your-project-name&amp;gt;"
BASE = f"https://dev.azure.com/{ORG}"
creds = base64.b64encode(f":{PAT}".encode()).decode()
def api_call(path, body=None, extra_params=''):
    url = f"{BASE}/{path}?api-version=7.0{extra_params}"
    headers = {"Authorization": f"Basic {creds}", "Content-Type": "application/json"}
    if body:
        req = urllib.request.Request(url, data=json.dumps(body).encode(), headers=headers)
    else:
        req = urllib.request.Request(url, headers=headers)
    return json.loads(urllib.request.urlopen(req, timeout=30).read())
wiql = "SELECT [System.Id] FROM workitems WHERE [System.WorkItemType] IN ('Epic', 'Issue', 'Task') AND [System.State] NOT IN ('Closed', 'Removed', 'Done') ORDER BY [Microsoft.VSTS.Common.BacklogPriority] ASC"
result = api_call(f"{PROJECT}/_apis/wit/wiql", {"query": wiql}, f"&amp;amp;$top={max_results}")
ids = [wi["id"] for wi in result.get("workItems", [])]
if not ids:
    return "The backlog is empty."
fields = "System.Id,System.Title,System.State,System.AssignedTo,System.WorkItemType"
id_str = ",".join(str(x) for x in ids[:200])
data = api_call(f"{PROJECT}/_apis/wit/workitems", extra_params=f"&amp;amp;ids={id_str}&amp;amp;$fields={fields}")
by_type = {}
for item in data.get("value", []):
    wtype = item.get("fields", {}).get("System.WorkItemType", "Other")
    by_type.setdefault(wtype, []).append(item)
type_order = ["Epic", "Issue", "Task"]
lines = [f"Active Backlog ({len(data.get('value', []))} items):", ""]
for wtype in type_order:
    items = by_type.get(wtype, [])
    if items:
        lines.append(f"{wtype}s ({len(items)}):")
        for item in items:
            f = item.get("fields", {})
            assigned = f.get("System.AssignedTo", {})
            if isinstance(assigned, dict):
                assigned = assigned.get("displayName", "Unassigned")
            lines.append(f"  - #{item['id']} {f.get('System.Title', 'Untitled')} - {f.get('System.State', '?')} (Assigned: {assigned})")
        lines.append("")
return "\n".join(lines)
$$;&lt;/LI-CODE&gt;
&lt;H3&gt;&lt;SPAN&gt;Step 4: Test the Functions&lt;/SPAN&gt;&lt;/H3&gt;
&lt;LI-CODE lang="python"&gt;SELECT &amp;lt;your-catalog&amp;gt;.&amp;lt;your-schema&amp;gt;.search_work_items('inspection', 5);

SELECT &amp;lt;your-catalog&amp;gt;.&amp;lt;your-schema&amp;gt;.list_project_backlog(50);&lt;/LI-CODE&gt;
&lt;H3&gt;&lt;SPAN&gt;Step 5: Add to Genie Code&lt;/SPAN&gt;&lt;/H3&gt;
&lt;OL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Open Genie Code in your workspace&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Switch to Agent Mode&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Click Settings (gear icon)&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Under "Unity Catalog Function", click Browse&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Navigate to mcga_catalog.devops_tools&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Select the functions and Save&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;H3&gt;&lt;SPAN&gt;Result&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;You can now ask Genie Code natural language questions like:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;"What's on the backlog?"&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;"Find work items related to vessel inspections"&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;"Search for anything about compliance"&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;H3&gt;&lt;SPAN&gt;Key Notes for UC Functions Approach&lt;/SPAN&gt;&lt;/H3&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Python UDFs cannot have default parameter values — all params must be required&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;The "Enable networking for UDFs" preview must be enabled for outbound HTTP calls&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Functions use urllib.request (standard library) — no pip dependencies needed&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;The Azure DevOps PAT is embedded in the function body. For production, consider using UC service credentials instead.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
    <pubDate>Wed, 25 Mar 2026 15:56:41 GMT</pubDate>
    <dc:creator>emma_s</dc:creator>
    <dc:date>2026-03-25T15:56:41Z</dc:date>
    <item>
      <title>Create an MCP for Azure DevOps To Use With Genie Code</title>
      <link>https://community.databricks.com/t5/community-articles/create-an-mcp-for-azure-devops-to-use-with-genie-code/m-p/152041#M1114</link>
      <description>&lt;H2&gt;&lt;SPAN&gt;Overview&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Prompted by a customer question, I wanted to see what was possible in terms of MCP integration into Genie Code, in order to try this out I decided to look at Azure Dev Ops, as it's a common workflow to want to see your tickets alongside the work you're doing. When I dug into it I realised the Azure out of the box MCP did not have the right protocols to enable Genie code to use it. Given the common nature of this kind of request I thought I'd share my learnings here. After some testing I found the best way to achieve this was using a python udf in devops and then setting this up as a Genie Code MCP server.&amp;nbsp; If you want to give this a go, here are the instructions and code.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;&amp;nbsp;What You'll Build&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Two Python UDFs in Unity Catalog that connect to Azure DevOps — search_work_items and list_project_backlog. No extra infrastructure. Add them to Genie Code via the UC Function browser and start asking natural language questions immediately.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;H2&gt;&lt;SPAN&gt;Prerequisites&lt;/SPAN&gt;&lt;/H2&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;A Databricks workspace with Apps enabled&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Databricks CLI installed and authenticated&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;An Azure DevOps organisation with a project&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;A Personal Access Token (PAT) for Azure DevOps with Work Items (Read) scope&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;H2&gt;&lt;SPAN&gt;Step 1: Create an Azure DevOps Project &amp;amp; Personal token&lt;/SPAN&gt;&lt;/H2&gt;
&lt;H3&gt;&lt;SPAN&gt;Create DevOps Project&lt;/SPAN&gt;&lt;/H3&gt;
&lt;OL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Go to &lt;A href="https://dev.azure.com" target="_blank" rel="noopener"&gt;https://dev.azure.com&lt;/A&gt; and sign in (or create a free account)&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Create a new organisation&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Create a project&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Add some work items (Epics, Issues, Tasks) to have data to query&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;H3&gt;&lt;SPAN&gt;Create a Personal Access Token&lt;/SPAN&gt;&lt;/H3&gt;
&lt;OL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Click the user settings icon (top right) → Personal access tokens&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Click New Token&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Name: &amp;lt;set-your-name&amp;gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Scopes: Full access (tighten for production)&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Copy the token — you'll need it in Step 3&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;H3&gt;&lt;SPAN&gt;Step 2: Enable Prerequisites&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;Before creating the UC Functions, enable outbound networking for UDFs:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;In your Databricks workspace, go to Settings &amp;gt; Preview features&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Enable "Enable networking for UDFs in Serverless SQL Warehouses"&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Restart your SQL warehouse after enabling. Changes can take up to 15 minutes to propagate.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3&gt;&lt;SPAN&gt;Step 3: Create the UC Functions&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;First, create a schema to hold the functions:&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;CREATE SCHEMA IF NOT EXISTS &amp;lt;your-catalog&amp;gt;.&amp;lt;your-schema&amp;gt;

COMMENT 'Azure DevOps MCP tools for Genie Code';&lt;/LI-CODE&gt;
&lt;P&gt;&lt;SPAN&gt;Note: Python UDFs do not support default parameter values — all parameters are required.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3&gt;&lt;SPAN&gt;Function 1: search_work_items&lt;/SPAN&gt;&lt;/H3&gt;
&lt;LI-CODE lang="python"&gt;CREATE OR REPLACE FUNCTION &amp;lt;your-catalog&amp;gt;.&amp;lt;your-schema&amp;gt;.search_work_items(

  query STRING COMMENT 'Text to search for in work item titles and descriptions',

  max_results INT COMMENT 'Maximum number of results to return, e.g. 20'

)

RETURNS STRING

LANGUAGE PYTHON

COMMENT 'Search Azure DevOps work items by text in title or description. Use this to find tasks, issues, and epics matching a keyword.'

AS $$

import urllib.request, json, base64

PAT = "&amp;lt;your-pat-token&amp;gt;"

ORG = "&amp;lt;your-org-name&amp;gt;"

PROJECT = "&amp;lt;your-project-name&amp;gt;"

BASE = f"https://dev.azure.com/{ORG}"

creds = base64.b64encode(f":{PAT}".encode()).decode()

def api_call(path, body=None, extra_params=''):

    url = f"{BASE}/{path}?api-version=7.0{extra_params}"

    headers = {"Authorization": f"Basic {creds}", "Content-Type": "application/json"}

    if body:

        req = urllib.request.Request(url, data=json.dumps(body).encode(), headers=headers)

    else:

        req = urllib.request.Request(url, headers=headers)

    return json.loads(urllib.request.urlopen(req, timeout=30).read())

wiql = f"SELECT [System.Id] FROM workitems WHERE [System.Title] CONTAINS '{query}' OR [System.Description] CONTAINS '{query}' ORDER BY [System.ChangedDate] DESC"

result = api_call(f"{PROJECT}/_apis/wit/wiql", {"query": wiql}, f"&amp;amp;$top={max_results}")

ids = [wi["id"] for wi in result.get("workItems", [])]

if not ids:

    return f"No work items found matching '{query}'."

fields = "System.Id,System.Title,System.State,System.AssignedTo,System.WorkItemType"

id_str = ",".join(str(x) for x in ids[:200])

data = api_call(f"{PROJECT}/_apis/wit/workitems", extra_params=f"&amp;amp;ids={id_str}&amp;amp;$fields={fields}")

lines = [f"Search results for '{query}' ({len(data.get('value', []))} items):", ""]

for item in data.get("value", []):

    f = item.get("fields", {})

    assigned = f.get("System.AssignedTo", {})

    if isinstance(assigned, dict):

        assigned = assigned.get("displayName", "Unassigned")

    lines.append(f"- #{item['id']} [{f.get('System.WorkItemType', '?')}] {f.get('System.Title', 'Untitled')} - {f.get('System.State', '?')} (Assigned: {assigned})")

return "\n".join(lines)

$$;&lt;/LI-CODE&gt;
&lt;H3&gt;&lt;SPAN&gt;Function 2: list_project_backlog&lt;/SPAN&gt;&lt;/H3&gt;
&lt;LI-CODE lang="python"&gt;CREATE OR REPLACE FUNCTION &amp;lt;your-catalog&amp;gt;.&amp;lt;your-schema&amp;gt;.list_project_backlog(
  max_results INT COMMENT 'Maximum number of backlog items to return, e.g. 50'
)
RETURNS STRING
LANGUAGE PYTHON
COMMENT 'List the active project backlog showing all open Epics, Issues, and Tasks grouped by type.'
AS $$
import urllib.request, json, base64
PAT = "&amp;lt;your-pat-token&amp;gt;"
ORG = "&amp;lt;your-org-name&amp;gt;"
PROJECT = "&amp;lt;your-project-name&amp;gt;"
BASE = f"https://dev.azure.com/{ORG}"
creds = base64.b64encode(f":{PAT}".encode()).decode()
def api_call(path, body=None, extra_params=''):
    url = f"{BASE}/{path}?api-version=7.0{extra_params}"
    headers = {"Authorization": f"Basic {creds}", "Content-Type": "application/json"}
    if body:
        req = urllib.request.Request(url, data=json.dumps(body).encode(), headers=headers)
    else:
        req = urllib.request.Request(url, headers=headers)
    return json.loads(urllib.request.urlopen(req, timeout=30).read())
wiql = "SELECT [System.Id] FROM workitems WHERE [System.WorkItemType] IN ('Epic', 'Issue', 'Task') AND [System.State] NOT IN ('Closed', 'Removed', 'Done') ORDER BY [Microsoft.VSTS.Common.BacklogPriority] ASC"
result = api_call(f"{PROJECT}/_apis/wit/wiql", {"query": wiql}, f"&amp;amp;$top={max_results}")
ids = [wi["id"] for wi in result.get("workItems", [])]
if not ids:
    return "The backlog is empty."
fields = "System.Id,System.Title,System.State,System.AssignedTo,System.WorkItemType"
id_str = ",".join(str(x) for x in ids[:200])
data = api_call(f"{PROJECT}/_apis/wit/workitems", extra_params=f"&amp;amp;ids={id_str}&amp;amp;$fields={fields}")
by_type = {}
for item in data.get("value", []):
    wtype = item.get("fields", {}).get("System.WorkItemType", "Other")
    by_type.setdefault(wtype, []).append(item)
type_order = ["Epic", "Issue", "Task"]
lines = [f"Active Backlog ({len(data.get('value', []))} items):", ""]
for wtype in type_order:
    items = by_type.get(wtype, [])
    if items:
        lines.append(f"{wtype}s ({len(items)}):")
        for item in items:
            f = item.get("fields", {})
            assigned = f.get("System.AssignedTo", {})
            if isinstance(assigned, dict):
                assigned = assigned.get("displayName", "Unassigned")
            lines.append(f"  - #{item['id']} {f.get('System.Title', 'Untitled')} - {f.get('System.State', '?')} (Assigned: {assigned})")
        lines.append("")
return "\n".join(lines)
$$;&lt;/LI-CODE&gt;
&lt;H3&gt;&lt;SPAN&gt;Step 4: Test the Functions&lt;/SPAN&gt;&lt;/H3&gt;
&lt;LI-CODE lang="python"&gt;SELECT &amp;lt;your-catalog&amp;gt;.&amp;lt;your-schema&amp;gt;.search_work_items('inspection', 5);

SELECT &amp;lt;your-catalog&amp;gt;.&amp;lt;your-schema&amp;gt;.list_project_backlog(50);&lt;/LI-CODE&gt;
&lt;H3&gt;&lt;SPAN&gt;Step 5: Add to Genie Code&lt;/SPAN&gt;&lt;/H3&gt;
&lt;OL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Open Genie Code in your workspace&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Switch to Agent Mode&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Click Settings (gear icon)&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Under "Unity Catalog Function", click Browse&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Navigate to mcga_catalog.devops_tools&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Select the functions and Save&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;H3&gt;&lt;SPAN&gt;Result&lt;/SPAN&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;You can now ask Genie Code natural language questions like:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;"What's on the backlog?"&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;"Find work items related to vessel inspections"&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;"Search for anything about compliance"&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;H3&gt;&lt;SPAN&gt;Key Notes for UC Functions Approach&lt;/SPAN&gt;&lt;/H3&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Python UDFs cannot have default parameter values — all params must be required&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;The "Enable networking for UDFs" preview must be enabled for outbound HTTP calls&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Functions use urllib.request (standard library) — no pip dependencies needed&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;The Azure DevOps PAT is embedded in the function body. For production, consider using UC service credentials instead.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Wed, 25 Mar 2026 15:56:41 GMT</pubDate>
      <guid>https://community.databricks.com/t5/community-articles/create-an-mcp-for-azure-devops-to-use-with-genie-code/m-p/152041#M1114</guid>
      <dc:creator>emma_s</dc:creator>
      <dc:date>2026-03-25T15:56:41Z</dc:date>
    </item>
    <item>
      <title>Re: Create an MCP for Azure DevOps To Use With Genie Code</title>
      <link>https://community.databricks.com/t5/community-articles/create-an-mcp-for-azure-devops-to-use-with-genie-code/m-p/157106#M1184</link>
      <description>&lt;P&gt;Azure DevOps now has a remote MCP server.&amp;nbsp; This would be much easier to use than creating a function for individual ADO API endpoints as you described above.&amp;nbsp; How can I configure a connection to this remote MCP from Databricks?&lt;BR /&gt;&lt;BR /&gt;I'd like to use EntraID Service Prinicipal authrntication with OAuth U2M per User.&amp;nbsp; When I try to setup this with the Unity Catalog HTTP connection I get "&lt;SPAN&gt;AADSTS900971: No reply address provided&lt;/SPAN&gt;" error.&lt;/P&gt;</description>
      <pubDate>Sun, 17 May 2026 15:09:33 GMT</pubDate>
      <guid>https://community.databricks.com/t5/community-articles/create-an-mcp-for-azure-devops-to-use-with-genie-code/m-p/157106#M1184</guid>
      <dc:creator>AndrewWooster</dc:creator>
      <dc:date>2026-05-17T15:09:33Z</dc:date>
    </item>
  </channel>
</rss>

