From 8e5e70ddd1c78ec4693d6c6fdd1d6dd4e2f83fed Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Tue, 28 Oct 2025 18:43:26 -0400 Subject: [PATCH 01/33] chore: add .worktrees/ to .gitignore Preparing for git worktree usage. This prevents accidentally committing worktree contents to the repository. Co-Authored-By: Claude --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 5bbf0b8..da86319 100644 --- a/.gitignore +++ b/.gitignore @@ -85,4 +85,8 @@ logs/ *.bak *.swp *.swo + +# Git worktrees +.worktrees/ + .plans/* From 8a4a4f83b229c9dc6c8bf03f90335118cf96f5bc Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Tue, 28 Oct 2025 18:55:26 -0400 Subject: [PATCH 02/33] chore: create src/netbox_mcp_server directory structure --- src/netbox_mcp_server/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/netbox_mcp_server/.gitkeep diff --git a/src/netbox_mcp_server/.gitkeep b/src/netbox_mcp_server/.gitkeep new file mode 100644 index 0000000..e69de29 From 9f89e602525f7cbdf796de6cc06294d8619ea096 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 09:10:17 -0400 Subject: [PATCH 03/33] feat: add package __init__.py with version and exports --- src/netbox_mcp_server/.gitkeep | 0 src/netbox_mcp_server/__init__.py | 9 +++++++++ 2 files changed, 9 insertions(+) delete mode 100644 src/netbox_mcp_server/.gitkeep create mode 100644 src/netbox_mcp_server/__init__.py diff --git a/src/netbox_mcp_server/.gitkeep b/src/netbox_mcp_server/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/netbox_mcp_server/__init__.py b/src/netbox_mcp_server/__init__.py new file mode 100644 index 0000000..55ee97d --- /dev/null +++ b/src/netbox_mcp_server/__init__.py @@ -0,0 +1,9 @@ +"""NetBox MCP Server - Read-only MCP server for NetBox infrastructure data.""" + +__version__ = "1.0.0" # Auto-managed by semantic-release + +__all__ = ["NetBoxRestClient", "NETBOX_OBJECT_TYPES", "Settings"] + +from netbox_mcp_server.netbox_client import NetBoxRestClient +from netbox_mcp_server.netbox_types import NETBOX_OBJECT_TYPES +from netbox_mcp_server.config import Settings From 94b04666c8d5ab192d5632ffb5da2f9be4c19951 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 09:18:37 -0400 Subject: [PATCH 04/33] feat: add __main__.py for module execution support --- src/netbox_mcp_server/__main__.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/netbox_mcp_server/__main__.py diff --git a/src/netbox_mcp_server/__main__.py b/src/netbox_mcp_server/__main__.py new file mode 100644 index 0000000..09b79b5 --- /dev/null +++ b/src/netbox_mcp_server/__main__.py @@ -0,0 +1,6 @@ +"""Entry point for python -m netbox_mcp_server execution.""" + +from netbox_mcp_server.server import main + +if __name__ == "__main__": + main() From 5026665dba9b7c82f5d1d3cc836dacac3a062ac4 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 09:25:05 -0400 Subject: [PATCH 05/33] refactor: move config.py to src/netbox_mcp_server/ --- config.py => src/netbox_mcp_server/config.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename config.py => src/netbox_mcp_server/config.py (100%) diff --git a/config.py b/src/netbox_mcp_server/config.py similarity index 100% rename from config.py rename to src/netbox_mcp_server/config.py From 234c447db3beaf307e9bb5722316f01133a44bbd Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 09:38:30 -0400 Subject: [PATCH 06/33] refactor: move netbox_types.py to src/netbox_mcp_server/ --- netbox_types.py => src/netbox_mcp_server/netbox_types.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename netbox_types.py => src/netbox_mcp_server/netbox_types.py (100%) diff --git a/netbox_types.py b/src/netbox_mcp_server/netbox_types.py similarity index 100% rename from netbox_types.py rename to src/netbox_mcp_server/netbox_types.py From 2393dac84ce35f6f22b3a90f346da02cf2848bed Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 09:55:50 -0400 Subject: [PATCH 07/33] refactor: move netbox_client.py to src/ and update imports --- netbox_client.py => src/netbox_mcp_server/netbox_client.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename netbox_client.py => src/netbox_mcp_server/netbox_client.py (100%) diff --git a/netbox_client.py b/src/netbox_mcp_server/netbox_client.py similarity index 100% rename from netbox_client.py rename to src/netbox_mcp_server/netbox_client.py From 27f935e49a03ed3972879a52ccbba22e02a367bc Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 09:59:16 -0400 Subject: [PATCH 08/33] refactor: move server.py to src/, update imports, add main() function --- server.py => src/netbox_mcp_server/server.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) rename server.py => src/netbox_mcp_server/server.py (98%) diff --git a/server.py b/src/netbox_mcp_server/server.py similarity index 98% rename from server.py rename to src/netbox_mcp_server/server.py index 3a5c653..0615d0a 100644 --- a/server.py +++ b/src/netbox_mcp_server/server.py @@ -6,9 +6,9 @@ from fastmcp import FastMCP from pydantic import Field -from config import Settings, configure_logging -from netbox_client import NetBoxRestClient -from netbox_types import NETBOX_OBJECT_TYPES +from netbox_mcp_server.config import Settings, configure_logging +from netbox_mcp_server.netbox_client import NetBoxRestClient +from netbox_mcp_server.netbox_types import NETBOX_OBJECT_TYPES def parse_cli_args() -> dict[str, Any]: @@ -505,7 +505,11 @@ def _endpoint_for_type(object_type: str) -> str: """ return NETBOX_OBJECT_TYPES[object_type]['endpoint'] -if __name__ == "__main__": + +def main() -> None: + """Main entry point for the MCP server.""" + global netbox + cli_overlay: dict[str, Any] = parse_cli_args() try: @@ -561,3 +565,7 @@ def _endpoint_for_type(object_type: str) -> str: except Exception as e: logger.error(f"Failed to start MCP server: {e}") sys.exit(1) + + +if __name__ == "__main__": + main() From 519ff7021acea4733f1e650f85cbd59de4ae92a0 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:08:17 -0400 Subject: [PATCH 09/33] feat: add console script entry point netbox-mcp-server --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 8e86e98..9510f8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,9 @@ dependencies = [ "pydantic-settings>=2.0", ] +[project.scripts] +netbox-mcp-server = "netbox_mcp_server.server:main" + [dependency-groups] dev = [ "pytest>=8.4.2", From 4134f01343179840eefc4ee0bbbb84f407899b77 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:09:33 -0400 Subject: [PATCH 10/33] chore: add python-semantic-release dev dependency --- pyproject.toml | 1 + uv.lock | 181 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 179 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9510f8f..eead450 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,4 +19,5 @@ netbox-mcp-server = "netbox_mcp_server.server:main" dev = [ "pytest>=8.4.2", "pytest-cov>=7.0.0", + "python-semantic-release>=10.4.1", ] diff --git a/uv.lock b/uv.lock index 3f692e4..e9dc63a 100644 --- a/uv.lock +++ b/uv.lock @@ -101,14 +101,26 @@ wheels = [ [[package]] name = "click" -version = "8.2.2" +version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/87/105111999772ec9730e3d4d910c723ea9763ece2ec441533a5cea1e87e3c/click-8.2.2.tar.gz", hash = "sha256:068616e6ef9705a07b6db727cb9c248f4eb9dae437a30239f56fa94b18b852ef", size = 263977, upload-time = "2025-08-02T02:23:41.102Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/85/e7297e34133ae1cfde3bffd30c24e1ef055248251baa877834e048687a28/click-8.2.2-py3-none-any.whl", hash = "sha256:52e1e9f5d3db8c85aa76968c7c67ed41ddbacb167f43201511c8fd61eb5ba2ca", size = 103900, upload-time = "2025-08-02T02:23:39.299Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, +] + +[[package]] +name = "click-option-group" +version = "0.5.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/ff/d291d66595b30b83d1cb9e314b2c9be7cfc7327d4a0d40a15da2416ea97b/click_option_group-0.5.9.tar.gz", hash = "sha256:f94ed2bc4cf69052e0f29592bd1e771a1789bd7bfc482dd0bc482134aff95823", size = 22222, upload-time = "2025-10-09T09:38:01.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/45/54bb2d8d4138964a94bef6e9afe48b0be4705ba66ac442ae7d8a8dc4ffef/click_option_group-0.5.9-py3-none-any.whl", hash = "sha256:ad2599248bd373e2e19bec5407967c3eec1d0d4fc4a5e77b08a0481e75991080", size = 11553, upload-time = "2025-10-09T09:38:00.066Z" }, ] [[package]] @@ -211,6 +223,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f0/8b/2c95f0645c6f40211896375e6fa51f504b8ccb29c21f6ae661fe87ab044e/cyclopts-3.24.0-py3-none-any.whl", hash = "sha256:809d04cde9108617106091140c3964ee6fceb33cecdd537f7ffa360bde13ed71", size = 86154, upload-time = "2025-09-08T15:40:56.41Z" }, ] +[[package]] +name = "deprecated" +version = "1.2.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744, upload-time = "2025-01-27T10:46:25.7Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998, upload-time = "2025-01-27T10:46:09.186Z" }, +] + [[package]] name = "dnspython" version = "2.8.0" @@ -238,6 +262,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/66/dd/f95350e853a4468ec37478414fc04ae2d61dad7a947b3015c3dcc51a09b9/docutils-0.22.2-py3-none-any.whl", hash = "sha256:b0e98d679283fc3bb0ead8a5da7f501baa632654e7056e9c5846842213d674d8", size = 632667, upload-time = "2025-09-20T17:55:43.052Z" }, ] +[[package]] +name = "dotty-dict" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/ab/88d67f02024700b48cd8232579ad1316aa9df2272c63049c27cc094229d6/dotty_dict-1.3.1.tar.gz", hash = "sha256:4b016e03b8ae265539757a53eba24b9bfda506fb94fbce0bee843c6f05541a15", size = 7699, upload-time = "2022-07-09T18:50:57.727Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/91/e0d457ee03ec33d79ee2cd8d212debb1bc21dfb99728ae35efdb5832dc22/dotty_dict-1.3.1-py3-none-any.whl", hash = "sha256:5022d234d9922f13aa711b4950372a06a6d64cb6d6db9ba43d0ba133ebfce31f", size = 7014, upload-time = "2022-07-09T18:50:55.058Z" }, +] + [[package]] name = "email-validator" version = "2.3.0" @@ -282,6 +315,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e2/c7/562ff39f25de27caec01e4c1e88cbb5fcae5160802ba3d90be33165df24f/fastmcp-2.12.4-py3-none-any.whl", hash = "sha256:56188fbbc1a9df58c537063f25958c57b5c4d715f73e395c41b51550b247d140", size = 329090, upload-time = "2025-09-26T16:43:25.314Z" }, ] +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.45" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -337,6 +394,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] +[[package]] +name = "importlib-resources" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, +] + [[package]] name = "iniconfig" version = "2.1.0" @@ -355,6 +421,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, ] +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + [[package]] name = "jsonschema" version = "4.25.0" @@ -515,6 +593,7 @@ dependencies = [ dev = [ { name = "pytest" }, { name = "pytest-cov" }, + { name = "python-semantic-release" }, ] [package.metadata] @@ -530,6 +609,7 @@ requires-dist = [ dev = [ { name = "pytest", specifier = ">=8.4.2" }, { name = "pytest-cov", specifier = ">=7.0.0" }, + { name = "python-semantic-release", specifier = ">=10.4.1" }, ] [[package]] @@ -757,6 +837,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, ] +[[package]] +name = "python-gitlab" +version = "6.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, + { name = "requests-toolbelt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/bd/b30f1d3b303cb5d3c72e2d57a847d699e8573cbdfd67ece5f1795e49da1c/python_gitlab-6.5.0.tar.gz", hash = "sha256:97553652d94b02de343e9ca92782239aa2b5f6594c5482331a9490d9d5e8737d", size = 400591, upload-time = "2025-10-17T21:40:02.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/bd/b0d440685fbcafee462bed793a74aea88541887c4c30556a55ac64914b8d/python_gitlab-6.5.0-py3-none-any.whl", hash = "sha256:494e1e8e5edd15286eaf7c286f3a06652688f1ee20a49e2a0218ddc5cc475e32", size = 144419, upload-time = "2025-10-17T21:40:01.233Z" }, +] + [[package]] name = "python-multipart" version = "0.0.20" @@ -766,6 +859,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, ] +[[package]] +name = "python-semantic-release" +version = "10.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "click-option-group" }, + { name = "deprecated" }, + { name = "dotty-dict" }, + { name = "gitpython" }, + { name = "importlib-resources" }, + { name = "jinja2" }, + { name = "pydantic" }, + { name = "python-gitlab" }, + { name = "requests" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "tomlkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/4e/ee80b30d85987414cdb2961797877177f65cb4213e1bf3cdae8143da7729/python_semantic_release-10.4.1.tar.gz", hash = "sha256:4bec21f7d3a419a2a62d16a9ff404481a90f011c762aef605caf48f8c11b3ed6", size = 605074, upload-time = "2025-09-13T03:29:58.966Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/e8/22fcba61fe7cb4cd5e0f0b6d4e0d02de3e68f83193dcb05ad87be11ed8d1/python_semantic_release-10.4.1-py3-none-any.whl", hash = "sha256:18a73619ffc6f1aca8e1106b03e139686bfbbf0120d1a97c948fc9620ab6beb5", size = 149618, upload-time = "2025-09-13T03:29:56.553Z" }, +] + [[package]] name = "pywin32" version = "311" @@ -822,6 +939,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, ] +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + [[package]] name = "rfc3339-validator" version = "0.1.4" @@ -895,6 +1024,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/8b/9286b7e822036a4a977f2f1e851c7345c20528dbd56b687bb67ed68a8ede/rpds_py-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff110acded3c22c033e637dd8896e411c7d3a11289b2edf041f86663dbc791e9", size = 231524, upload-time = "2025-07-01T15:55:15.745Z" }, ] +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -904,6 +1042,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -937,6 +1084,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload-time = "2025-07-20T17:31:56.738Z" }, ] +[[package]] +name = "tomlkit" +version = "0.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, +] + [[package]] name = "typing-extensions" version = "4.14.1" @@ -991,3 +1147,22 @@ sdist = { url = "https://files.pythonhosted.org/packages/32/af/d4502dc713b4ccea7 wheels = [ { url = "https://files.pythonhosted.org/packages/ee/ea/c67e1dee1ba208ed22c06d1d547ae5e293374bfc43e0eb0ef5e262b68561/werkzeug-3.1.1-py3-none-any.whl", hash = "sha256:a71124d1ef06008baafa3d266c02f56e1836a5984afd6dd6c9230669d60d9fb5", size = 224371, upload-time = "2024-11-01T16:40:43.994Z" }, ] + +[[package]] +name = "wrapt" +version = "1.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, +] From d22b5e4dca9f823128eae4e42e5bfe3a1f5bacaa Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:12:28 -0400 Subject: [PATCH 11/33] feat: configure python-semantic-release for automated versioning --- pyproject.toml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index eead450..aa7bc2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,3 +21,19 @@ dev = [ "pytest-cov>=7.0.0", "python-semantic-release>=10.4.1", ] + +[tool.semantic_release] +version_toml = ["pyproject.toml:project.version"] +version_variables = ["src/netbox_mcp_server/__init__.py:__version__"] +branch = "main" +upload_to_vcs_release = true +build_command = "uv build" +tag_format = "v{version}" + +[tool.semantic_release.commit_parser_options] +allowed_tags = ["feat", "fix", "docs", "chore", "refactor", "test", "ci", "perf"] +minor_tags = ["feat"] +patch_tags = ["fix", "perf"] + +[tool.semantic_release.changelog] +changelog_file = "CHANGELOG.md" From eb4c4894863107383e388ef2ecce40054b78bbf2 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:17:22 -0400 Subject: [PATCH 12/33] test: update test_config.py imports to use package structure --- pyproject.toml | 4 ++++ tests/test_config.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index aa7bc2f..b75ebe5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,7 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + [project] name = "netbox-mcp-server" version = "0.1.0" diff --git a/tests/test_config.py b/tests/test_config.py index 4d9c66d..1a15aae 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -7,8 +7,8 @@ import pytest from pydantic import ValidationError -from config import Settings, configure_logging -from server import parse_cli_args +from netbox_mcp_server.config import Settings, configure_logging +from netbox_mcp_server.server import parse_cli_args def test_settings_requires_netbox_url(): From 944e9fead1d12319a6f7f97cc5b6f0746a8f93fa Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:17:39 -0400 Subject: [PATCH 13/33] test: update test_filter_validation.py imports to use package structure --- tests/test_filter_validation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_filter_validation.py b/tests/test_filter_validation.py index 6268683..8e8bd12 100644 --- a/tests/test_filter_validation.py +++ b/tests/test_filter_validation.py @@ -2,7 +2,7 @@ import pytest -from server import validate_filters +from netbox_mcp_server.server import validate_filters def test_direct_field_filters_pass(): From b252baa4eb79ce4bfb223eb92e853f0916439207 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:18:26 -0400 Subject: [PATCH 14/33] test: update test_ordering.py imports to use package structure --- tests/test_ordering.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/test_ordering.py b/tests/test_ordering.py index 02a53e5..5e997c5 100644 --- a/tests/test_ordering.py +++ b/tests/test_ordering.py @@ -5,7 +5,7 @@ import pytest from pydantic import TypeAdapter, ValidationError -from server import netbox_get_objects +from netbox_mcp_server.server import netbox_get_objects def test_ordering_rejects_invalid_types(): @@ -22,7 +22,7 @@ def test_ordering_rejects_invalid_types(): with pytest.raises(ValidationError): adapter.validate_python(["name", 123]) -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_ordering_none_omits_parameter(mock_netbox): """When ordering=None, should not include ordering in API params.""" mock_netbox.get.return_value = {"count": 0, "results": [], "next": None, "previous": None} @@ -36,7 +36,7 @@ def test_ordering_none_omits_parameter(mock_netbox): assert "ordering" not in params -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_ordering_empty_string_omits_parameter(mock_netbox): """When ordering='', should not include ordering in API params.""" mock_netbox.get.return_value = {"count": 0, "results": [], "next": None, "previous": None} @@ -50,7 +50,7 @@ def test_ordering_empty_string_omits_parameter(mock_netbox): assert "ordering" not in params -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_ordering_single_field_ascending(mock_netbox): """When ordering='name', should pass 'name' to API params.""" mock_netbox.get.return_value = {"count": 0, "results": [], "next": None, "previous": None} @@ -63,7 +63,7 @@ def test_ordering_single_field_ascending(mock_netbox): assert params["ordering"] == "name" -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_ordering_single_field_descending(mock_netbox): """When ordering='-id', should pass '-id' to API params.""" mock_netbox.get.return_value = {"count": 0, "results": [], "next": None, "previous": None} @@ -76,7 +76,7 @@ def test_ordering_single_field_descending(mock_netbox): assert params["ordering"] == "-id" -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_ordering_multiple_fields_as_list(mock_netbox): """When ordering=['facility', '-name'], should pass comma-separated string.""" mock_netbox.get.return_value = {"count": 0, "results": [], "next": None, "previous": None} @@ -90,7 +90,7 @@ def test_ordering_multiple_fields_as_list(mock_netbox): assert params["ordering"] == "facility,-name" -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_ordering_empty_list_omits_parameter(mock_netbox): """When ordering=[], should not include ordering in API params.""" mock_netbox.get.return_value = {"count": 0, "results": [], "next": None, "previous": None} From 2616e8ea944b5f3af989b7a464332116355ccd56 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:18:43 -0400 Subject: [PATCH 15/33] test: update test_pagination.py imports to use package structure --- tests/test_pagination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 537a4f2..c0ec160 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -5,7 +5,7 @@ import pytest from pydantic import TypeAdapter, ValidationError -from server import netbox_get_objects +from netbox_mcp_server.server import netbox_get_objects def test_limit_validation_rejects_values_over_100(): From 5d4dc8e7e742c803ff7e7967d624573920c1f1e0 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:19:12 -0400 Subject: [PATCH 16/33] test: update test_search.py imports to use package structure --- tests/test_search.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_search.py b/tests/test_search.py index 96ea7b1..67c0f76 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -5,8 +5,8 @@ import pytest from pydantic import TypeAdapter, ValidationError -from netbox_types import NETBOX_OBJECT_TYPES -from server import netbox_search_objects +from netbox_mcp_server.netbox_types import NETBOX_OBJECT_TYPES +from netbox_mcp_server.server import netbox_search_objects # ============================================================================ # Parameter Validation Tests @@ -41,7 +41,7 @@ def test_invalid_object_type_raises_error(): # ============================================================================ -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_searches_default_types_when_none_specified(mock_netbox): """When object_types=None, should search 8 default common types.""" mock_netbox.get.return_value = { @@ -59,7 +59,7 @@ def test_searches_default_types_when_none_specified(mock_netbox): assert len(result) == 8 -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_custom_object_types_limits_search_scope(mock_netbox): """When object_types specified, should only search those types.""" mock_netbox.get.return_value = { @@ -81,7 +81,7 @@ def test_custom_object_types_limits_search_scope(mock_netbox): # ============================================================================ -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_field_projection_applied_to_queries(mock_netbox): """When fields specified, should apply to all queries as comma-separated string.""" mock_netbox.get.return_value = { @@ -106,7 +106,7 @@ def test_field_projection_applied_to_queries(mock_netbox): # ============================================================================ -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_result_structure_with_empty_and_populated_results(mock_netbox): """Should return dict with all types as keys, empty lists for no matches.""" @@ -140,7 +140,7 @@ def mock_get_side_effect(endpoint, params): # ============================================================================ -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_continues_searching_when_one_type_fails(mock_netbox): """If one object type fails, should continue searching others.""" @@ -171,7 +171,7 @@ def mock_get_side_effect(endpoint, params): # ============================================================================ -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_api_parameters_passed_correctly(mock_netbox): """Should pass query, limit, and fields to NetBox API correctly.""" mock_netbox.get.return_value = { @@ -193,7 +193,7 @@ def test_api_parameters_passed_correctly(mock_netbox): assert params["fields"] == "id" -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_uses_correct_api_endpoints(mock_netbox): """Should use correct API endpoints from NETBOX_OBJECT_TYPES mapping.""" mock_netbox.get.return_value = { @@ -215,7 +215,7 @@ def test_uses_correct_api_endpoints(mock_netbox): # ============================================================================ -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_extracts_results_from_paginated_response(mock_netbox): """Should extract 'results' array from NetBox paginated response structure. From c1b99c74602c869837c15c69c6f22cc1370ef860 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:19:42 -0400 Subject: [PATCH 17/33] test: update test_brief.py imports to use package structure --- tests/test_brief.py | 14 +++++++------- uv.lock | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_brief.py b/tests/test_brief.py index 92eefe0..8b60f2a 100644 --- a/tests/test_brief.py +++ b/tests/test_brief.py @@ -2,10 +2,10 @@ from unittest.mock import patch -from server import netbox_get_object_by_id, netbox_get_objects +from netbox_mcp_server.server import netbox_get_object_by_id, netbox_get_objects -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_brief_false_omits_parameter_get_objects(mock_netbox): """When brief=False (default), should not include brief in API params for netbox_get_objects.""" mock_netbox.get.return_value = {"count": 0, "results": [], "next": None, "previous": None} @@ -19,7 +19,7 @@ def test_brief_false_omits_parameter_get_objects(mock_netbox): assert "brief" not in params -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_brief_default_omits_parameter_get_objects(mock_netbox): """When brief not specified (uses default False), should not include brief in API params.""" mock_netbox.get.return_value = {"count": 0, "results": [], "next": None, "previous": None} @@ -33,7 +33,7 @@ def test_brief_default_omits_parameter_get_objects(mock_netbox): assert "brief" not in params -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_brief_true_includes_parameter_get_objects(mock_netbox): """When brief=True, should pass 'brief': '1' to API params for netbox_get_objects.""" mock_netbox.get.return_value = {"count": 0, "results": [], "next": None, "previous": None} @@ -46,7 +46,7 @@ def test_brief_true_includes_parameter_get_objects(mock_netbox): assert params["brief"] == "1" -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_brief_false_omits_parameter_get_by_id(mock_netbox): """When brief=False (default), should not include brief in API params for netbox_get_object_by_id.""" mock_netbox.get.return_value = {"id": 1, "name": "Test Site"} @@ -60,7 +60,7 @@ def test_brief_false_omits_parameter_get_by_id(mock_netbox): assert "brief" not in params -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_brief_default_omits_parameter_get_by_id(mock_netbox): """When brief not specified (uses default False), should not include brief in API params.""" mock_netbox.get.return_value = {"id": 1, "name": "Test Site"} @@ -74,7 +74,7 @@ def test_brief_default_omits_parameter_get_by_id(mock_netbox): assert "brief" not in params -@patch("server.netbox") +@patch("netbox_mcp_server.server.netbox") def test_brief_true_includes_parameter_get_by_id(mock_netbox): """When brief=True, should pass 'brief': '1' to API params for netbox_get_object_by_id.""" mock_netbox.get.return_value = {"id": 1, "url": "http://example.com/api/dcim/sites/1/"} diff --git a/uv.lock b/uv.lock index e9dc63a..65a3ec0 100644 --- a/uv.lock +++ b/uv.lock @@ -580,7 +580,7 @@ wheels = [ [[package]] name = "netbox-mcp-server" version = "0.1.0" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "fastmcp" }, { name = "httpx" }, From 877784c3c01fea282adfa923d88b3ed76db30334 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:21:44 -0400 Subject: [PATCH 18/33] chore: create .github/workflows directory for CI/CD --- .github/workflows/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .github/workflows/.gitkeep diff --git a/.github/workflows/.gitkeep b/.github/workflows/.gitkeep new file mode 100644 index 0000000..e69de29 From 0cbd557e41e58c23fc3d234f399b6d387281692b Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:23:35 -0400 Subject: [PATCH 19/33] ci: add test workflow for automated testing --- .github/workflows/test.yml | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..f4e8626 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,40 @@ +name: Test + +on: + pull_request: + push: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + + services: + netbox: + image: netboxcommunity/netbox:latest + env: + SKIP_SUPERUSER: true + ports: + - 8000:8080 + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install dependencies + run: uv sync + + - name: Run tests + run: uv run pytest -v + env: + NETBOX_URL: http://localhost:8000 + NETBOX_TOKEN: ${{ secrets.NETBOX_TOKEN }} From babad3c867d5c02ac8ad1f276ba1f3a51c468e93 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:24:08 -0400 Subject: [PATCH 20/33] ci: add release workflow for automated semantic releases --- .github/workflows/release.yml | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..491dad7 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,38 @@ +name: Release + +permissions: + contents: write + issues: write + pull-requests: write + +on: + push: + branches: [main] + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install dependencies + run: uv sync + + - name: Python Semantic Release + uses: python-semantic-release/python-semantic-release@v9 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} From 8aa150dc2f89888495955e0fa92b65084bd3bdd3 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:26:19 -0400 Subject: [PATCH 21/33] docs: add breaking change notice to README for v1.0.0 --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 368bd96..3d4df83 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ # NetBox MCP Server +> **⚠️ Breaking Change in v1.0.0**: The project structure has changed. +> If upgrading from v0.1.0, update your configuration: +> - Change `uv run server.py` to `uv run netbox-mcp-server` +> - Update Claude Desktop/Code configs to use `netbox-mcp-server` instead of `server.py` +> - Docker users: rebuild images with updated CMD +> - See [CHANGELOG.md](CHANGELOG.md) for full details + This is a simple read-only [Model Context Protocol](https://modelcontextprotocol.io/) server for NetBox. It enables you to interact with your data in NetBox directly via LLMs that support MCP. ## Tools From 736e10cae7b4d57add5f52c4abfecc96bde305a4 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:27:44 -0400 Subject: [PATCH 22/33] docs: update README commands to use netbox-mcp-server --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 3d4df83..740f62b 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ This is a simple read-only [Model Context Protocol](https://modelcontextprotocol pip install -e . ``` -3. Verify the server can run: `NETBOX_URL=https://netbox.example.com/ NETBOX_TOKEN= uv run server.py` +3. Verify the server can run: `NETBOX_URL=https://netbox.example.com/ NETBOX_TOKEN= uv run netbox-mcp-server` 4. Add the MCP server to your LLM client. See below for some examples with Claude. @@ -47,7 +47,7 @@ Add the server using the `claude mcp add` command: claude mcp add --transport stdio netbox \ --env NETBOX_URL=https://netbox.example.com/ \ --env NETBOX_TOKEN= \ - -- uv --directory /path/to/netbox-mcp-server run server.py + -- uv --directory /path/to/netbox-mcp-server run netbox-mcp-server ``` **Important notes:** @@ -68,7 +68,7 @@ For HTTP transport, first start the server manually: NETBOX_URL=https://netbox.example.com/ \ NETBOX_TOKEN= \ TRANSPORT=http \ -uv run server.py +uv run netbox-mcp-server ``` Then add the running server to Claude Code: @@ -205,10 +205,10 @@ export TRANSPORT=http export HOST=127.0.0.1 export PORT=8000 -uv run server.py +uv run netbox-mcp-server # Or using CLI arguments -uv run server.py \ +uv run netbox-mcp-server \ --netbox-url https://netbox.example.com/ \ --netbox-token \ --transport http \ @@ -244,11 +244,11 @@ LOG_LEVEL=INFO All configuration options can be overridden via CLI arguments: ```bash -uv run server.py --help +uv run netbox-mcp-server --help # Common examples: -uv run server.py --log-level DEBUG --no-verify-ssl # Development -uv run server.py --transport http --port 9000 # Custom HTTP port +uv run netbox-mcp-server --log-level DEBUG --no-verify-ssl # Development +uv run netbox-mcp-server --transport http --port 9000 # Custom HTTP port ``` ## Docker Usage From eceed32ee7f4b92f0107758094a2e844892b92c4 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:28:09 -0400 Subject: [PATCH 23/33] docs: update Claude Desktop/Code config examples in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 740f62b..8378657 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ Add the server configuration to your Claude Desktop config file. On Mac, edit `~ "--directory", "/path/to/netbox-mcp-server", "run", - "server.py" + "netbox-mcp-server" ], "env": { "NETBOX_URL": "https://netbox.example.com/", @@ -183,7 +183,7 @@ For local Claude Desktop or Claude Code usage with stdio transport: "mcpServers": { "netbox": { "command": "uv", - "args": ["--directory", "/path/to/netbox-mcp-server", "run", "server.py"], + "args": ["--directory", "/path/to/netbox-mcp-server", "run", "netbox-mcp-server"], "env": { "NETBOX_URL": "https://netbox.example.com/", "NETBOX_TOKEN": "" From d431fb7c71f6887266309f0e4fa134af10e1f501 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:28:35 -0400 Subject: [PATCH 24/33] docs: update project structure in CLAUDE.md --- claude.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/claude.md b/claude.md index 70e8674..9e8b64c 100644 --- a/claude.md +++ b/claude.md @@ -18,12 +18,20 @@ A read-only [Model Context Protocol](https://modelcontextprotocol.io/) server th ```text . -├── server.py # Main MCP server with tool definitions -├── netbox_client.py # NetBox REST API client abstraction -├── pyproject.toml # Dependencies and project metadata -├── README.md # User-facing documentation -├── SECURITY.md # Security policy and reporting -└── LICENSE # Apache 2.0 license +├── src/ +│ └── netbox_mcp_server/ +│ ├── __init__.py # Package initialization with __version__ +│ ├── __main__.py # Entry point for module execution +│ ├── server.py # Main MCP server with tool definitions +│ ├── netbox_client.py # NetBox REST API client abstraction +│ ├── netbox_types.py # NetBox object type mappings +│ └── config.py # Settings and logging configuration +├── tests/ # Test suite +├── .github/workflows/ # CI/CD automation +├── pyproject.toml # Dependencies and project metadata +├── README.md # User-facing documentation +├── CHANGELOG.md # Auto-generated release notes +└── LICENSE # Apache 2.0 license ``` **Design Pattern**: Clean separation between MCP server logic (`server.py`) and NetBox API client (`netbox_client.py`) to support future plugin-based implementations. From 8954399c24050c0a217a43984f8f997cb1ccf352 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:29:04 -0400 Subject: [PATCH 25/33] docs: update common commands in CLAUDE.md --- claude.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/claude.md b/claude.md index 9e8b64c..1ff2bf3 100644 --- a/claude.md +++ b/claude.md @@ -43,13 +43,16 @@ A read-only [Model Context Protocol](https://modelcontextprotocol.io/) server th uv sync # Run the server locally (requires env vars) -NETBOX_URL=https://netbox.example.com/ NETBOX_TOKEN= uv run server.py +NETBOX_URL=https://netbox.example.com/ NETBOX_TOKEN= uv run netbox-mcp-server + +# Alternative: module execution +uv run -m netbox_mcp_server # Add to Claude Code (for development/testing) claude mcp add --transport stdio netbox \ --env NETBOX_URL=https://netbox.example.com/ \ --env NETBOX_TOKEN= \ - -- uv --directory /path/to/netbox-mcp-server run server.py + -- uv --directory /path/to/netbox-mcp-server run netbox-mcp-server ``` ## Development Philosophy From bedf7a0e51f609d36ebf08eae29de9ab846a38a8 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:30:20 -0400 Subject: [PATCH 26/33] docs: add version management section to CLAUDE.md --- claude.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/claude.md b/claude.md index 1ff2bf3..753a7c7 100644 --- a/claude.md +++ b/claude.md @@ -68,6 +68,21 @@ claude mcp add --transport stdio netbox \ - **Functional where clear**: Use functional, stateless approaches when they improve clarity - **Clean core logic**: Keep business logic clean; push implementation details to the edges +## Version Management + +This project uses [python-semantic-release](https://python-semantic-release.readthedocs.io/) for automated version management. Versions are automatically determined from commit messages following [Conventional Commits](https://www.conventionalcommits.org/). + +**Release triggers:** +- `feat:` commits trigger minor version bumps (1.0.0 → 1.1.0) +- `fix:` and `perf:` commits trigger patch version bumps (1.0.0 → 1.0.1) +- Commits with `BREAKING CHANGE:` in the body trigger major version bumps (1.0.0 → 2.0.0) +- `docs:`, `test:`, `chore:`, `ci:`, `refactor:` commits are logged but don't trigger releases + +**Workflow:** +- Merge to `main` automatically triggers release analysis +- If commits warrant a release, version is bumped and CHANGELOG updated +- GitHub Release is created with auto-generated release notes + ## Code Standards ### Python Conventions From d88af76a538718fd5cc194a504534a62ccd4c743 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:31:01 -0400 Subject: [PATCH 27/33] chore: update Dockerfile to use netbox-mcp-server command --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index abfae27..37b8782 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,4 +33,4 @@ ENV PATH="/app/.venv/bin:$PATH" EXPOSE 8000 -CMD ["python", "-u", "server.py"] +CMD ["netbox-mcp-server"] From aa25162ac9306d5f3c599cddb6208b3c2d6970b4 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 10:36:53 -0400 Subject: [PATCH 28/33] docs: minor formatting fixes to CLAUDE.md and add implementation plan --- claude.md | 3 +- ...-layout-semantic-release-implementation.md | 1386 +++++++++++++++++ 2 files changed, 1388 insertions(+), 1 deletion(-) create mode 100644 docs/plans/2025-10-28-src-layout-semantic-release-implementation.md diff --git a/claude.md b/claude.md index 753a7c7..b7135e6 100644 --- a/claude.md +++ b/claude.md @@ -73,12 +73,14 @@ claude mcp add --transport stdio netbox \ This project uses [python-semantic-release](https://python-semantic-release.readthedocs.io/) for automated version management. Versions are automatically determined from commit messages following [Conventional Commits](https://www.conventionalcommits.org/). **Release triggers:** + - `feat:` commits trigger minor version bumps (1.0.0 → 1.1.0) - `fix:` and `perf:` commits trigger patch version bumps (1.0.0 → 1.0.1) - Commits with `BREAKING CHANGE:` in the body trigger major version bumps (1.0.0 → 2.0.0) - `docs:`, `test:`, `chore:`, `ci:`, `refactor:` commits are logged but don't trigger releases **Workflow:** + - Merge to `main` automatically triggers release analysis - If commits warrant a release, version is bumped and CHANGELOG updated - GitHub Release is created with auto-generated release notes @@ -286,7 +288,6 @@ Currently no automated test suite. When adding tests: - ❌ **NEVER commit directly to `main`** - Always use feature branches - ✅ **DO keep commits professional and concise** and focused on the change - ## Decision Heuristics ### When to Add a New Tool diff --git a/docs/plans/2025-10-28-src-layout-semantic-release-implementation.md b/docs/plans/2025-10-28-src-layout-semantic-release-implementation.md new file mode 100644 index 0000000..b0a5002 --- /dev/null +++ b/docs/plans/2025-10-28-src-layout-semantic-release-implementation.md @@ -0,0 +1,1386 @@ +# src/ Layout + Semantic Release Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Restructure NetBox MCP server to use Python src/ layout, add python-semantic-release automation, and establish CI/CD pipelines for v1.0.0 release. + +**Architecture:** Standard Python src/ layout with console script entry point. python-semantic-release for automated versioning and CHANGELOG generation. GitHub Actions for CI testing and release automation. + +**Tech Stack:** Python 3.13, uv, python-semantic-release, GitHub Actions, pytest + +--- + +## Phase 1: Structure Setup + +### Task 1: Create Package Directory Structure + +**Files:** +- Create: `src/netbox_mcp_server/` (directory) + +**Step 1: Create the src/ and package directories** + +```bash +mkdir -p src/netbox_mcp_server +``` + +**Step 2: Verify directory structure** + +```bash +ls -la src/netbox_mcp_server/ +``` + +Expected: Empty directory + +**Step 3: Commit** + +```bash +git add src/ +git commit -m "chore: create src/netbox_mcp_server directory structure" +``` + +--- + +### Task 2: Create Package __init__.py + +**Files:** +- Create: `src/netbox_mcp_server/__init__.py` + +**Step 1: Write the package initialization file** + +```python +"""NetBox MCP Server - Read-only MCP server for NetBox infrastructure data.""" + +__version__ = "1.0.0" # Auto-managed by semantic-release + +__all__ = ["NetBoxRestClient", "NETBOX_OBJECT_TYPES", "Settings"] + +from netbox_mcp_server.netbox_client import NetBoxRestClient +from netbox_mcp_server.netbox_types import NETBOX_OBJECT_TYPES +from netbox_mcp_server.config import Settings +``` + +**Step 2: Save to file** + +```bash +cat > src/netbox_mcp_server/__init__.py << 'EOF' +[paste content above] +EOF +``` + +**Step 3: Verify file created** + +```bash +cat src/netbox_mcp_server/__init__.py +``` + +**Step 4: Commit** + +```bash +git add src/netbox_mcp_server/__init__.py +git commit -m "feat: add package __init__.py with version and exports" +``` + +--- + +### Task 3: Create __main__.py Entry Point + +**Files:** +- Create: `src/netbox_mcp_server/__main__.py` + +**Step 1: Write the module execution entry point** + +```python +"""Entry point for python -m netbox_mcp_server execution.""" + +from netbox_mcp_server.server import main + +if __name__ == "__main__": + main() +``` + +**Step 2: Save to file** + +```bash +cat > src/netbox_mcp_server/__main__.py << 'EOF' +[paste content above] +EOF +``` + +**Step 3: Commit** + +```bash +git add src/netbox_mcp_server/__main__.py +git commit -m "feat: add __main__.py for module execution support" +``` + +--- + +### Task 4: Move config.py to Package + +**Files:** +- Move: `config.py` → `src/netbox_mcp_server/config.py` +- Modify: `src/netbox_mcp_server/config.py` (imports) + +**Step 1: Move the file** + +```bash +git mv config.py src/netbox_mcp_server/config.py +``` + +**Step 2: Verify no import changes needed** + +Since config.py doesn't import other local modules, no changes needed. + +**Step 3: Commit** + +```bash +git commit -m "refactor: move config.py to src/netbox_mcp_server/" +``` + +--- + +### Task 5: Move netbox_types.py to Package + +**Files:** +- Move: `netbox_types.py` → `src/netbox_mcp_server/netbox_types.py` + +**Step 1: Move the file** + +```bash +git mv netbox_types.py src/netbox_mcp_server/netbox_types.py +``` + +**Step 2: Verify no import changes needed** + +Since netbox_types.py has no local imports, no changes needed. + +**Step 3: Commit** + +```bash +git commit -m "refactor: move netbox_types.py to src/netbox_mcp_server/" +``` + +--- + +### Task 6: Move netbox_client.py to Package + +**Files:** +- Move: `netbox_client.py` → `src/netbox_mcp_server/netbox_client.py` +- Modify: `src/netbox_mcp_server/netbox_client.py` (imports) + +**Step 1: Move the file** + +```bash +git mv netbox_client.py src/netbox_mcp_server/netbox_client.py +``` + +**Step 2: Update imports in netbox_client.py** + +Read the file and update any local imports: + +```python +# Change from: +from netbox_types import NETBOX_OBJECT_TYPES + +# To: +from netbox_mcp_server.netbox_types import NETBOX_OBJECT_TYPES +``` + +**Step 3: Commit** + +```bash +git add src/netbox_mcp_server/netbox_client.py +git commit -m "refactor: move netbox_client.py to src/ and update imports" +``` + +--- + +### Task 7: Move server.py to Package + +**Files:** +- Move: `server.py` → `src/netbox_mcp_server/server.py` +- Modify: `src/netbox_mcp_server/server.py` (imports and main function) + +**Step 1: Move the file** + +```bash +git mv server.py src/netbox_mcp_server/server.py +``` + +**Step 2: Update imports in server.py** + +```python +# Change from: +from config import Settings, configure_logging +from netbox_client import NetBoxRestClient +from netbox_types import NETBOX_OBJECT_TYPES + +# To: +from netbox_mcp_server.config import Settings, configure_logging +from netbox_mcp_server.netbox_client import NetBoxRestClient +from netbox_mcp_server.netbox_types import NETBOX_OBJECT_TYPES +``` + +**Step 3: Add main() function wrapper** + +At the end of the file, wrap the current execution logic: + +```python +def main(): + """Main entry point for the MCP server.""" + args = parse_cli_args() + settings = Settings() + configure_logging(settings.log_level) + + # Initialize NetBox client + netbox = NetBoxRestClient( + base_url=settings.netbox_url, + token=settings.netbox_token + ) + + # Log startup + logger = logging.getLogger(__name__) + logger.info(settings.model_dump()) + logger.info(f"Starting NetBox MCP server on {settings.netbox_url}") + + # Run the server + mcp.run() + +if __name__ == "__main__": + main() +``` + +**Step 4: Commit** + +```bash +git add src/netbox_mcp_server/server.py +git commit -m "refactor: move server.py to src/, update imports, add main() function" +``` + +--- + +## Phase 2: Configuration Updates + +### Task 8: Add Console Script Entry Point + +**Files:** +- Modify: `pyproject.toml` + +**Step 1: Add [project.scripts] section** + +Add after the `[project]` section: + +```toml +[project.scripts] +netbox-mcp-server = "netbox_mcp_server.server:main" +``` + +**Step 2: Verify toml syntax** + +```bash +uv sync --no-install-project +``` + +Expected: No errors + +**Step 3: Commit** + +```bash +git add pyproject.toml +git commit -m "feat: add console script entry point netbox-mcp-server" +``` + +--- + +### Task 9: Add python-semantic-release Dependency + +**Files:** +- Modify: `pyproject.toml` + +**Step 1: Add dependency using uv** + +```bash +uv add --dev python-semantic-release +``` + +**Step 2: Verify installation** + +```bash +uv run semantic-release --version +``` + +Expected: Version output (e.g., "9.x.x") + +**Step 3: Commit** + +```bash +git add pyproject.toml uv.lock +git commit -m "chore: add python-semantic-release dev dependency" +``` + +--- + +### Task 10: Configure python-semantic-release + +**Files:** +- Modify: `pyproject.toml` + +**Step 1: Add [tool.semantic_release] configuration** + +Add to the end of pyproject.toml: + +```toml +[tool.semantic_release] +version_toml = ["pyproject.toml:project.version"] +version_variables = ["src/netbox_mcp_server/__init__.py:__version__"] +branch = "main" +upload_to_vcs_release = true +build_command = "uv build" +tag_format = "v{version}" + +[tool.semantic_release.commit_parser_options] +allowed_tags = ["feat", "fix", "docs", "chore", "refactor", "test", "ci", "perf"] +minor_tags = ["feat"] +patch_tags = ["fix", "perf"] + +[tool.semantic_release.changelog] +changelog_file = "CHANGELOG.md" +``` + +**Step 2: Test configuration with dry-run** + +```bash +uv run semantic-release version --no-push --no-commit --no-tag +``` + +Expected: Would bump to 1.0.0 (or similar output showing it works) + +**Step 3: Commit** + +```bash +git add pyproject.toml +git commit -m "feat: configure python-semantic-release for automated versioning" +``` + +--- + +## Phase 3: Test Updates + +### Task 11: Update test_config.py Imports + +**Files:** +- Modify: `tests/test_config.py` + +**Step 1: Update imports** + +```python +# Change from: +from config import Settings, configure_logging, parse_cli_args + +# To: +from netbox_mcp_server.config import Settings, configure_logging, parse_cli_args +``` + +**Step 2: Run tests to verify** + +```bash +uv run pytest tests/test_config.py -v +``` + +Expected: All tests pass + +**Step 3: Commit** + +```bash +git add tests/test_config.py +git commit -m "test: update test_config.py imports to use package structure" +``` + +--- + +### Task 12: Update test_filter_validation.py Imports + +**Files:** +- Modify: `tests/test_filter_validation.py` + +**Step 1: Update imports** + +```python +# Change from: +from server import validate_filters + +# To: +from netbox_mcp_server.server import validate_filters +``` + +**Step 2: Run tests to verify** + +```bash +uv run pytest tests/test_filter_validation.py -v +``` + +Expected: All tests pass + +**Step 3: Commit** + +```bash +git add tests/test_filter_validation.py +git commit -m "test: update test_filter_validation.py imports to use package structure" +``` + +--- + +### Task 13: Update test_ordering.py Imports + +**Files:** +- Modify: `tests/test_ordering.py` + +**Step 1: Update imports** + +```python +# Change from: +from server import netbox_get_objects + +# To: +from netbox_mcp_server.server import netbox_get_objects +``` + +**Step 2: Run tests to verify** + +```bash +uv run pytest tests/test_ordering.py -v +``` + +Expected: All tests pass + +**Step 3: Commit** + +```bash +git add tests/test_ordering.py +git commit -m "test: update test_ordering.py imports to use package structure" +``` + +--- + +### Task 14: Update test_pagination.py Imports + +**Files:** +- Modify: `tests/test_pagination.py` + +**Step 1: Update imports** + +```python +# Change from: +from server import netbox_get_objects + +# To: +from netbox_mcp_server.server import netbox_get_objects +``` + +**Step 2: Run tests to verify** + +```bash +uv run pytest tests/test_pagination.py -v +``` + +Expected: All tests pass + +**Step 3: Commit** + +```bash +git add tests/test_pagination.py +git commit -m "test: update test_pagination.py imports to use package structure" +``` + +--- + +### Task 15: Update test_search.py Imports + +**Files:** +- Modify: `tests/test_search.py` + +**Step 1: Update imports** + +```python +# Change from: +from server import netbox_search_objects +from netbox_types import NETBOX_OBJECT_TYPES + +# To: +from netbox_mcp_server.server import netbox_search_objects +from netbox_mcp_server.netbox_types import NETBOX_OBJECT_TYPES +``` + +**Step 2: Run tests to verify** + +```bash +uv run pytest tests/test_search.py -v +``` + +Expected: All tests pass + +**Step 3: Commit** + +```bash +git add tests/test_search.py +git commit -m "test: update test_search.py imports to use package structure" +``` + +--- + +### Task 16: Update test_brief.py Imports + +**Files:** +- Modify: `tests/test_brief.py` + +**Step 1: Update imports** + +```python +# Change from: +from server import netbox_get_objects, netbox_get_object_by_id + +# To: +from netbox_mcp_server.server import netbox_get_objects, netbox_get_object_by_id +``` + +**Step 2: Run tests to verify** + +```bash +uv run pytest tests/test_brief.py -v +``` + +Expected: All tests pass + +**Step 3: Commit** + +```bash +git add tests/test_brief.py +git commit -m "test: update test_brief.py imports to use package structure" +``` + +--- + +### Task 17: Run Full Test Suite + +**Files:** +- None (verification step) + +**Step 1: Run all tests** + +```bash +uv run pytest -v +``` + +Expected: All 43 tests pass + +**Step 2: Verify no import errors** + +Check output for any import-related failures. + +**Step 3: If all pass, ready for next phase** + +No commit needed - this is verification only. + +--- + +## Phase 4: CI/CD Workflows + +### Task 18: Create GitHub Workflows Directory + +**Files:** +- Create: `.github/workflows/` (directory) + +**Step 1: Create directory** + +```bash +mkdir -p .github/workflows +``` + +**Step 2: Commit** + +```bash +git add .github/ +git commit -m "chore: create .github/workflows directory for CI/CD" +``` + +--- + +### Task 19: Create Test Workflow + +**Files:** +- Create: `.github/workflows/test.yml` + +**Step 1: Write test workflow** + +```yaml +name: Test + +on: + pull_request: + push: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + + services: + netbox: + image: netboxcommunity/netbox:latest + env: + SKIP_SUPERUSER: true + ports: + - 8000:8080 + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install dependencies + run: uv sync + + - name: Run tests + run: uv run pytest -v + env: + NETBOX_URL: http://localhost:8000 + NETBOX_TOKEN: ${{ secrets.NETBOX_TOKEN }} +``` + +**Step 2: Validate YAML syntax** + +```bash +python3 -c "import yaml; yaml.safe_load(open('.github/workflows/test.yml'))" +``` + +Expected: No errors (silent success) + +**Step 3: Commit** + +```bash +git add .github/workflows/test.yml +git commit -m "ci: add test workflow for automated testing" +``` + +--- + +### Task 20: Create Release Workflow + +**Files:** +- Create: `.github/workflows/release.yml` + +**Step 1: Write release workflow** + +```yaml +name: Release + +permissions: + contents: write + issues: write + pull-requests: write + +on: + push: + branches: [main] + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install dependencies + run: uv sync + + - name: Python Semantic Release + uses: python-semantic-release/python-semantic-release@v9 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} +``` + +**Step 2: Validate YAML syntax** + +```bash +python3 -c "import yaml; yaml.safe_load(open('.github/workflows/release.yml'))" +``` + +Expected: No errors + +**Step 3: Commit** + +```bash +git add .github/workflows/release.yml +git commit -m "ci: add release workflow for automated semantic releases" +``` + +--- + +## Phase 5: Documentation Updates + +### Task 21: Update README.md - Breaking Change Notice + +**Files:** +- Modify: `README.md` + +**Step 1: Add breaking change notice after title** + +Find the title (line 1-2) and add immediately after: + +```markdown +> **⚠️ Breaking Change in v1.0.0**: The project structure has changed. +> If upgrading from v0.1.0, update your configuration: +> - Change `uv run server.py` to `uv run netbox-mcp-server` +> - Update Claude Desktop/Code configs to use `netbox-mcp-server` instead of `server.py` +> - Docker users: rebuild images with updated CMD +> - See [CHANGELOG.md](CHANGELOG.md) for full details +``` + +**Step 2: Verify formatting** + +```bash +head -20 README.md +``` + +**Step 3: Commit** + +```bash +git add README.md +git commit -m "docs: add breaking change notice to README for v1.0.0" +``` + +--- + +### Task 22: Update README.md - Running Locally Section + +**Files:** +- Modify: `README.md` + +**Step 1: Find and update "Running Locally" or similar section** + +Change all instances of: + +```bash +# Before +uv run server.py + +# After +uv run netbox-mcp-server +``` + +**Step 2: Verify all occurrences updated** + +```bash +grep "uv run server.py" README.md +``` + +Expected: No results (empty) + +**Step 3: Commit** + +```bash +git add README.md +git commit -m "docs: update README commands to use netbox-mcp-server" +``` + +--- + +### Task 23: Update README.md - Claude Desktop/Code Config + +**Files:** +- Modify: `README.md` + +**Step 1: Find Claude Desktop/Code configuration section** + +Update the JSON example: + +```json +{ + "mcpServers": { + "netbox": { + "command": "uv", + "args": [ + "--directory", + "/absolute/path/to/netbox-mcp-server", + "run", + "netbox-mcp-server" + ], + "env": { + "NETBOX_URL": "https://demo.netbox.dev/", + "NETBOX_TOKEN": "your-token-here" + } + } + } +} +``` + +**Step 2: Verify JSON syntax** + +```bash +python3 -c "import json; json.load(open('/dev/stdin'))" < README.md || echo "Check JSON manually" +``` + +**Step 3: Commit** + +```bash +git add README.md +git commit -m "docs: update Claude Desktop/Code config examples in README" +``` + +--- + +### Task 24: Update CLAUDE.md - Project Structure + +**Files:** +- Modify: `CLAUDE.md` + +**Step 1: Find "Project Structure" section** + +Replace with: + +```markdown +## Project Structure + +```text +. +├── src/ +│ └── netbox_mcp_server/ +│ ├── __init__.py # Package initialization with __version__ +│ ├── __main__.py # Entry point for module execution +│ ├── server.py # Main MCP server with tool definitions +│ ├── netbox_client.py # NetBox REST API client abstraction +│ ├── netbox_types.py # NetBox object type mappings +│ └── config.py # Settings and logging configuration +├── tests/ # Test suite +├── .github/workflows/ # CI/CD automation +├── pyproject.toml # Dependencies and project metadata +├── README.md # User-facing documentation +├── CHANGELOG.md # Auto-generated release notes +└── LICENSE # Apache 2.0 license +``` +``` + +**Step 2: Commit** + +```bash +git add CLAUDE.md +git commit -m "docs: update project structure in CLAUDE.md" +``` + +--- + +### Task 25: Update CLAUDE.md - Common Commands + +**Files:** +- Modify: `CLAUDE.md` + +**Step 1: Find "Common Commands" section** + +Update all command examples: + +```markdown +## Common Commands + +```bash +# Install dependencies (ONLY use uv, NEVER pip) +uv sync + +# Run the server locally (requires env vars) +NETBOX_URL=https://netbox.example.com/ NETBOX_TOKEN= uv run netbox-mcp-server + +# Alternative: module execution +uv run -m netbox_mcp_server + +# Add to Claude Code (for development/testing) +claude mcp add --transport stdio netbox \ + --env NETBOX_URL=https://netbox.example.com/ \ + --env NETBOX_TOKEN= \ + -- uv --directory /path/to/netbox-mcp-server run netbox-mcp-server +``` +``` + +**Step 2: Commit** + +```bash +git add CLAUDE.md +git commit -m "docs: update common commands in CLAUDE.md" +``` + +--- + +### Task 26: Update CLAUDE.md - Add Version Management Section + +**Files:** +- Modify: `CLAUDE.md` + +**Step 1: Add new section after "Development Philosophy"** + +```markdown +## Version Management + +This project uses [python-semantic-release](https://python-semantic-release.readthedocs.io/) for automated version management. Versions are automatically determined from commit messages following [Conventional Commits](https://www.conventionalcommits.org/). + +**Release triggers:** +- `feat:` commits trigger minor version bumps (1.0.0 → 1.1.0) +- `fix:` and `perf:` commits trigger patch version bumps (1.0.0 → 1.0.1) +- Commits with `BREAKING CHANGE:` in the body trigger major version bumps (1.0.0 → 2.0.0) +- `docs:`, `test:`, `chore:`, `ci:`, `refactor:` commits are logged but don't trigger releases + +**Workflow:** +- Merge to `main` automatically triggers release analysis +- If commits warrant a release, version is bumped and CHANGELOG updated +- GitHub Release is created with auto-generated release notes +``` + +**Step 2: Commit** + +```bash +git add CLAUDE.md +git commit -m "docs: add version management section to CLAUDE.md" +``` + +--- + +### Task 27: Update Dockerfile + +**Files:** +- Modify: `Dockerfile` + +**Step 1: Find CMD line and update** + +Change from: + +```dockerfile +CMD ["python", "-u", "server.py"] +``` + +To: + +```dockerfile +CMD ["netbox-mcp-server"] +``` + +**Step 2: Verify Docker syntax** + +```bash +docker build -t netbox-mcp-test . --dry-run 2>/dev/null || echo "Build check (ignore if Docker not running)" +``` + +**Step 3: Commit** + +```bash +git add Dockerfile +git commit -m "chore: update Dockerfile to use netbox-mcp-server command" +``` + +--- + +## Phase 6: Testing & Verification + +### Task 28: Verify Console Script Works + +**Files:** +- None (verification) + +**Step 1: Sync dependencies to ensure script installed** + +```bash +uv sync +``` + +**Step 2: Test console script execution (will fail without env vars - expected)** + +```bash +uv run netbox-mcp-server --help 2>&1 || echo "Expected: Shows help or env var error" +``` + +Expected: Either help output or error about missing NETBOX_URL/NETBOX_TOKEN + +**Step 3: Verify script is in uv environment** + +```bash +uv run which netbox-mcp-server +``` + +Expected: Path to script in .venv/bin/ + +--- + +### Task 29: Run Full Test Suite Again + +**Files:** +- None (verification) + +**Step 1: Run all tests** + +```bash +uv run pytest -v --tb=short +``` + +Expected: All 43 tests pass + +**Step 2: Check for any warnings** + +Review test output for deprecation warnings or issues. + +**Step 3: Generate coverage report** + +```bash +uv run pytest --cov=netbox_mcp_server --cov-report=term-missing +``` + +--- + +### Task 30: Verify Import Structure + +**Files:** +- None (verification) + +**Step 1: Test package imports in Python** + +```bash +uv run python -c "from netbox_mcp_server import NetBoxRestClient, NETBOX_OBJECT_TYPES, Settings; print('Imports successful')" +``` + +Expected: "Imports successful" + +**Step 2: Test version access** + +```bash +uv run python -c "from netbox_mcp_server import __version__; print(f'Version: {__version__}')" +``` + +Expected: "Version: 1.0.0" + +**Step 3: Test module execution** + +```bash +uv run python -m netbox_mcp_server --help 2>&1 || echo "Expected: help or env error" +``` + +--- + +### Task 31: Validate GitHub Actions YAML + +**Files:** +- None (verification) + +**Step 1: Validate test.yml** + +```bash +python3 -c "import yaml; print('test.yml valid') if yaml.safe_load(open('.github/workflows/test.yml')) else print('INVALID')" +``` + +Expected: "test.yml valid" + +**Step 2: Validate release.yml** + +```bash +python3 -c "import yaml; print('release.yml valid') if yaml.safe_load(open('.github/workflows/release.yml')) else print('INVALID')" +``` + +Expected: "release.yml valid" + +--- + +## Phase 7: Final Commit & Push + +### Task 32: Create Breaking Change Commit + +**Files:** +- All modified files + +**Step 1: Check git status** + +```bash +git status +``` + +Expected: Should show "nothing to commit, working tree clean" (all previous commits done) + +**Step 2: If any uncommitted files, stage them** + +```bash +git add . +``` + +**Step 3: Verify all changes are in git history** + +```bash +git log --oneline -20 +``` + +Expected: See all commits from previous tasks + +--- + +### Task 33: Push Feature Branch + +**Files:** +- None (git operation) + +**Step 1: Push feature branch to remote** + +```bash +git push -u origin feat/src-layout-and-semantic-release +``` + +Expected: Branch pushed successfully + +**Step 2: Note the branch URL** + +GitHub will provide a URL to create a pull request. + +--- + +### Task 34: Create Pull Request + +**Files:** +- None (GitHub operation) + +**Step 1: Open GitHub PR** + +Use the URL from previous step or manually create PR with: + +**Title:** +``` +feat!: restructure to src/ layout and add semantic release +``` + +**Body:** +```markdown +## Summary + +Restructures the project to use standard Python src/ layout and adds automated semantic release capabilities via python-semantic-release. + +## Breaking Changes + +⚠️ **This is a breaking change for users:** + +- **Command change**: `uv run server.py` → `uv run netbox-mcp-server` +- **Claude config change**: Update `args` to use `netbox-mcp-server` instead of `server.py` +- **Docker change**: Rebuild images (CMD updated) +- **Import changes** (contributors): `from server import` → `from netbox_mcp_server.server import` + +## Changes + +### Structure +- Moved all Python modules to `src/netbox_mcp_server/` +- Added package `__init__.py` with version export +- Added `__main__.py` for module execution support +- Added console script entry point in pyproject.toml + +### Automation +- Added python-semantic-release for automated versioning +- Added CI test workflow (pytest on PRs and main) +- Added release workflow (automatic on push to main) +- Configured conventional commits for version bumping + +### Code +- Updated all module imports to use package structure +- Added `main()` function to server.py +- Updated all 6 test files with new imports + +### Documentation +- Updated README.md with breaking change notice +- Updated all command examples in README.md +- Updated CLAUDE.md project structure +- Updated Dockerfile CMD +- Added version management documentation + +## Testing + +- ✅ All 43 tests passing +- ✅ Console script `netbox-mcp-server` works +- ✅ Module execution `python -m netbox_mcp_server` works +- ✅ Package imports validated +- ✅ CI workflows validated + +## Release Plan + +When merged, this will automatically trigger a v1.0.0 release via python-semantic-release due to the breaking change in the commit history. + +Closes #[issue number if applicable] +``` + +--- + +## Post-Merge Verification + +### Task 35: Monitor Release Workflow + +**After PR is merged to main:** + +**Step 1: Watch GitHub Actions** + +Navigate to: `https://github.com/netboxlabs/netbox-mcp-server/actions` + +**Step 2: Wait for "Release" workflow to complete** + +Expected: Green checkmark, workflow completes successfully + +**Step 3: Verify outputs** +- Version bumped to 1.0.0 in pyproject.toml +- CHANGELOG.md created and committed +- Git tag `v1.0.0` created +- GitHub Release created + +--- + +### Task 36: Verify GitHub Release + +**Files:** +- None (GitHub verification) + +**Step 1: Navigate to GitHub Releases page** + +`https://github.com/netboxlabs/netbox-mcp-server/releases` + +**Step 2: Verify v1.0.0 release exists** + +Check: +- Tag is `v1.0.0` +- Release notes include breaking change information +- Release notes auto-generated from commits + +--- + +### Task 37: Test v1.0.0 Locally + +**Files:** +- None (verification) + +**Step 1: Pull latest main** + +```bash +git checkout main +git pull +``` + +**Step 2: Verify version in files** + +```bash +grep "version = " pyproject.toml +grep "__version__ = " src/netbox_mcp_server/__init__.py +``` + +Expected: Both show "1.0.0" + +**Step 3: Verify CHANGELOG exists** + +```bash +cat CHANGELOG.md | head -20 +``` + +Expected: Shows v1.0.0 entry with breaking change notice + +--- + +### Task 38: Clean Up Worktree (Optional) + +**Files:** +- None (cleanup) + +**Step 1: Return to main working directory** + +```bash +cd ../.. # Back to main repo root +``` + +**Step 2: Remove worktree** + +```bash +git worktree remove .worktrees/feat-src-layout-and-semantic-release +``` + +**Step 3: Prune worktree references** + +```bash +git worktree prune +``` + +--- + +## Success Criteria Checklist + +- [ ] All 43 tests passing with new structure +- [ ] Console script `netbox-mcp-server` executes successfully +- [ ] Module execution `python -m netbox_mcp_server` works +- [ ] All imports use `netbox_mcp_server.*` package structure +- [ ] pyproject.toml has console script entry point +- [ ] pyproject.toml has semantic-release configuration +- [ ] python-semantic-release dev dependency added +- [ ] Test workflow `.github/workflows/test.yml` created +- [ ] Release workflow `.github/workflows/release.yml` created +- [ ] README.md updated with breaking change notice +- [ ] README.md commands updated to use `netbox-mcp-server` +- [ ] CLAUDE.md project structure updated +- [ ] CLAUDE.md commands updated +- [ ] CLAUDE.md has version management section +- [ ] Dockerfile CMD updated to use `netbox-mcp-server` +- [ ] Feature branch pushed to GitHub +- [ ] Pull request created with breaking change notice +- [ ] PR merged to main +- [ ] Release workflow executed successfully +- [ ] GitHub Release v1.0.0 created +- [ ] CHANGELOG.md generated and committed +- [ ] Version 1.0.0 in pyproject.toml and __init__.py + +--- + +## Rollback Plan (If Needed) + +If issues arise after merge: + +1. Create hotfix branch from commit before merge +2. Revert breaking changes +3. Create patch release +4. Fix issues in separate PR + +**Prevention:** +- All tasks have verification steps +- Tests run before merge +- Can test PR branch before merging + +--- + +## Notes for Implementer + +- **DRY**: Each file is moved/modified once +- **YAGNI**: No extra features, just what's needed for refactoring +- **TDD**: Tests updated immediately after code changes +- **Commits**: Frequent, atomic commits for easy review +- **Verification**: Tests run after each import change + +**Estimated total time**: 2-3 hours for careful implementation + +**Key risks**: +1. Import errors - mitigated by testing after each change +2. CI workflow misconfiguration - mitigated by YAML validation +3. Semantic release issues - mitigated by dry-run testing + +**Support**: +- Design document: `.plans/2025-10-28-src-layout-semantic-release-design.md` +- Python-semantic-release docs: https://python-semantic-release.readthedocs.io/ +- Conventional commits: https://www.conventionalcommits.org/ From 456376da80296e905912f0e15786f90ddf4dd9f5 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 11:30:48 -0400 Subject: [PATCH 29/33] fix: improved specification for changed_object_type_id --- src/netbox_mcp_server/server.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/netbox_mcp_server/server.py b/src/netbox_mcp_server/server.py index 0615d0a..0abf64e 100644 --- a/src/netbox_mcp_server/server.py +++ b/src/netbox_mcp_server/server.py @@ -368,7 +368,8 @@ def netbox_get_changelogs(filters: dict): Filtering options include: - user_id: Filter by user ID who made the change - user: Filter by username who made the change - - changed_object_type_id: Filter by ContentType ID of the changed object + - changed_object_type_id: Filter by numeric ContentType ID (e.g., 21 for dcim.device) + Note: This expects a numeric ID, not an object type string - changed_object_id: Filter by ID of the changed object - object_repr: Filter by object representation (usually contains object name) - action: Filter by action type (created, updated, deleted) @@ -376,9 +377,12 @@ def netbox_get_changelogs(filters: dict): - time_after: Filter for changes made after a given time (ISO 8601 format) - q: Search term to filter by object representation - Example: - To find all changes made to a specific device with ID 123: - {"changed_object_type_id": "dcim.device", "changed_object_id": 123} + Examples: + To find all changes made to a specific object by ID: + {"changed_object_id": 123} + + To find changes by object name pattern: + {"object_repr": "router-01"} To find all deletions in the last 24 hours: {"action": "delete", "time_after": "2023-01-01T00:00:00Z"} From b16c82470caad0efaf1f7aa35a036899bf9f0382 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 11:30:58 -0400 Subject: [PATCH 30/33] chore: removed spec doc --- ...-layout-semantic-release-implementation.md | 1386 ----------------- 1 file changed, 1386 deletions(-) delete mode 100644 docs/plans/2025-10-28-src-layout-semantic-release-implementation.md diff --git a/docs/plans/2025-10-28-src-layout-semantic-release-implementation.md b/docs/plans/2025-10-28-src-layout-semantic-release-implementation.md deleted file mode 100644 index b0a5002..0000000 --- a/docs/plans/2025-10-28-src-layout-semantic-release-implementation.md +++ /dev/null @@ -1,1386 +0,0 @@ -# src/ Layout + Semantic Release Implementation Plan - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**Goal:** Restructure NetBox MCP server to use Python src/ layout, add python-semantic-release automation, and establish CI/CD pipelines for v1.0.0 release. - -**Architecture:** Standard Python src/ layout with console script entry point. python-semantic-release for automated versioning and CHANGELOG generation. GitHub Actions for CI testing and release automation. - -**Tech Stack:** Python 3.13, uv, python-semantic-release, GitHub Actions, pytest - ---- - -## Phase 1: Structure Setup - -### Task 1: Create Package Directory Structure - -**Files:** -- Create: `src/netbox_mcp_server/` (directory) - -**Step 1: Create the src/ and package directories** - -```bash -mkdir -p src/netbox_mcp_server -``` - -**Step 2: Verify directory structure** - -```bash -ls -la src/netbox_mcp_server/ -``` - -Expected: Empty directory - -**Step 3: Commit** - -```bash -git add src/ -git commit -m "chore: create src/netbox_mcp_server directory structure" -``` - ---- - -### Task 2: Create Package __init__.py - -**Files:** -- Create: `src/netbox_mcp_server/__init__.py` - -**Step 1: Write the package initialization file** - -```python -"""NetBox MCP Server - Read-only MCP server for NetBox infrastructure data.""" - -__version__ = "1.0.0" # Auto-managed by semantic-release - -__all__ = ["NetBoxRestClient", "NETBOX_OBJECT_TYPES", "Settings"] - -from netbox_mcp_server.netbox_client import NetBoxRestClient -from netbox_mcp_server.netbox_types import NETBOX_OBJECT_TYPES -from netbox_mcp_server.config import Settings -``` - -**Step 2: Save to file** - -```bash -cat > src/netbox_mcp_server/__init__.py << 'EOF' -[paste content above] -EOF -``` - -**Step 3: Verify file created** - -```bash -cat src/netbox_mcp_server/__init__.py -``` - -**Step 4: Commit** - -```bash -git add src/netbox_mcp_server/__init__.py -git commit -m "feat: add package __init__.py with version and exports" -``` - ---- - -### Task 3: Create __main__.py Entry Point - -**Files:** -- Create: `src/netbox_mcp_server/__main__.py` - -**Step 1: Write the module execution entry point** - -```python -"""Entry point for python -m netbox_mcp_server execution.""" - -from netbox_mcp_server.server import main - -if __name__ == "__main__": - main() -``` - -**Step 2: Save to file** - -```bash -cat > src/netbox_mcp_server/__main__.py << 'EOF' -[paste content above] -EOF -``` - -**Step 3: Commit** - -```bash -git add src/netbox_mcp_server/__main__.py -git commit -m "feat: add __main__.py for module execution support" -``` - ---- - -### Task 4: Move config.py to Package - -**Files:** -- Move: `config.py` → `src/netbox_mcp_server/config.py` -- Modify: `src/netbox_mcp_server/config.py` (imports) - -**Step 1: Move the file** - -```bash -git mv config.py src/netbox_mcp_server/config.py -``` - -**Step 2: Verify no import changes needed** - -Since config.py doesn't import other local modules, no changes needed. - -**Step 3: Commit** - -```bash -git commit -m "refactor: move config.py to src/netbox_mcp_server/" -``` - ---- - -### Task 5: Move netbox_types.py to Package - -**Files:** -- Move: `netbox_types.py` → `src/netbox_mcp_server/netbox_types.py` - -**Step 1: Move the file** - -```bash -git mv netbox_types.py src/netbox_mcp_server/netbox_types.py -``` - -**Step 2: Verify no import changes needed** - -Since netbox_types.py has no local imports, no changes needed. - -**Step 3: Commit** - -```bash -git commit -m "refactor: move netbox_types.py to src/netbox_mcp_server/" -``` - ---- - -### Task 6: Move netbox_client.py to Package - -**Files:** -- Move: `netbox_client.py` → `src/netbox_mcp_server/netbox_client.py` -- Modify: `src/netbox_mcp_server/netbox_client.py` (imports) - -**Step 1: Move the file** - -```bash -git mv netbox_client.py src/netbox_mcp_server/netbox_client.py -``` - -**Step 2: Update imports in netbox_client.py** - -Read the file and update any local imports: - -```python -# Change from: -from netbox_types import NETBOX_OBJECT_TYPES - -# To: -from netbox_mcp_server.netbox_types import NETBOX_OBJECT_TYPES -``` - -**Step 3: Commit** - -```bash -git add src/netbox_mcp_server/netbox_client.py -git commit -m "refactor: move netbox_client.py to src/ and update imports" -``` - ---- - -### Task 7: Move server.py to Package - -**Files:** -- Move: `server.py` → `src/netbox_mcp_server/server.py` -- Modify: `src/netbox_mcp_server/server.py` (imports and main function) - -**Step 1: Move the file** - -```bash -git mv server.py src/netbox_mcp_server/server.py -``` - -**Step 2: Update imports in server.py** - -```python -# Change from: -from config import Settings, configure_logging -from netbox_client import NetBoxRestClient -from netbox_types import NETBOX_OBJECT_TYPES - -# To: -from netbox_mcp_server.config import Settings, configure_logging -from netbox_mcp_server.netbox_client import NetBoxRestClient -from netbox_mcp_server.netbox_types import NETBOX_OBJECT_TYPES -``` - -**Step 3: Add main() function wrapper** - -At the end of the file, wrap the current execution logic: - -```python -def main(): - """Main entry point for the MCP server.""" - args = parse_cli_args() - settings = Settings() - configure_logging(settings.log_level) - - # Initialize NetBox client - netbox = NetBoxRestClient( - base_url=settings.netbox_url, - token=settings.netbox_token - ) - - # Log startup - logger = logging.getLogger(__name__) - logger.info(settings.model_dump()) - logger.info(f"Starting NetBox MCP server on {settings.netbox_url}") - - # Run the server - mcp.run() - -if __name__ == "__main__": - main() -``` - -**Step 4: Commit** - -```bash -git add src/netbox_mcp_server/server.py -git commit -m "refactor: move server.py to src/, update imports, add main() function" -``` - ---- - -## Phase 2: Configuration Updates - -### Task 8: Add Console Script Entry Point - -**Files:** -- Modify: `pyproject.toml` - -**Step 1: Add [project.scripts] section** - -Add after the `[project]` section: - -```toml -[project.scripts] -netbox-mcp-server = "netbox_mcp_server.server:main" -``` - -**Step 2: Verify toml syntax** - -```bash -uv sync --no-install-project -``` - -Expected: No errors - -**Step 3: Commit** - -```bash -git add pyproject.toml -git commit -m "feat: add console script entry point netbox-mcp-server" -``` - ---- - -### Task 9: Add python-semantic-release Dependency - -**Files:** -- Modify: `pyproject.toml` - -**Step 1: Add dependency using uv** - -```bash -uv add --dev python-semantic-release -``` - -**Step 2: Verify installation** - -```bash -uv run semantic-release --version -``` - -Expected: Version output (e.g., "9.x.x") - -**Step 3: Commit** - -```bash -git add pyproject.toml uv.lock -git commit -m "chore: add python-semantic-release dev dependency" -``` - ---- - -### Task 10: Configure python-semantic-release - -**Files:** -- Modify: `pyproject.toml` - -**Step 1: Add [tool.semantic_release] configuration** - -Add to the end of pyproject.toml: - -```toml -[tool.semantic_release] -version_toml = ["pyproject.toml:project.version"] -version_variables = ["src/netbox_mcp_server/__init__.py:__version__"] -branch = "main" -upload_to_vcs_release = true -build_command = "uv build" -tag_format = "v{version}" - -[tool.semantic_release.commit_parser_options] -allowed_tags = ["feat", "fix", "docs", "chore", "refactor", "test", "ci", "perf"] -minor_tags = ["feat"] -patch_tags = ["fix", "perf"] - -[tool.semantic_release.changelog] -changelog_file = "CHANGELOG.md" -``` - -**Step 2: Test configuration with dry-run** - -```bash -uv run semantic-release version --no-push --no-commit --no-tag -``` - -Expected: Would bump to 1.0.0 (or similar output showing it works) - -**Step 3: Commit** - -```bash -git add pyproject.toml -git commit -m "feat: configure python-semantic-release for automated versioning" -``` - ---- - -## Phase 3: Test Updates - -### Task 11: Update test_config.py Imports - -**Files:** -- Modify: `tests/test_config.py` - -**Step 1: Update imports** - -```python -# Change from: -from config import Settings, configure_logging, parse_cli_args - -# To: -from netbox_mcp_server.config import Settings, configure_logging, parse_cli_args -``` - -**Step 2: Run tests to verify** - -```bash -uv run pytest tests/test_config.py -v -``` - -Expected: All tests pass - -**Step 3: Commit** - -```bash -git add tests/test_config.py -git commit -m "test: update test_config.py imports to use package structure" -``` - ---- - -### Task 12: Update test_filter_validation.py Imports - -**Files:** -- Modify: `tests/test_filter_validation.py` - -**Step 1: Update imports** - -```python -# Change from: -from server import validate_filters - -# To: -from netbox_mcp_server.server import validate_filters -``` - -**Step 2: Run tests to verify** - -```bash -uv run pytest tests/test_filter_validation.py -v -``` - -Expected: All tests pass - -**Step 3: Commit** - -```bash -git add tests/test_filter_validation.py -git commit -m "test: update test_filter_validation.py imports to use package structure" -``` - ---- - -### Task 13: Update test_ordering.py Imports - -**Files:** -- Modify: `tests/test_ordering.py` - -**Step 1: Update imports** - -```python -# Change from: -from server import netbox_get_objects - -# To: -from netbox_mcp_server.server import netbox_get_objects -``` - -**Step 2: Run tests to verify** - -```bash -uv run pytest tests/test_ordering.py -v -``` - -Expected: All tests pass - -**Step 3: Commit** - -```bash -git add tests/test_ordering.py -git commit -m "test: update test_ordering.py imports to use package structure" -``` - ---- - -### Task 14: Update test_pagination.py Imports - -**Files:** -- Modify: `tests/test_pagination.py` - -**Step 1: Update imports** - -```python -# Change from: -from server import netbox_get_objects - -# To: -from netbox_mcp_server.server import netbox_get_objects -``` - -**Step 2: Run tests to verify** - -```bash -uv run pytest tests/test_pagination.py -v -``` - -Expected: All tests pass - -**Step 3: Commit** - -```bash -git add tests/test_pagination.py -git commit -m "test: update test_pagination.py imports to use package structure" -``` - ---- - -### Task 15: Update test_search.py Imports - -**Files:** -- Modify: `tests/test_search.py` - -**Step 1: Update imports** - -```python -# Change from: -from server import netbox_search_objects -from netbox_types import NETBOX_OBJECT_TYPES - -# To: -from netbox_mcp_server.server import netbox_search_objects -from netbox_mcp_server.netbox_types import NETBOX_OBJECT_TYPES -``` - -**Step 2: Run tests to verify** - -```bash -uv run pytest tests/test_search.py -v -``` - -Expected: All tests pass - -**Step 3: Commit** - -```bash -git add tests/test_search.py -git commit -m "test: update test_search.py imports to use package structure" -``` - ---- - -### Task 16: Update test_brief.py Imports - -**Files:** -- Modify: `tests/test_brief.py` - -**Step 1: Update imports** - -```python -# Change from: -from server import netbox_get_objects, netbox_get_object_by_id - -# To: -from netbox_mcp_server.server import netbox_get_objects, netbox_get_object_by_id -``` - -**Step 2: Run tests to verify** - -```bash -uv run pytest tests/test_brief.py -v -``` - -Expected: All tests pass - -**Step 3: Commit** - -```bash -git add tests/test_brief.py -git commit -m "test: update test_brief.py imports to use package structure" -``` - ---- - -### Task 17: Run Full Test Suite - -**Files:** -- None (verification step) - -**Step 1: Run all tests** - -```bash -uv run pytest -v -``` - -Expected: All 43 tests pass - -**Step 2: Verify no import errors** - -Check output for any import-related failures. - -**Step 3: If all pass, ready for next phase** - -No commit needed - this is verification only. - ---- - -## Phase 4: CI/CD Workflows - -### Task 18: Create GitHub Workflows Directory - -**Files:** -- Create: `.github/workflows/` (directory) - -**Step 1: Create directory** - -```bash -mkdir -p .github/workflows -``` - -**Step 2: Commit** - -```bash -git add .github/ -git commit -m "chore: create .github/workflows directory for CI/CD" -``` - ---- - -### Task 19: Create Test Workflow - -**Files:** -- Create: `.github/workflows/test.yml` - -**Step 1: Write test workflow** - -```yaml -name: Test - -on: - pull_request: - push: - branches: [main] - -jobs: - test: - runs-on: ubuntu-latest - - services: - netbox: - image: netboxcommunity/netbox:latest - env: - SKIP_SUPERUSER: true - ports: - - 8000:8080 - - steps: - - uses: actions/checkout@v4 - - - name: Install uv - uses: astral-sh/setup-uv@v3 - with: - version: "latest" - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.13" - - - name: Install dependencies - run: uv sync - - - name: Run tests - run: uv run pytest -v - env: - NETBOX_URL: http://localhost:8000 - NETBOX_TOKEN: ${{ secrets.NETBOX_TOKEN }} -``` - -**Step 2: Validate YAML syntax** - -```bash -python3 -c "import yaml; yaml.safe_load(open('.github/workflows/test.yml'))" -``` - -Expected: No errors (silent success) - -**Step 3: Commit** - -```bash -git add .github/workflows/test.yml -git commit -m "ci: add test workflow for automated testing" -``` - ---- - -### Task 20: Create Release Workflow - -**Files:** -- Create: `.github/workflows/release.yml` - -**Step 1: Write release workflow** - -```yaml -name: Release - -permissions: - contents: write - issues: write - pull-requests: write - -on: - push: - branches: [main] - -jobs: - release: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Install uv - uses: astral-sh/setup-uv@v3 - with: - version: "latest" - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.13" - - - name: Install dependencies - run: uv sync - - - name: Python Semantic Release - uses: python-semantic-release/python-semantic-release@v9 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} -``` - -**Step 2: Validate YAML syntax** - -```bash -python3 -c "import yaml; yaml.safe_load(open('.github/workflows/release.yml'))" -``` - -Expected: No errors - -**Step 3: Commit** - -```bash -git add .github/workflows/release.yml -git commit -m "ci: add release workflow for automated semantic releases" -``` - ---- - -## Phase 5: Documentation Updates - -### Task 21: Update README.md - Breaking Change Notice - -**Files:** -- Modify: `README.md` - -**Step 1: Add breaking change notice after title** - -Find the title (line 1-2) and add immediately after: - -```markdown -> **⚠️ Breaking Change in v1.0.0**: The project structure has changed. -> If upgrading from v0.1.0, update your configuration: -> - Change `uv run server.py` to `uv run netbox-mcp-server` -> - Update Claude Desktop/Code configs to use `netbox-mcp-server` instead of `server.py` -> - Docker users: rebuild images with updated CMD -> - See [CHANGELOG.md](CHANGELOG.md) for full details -``` - -**Step 2: Verify formatting** - -```bash -head -20 README.md -``` - -**Step 3: Commit** - -```bash -git add README.md -git commit -m "docs: add breaking change notice to README for v1.0.0" -``` - ---- - -### Task 22: Update README.md - Running Locally Section - -**Files:** -- Modify: `README.md` - -**Step 1: Find and update "Running Locally" or similar section** - -Change all instances of: - -```bash -# Before -uv run server.py - -# After -uv run netbox-mcp-server -``` - -**Step 2: Verify all occurrences updated** - -```bash -grep "uv run server.py" README.md -``` - -Expected: No results (empty) - -**Step 3: Commit** - -```bash -git add README.md -git commit -m "docs: update README commands to use netbox-mcp-server" -``` - ---- - -### Task 23: Update README.md - Claude Desktop/Code Config - -**Files:** -- Modify: `README.md` - -**Step 1: Find Claude Desktop/Code configuration section** - -Update the JSON example: - -```json -{ - "mcpServers": { - "netbox": { - "command": "uv", - "args": [ - "--directory", - "/absolute/path/to/netbox-mcp-server", - "run", - "netbox-mcp-server" - ], - "env": { - "NETBOX_URL": "https://demo.netbox.dev/", - "NETBOX_TOKEN": "your-token-here" - } - } - } -} -``` - -**Step 2: Verify JSON syntax** - -```bash -python3 -c "import json; json.load(open('/dev/stdin'))" < README.md || echo "Check JSON manually" -``` - -**Step 3: Commit** - -```bash -git add README.md -git commit -m "docs: update Claude Desktop/Code config examples in README" -``` - ---- - -### Task 24: Update CLAUDE.md - Project Structure - -**Files:** -- Modify: `CLAUDE.md` - -**Step 1: Find "Project Structure" section** - -Replace with: - -```markdown -## Project Structure - -```text -. -├── src/ -│ └── netbox_mcp_server/ -│ ├── __init__.py # Package initialization with __version__ -│ ├── __main__.py # Entry point for module execution -│ ├── server.py # Main MCP server with tool definitions -│ ├── netbox_client.py # NetBox REST API client abstraction -│ ├── netbox_types.py # NetBox object type mappings -│ └── config.py # Settings and logging configuration -├── tests/ # Test suite -├── .github/workflows/ # CI/CD automation -├── pyproject.toml # Dependencies and project metadata -├── README.md # User-facing documentation -├── CHANGELOG.md # Auto-generated release notes -└── LICENSE # Apache 2.0 license -``` -``` - -**Step 2: Commit** - -```bash -git add CLAUDE.md -git commit -m "docs: update project structure in CLAUDE.md" -``` - ---- - -### Task 25: Update CLAUDE.md - Common Commands - -**Files:** -- Modify: `CLAUDE.md` - -**Step 1: Find "Common Commands" section** - -Update all command examples: - -```markdown -## Common Commands - -```bash -# Install dependencies (ONLY use uv, NEVER pip) -uv sync - -# Run the server locally (requires env vars) -NETBOX_URL=https://netbox.example.com/ NETBOX_TOKEN= uv run netbox-mcp-server - -# Alternative: module execution -uv run -m netbox_mcp_server - -# Add to Claude Code (for development/testing) -claude mcp add --transport stdio netbox \ - --env NETBOX_URL=https://netbox.example.com/ \ - --env NETBOX_TOKEN= \ - -- uv --directory /path/to/netbox-mcp-server run netbox-mcp-server -``` -``` - -**Step 2: Commit** - -```bash -git add CLAUDE.md -git commit -m "docs: update common commands in CLAUDE.md" -``` - ---- - -### Task 26: Update CLAUDE.md - Add Version Management Section - -**Files:** -- Modify: `CLAUDE.md` - -**Step 1: Add new section after "Development Philosophy"** - -```markdown -## Version Management - -This project uses [python-semantic-release](https://python-semantic-release.readthedocs.io/) for automated version management. Versions are automatically determined from commit messages following [Conventional Commits](https://www.conventionalcommits.org/). - -**Release triggers:** -- `feat:` commits trigger minor version bumps (1.0.0 → 1.1.0) -- `fix:` and `perf:` commits trigger patch version bumps (1.0.0 → 1.0.1) -- Commits with `BREAKING CHANGE:` in the body trigger major version bumps (1.0.0 → 2.0.0) -- `docs:`, `test:`, `chore:`, `ci:`, `refactor:` commits are logged but don't trigger releases - -**Workflow:** -- Merge to `main` automatically triggers release analysis -- If commits warrant a release, version is bumped and CHANGELOG updated -- GitHub Release is created with auto-generated release notes -``` - -**Step 2: Commit** - -```bash -git add CLAUDE.md -git commit -m "docs: add version management section to CLAUDE.md" -``` - ---- - -### Task 27: Update Dockerfile - -**Files:** -- Modify: `Dockerfile` - -**Step 1: Find CMD line and update** - -Change from: - -```dockerfile -CMD ["python", "-u", "server.py"] -``` - -To: - -```dockerfile -CMD ["netbox-mcp-server"] -``` - -**Step 2: Verify Docker syntax** - -```bash -docker build -t netbox-mcp-test . --dry-run 2>/dev/null || echo "Build check (ignore if Docker not running)" -``` - -**Step 3: Commit** - -```bash -git add Dockerfile -git commit -m "chore: update Dockerfile to use netbox-mcp-server command" -``` - ---- - -## Phase 6: Testing & Verification - -### Task 28: Verify Console Script Works - -**Files:** -- None (verification) - -**Step 1: Sync dependencies to ensure script installed** - -```bash -uv sync -``` - -**Step 2: Test console script execution (will fail without env vars - expected)** - -```bash -uv run netbox-mcp-server --help 2>&1 || echo "Expected: Shows help or env var error" -``` - -Expected: Either help output or error about missing NETBOX_URL/NETBOX_TOKEN - -**Step 3: Verify script is in uv environment** - -```bash -uv run which netbox-mcp-server -``` - -Expected: Path to script in .venv/bin/ - ---- - -### Task 29: Run Full Test Suite Again - -**Files:** -- None (verification) - -**Step 1: Run all tests** - -```bash -uv run pytest -v --tb=short -``` - -Expected: All 43 tests pass - -**Step 2: Check for any warnings** - -Review test output for deprecation warnings or issues. - -**Step 3: Generate coverage report** - -```bash -uv run pytest --cov=netbox_mcp_server --cov-report=term-missing -``` - ---- - -### Task 30: Verify Import Structure - -**Files:** -- None (verification) - -**Step 1: Test package imports in Python** - -```bash -uv run python -c "from netbox_mcp_server import NetBoxRestClient, NETBOX_OBJECT_TYPES, Settings; print('Imports successful')" -``` - -Expected: "Imports successful" - -**Step 2: Test version access** - -```bash -uv run python -c "from netbox_mcp_server import __version__; print(f'Version: {__version__}')" -``` - -Expected: "Version: 1.0.0" - -**Step 3: Test module execution** - -```bash -uv run python -m netbox_mcp_server --help 2>&1 || echo "Expected: help or env error" -``` - ---- - -### Task 31: Validate GitHub Actions YAML - -**Files:** -- None (verification) - -**Step 1: Validate test.yml** - -```bash -python3 -c "import yaml; print('test.yml valid') if yaml.safe_load(open('.github/workflows/test.yml')) else print('INVALID')" -``` - -Expected: "test.yml valid" - -**Step 2: Validate release.yml** - -```bash -python3 -c "import yaml; print('release.yml valid') if yaml.safe_load(open('.github/workflows/release.yml')) else print('INVALID')" -``` - -Expected: "release.yml valid" - ---- - -## Phase 7: Final Commit & Push - -### Task 32: Create Breaking Change Commit - -**Files:** -- All modified files - -**Step 1: Check git status** - -```bash -git status -``` - -Expected: Should show "nothing to commit, working tree clean" (all previous commits done) - -**Step 2: If any uncommitted files, stage them** - -```bash -git add . -``` - -**Step 3: Verify all changes are in git history** - -```bash -git log --oneline -20 -``` - -Expected: See all commits from previous tasks - ---- - -### Task 33: Push Feature Branch - -**Files:** -- None (git operation) - -**Step 1: Push feature branch to remote** - -```bash -git push -u origin feat/src-layout-and-semantic-release -``` - -Expected: Branch pushed successfully - -**Step 2: Note the branch URL** - -GitHub will provide a URL to create a pull request. - ---- - -### Task 34: Create Pull Request - -**Files:** -- None (GitHub operation) - -**Step 1: Open GitHub PR** - -Use the URL from previous step or manually create PR with: - -**Title:** -``` -feat!: restructure to src/ layout and add semantic release -``` - -**Body:** -```markdown -## Summary - -Restructures the project to use standard Python src/ layout and adds automated semantic release capabilities via python-semantic-release. - -## Breaking Changes - -⚠️ **This is a breaking change for users:** - -- **Command change**: `uv run server.py` → `uv run netbox-mcp-server` -- **Claude config change**: Update `args` to use `netbox-mcp-server` instead of `server.py` -- **Docker change**: Rebuild images (CMD updated) -- **Import changes** (contributors): `from server import` → `from netbox_mcp_server.server import` - -## Changes - -### Structure -- Moved all Python modules to `src/netbox_mcp_server/` -- Added package `__init__.py` with version export -- Added `__main__.py` for module execution support -- Added console script entry point in pyproject.toml - -### Automation -- Added python-semantic-release for automated versioning -- Added CI test workflow (pytest on PRs and main) -- Added release workflow (automatic on push to main) -- Configured conventional commits for version bumping - -### Code -- Updated all module imports to use package structure -- Added `main()` function to server.py -- Updated all 6 test files with new imports - -### Documentation -- Updated README.md with breaking change notice -- Updated all command examples in README.md -- Updated CLAUDE.md project structure -- Updated Dockerfile CMD -- Added version management documentation - -## Testing - -- ✅ All 43 tests passing -- ✅ Console script `netbox-mcp-server` works -- ✅ Module execution `python -m netbox_mcp_server` works -- ✅ Package imports validated -- ✅ CI workflows validated - -## Release Plan - -When merged, this will automatically trigger a v1.0.0 release via python-semantic-release due to the breaking change in the commit history. - -Closes #[issue number if applicable] -``` - ---- - -## Post-Merge Verification - -### Task 35: Monitor Release Workflow - -**After PR is merged to main:** - -**Step 1: Watch GitHub Actions** - -Navigate to: `https://github.com/netboxlabs/netbox-mcp-server/actions` - -**Step 2: Wait for "Release" workflow to complete** - -Expected: Green checkmark, workflow completes successfully - -**Step 3: Verify outputs** -- Version bumped to 1.0.0 in pyproject.toml -- CHANGELOG.md created and committed -- Git tag `v1.0.0` created -- GitHub Release created - ---- - -### Task 36: Verify GitHub Release - -**Files:** -- None (GitHub verification) - -**Step 1: Navigate to GitHub Releases page** - -`https://github.com/netboxlabs/netbox-mcp-server/releases` - -**Step 2: Verify v1.0.0 release exists** - -Check: -- Tag is `v1.0.0` -- Release notes include breaking change information -- Release notes auto-generated from commits - ---- - -### Task 37: Test v1.0.0 Locally - -**Files:** -- None (verification) - -**Step 1: Pull latest main** - -```bash -git checkout main -git pull -``` - -**Step 2: Verify version in files** - -```bash -grep "version = " pyproject.toml -grep "__version__ = " src/netbox_mcp_server/__init__.py -``` - -Expected: Both show "1.0.0" - -**Step 3: Verify CHANGELOG exists** - -```bash -cat CHANGELOG.md | head -20 -``` - -Expected: Shows v1.0.0 entry with breaking change notice - ---- - -### Task 38: Clean Up Worktree (Optional) - -**Files:** -- None (cleanup) - -**Step 1: Return to main working directory** - -```bash -cd ../.. # Back to main repo root -``` - -**Step 2: Remove worktree** - -```bash -git worktree remove .worktrees/feat-src-layout-and-semantic-release -``` - -**Step 3: Prune worktree references** - -```bash -git worktree prune -``` - ---- - -## Success Criteria Checklist - -- [ ] All 43 tests passing with new structure -- [ ] Console script `netbox-mcp-server` executes successfully -- [ ] Module execution `python -m netbox_mcp_server` works -- [ ] All imports use `netbox_mcp_server.*` package structure -- [ ] pyproject.toml has console script entry point -- [ ] pyproject.toml has semantic-release configuration -- [ ] python-semantic-release dev dependency added -- [ ] Test workflow `.github/workflows/test.yml` created -- [ ] Release workflow `.github/workflows/release.yml` created -- [ ] README.md updated with breaking change notice -- [ ] README.md commands updated to use `netbox-mcp-server` -- [ ] CLAUDE.md project structure updated -- [ ] CLAUDE.md commands updated -- [ ] CLAUDE.md has version management section -- [ ] Dockerfile CMD updated to use `netbox-mcp-server` -- [ ] Feature branch pushed to GitHub -- [ ] Pull request created with breaking change notice -- [ ] PR merged to main -- [ ] Release workflow executed successfully -- [ ] GitHub Release v1.0.0 created -- [ ] CHANGELOG.md generated and committed -- [ ] Version 1.0.0 in pyproject.toml and __init__.py - ---- - -## Rollback Plan (If Needed) - -If issues arise after merge: - -1. Create hotfix branch from commit before merge -2. Revert breaking changes -3. Create patch release -4. Fix issues in separate PR - -**Prevention:** -- All tasks have verification steps -- Tests run before merge -- Can test PR branch before merging - ---- - -## Notes for Implementer - -- **DRY**: Each file is moved/modified once -- **YAGNI**: No extra features, just what's needed for refactoring -- **TDD**: Tests updated immediately after code changes -- **Commits**: Frequent, atomic commits for easy review -- **Verification**: Tests run after each import change - -**Estimated total time**: 2-3 hours for careful implementation - -**Key risks**: -1. Import errors - mitigated by testing after each change -2. CI workflow misconfiguration - mitigated by YAML validation -3. Semantic release issues - mitigated by dry-run testing - -**Support**: -- Design document: `.plans/2025-10-28-src-layout-semantic-release-design.md` -- Python-semantic-release docs: https://python-semantic-release.readthedocs.io/ -- Conventional commits: https://www.conventionalcommits.org/ From 7eb16057a5c136ec4dab29931cae85aaf1b7e2a8 Mon Sep 17 00:00:00 2001 From: Anton BL Date: Wed, 29 Oct 2025 11:40:15 -0400 Subject: [PATCH 31/33] Update .github/workflows/release.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 491dad7..51116c0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,6 +33,6 @@ jobs: run: uv sync - name: Python Semantic Release - uses: python-semantic-release/python-semantic-release@v9 + uses: python-semantic-release/python-semantic-release@v10 with: github_token: ${{ secrets.GITHUB_TOKEN }} From c399c1b71c9d09bc7848c7d1d01342dae721b721 Mon Sep 17 00:00:00 2001 From: Anton BL Date: Wed, 29 Oct 2025 11:40:22 -0400 Subject: [PATCH 32/33] Update src/netbox_mcp_server/__init__.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/netbox_mcp_server/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/netbox_mcp_server/__init__.py b/src/netbox_mcp_server/__init__.py index 55ee97d..6a277b9 100644 --- a/src/netbox_mcp_server/__init__.py +++ b/src/netbox_mcp_server/__init__.py @@ -1,6 +1,6 @@ """NetBox MCP Server - Read-only MCP server for NetBox infrastructure data.""" -__version__ = "1.0.0" # Auto-managed by semantic-release +__version__ = "0.1.0" # Auto-managed by semantic-release __all__ = ["NetBoxRestClient", "NETBOX_OBJECT_TYPES", "Settings"] From 7e10b68a3ec1e3d722edd55f83f90c9f110d8008 Mon Sep 17 00:00:00 2001 From: Anton Bubna-Litic Date: Wed, 29 Oct 2025 11:58:38 -0400 Subject: [PATCH 33/33] fix: updated to manual releases --- .github/workflows/.gitkeep | 0 .github/workflows/release.yml | 3 +-- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 .github/workflows/.gitkeep diff --git a/.github/workflows/.gitkeep b/.github/workflows/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 51116c0..5e1c2cd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,8 +6,7 @@ permissions: pull-requests: write on: - push: - branches: [main] + workflow_dispatch: jobs: release: