diff --git a/pyproject.toml b/pyproject.toml index 2fff42e..a1dc0e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ # `client.services` catalog resource and the extended # `client.dependencies` (component-level track + alert sensitivity) # that the services/dependencies tool modules call directly. - "devhelm>=1.3.0", + "devhelm>=1.4.0", "fastmcp>=3.2.3,<4", ] diff --git a/src/devhelm_mcp/tools/api_keys.py b/src/devhelm_mcp/tools/api_keys.py index fb826ba..040c3a8 100644 --- a/src/devhelm_mcp/tools/api_keys.py +++ b/src/devhelm_mcp/tools/api_keys.py @@ -24,6 +24,14 @@ def list_api_keys(api_token: str | None = None) -> ToolResult: except DevhelmError as e: raise_tool_error(e) + @mcp.tool() + def get_api_key(key_id: str, api_token: str | None = None) -> ToolResult: + """Get a single API key's metadata by id. The secret value is never returned.""" + try: + return serialize(get_client(api_token).api_keys.get(key_id)) + except DevhelmError as e: + raise_tool_error(e) + @mcp.tool() def create_api_key( body: CreateApiKeyRequest, api_token: str | None = None diff --git a/tests/test_tools.py b/tests/test_tools.py index 4b7a806..c09259f 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -11,7 +11,7 @@ import pytest -from devhelm_mcp.server import mcp +from devhelm_mcp.server import _strip_internal_schema_fields, mcp RegisteredTools = dict[str, Any] @@ -75,6 +75,7 @@ "delete_webhook", "test_webhook", "list_api_keys", + "get_api_key", "create_api_key", "revoke_api_key", "delete_api_key", @@ -141,7 +142,16 @@ @pytest.fixture(scope="module") def registered_tools() -> RegisteredTools: - tools = asyncio.run(mcp.list_tools()) + # Mirror production startup: the schema strip (api_token / managedBy) is + # applied in the HTTP lifespan and stdio entrypoint, NOT at import time + # (that was moved out in the v0.7.2 hotfix to avoid an asyncio.run() crash + # under Uvicorn). The fixture must apply it too, otherwise it asserts + # against the pre-strip schema and the hidden-field tests fail. + async def _build() -> list[Any]: + await _strip_internal_schema_fields() + return list(await mcp.list_tools()) + + tools = asyncio.run(_build()) return {t.name: t for t in tools} diff --git a/uv.lock b/uv.lock index 8bb42ff..d455430 100644 --- a/uv.lock +++ b/uv.lock @@ -388,15 +388,15 @@ wheels = [ [[package]] name = "devhelm" -version = "1.3.0" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, { name = "pydantic", extra = ["email"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/2e/317082ad26945cdf815040ce48c45ecde7537ccae89d8011bc02840c9325/devhelm-1.3.0.tar.gz", hash = "sha256:fb270af6919bf6bc426de850b0bbd6251ec72a061f6d3170a79e2ead271283cb", size = 261987, upload-time = "2026-06-10T14:00:21.546Z" } +sdist = { url = "https://files.pythonhosted.org/packages/03/e2/9446a3c3751fa0b09462172f9f9208777aeb4bcb624eaa58a1a38eff42af/devhelm-1.4.0.tar.gz", hash = "sha256:739cd4eb8e35d7a42a63e961c62d4bc62470a70a83a67652a4e4037ce0788a98", size = 263937, upload-time = "2026-06-12T12:16:36.526Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/62/261bc0a6551d8098670e755e4a44a74ea8c032edda472364d9a6bd73992c/devhelm-1.3.0-py3-none-any.whl", hash = "sha256:19fac4faad7af71e384ebcb7afd63c8198ad03367c411f88769ac81a1bcbdea2", size = 88194, upload-time = "2026-06-10T14:00:20.547Z" }, + { url = "https://files.pythonhosted.org/packages/1d/13/19cd23030b714a573e1d4f8d61ada95277c818c9915382b4edf3fb066a4b/devhelm-1.4.0-py3-none-any.whl", hash = "sha256:9b2b1dfb450f0871dc5806b462d41a3603932013148914c7447b278fd7995edc", size = 88701, upload-time = "2026-06-12T12:16:35.469Z" }, ] [[package]] @@ -419,7 +419,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "devhelm", specifier = ">=1.3.0" }, + { name = "devhelm", specifier = ">=1.4.0" }, { name = "fastmcp", specifier = ">=3.2.3,<4" }, ]