rootly-mcp-server 2.1.3__tar.gz → 2.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/PKG-INFO +1 -1
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/pyproject.toml +1 -1
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/src/rootly_mcp_server/server.py +11 -1
- rootly_mcp_server-2.1.4/tests/unit/test_http_headers.py +277 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/.beads/issues.jsonl +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/.gitattributes +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/.github/dependabot.yml +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/.github/workflows/ci.yml +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/.github/workflows/lint.yml +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/.github/workflows/pypi-release.yml +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/.github/workflows/test.yml +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/.gitignore +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/.semaphore/deploy.yml +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/.semaphore/semaphore.yml +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/.semaphore/update-task-definition.sh +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/CONTRIBUTING.md +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/Dockerfile +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/LICENSE +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/README.md +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/examples/skills/README.md +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/rootly-mcp-server-demo.gif +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/rootly_openapi.json +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/scripts/setup-hooks.sh +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/src/rootly_mcp_server/__init__.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/src/rootly_mcp_server/__main__.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/src/rootly_mcp_server/client.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/src/rootly_mcp_server/data/__init__.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/src/rootly_mcp_server/exceptions.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/src/rootly_mcp_server/monitoring.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/src/rootly_mcp_server/och_client.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/src/rootly_mcp_server/pagination.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/src/rootly_mcp_server/security.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/src/rootly_mcp_server/smart_utils.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/src/rootly_mcp_server/texttest.json +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/src/rootly_mcp_server/utils.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/src/rootly_mcp_server/validators.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/README.md +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/conftest.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/integration/local/test_basic.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/integration/local/test_smart_tools.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/integration/remote/test_essential.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/test_client.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/unit/test_authentication.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/unit/test_exceptions.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/unit/test_och_client.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/unit/test_oncall_handoff.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/unit/test_oncall_metrics.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/unit/test_oncall_new_tools.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/unit/test_security.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/unit/test_server.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/unit/test_smart_utils.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/unit/test_tools.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/unit/test_utils.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/unit/test_validators.py +0 -0
- {rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rootly-mcp-server
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.4
|
|
4
4
|
Summary: Secure Model Context Protocol server for Rootly APIs with AI SRE capabilities, comprehensive error handling, and input validation
|
|
5
5
|
Project-URL: Homepage, https://github.com/Rootly-AI-Labs/Rootly-MCP-server
|
|
6
6
|
Project-URL: Issues, https://github.com/Rootly-AI-Labs/Rootly-MCP-server/issues
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "rootly-mcp-server"
|
|
3
|
-
version = "2.1.
|
|
3
|
+
version = "2.1.4"
|
|
4
4
|
description = "Secure Model Context Protocol server for Rootly APIs with AI SRE capabilities, comprehensive error handling, and input validation"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -381,11 +381,21 @@ class AuthenticatedHTTPXClient:
|
|
|
381
381
|
return transformed
|
|
382
382
|
|
|
383
383
|
async def request(self, method: str, url: str, **kwargs):
|
|
384
|
-
"""Override request to transform parameters."""
|
|
384
|
+
"""Override request to transform parameters and ensure correct headers."""
|
|
385
385
|
# Transform query parameters
|
|
386
386
|
if "params" in kwargs:
|
|
387
387
|
kwargs["params"] = self._transform_params(kwargs["params"])
|
|
388
388
|
|
|
389
|
+
# Ensure Content-Type and Accept headers are always set correctly for Rootly API
|
|
390
|
+
# This is critical because FastMCP may pass headers from the MCP client request
|
|
391
|
+
# (e.g., Content-Type: application/json from SSE) which would override our defaults
|
|
392
|
+
if "headers" in kwargs:
|
|
393
|
+
headers = dict(kwargs["headers"]) if kwargs["headers"] else {}
|
|
394
|
+
# Always use JSON-API content type for Rootly API
|
|
395
|
+
headers["Content-Type"] = "application/vnd.api+json"
|
|
396
|
+
headers["Accept"] = "application/vnd.api+json"
|
|
397
|
+
kwargs["headers"] = headers
|
|
398
|
+
|
|
389
399
|
# Call the underlying client's request method and let it handle everything
|
|
390
400
|
return await self.client.request(method, url, **kwargs)
|
|
391
401
|
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for HTTP header handling in AuthenticatedHTTPXClient.
|
|
3
|
+
|
|
4
|
+
Tests cover:
|
|
5
|
+
- Content-Type header override for Rootly JSON-API format
|
|
6
|
+
- Header handling when FastMCP passes MCP client headers
|
|
7
|
+
- Ensuring correct headers reach the Rootly API
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestAuthenticatedHTTPXClientHeaders:
|
|
16
|
+
"""Tests for header handling in AuthenticatedHTTPXClient."""
|
|
17
|
+
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def mock_httpx_client(self):
|
|
20
|
+
"""Create a mock httpx.AsyncClient."""
|
|
21
|
+
mock_client = AsyncMock()
|
|
22
|
+
mock_response = MagicMock()
|
|
23
|
+
mock_response.status_code = 200
|
|
24
|
+
mock_response.json.return_value = {"data": []}
|
|
25
|
+
mock_client.request.return_value = mock_response
|
|
26
|
+
return mock_client
|
|
27
|
+
|
|
28
|
+
@pytest.mark.asyncio
|
|
29
|
+
async def test_overrides_content_type_from_mcp_client(self, mock_httpx_client):
|
|
30
|
+
"""Test that Content-Type is overridden when MCP client sends application/json."""
|
|
31
|
+
from rootly_mcp_server.server import AuthenticatedHTTPXClient
|
|
32
|
+
|
|
33
|
+
with patch.object(AuthenticatedHTTPXClient, "_get_api_token", return_value="test-token"):
|
|
34
|
+
client = AuthenticatedHTTPXClient()
|
|
35
|
+
client.client = mock_httpx_client
|
|
36
|
+
|
|
37
|
+
# Simulate FastMCP passing headers from MCP client request
|
|
38
|
+
# This is what causes the 415 error - MCP client sends application/json
|
|
39
|
+
mcp_headers = {
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
"Accept": "application/json",
|
|
42
|
+
"Authorization": "Bearer user-token",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await client.request("GET", "/v1/teams", headers=mcp_headers)
|
|
46
|
+
|
|
47
|
+
# Verify the request was made with correct JSON-API headers
|
|
48
|
+
call_kwargs = mock_httpx_client.request.call_args[1]
|
|
49
|
+
assert call_kwargs["headers"]["Content-Type"] == "application/vnd.api+json"
|
|
50
|
+
assert call_kwargs["headers"]["Accept"] == "application/vnd.api+json"
|
|
51
|
+
# Authorization should be preserved
|
|
52
|
+
assert call_kwargs["headers"]["Authorization"] == "Bearer user-token"
|
|
53
|
+
|
|
54
|
+
@pytest.mark.asyncio
|
|
55
|
+
async def test_sets_headers_when_empty_headers_passed(self, mock_httpx_client):
|
|
56
|
+
"""Test that headers are set correctly when empty headers dict is passed."""
|
|
57
|
+
from rootly_mcp_server.server import AuthenticatedHTTPXClient
|
|
58
|
+
|
|
59
|
+
with patch.object(AuthenticatedHTTPXClient, "_get_api_token", return_value="test-token"):
|
|
60
|
+
client = AuthenticatedHTTPXClient()
|
|
61
|
+
client.client = mock_httpx_client
|
|
62
|
+
|
|
63
|
+
# FastMCP might pass empty headers
|
|
64
|
+
await client.request("GET", "/v1/incidents", headers={})
|
|
65
|
+
|
|
66
|
+
call_kwargs = mock_httpx_client.request.call_args[1]
|
|
67
|
+
assert call_kwargs["headers"]["Content-Type"] == "application/vnd.api+json"
|
|
68
|
+
assert call_kwargs["headers"]["Accept"] == "application/vnd.api+json"
|
|
69
|
+
|
|
70
|
+
@pytest.mark.asyncio
|
|
71
|
+
async def test_preserves_other_headers(self, mock_httpx_client):
|
|
72
|
+
"""Test that non-content-type headers are preserved."""
|
|
73
|
+
from rootly_mcp_server.server import AuthenticatedHTTPXClient
|
|
74
|
+
|
|
75
|
+
with patch.object(AuthenticatedHTTPXClient, "_get_api_token", return_value="test-token"):
|
|
76
|
+
client = AuthenticatedHTTPXClient()
|
|
77
|
+
client.client = mock_httpx_client
|
|
78
|
+
|
|
79
|
+
custom_headers = {
|
|
80
|
+
"Content-Type": "application/json", # Should be overridden
|
|
81
|
+
"X-Custom-Header": "custom-value", # Should be preserved
|
|
82
|
+
"X-Request-ID": "12345", # Should be preserved
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await client.request("POST", "/v1/incidents", headers=custom_headers)
|
|
86
|
+
|
|
87
|
+
call_kwargs = mock_httpx_client.request.call_args[1]
|
|
88
|
+
assert call_kwargs["headers"]["Content-Type"] == "application/vnd.api+json"
|
|
89
|
+
assert call_kwargs["headers"]["X-Custom-Header"] == "custom-value"
|
|
90
|
+
assert call_kwargs["headers"]["X-Request-ID"] == "12345"
|
|
91
|
+
|
|
92
|
+
@pytest.mark.asyncio
|
|
93
|
+
async def test_no_headers_kwarg_works(self, mock_httpx_client):
|
|
94
|
+
"""Test that requests without headers kwarg still work."""
|
|
95
|
+
from rootly_mcp_server.server import AuthenticatedHTTPXClient
|
|
96
|
+
|
|
97
|
+
with patch.object(AuthenticatedHTTPXClient, "_get_api_token", return_value="test-token"):
|
|
98
|
+
client = AuthenticatedHTTPXClient()
|
|
99
|
+
client.client = mock_httpx_client
|
|
100
|
+
|
|
101
|
+
# Request without headers kwarg (relies on client defaults)
|
|
102
|
+
await client.request("GET", "/v1/users")
|
|
103
|
+
|
|
104
|
+
# Should still make the request successfully
|
|
105
|
+
mock_httpx_client.request.assert_called_once()
|
|
106
|
+
|
|
107
|
+
@pytest.mark.asyncio
|
|
108
|
+
async def test_none_headers_handled(self, mock_httpx_client):
|
|
109
|
+
"""Test that None headers are handled gracefully."""
|
|
110
|
+
from rootly_mcp_server.server import AuthenticatedHTTPXClient
|
|
111
|
+
|
|
112
|
+
with patch.object(AuthenticatedHTTPXClient, "_get_api_token", return_value="test-token"):
|
|
113
|
+
client = AuthenticatedHTTPXClient()
|
|
114
|
+
client.client = mock_httpx_client
|
|
115
|
+
|
|
116
|
+
# FastMCP might pass headers=None
|
|
117
|
+
await client.request("GET", "/v1/schedules", headers=None)
|
|
118
|
+
|
|
119
|
+
call_kwargs = mock_httpx_client.request.call_args[1]
|
|
120
|
+
assert call_kwargs["headers"]["Content-Type"] == "application/vnd.api+json"
|
|
121
|
+
assert call_kwargs["headers"]["Accept"] == "application/vnd.api+json"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class TestHTTPMethodsWithHeaders:
|
|
125
|
+
"""Test all HTTP methods correctly handle headers."""
|
|
126
|
+
|
|
127
|
+
@pytest.fixture
|
|
128
|
+
def mock_httpx_client(self):
|
|
129
|
+
"""Create a mock httpx.AsyncClient."""
|
|
130
|
+
mock_client = AsyncMock()
|
|
131
|
+
mock_response = MagicMock()
|
|
132
|
+
mock_response.status_code = 200
|
|
133
|
+
mock_response.json.return_value = {"data": []}
|
|
134
|
+
mock_client.request.return_value = mock_response
|
|
135
|
+
return mock_client
|
|
136
|
+
|
|
137
|
+
@pytest.mark.asyncio
|
|
138
|
+
async def test_get_method_headers(self, mock_httpx_client):
|
|
139
|
+
"""Test GET method correctly overrides headers."""
|
|
140
|
+
from rootly_mcp_server.server import AuthenticatedHTTPXClient
|
|
141
|
+
|
|
142
|
+
with patch.object(AuthenticatedHTTPXClient, "_get_api_token", return_value="test-token"):
|
|
143
|
+
client = AuthenticatedHTTPXClient()
|
|
144
|
+
client.client = mock_httpx_client
|
|
145
|
+
|
|
146
|
+
await client.get("/v1/teams", headers={"Content-Type": "application/json"})
|
|
147
|
+
|
|
148
|
+
call_kwargs = mock_httpx_client.request.call_args[1]
|
|
149
|
+
assert call_kwargs["headers"]["Content-Type"] == "application/vnd.api+json"
|
|
150
|
+
|
|
151
|
+
@pytest.mark.asyncio
|
|
152
|
+
async def test_post_method_headers(self, mock_httpx_client):
|
|
153
|
+
"""Test POST method correctly overrides headers."""
|
|
154
|
+
from rootly_mcp_server.server import AuthenticatedHTTPXClient
|
|
155
|
+
|
|
156
|
+
with patch.object(AuthenticatedHTTPXClient, "_get_api_token", return_value="test-token"):
|
|
157
|
+
client = AuthenticatedHTTPXClient()
|
|
158
|
+
client.client = mock_httpx_client
|
|
159
|
+
|
|
160
|
+
await client.post(
|
|
161
|
+
"/v1/incidents",
|
|
162
|
+
headers={"Content-Type": "application/json"},
|
|
163
|
+
json={"title": "Test"},
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
call_kwargs = mock_httpx_client.request.call_args[1]
|
|
167
|
+
assert call_kwargs["headers"]["Content-Type"] == "application/vnd.api+json"
|
|
168
|
+
|
|
169
|
+
@pytest.mark.asyncio
|
|
170
|
+
async def test_patch_method_headers(self, mock_httpx_client):
|
|
171
|
+
"""Test PATCH method correctly overrides headers."""
|
|
172
|
+
from rootly_mcp_server.server import AuthenticatedHTTPXClient
|
|
173
|
+
|
|
174
|
+
with patch.object(AuthenticatedHTTPXClient, "_get_api_token", return_value="test-token"):
|
|
175
|
+
client = AuthenticatedHTTPXClient()
|
|
176
|
+
client.client = mock_httpx_client
|
|
177
|
+
|
|
178
|
+
await client.patch(
|
|
179
|
+
"/v1/incidents/123",
|
|
180
|
+
headers={"Content-Type": "application/json"},
|
|
181
|
+
json={"status": "resolved"},
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
call_kwargs = mock_httpx_client.request.call_args[1]
|
|
185
|
+
assert call_kwargs["headers"]["Content-Type"] == "application/vnd.api+json"
|
|
186
|
+
|
|
187
|
+
@pytest.mark.asyncio
|
|
188
|
+
async def test_delete_method_headers(self, mock_httpx_client):
|
|
189
|
+
"""Test DELETE method correctly overrides headers."""
|
|
190
|
+
from rootly_mcp_server.server import AuthenticatedHTTPXClient
|
|
191
|
+
|
|
192
|
+
with patch.object(AuthenticatedHTTPXClient, "_get_api_token", return_value="test-token"):
|
|
193
|
+
client = AuthenticatedHTTPXClient()
|
|
194
|
+
client.client = mock_httpx_client
|
|
195
|
+
|
|
196
|
+
await client.delete("/v1/incidents/123", headers={"Content-Type": "application/json"})
|
|
197
|
+
|
|
198
|
+
call_kwargs = mock_httpx_client.request.call_args[1]
|
|
199
|
+
assert call_kwargs["headers"]["Content-Type"] == "application/vnd.api+json"
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class TestFastMCPIntegrationScenario:
|
|
203
|
+
"""Test scenarios that simulate FastMCP's behavior."""
|
|
204
|
+
|
|
205
|
+
@pytest.fixture
|
|
206
|
+
def mock_httpx_client(self):
|
|
207
|
+
"""Create a mock httpx.AsyncClient."""
|
|
208
|
+
mock_client = AsyncMock()
|
|
209
|
+
mock_response = MagicMock()
|
|
210
|
+
mock_response.status_code = 200
|
|
211
|
+
mock_response.json.return_value = {"data": [{"id": "1", "type": "teams"}]}
|
|
212
|
+
mock_client.request.return_value = mock_response
|
|
213
|
+
return mock_client
|
|
214
|
+
|
|
215
|
+
@pytest.mark.asyncio
|
|
216
|
+
async def test_simulated_fastmcp_listteams_call(self, mock_httpx_client):
|
|
217
|
+
"""Simulate the exact scenario that causes 415 error with listTeams."""
|
|
218
|
+
from rootly_mcp_server.server import AuthenticatedHTTPXClient
|
|
219
|
+
|
|
220
|
+
with patch.object(AuthenticatedHTTPXClient, "_get_api_token", return_value="test-token"):
|
|
221
|
+
client = AuthenticatedHTTPXClient()
|
|
222
|
+
client.client = mock_httpx_client
|
|
223
|
+
|
|
224
|
+
# This simulates what FastMCP does:
|
|
225
|
+
# 1. Gets headers from MCP client HTTP request (SSE connection)
|
|
226
|
+
# 2. These headers include Content-Type: application/json
|
|
227
|
+
# 3. Passes them to our client
|
|
228
|
+
mcp_client_headers = {
|
|
229
|
+
"host": "mcp.rootly.com",
|
|
230
|
+
"content-type": "application/json", # From MCP client
|
|
231
|
+
"accept": "text/event-stream",
|
|
232
|
+
"authorization": "Bearer user-api-token",
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
# Make request like FastMCP would
|
|
236
|
+
await client.request(
|
|
237
|
+
method="GET",
|
|
238
|
+
url="/v1/teams",
|
|
239
|
+
params={"page[size]": 10},
|
|
240
|
+
headers=mcp_client_headers,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Verify correct headers were sent to Rootly API
|
|
244
|
+
call_kwargs = mock_httpx_client.request.call_args[1]
|
|
245
|
+
assert call_kwargs["headers"]["Content-Type"] == "application/vnd.api+json"
|
|
246
|
+
assert call_kwargs["headers"]["Accept"] == "application/vnd.api+json"
|
|
247
|
+
# Auth header should be preserved for hosted mode
|
|
248
|
+
assert call_kwargs["headers"]["authorization"] == "Bearer user-api-token"
|
|
249
|
+
|
|
250
|
+
@pytest.mark.asyncio
|
|
251
|
+
async def test_simulated_fastmcp_getcurrentuser_call(self, mock_httpx_client):
|
|
252
|
+
"""Simulate the exact scenario that causes 415 error with getCurrentUser."""
|
|
253
|
+
from rootly_mcp_server.server import AuthenticatedHTTPXClient
|
|
254
|
+
|
|
255
|
+
mock_httpx_client.request.return_value.json.return_value = {
|
|
256
|
+
"data": {"id": "123", "type": "users", "attributes": {"name": "Test User"}}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
with patch.object(AuthenticatedHTTPXClient, "_get_api_token", return_value="test-token"):
|
|
260
|
+
client = AuthenticatedHTTPXClient()
|
|
261
|
+
client.client = mock_httpx_client
|
|
262
|
+
|
|
263
|
+
# Simulate FastMCP headers for getCurrentUser
|
|
264
|
+
mcp_client_headers = {
|
|
265
|
+
"content-type": "application/json",
|
|
266
|
+
"accept": "application/json",
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
await client.request(
|
|
270
|
+
method="GET",
|
|
271
|
+
url="/v1/users/me",
|
|
272
|
+
headers=mcp_client_headers,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
call_kwargs = mock_httpx_client.request.call_args[1]
|
|
276
|
+
assert call_kwargs["headers"]["Content-Type"] == "application/vnd.api+json"
|
|
277
|
+
assert call_kwargs["headers"]["Accept"] == "application/vnd.api+json"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/integration/local/test_smart_tools.py
RENAMED
|
File without changes
|
{rootly_mcp_server-2.1.3 → rootly_mcp_server-2.1.4}/tests/integration/remote/test_essential.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|