Azure / Azure/azure-sdk-for-python
Structured inputs: MCP server_url template ({{server_url}}) rejected at agent create — 'not a valid URI'
- Dominant language
- Python
- Stars
- 5.6k
- Forks
- 3.4k
- Avg merge
- 1d 21h
- Merged PRs (30d)
- 193
Description
# Structured inputs: MCP `server_url` template (`{{server_url}}`) rejected at agent create — `format: '{{server_url}}' is not a valid URI`
## Description
Following [Customize agent behavior at runtime with structured inputs → *Use structured inputs with MCP servers*](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/structured-inputs?pivots=python#use-structured-inputs-with-mcp-servers), creating a prompt agent whose MCP tool puts a handlebar template in `server_url` is rejected at **create-version** time. The `server_url` is validated as a strict URI *before* structured-input substitution, so the documented placeholder can never be stored.
## Repro (the doc's example)
```python
# azure-ai-projects==2.2.0, openai==2.44.0
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import MCPTool, PromptAgentDefinition, StructuredInputDefinition
from azure.identity import DefaultAzureCredential
project = AIProjectClient(
endpoint="https://.services.ai.azure.com/api/projects/",
credential=DefaultAzureCredential(),
)
tool = MCPTool(server_label="{{server_label}}", server_url="{{server_url}}", require_approval="never")
project.agents.create_version(
agent_name="mcp-dynamic-agent",
definition=PromptAgentDefinition(
model="gpt-4o-mini",
instructions="You are a helpful assistant.",
tools=[tool],
structured_inputs={
"server_label": StructuredInputDefinition(description="MCP server label", required=True, schema={"type": "string"}),
"server_url": StructuredInputDefinition(description="MCP server URL", required=True, schema={"type": "string"}),
},
),
)
```
**Result:**
```
azure.core.exceptions.HttpResponseError: (invalid_payload)
format: '{{server_url}}' is not a valid URI
param: /definition/tools/0/server_url
```
Equivalent raw REST (`POST {endpoint}/agents//versions?api-version=v1`, body `{"definition": {...}}`) returns the same `400`.
## The failure is specific to `server_url`
Templating works for the **other** MCP fields in the same example — only `server_url` is URI-validated. With a *static* `server_url`, the identical definition creates successfully and the templates are stored verbatim:
```jsonc
// POST .../agents?api-version=v1 -> 200 OK; GET shows the stored definition:
"tools": [{
"type": "mcp",
"server_label": "{{server_label}}", // <- stored literally (OK)
"server_url": "https://gitmcp.io/org/repo", // <- must be a real URI
"headers": { "Authorization": "{{auth_token}}" } // <- stored literally (OK)
}]
```
So the substitution engine accepts templates in `server_label` and `headers`; the issue is isolated to the URI-format validation on `server_url`.
If the template is instead embedded in an otherwise-valid path (`"https://host/{{segment}}"`), create **succeeds** but the braces are **percent-encoded at store time** → the stored value becomes `https://host/%7B%7Bsegment%7D%7D`, so runtime substitution never matches and the MCP client connects to the literal encoded URL.
## Expected
`server_url` should accept a handlebar template (skipping URI-format validation/normalization when the value is a template), consistent with `server_label`/`headers` and with the documentation, and substitute it from the request-body `structured_inputs` at `POST /openai/v1/responses`.
## Environment
- `azure-ai-projects==2.2.0`, `openai==2.44.0`, `api-version=v1` (also reproduced on `2025-11-15-preview`).
- Reproduced via the Python SDK and raw REST, on multiple Foundry resources across different regions/tenants.
## Ask
Relax `server_url` (and confirm `server_label`) URI-format validation for handlebar templates so the documented MCP structured-input pattern works — or, if the feature is not yet enabled in these regions, please annotate the docs and advise how to opt in.
Contributor guide
Assessment
This issue has not been assessed yet.