Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions hyperforge/src/hyperforge/api/v1/mcp_interaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,7 @@ def _default_oauth_metadata(app: "HTTPApplication") -> tuple[list[str], list[str
return [], []


def _get_mcp_auth_config(
app: "HTTPApplication", agent_id: str
):
def _get_mcp_auth_config(app: "HTTPApplication", agent_id: str):
return get_enabled_mcp_auth(app._agents_cfg, agent_id)


Expand Down
8 changes: 4 additions & 4 deletions hyperforge/src/hyperforge/standalone/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
from hyperforge.standalone.config import StandAloneAgentConfig, StandaloneMCPAuthConfig

_HASHES = {
"RS256": SHA256,
"RS384": SHA384,
"RS512": SHA512,
"RS256": SHA256(),
"RS384": SHA384(),
"RS512": SHA512(),
}


Expand Down Expand Up @@ -110,7 +110,7 @@ async def _validate_jwt(
signature,
signed_payload,
padding.PKCS1v15(),
_HASHES[alg](),
_HASHES[alg],
)
except Exception as exc:
raise AuthenticationError("Invalid bearer token signature") from exc
Expand Down
20 changes: 11 additions & 9 deletions hyperforge/tests/standalone/test_mcp_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.primitives.hashes import SHA256
from httpx import ASGITransport, AsyncClient
from starlette.datastructures import Headers

from hyperforge.api.v1.mcp_interaction import _prepare_interaction_headers
from hyperforge.standalone import oauth as standalone_oauth
from hyperforge.standalone.app import StandaloneApplication
from hyperforge.standalone.config import StandaloneConfig
from hyperforge.standalone.settings import StandaloneSettings
from starlette.datastructures import Headers

pytestmark = pytest.mark.asyncio

Expand Down Expand Up @@ -103,6 +104,7 @@ async def client(agents_config, standalone_settings):
def signing_key():
return rsa.generate_private_key(public_exponent=65537, key_size=2048)


@pytest.fixture
def jwks(signing_key):
public_numbers = signing_key.public_key().public_numbers()
Expand Down Expand Up @@ -231,9 +233,7 @@ async def test_protected_mcp_default_metadata_url_uses_https(client: AsyncClient
)


async def test_protected_mcp_accepts_signed_bearer(
client: AsyncClient, signing_key
):
async def test_protected_mcp_accepts_signed_bearer(client: AsyncClient, signing_key):
response = await client.delete(
f"/api/v1/agent/{AGENT_ID}/session/s1/mcp",
headers={"Authorization": f"Bearer {make_token(signing_key)}"},
Expand All @@ -247,7 +247,9 @@ async def test_protected_mcp_rejects_token_with_wrong_audience(
):
response = await client.delete(
f"/api/v1/agent/{AGENT_ID}/session/s1/mcp",
headers={"Authorization": f"Bearer {make_token(signing_key, aud='api://other')}"},
headers={
"Authorization": f"Bearer {make_token(signing_key, aud='api://other')}"
},
)

assert response.status_code == 401
Expand All @@ -264,9 +266,7 @@ async def test_protected_mcp_rejects_tampered_token(client: AsyncClient, signing
"scp": REQUIRED_SCOPE,
"sub": "tampered",
}
tampered_token = (
f"{header_raw}.{_b64encode_json(tampered_claims)}.{signature_raw}"
)
tampered_token = f"{header_raw}.{_b64encode_json(tampered_claims)}.{signature_raw}"

response = await client.delete(
f"/api/v1/agent/{AGENT_ID}/session/s1/mcp",
Expand All @@ -285,7 +285,9 @@ async def test_protected_mcp_rejects_token_without_required_scope(
):
response = await client.delete(
f"/api/v1/agent/{AGENT_ID}/session/s1/mcp",
headers={"Authorization": f"Bearer {make_token(signing_key, scp='other:read')}"},
headers={
"Authorization": f"Bearer {make_token(signing_key, scp='other:read')}"
},
)

assert response.status_code == 401
Expand Down
Loading