Compare commits

..

2 Commits

Author SHA1 Message Date
Alex
0650d1947c docs: add stop endpoint page to api-reference
Address review feedback to create a dedicated stop endpoint page
in api-reference, matching the pattern used by kickoff endpoint.
Added stop.mdx for all languages (en, ko, pt-BR, ar) and updated
docs.json navigation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-02 13:12:30 -07:00
Iris Clawd
0e07dd0b1a docs: document /stop/{kickoff_id} endpoint for cancelling executions 2026-04-02 13:11:31 -07:00
16 changed files with 346 additions and 195 deletions

View File

@@ -0,0 +1,8 @@
---
title: "POST /stop/{kickoff_id}"
description: "إيقاف تنفيذ الطاقم الجاري"
openapi: "/enterprise-api.en.yaml POST /stop/{kickoff_id}"
mode: "wide"
---

View File

@@ -495,7 +495,8 @@
"en/api-reference/inputs",
"en/api-reference/kickoff",
"en/api-reference/resume",
"en/api-reference/status"
"en/api-reference/status",
"en/api-reference/stop"
]
}
]
@@ -964,7 +965,8 @@
"en/api-reference/inputs",
"en/api-reference/kickoff",
"en/api-reference/resume",
"en/api-reference/status"
"en/api-reference/status",
"en/api-reference/stop"
]
}
]
@@ -1433,7 +1435,8 @@
"en/api-reference/inputs",
"en/api-reference/kickoff",
"en/api-reference/resume",
"en/api-reference/status"
"en/api-reference/status",
"en/api-reference/stop"
]
}
]
@@ -1902,7 +1905,8 @@
"en/api-reference/inputs",
"en/api-reference/kickoff",
"en/api-reference/resume",
"en/api-reference/status"
"en/api-reference/status",
"en/api-reference/stop"
]
}
]
@@ -2372,7 +2376,8 @@
"en/api-reference/inputs",
"en/api-reference/kickoff",
"en/api-reference/resume",
"en/api-reference/status"
"en/api-reference/status",
"en/api-reference/stop"
]
}
]
@@ -2840,7 +2845,8 @@
"en/api-reference/inputs",
"en/api-reference/kickoff",
"en/api-reference/resume",
"en/api-reference/status"
"en/api-reference/status",
"en/api-reference/stop"
]
}
]
@@ -3311,7 +3317,8 @@
"en/api-reference/inputs",
"en/api-reference/kickoff",
"en/api-reference/resume",
"en/api-reference/status"
"en/api-reference/status",
"en/api-reference/stop"
]
}
]
@@ -3796,7 +3803,8 @@
"pt-BR/api-reference/inputs",
"pt-BR/api-reference/kickoff",
"pt-BR/api-reference/resume",
"pt-BR/api-reference/status"
"pt-BR/api-reference/status",
"pt-BR/api-reference/stop"
]
}
]
@@ -4250,7 +4258,8 @@
"pt-BR/api-reference/inputs",
"pt-BR/api-reference/kickoff",
"pt-BR/api-reference/resume",
"pt-BR/api-reference/status"
"pt-BR/api-reference/status",
"pt-BR/api-reference/stop"
]
}
]
@@ -4704,7 +4713,8 @@
"pt-BR/api-reference/inputs",
"pt-BR/api-reference/kickoff",
"pt-BR/api-reference/resume",
"pt-BR/api-reference/status"
"pt-BR/api-reference/status",
"pt-BR/api-reference/stop"
]
}
]
@@ -5158,7 +5168,8 @@
"pt-BR/api-reference/inputs",
"pt-BR/api-reference/kickoff",
"pt-BR/api-reference/resume",
"pt-BR/api-reference/status"
"pt-BR/api-reference/status",
"pt-BR/api-reference/stop"
]
}
]
@@ -5611,7 +5622,8 @@
"pt-BR/api-reference/inputs",
"pt-BR/api-reference/kickoff",
"pt-BR/api-reference/resume",
"pt-BR/api-reference/status"
"pt-BR/api-reference/status",
"pt-BR/api-reference/stop"
]
}
]
@@ -6064,7 +6076,8 @@
"pt-BR/api-reference/inputs",
"pt-BR/api-reference/kickoff",
"pt-BR/api-reference/resume",
"pt-BR/api-reference/status"
"pt-BR/api-reference/status",
"pt-BR/api-reference/stop"
]
}
]
@@ -6518,7 +6531,8 @@
"pt-BR/api-reference/inputs",
"pt-BR/api-reference/kickoff",
"pt-BR/api-reference/resume",
"pt-BR/api-reference/status"
"pt-BR/api-reference/status",
"pt-BR/api-reference/stop"
]
}
]
@@ -7015,7 +7029,8 @@
"ko/api-reference/inputs",
"ko/api-reference/kickoff",
"ko/api-reference/resume",
"ko/api-reference/status"
"ko/api-reference/status",
"ko/api-reference/stop"
]
}
]
@@ -7481,7 +7496,8 @@
"ko/api-reference/inputs",
"ko/api-reference/kickoff",
"ko/api-reference/resume",
"ko/api-reference/status"
"ko/api-reference/status",
"ko/api-reference/stop"
]
}
]
@@ -7947,7 +7963,8 @@
"ko/api-reference/inputs",
"ko/api-reference/kickoff",
"ko/api-reference/resume",
"ko/api-reference/status"
"ko/api-reference/status",
"ko/api-reference/stop"
]
}
]
@@ -8413,7 +8430,8 @@
"ko/api-reference/inputs",
"ko/api-reference/kickoff",
"ko/api-reference/resume",
"ko/api-reference/status"
"ko/api-reference/status",
"ko/api-reference/stop"
]
}
]
@@ -8878,7 +8896,8 @@
"ko/api-reference/inputs",
"ko/api-reference/kickoff",
"ko/api-reference/resume",
"ko/api-reference/status"
"ko/api-reference/status",
"ko/api-reference/stop"
]
}
]
@@ -9343,7 +9362,8 @@
"ko/api-reference/inputs",
"ko/api-reference/kickoff",
"ko/api-reference/resume",
"ko/api-reference/status"
"ko/api-reference/status",
"ko/api-reference/stop"
]
}
]
@@ -9809,7 +9829,8 @@
"ko/api-reference/inputs",
"ko/api-reference/kickoff",
"ko/api-reference/resume",
"ko/api-reference/status"
"ko/api-reference/status",
"ko/api-reference/stop"
]
}
]
@@ -10306,7 +10327,8 @@
"ar/api-reference/inputs",
"ar/api-reference/kickoff",
"ar/api-reference/resume",
"ar/api-reference/status"
"ar/api-reference/status",
"ar/api-reference/stop"
]
}
]
@@ -10772,7 +10794,8 @@
"ar/api-reference/inputs",
"ar/api-reference/kickoff",
"ar/api-reference/resume",
"ar/api-reference/status"
"ar/api-reference/status",
"ar/api-reference/stop"
]
}
]
@@ -11238,7 +11261,8 @@
"ar/api-reference/inputs",
"ar/api-reference/kickoff",
"ar/api-reference/resume",
"ar/api-reference/status"
"ar/api-reference/status",
"ar/api-reference/stop"
]
}
]
@@ -11704,7 +11728,8 @@
"ar/api-reference/inputs",
"ar/api-reference/kickoff",
"ar/api-reference/resume",
"ar/api-reference/status"
"ar/api-reference/status",
"ar/api-reference/stop"
]
}
]
@@ -12169,7 +12194,8 @@
"ar/api-reference/inputs",
"ar/api-reference/kickoff",
"ar/api-reference/resume",
"ar/api-reference/status"
"ar/api-reference/status",
"ar/api-reference/stop"
]
}
]
@@ -12634,7 +12660,8 @@
"ar/api-reference/inputs",
"ar/api-reference/kickoff",
"ar/api-reference/resume",
"ar/api-reference/status"
"ar/api-reference/status",
"ar/api-reference/stop"
]
}
]
@@ -13100,7 +13127,8 @@
"ar/api-reference/inputs",
"ar/api-reference/kickoff",
"ar/api-reference/resume",
"ar/api-reference/status"
"ar/api-reference/status",
"ar/api-reference/stop"
]
}
]

View File

@@ -0,0 +1,8 @@
---
title: "POST /stop/{kickoff_id}"
description: "Stop a running crew execution"
openapi: "/enterprise-api.en.yaml POST /stop/{kickoff_id}"
mode: "wide"
---

View File

@@ -146,6 +146,36 @@ curl -X GET \
https://your-crew-url.crewai.com/status/abcd1234-5678-90ef-ghij-klmnopqrstuv
```
## Stopping a Running Execution
You can stop or cancel a running crew or flow execution at any time using the stop endpoint. This is useful when you need to abort a long-running execution or cancel one that is no longer needed.
### Stop an Execution
Send a POST request with the `kickoff_id` of the execution you want to stop:
```bash
curl -X POST \
-H "Authorization: Bearer YOUR_CREW_TOKEN" \
https://your-crew-url.crewai.com/stop/abcd1234-5678-90ef-ghij-klmnopqrstuv
```
**Success Response:**
```json
{"status": "stopped", "kickoffId": "abcd1234-5678-90ef-ghij-klmnopqrstuv"}
```
**Error Response** (when the execution has already finished):
```json
{"detail": "Cannot stop execution. Current state: SUCCESS"}
```
<Note>
You cannot stop executions that have already completed (`SUCCESS`), failed (`FAILURE`), or been revoked (`REVOKED`). The API returns a `400` status code in those cases.
</Note>
## Handling Executions
### Long-Running Executions

View File

@@ -36,6 +36,7 @@ info:
1. **Discover inputs** using `GET /inputs`
2. **Start execution** using `POST /kickoff`
3. **Monitor progress** using `GET /{kickoff_id}/status`
4. **Stop execution** (if needed) using `POST /stop/{kickoff_id}`
version: 1.0.0
contact:
name: CrewAI Support
@@ -284,6 +285,56 @@ paths:
"500":
$ref: "#/components/responses/ServerError"
/stop/{kickoff_id}:
post:
summary: Stop Crew Execution
description: |
**📋 Reference Example Only** - *This shows the request format. To test with your actual crew, copy the cURL example and replace the URL + token with your real values.*
Stops or cancels a running crew or flow execution. The execution must be in an active state
(not SUCCESS, FAILURE, or REVOKED).
operationId: stopCrewExecution
parameters:
- name: kickoff_id
in: path
required: true
description: The kickoff ID of the execution to stop
schema:
type: string
format: uuid
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
responses:
"200":
description: Successfully stopped the execution
content:
application/json:
schema:
$ref: "#/components/schemas/StopExecutionResponse"
example:
status: "stopped"
kickoffId: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
"400":
description: Execution is already in a terminal state (SUCCESS, FAILURE, or REVOKED)
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
detail: "Cannot stop execution. Current state: SUCCESS"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
description: Kickoff ID not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
error: "Execution not found"
message: "No execution found with ID: abcd1234-5678-90ef-ghij-klmnopqrstuv"
"500":
$ref: "#/components/responses/ServerError"
/resume:
post:
summary: Resume Crew Execution with Human Feedback
@@ -508,6 +559,19 @@ components:
description: Time taken to execute this task in seconds
example: 45.2
StopExecutionResponse:
type: object
properties:
status:
type: string
enum: ["stopped"]
description: Indicates the execution was successfully stopped
example: "stopped"
kickoffId:
type: string
description: The kickoff ID of the stopped execution
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
Error:
type: object
properties:

View File

@@ -36,6 +36,7 @@ info:
1. **Discover inputs** using `GET /inputs`
2. **Start execution** using `POST /kickoff`
3. **Monitor progress** using `GET /{kickoff_id}/status`
4. **Stop execution** (if needed) using `POST /stop/{kickoff_id}`
version: 1.0.0
contact:
name: CrewAI Support
@@ -284,6 +285,56 @@ paths:
"500":
$ref: "#/components/responses/ServerError"
/stop/{kickoff_id}:
post:
summary: Stop Crew Execution
description: |
**📋 Reference Example Only** - *This shows the request format. To test with your actual crew, copy the cURL example and replace the URL + token with your real values.*
Stops or cancels a running crew or flow execution. The execution must be in an active state
(not SUCCESS, FAILURE, or REVOKED).
operationId: stopCrewExecution
parameters:
- name: kickoff_id
in: path
required: true
description: The kickoff ID of the execution to stop
schema:
type: string
format: uuid
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
responses:
"200":
description: Successfully stopped the execution
content:
application/json:
schema:
$ref: "#/components/schemas/StopExecutionResponse"
example:
status: "stopped"
kickoffId: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
"400":
description: Execution is already in a terminal state (SUCCESS, FAILURE, or REVOKED)
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
detail: "Cannot stop execution. Current state: SUCCESS"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
description: Kickoff ID not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
example:
error: "Execution not found"
message: "No execution found with ID: abcd1234-5678-90ef-ghij-klmnopqrstuv"
"500":
$ref: "#/components/responses/ServerError"
/resume:
post:
summary: Resume Crew Execution with Human Feedback
@@ -508,6 +559,19 @@ components:
description: Time taken to execute this task in seconds
example: 45.2
StopExecutionResponse:
type: object
properties:
status:
type: string
enum: ["stopped"]
description: Indicates the execution was successfully stopped
example: "stopped"
kickoffId:
type: string
description: The kickoff ID of the stopped execution
example: "abcd1234-5678-90ef-ghij-klmnopqrstuv"
Error:
type: object
properties:

View File

@@ -120,6 +120,46 @@ paths:
'500':
$ref: '#/components/responses/ServerError'
/stop/{kickoff_id}:
post:
summary: 실행 중지
description: |
**📋 참조 예제만 제공** - *요청 형식을 보여줍니다. 실제 호출은 cURL 예제를 복사해 URL과 토큰을 교체하세요.*
실행 중인 crew 또는 flow 실행을 중지하거나 취소합니다. 실행이 활성 상태여야 합니다
(SUCCESS, FAILURE, REVOKED 상태가 아닌 경우).
operationId: stopCrewExecution
parameters:
- name: kickoff_id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: 실행을 성공적으로 중지
content:
application/json:
schema:
$ref: '#/components/schemas/StopExecutionResponse'
'400':
description: 실행이 이미 종료 상태 (SUCCESS, FAILURE, REVOKED)
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
$ref: '#/components/responses/UnauthorizedError'
'404':
description: Kickoff ID를 찾을 수 없음
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
$ref: '#/components/responses/ServerError'
/resume:
post:
summary: Resume Crew Execution with Human Feedback
@@ -314,6 +354,15 @@ components:
execution_time:
type: number
StopExecutionResponse:
type: object
properties:
status:
type: string
enum: ["stopped"]
kickoffId:
type: string
Error:
type: object
properties:

View File

@@ -36,6 +36,7 @@ info:
1. **Descubra os inputs** usando `GET /inputs`
2. **Inicie a execução** usando `POST /kickoff`
3. **Monitore o progresso** usando `GET /{kickoff_id}/status`
4. **Pare a execução** (se necessário) usando `POST /stop/{kickoff_id}`
version: 1.0.0
contact:
name: CrewAI Suporte
@@ -156,6 +157,46 @@ paths:
"500":
$ref: "#/components/responses/ServerError"
/stop/{kickoff_id}:
post:
summary: Parar Execução da Crew
description: |
**📋 Exemplo de Referência** - *Mostra o formato da requisição. Para testar com sua crew real, copie o cURL e substitua URL + token.*
Para ou cancela uma execução de crew ou flow em andamento. A execução deve estar em um estado ativo
(não SUCCESS, FAILURE ou REVOKED).
operationId: stopCrewExecution
parameters:
- name: kickoff_id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Execução parada com sucesso
content:
application/json:
schema:
$ref: "#/components/schemas/StopExecutionResponse"
"400":
description: Execução já em estado terminal (SUCCESS, FAILURE ou REVOKED)
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
description: Kickoff ID não encontrado
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
"500":
$ref: "#/components/responses/ServerError"
/resume:
post:
summary: Resume Crew Execution with Human Feedback
@@ -351,6 +392,15 @@ components:
execution_time:
type: number
StopExecutionResponse:
type: object
properties:
status:
type: string
enum: ["stopped"]
kickoffId:
type: string
Error:
type: object
properties:

View File

@@ -0,0 +1,8 @@
---
title: "POST /stop/{kickoff_id}"
description: "실행 중인 크루 실행 중지"
openapi: "/enterprise-api.ko.yaml POST /stop/{kickoff_id}"
mode: "wide"
---

View File

@@ -0,0 +1,8 @@
---
title: "POST /stop/{kickoff_id}"
description: "Parar uma execução de crew em andamento"
openapi: "/enterprise-api.pt-BR.yaml POST /stop/{kickoff_id}"
mode: "wide"
---

View File

@@ -7,7 +7,6 @@ various transport types, similar to OpenAI's Agents SDK.
from pydantic import BaseModel, Field
from crewai.mcp.filters import ToolFilter
from crewai.mcp.transports.stdio import DEFAULT_ALLOWED_COMMANDS
class MCPServerStdio(BaseModel):
@@ -45,14 +44,6 @@ class MCPServerStdio(BaseModel):
default=None,
description="Optional tool filter for filtering available tools.",
)
allowed_commands: frozenset[str] | None = Field(
default=DEFAULT_ALLOWED_COMMANDS,
description=(
"Optional frozenset of allowed command basenames for security validation. "
"Defaults to common runtimes (python, node, npx, uvx, uv, deno, docker). "
"Set to None to disable the allowlist check."
),
)
cache_tools_list: bool = Field(
default=False,
description="Whether to cache the tool list for faster subsequent access.",

View File

@@ -292,7 +292,6 @@ class MCPToolResolver:
command=mcp_config.command,
args=mcp_config.args,
env=mcp_config.env,
allowed_commands=mcp_config.allowed_commands,
)
server_name = f"{mcp_config.command}_{'_'.join(mcp_config.args)}"
elif isinstance(mcp_config, MCPServerHTTP):

View File

@@ -3,12 +3,11 @@
from crewai.mcp.transports.base import BaseTransport, TransportType
from crewai.mcp.transports.http import HTTPTransport
from crewai.mcp.transports.sse import SSETransport
from crewai.mcp.transports.stdio import DEFAULT_ALLOWED_COMMANDS, StdioTransport
from crewai.mcp.transports.stdio import StdioTransport
__all__ = [
"BaseTransport",
"DEFAULT_ALLOWED_COMMANDS",
"HTTPTransport",
"SSETransport",
"StdioTransport",

View File

@@ -9,22 +9,6 @@ from typing_extensions import Self
from crewai.mcp.transports.base import BaseTransport, TransportType
# Default allowlist for common MCP server runtimes.
# Covers the vast majority of MCP server launch commands.
# Pass ``allowed_commands=None`` to disable validation entirely.
DEFAULT_ALLOWED_COMMANDS: frozenset[str] = frozenset(
{
"python",
"python3",
"node",
"npx",
"uvx",
"uv",
"deno",
"docker",
}
)
class StdioTransport(BaseTransport):
"""Stdio transport for connecting to local MCP servers.
@@ -50,7 +34,6 @@ class StdioTransport(BaseTransport):
command: str,
args: list[str] | None = None,
env: dict[str, str] | None = None,
allowed_commands: frozenset[str] | None = DEFAULT_ALLOWED_COMMANDS,
**kwargs: Any,
) -> None:
"""Initialize stdio transport.
@@ -59,26 +42,9 @@ class StdioTransport(BaseTransport):
command: Command to execute (e.g., "python", "node", "npx").
args: Command arguments (e.g., ["server.py"] or ["-y", "@mcp/server"]).
env: Environment variables to pass to the process.
allowed_commands: Optional frozenset of allowed command basenames.
Defaults to ``DEFAULT_ALLOWED_COMMANDS`` which includes common
runtimes (python, node, npx, uvx, uv, deno, docker). Pass
``None`` to disable the check entirely.
**kwargs: Additional transport options.
"""
super().__init__(**kwargs)
if allowed_commands is not None:
base_command = os.path.basename(command)
# Strip extension for Windows compatibility (e.g., python.exe -> python)
base_command = os.path.splitext(base_command)[0]
if base_command not in allowed_commands:
raise ValueError(
f"Command '{command}' is not in the allowed commands list: "
f"{sorted(allowed_commands)}. "
f"To allow this command, add it to allowed_commands or pass "
f"allowed_commands=None to disable this check."
)
self.command = command
self.args = args or []
self.env = env or {}

View File

@@ -1,28 +0,0 @@
"""Tests for MCPServerStdio allowed_commands config integration."""
from crewai.mcp.config import MCPServerStdio
from crewai.mcp.transports.stdio import DEFAULT_ALLOWED_COMMANDS
class TestMCPServerStdioConfig:
"""Tests for the allowed_commands field on MCPServerStdio."""
def test_default_allowed_commands(self):
"""MCPServerStdio should default to DEFAULT_ALLOWED_COMMANDS."""
config = MCPServerStdio(command="python", args=["server.py"])
assert config.allowed_commands == DEFAULT_ALLOWED_COMMANDS
def test_custom_allowed_commands(self):
"""Users can override allowed_commands in config."""
custom = frozenset({"my-runtime"})
config = MCPServerStdio(
command="my-runtime", args=[], allowed_commands=custom
)
assert config.allowed_commands == custom
def test_none_allowed_commands(self):
"""Users can disable the allowlist via config."""
config = MCPServerStdio(
command="anything", args=[], allowed_commands=None
)
assert config.allowed_commands is None

View File

@@ -1,93 +0,0 @@
"""Tests for StdioTransport command allowlist validation."""
import pytest
from crewai.mcp.transports.stdio import DEFAULT_ALLOWED_COMMANDS, StdioTransport
class TestStdioTransportAllowlist:
"""Tests for the command allowlist feature."""
def test_default_allowed_commands_contains_common_runtimes(self):
"""DEFAULT_ALLOWED_COMMANDS should include all common MCP server runtimes."""
expected = {"python", "python3", "node", "npx", "uvx", "uv", "deno", "docker"}
assert expected == DEFAULT_ALLOWED_COMMANDS
def test_allowed_command_passes_validation(self):
"""Commands in the default allowlist should be accepted."""
for cmd in DEFAULT_ALLOWED_COMMANDS:
transport = StdioTransport(command=cmd, args=["server.py"])
assert transport.command == cmd
def test_allowed_command_with_full_path(self):
"""Full paths to allowed commands should pass (basename is checked)."""
transport = StdioTransport(command="/usr/bin/python3", args=["server.py"])
assert transport.command == "/usr/bin/python3"
def test_disallowed_command_raises_value_error(self):
"""Commands not in the allowlist should raise ValueError."""
with pytest.raises(ValueError, match="not in the allowed commands list"):
StdioTransport(command="malicious-binary", args=["--evil"])
def test_disallowed_command_with_full_path_raises(self):
"""Full paths to disallowed commands should also be rejected."""
with pytest.raises(ValueError, match="not in the allowed commands list"):
StdioTransport(command="/tmp/evil/script", args=[])
def test_allowed_commands_none_disables_validation(self):
"""Setting allowed_commands=None should disable the check entirely."""
transport = StdioTransport(
command="any-custom-binary",
args=["--flag"],
allowed_commands=None,
)
assert transport.command == "any-custom-binary"
def test_custom_allowlist(self):
"""Users should be able to pass a custom allowlist."""
custom = frozenset({"my-server", "python"})
# Allowed
transport = StdioTransport(
command="my-server", args=[], allowed_commands=custom
)
assert transport.command == "my-server"
# Not allowed
with pytest.raises(ValueError, match="not in the allowed commands list"):
StdioTransport(command="node", args=[], allowed_commands=custom)
def test_extended_allowlist(self):
"""Users should be able to extend the default allowlist."""
extended = DEFAULT_ALLOWED_COMMANDS | frozenset({"my-custom-runtime"})
transport = StdioTransport(
command="my-custom-runtime", args=[], allowed_commands=extended
)
assert transport.command == "my-custom-runtime"
# Original defaults still work
transport2 = StdioTransport(
command="python", args=["server.py"], allowed_commands=extended
)
assert transport2.command == "python"
def test_error_message_includes_sorted_allowed_commands(self):
"""The error message should list the allowed commands for discoverability."""
with pytest.raises(ValueError) as exc_info:
StdioTransport(command="bad-cmd", args=[])
error_msg = str(exc_info.value)
assert "bad-cmd" in error_msg
assert "allowed_commands=None" in error_msg
def test_args_and_env_still_work(self):
"""Existing args and env functionality should be unaffected."""
transport = StdioTransport(
command="python",
args=["server.py", "--port", "8080"],
env={"API_KEY": "test123"},
)
assert transport.command == "python"
assert transport.args == ["server.py", "--port", "8080"]
assert transport.env == {"API_KEY": "test123"}