Skip to content

Commit d5c4287

Browse files
committed
Reduce response size by omitting bulky fields (such as logo)
1 parent b56f669 commit d5c4287

4 files changed

Lines changed: 63 additions & 9 deletions

File tree

src/netlicensing_mcp/tools/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from netlicensing_mcp.tools import (
44
bundles,
5+
helpers,
56
license_templates,
67
licensees,
78
licenses,
@@ -15,6 +16,7 @@
1516

1617
__all__ = [
1718
"bundles",
19+
"helpers",
1820
"license_templates",
1921
"licensees",
2022
"licenses",

src/netlicensing_mcp/tools/bundles.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@
33
from __future__ import annotations
44

55
from netlicensing_mcp.client import nl_delete, nl_get, nl_post
6+
from netlicensing_mcp.tools.helpers import strip_output_fields
67

78

89
async def list_bundles() -> dict:
910
"""List all bundles in the account."""
10-
return await nl_get("/bundle")
11+
return strip_output_fields(await nl_get("/bundle"))
1112

1213

1314
async def get_bundle(bundle_number: str) -> dict:
1415
"""Get a single bundle by its number."""
15-
return await nl_get(f"/bundle/{bundle_number}")
16+
return strip_output_fields(await nl_get(f"/bundle/{bundle_number}"))
1617

1718

1819
async def create_bundle(
@@ -30,6 +31,9 @@ async def create_bundle(
3031
license_template_numbers: list of license template numbers included in the bundle.
3132
price: bundle price (optional).
3233
currency: ISO 4217 currency code (e.g. EUR, USD).
34+
35+
Note: 'logo' is intentionally not passed — omitting it avoids accidentally
36+
clearing an existing bundle logo on the NetLicensing server.
3337
"""
3438
data: dict[str, str] = {
3539
"number": number,
@@ -44,7 +48,7 @@ async def create_bundle(
4448
data["price"] = str(price)
4549
if currency:
4650
data["currency"] = currency
47-
return await nl_post("/bundle", data)
51+
return strip_output_fields(await nl_post("/bundle", data))
4852

4953

5054
async def update_bundle(
@@ -56,7 +60,11 @@ async def update_bundle(
5660
currency: str | None = None,
5761
description: str | None = None,
5862
) -> dict:
59-
"""Update fields of an existing bundle."""
63+
"""Update fields of an existing bundle.
64+
65+
Note: 'logo' is intentionally not passed — omitting it preserves the
66+
existing bundle logo on the NetLicensing server.
67+
"""
6068
data: dict[str, str] = {}
6169
if name is not None:
6270
data["name"] = name
@@ -70,7 +78,7 @@ async def update_bundle(
7078
data["currency"] = currency
7179
if description is not None:
7280
data["description"] = description
73-
return await nl_post(f"/bundle/{bundle_number}", data)
81+
return strip_output_fields(await nl_post(f"/bundle/{bundle_number}", data))
7482

7583

7684
async def delete_bundle(bundle_number: str, force_cascade: bool = False) -> str:
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""Shared helpers for MCP tool output post-processing."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any
6+
7+
# Fields that carry large binary/base64 blobs and should be stripped from all
8+
# tool outputs to keep MCP responses concise. They are intentionally NOT sent
9+
# in create/update requests either — omitting them preserves the existing value
10+
# on the NetLicensing server side.
11+
STRIP_OUTPUT_FIELDS: frozenset[str] = frozenset({"logo"})
12+
13+
14+
def strip_output_fields(data: Any, fields: frozenset[str] = STRIP_OUTPUT_FIELDS) -> Any:
15+
"""Recursively remove large/binary fields from a NetLicensing API response.
16+
17+
Handles two common patterns:
18+
- Plain dict keys (e.g. ``{"logo": "data:image/png;base64,...", ...}``)
19+
- NetLicensing *property* arrays (``[{"name": "logo", "value": "..."}, ...]``)
20+
21+
Any field whose name appears in *fields* is dropped at every nesting level.
22+
"""
23+
if isinstance(data, list):
24+
return [strip_output_fields(item, fields) for item in data]
25+
if isinstance(data, dict):
26+
# 1. Drop matching top-level keys and recurse into kept values.
27+
result = {k: strip_output_fields(v, fields) for k, v in data.items() if k not in fields}
28+
# 2. If this dict contains a NetLicensing "property" list, also filter
29+
# entries whose "name" field matches.
30+
if "property" in result and isinstance(result["property"], list):
31+
result["property"] = [
32+
p
33+
for p in result["property"]
34+
if not (isinstance(p, dict) and p.get("name") in fields)
35+
]
36+
return result
37+
return data

src/netlicensing_mcp/tools/products.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
from netlicensing_mcp.client import nl_delete, nl_get, nl_post
6+
from netlicensing_mcp.tools.helpers import strip_output_fields
67

78

89
async def list_products(filter_str: str | None = None) -> dict:
@@ -13,12 +14,12 @@ async def list_products(filter_str: str | None = None) -> dict:
1314
params: dict[str, str] = {}
1415
if filter_str:
1516
params["filter"] = filter_str
16-
return await nl_get("/product", params or None)
17+
return strip_output_fields(await nl_get("/product", params or None))
1718

1819

1920
async def get_product(product_number: str) -> dict:
2021
"""Get a single product by its number."""
21-
return await nl_get(f"/product/{product_number}")
22+
return strip_output_fields(await nl_get(f"/product/{product_number}"))
2223

2324

2425
async def create_product(
@@ -38,6 +39,9 @@ async def create_product(
3839
vat_mode: GROSS | NET
3940
licensee_auto_create: if true, non-existing licensees are created on first validation.
4041
licensee_secret_mode: DISABLED | PREDEFINED | CLIENT
42+
43+
Note: 'logo' is intentionally not passed — omitting it avoids accidentally
44+
clearing an existing product logo on the NetLicensing server.
4145
"""
4246
data: dict[str, str] = {
4347
"number": number,
@@ -55,7 +59,7 @@ async def create_product(
5559
data["vatMode"] = vat_mode
5660
if licensee_secret_mode:
5761
data["licenseeSecretMode"] = licensee_secret_mode
58-
return await nl_post("/product", data)
62+
return strip_output_fields(await nl_post("/product", data))
5963

6064

6165
async def update_product(
@@ -72,6 +76,9 @@ async def update_product(
7276
"""Update fields of an existing product.
7377
7478
licensee_secret_mode: DISABLED | PREDEFINED | CLIENT
79+
80+
Note: 'logo' is intentionally not passed — omitting it preserves the
81+
existing product logo on the NetLicensing server.
7582
"""
7683
data: dict[str, str] = {}
7784
if name is not None:
@@ -90,7 +97,7 @@ async def update_product(
9097
data["vatMode"] = vat_mode
9198
if licensee_secret_mode is not None:
9299
data["licenseeSecretMode"] = licensee_secret_mode
93-
return await nl_post(f"/product/{product_number}", data)
100+
return strip_output_fields(await nl_post(f"/product/{product_number}", data))
94101

95102

96103
async def delete_product(product_number: str, force_cascade: bool = False) -> str:

0 commit comments

Comments
 (0)