mcp-simplr 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-simplr
3
+ Version: 0.1.0
4
+ Summary: Simplr client library for MCP services
5
+ Author-email: contact@mcp-simplr.au
6
+ License: MIT
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: httpx>=0.27
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest>=8.0; extra == "dev"
12
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
13
+ Requires-Dist: pytest-mock>=3.12; extra == "dev"
14
+ Requires-Dist: respx>=0.21; extra == "dev"
15
+
16
+ # mcp-simplr
17
+
18
+ Python client library for [MCP Simplr](https://mcp-simplr.au) — payments, emailing, and auth for MCP services.
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ pip install mcp-simplr
24
+ ```
25
+
26
+ ## Payments
27
+
28
+ Customers register once on the platform and receive a `customer_token`. MCP owners accept this token from their users and pass it to `charge()` — no customer registration code needed on your end.
29
+
30
+ ### Token billing — charge per output token
31
+
32
+ Best for AI tools where cost scales with usage.
33
+
34
+ ```python
35
+ from mcp_simplr import MCPPayments, PaymentConfig, PaymentModel
36
+
37
+ payments = MCPPayments(PaymentConfig(
38
+ api_key="simplr_owner_...",
39
+ payment_model=PaymentModel.TOKEN,
40
+ price_per_token=0.000_5, # AUD per output token
41
+ currency="aud",
42
+ environment="production",
43
+ ))
44
+
45
+ # Charge explicitly
46
+ await payments.charge(customer_token, tokens=500)
47
+
48
+ # Or use the decorator — counts tokens and charges automatically
49
+ @payments.track_usage(customer_id="<customer-token>")
50
+ async def my_tool(query: str) -> str:
51
+ return await run_model(query)
52
+
53
+ # Dynamic customer token from request payload
54
+ @payments.track_usage(customer_id_key="customer_token")
55
+ async def my_tool(query: str, customer_token: str) -> str:
56
+ return await run_model(query)
57
+ ```
58
+
59
+ ### Fixed billing — charge a set amount per call
60
+
61
+ Best for tools with a predictable cost per request.
62
+
63
+ ```python
64
+ from mcp_simplr import MCPPayments, PaymentConfig, PaymentModel
65
+
66
+ payments = MCPPayments(PaymentConfig(
67
+ api_key="simplr_owner_...",
68
+ payment_model=PaymentModel.FIXED,
69
+ currency="aud",
70
+ environment="production",
71
+ ))
72
+
73
+ await payments.charge(
74
+ customer_token,
75
+ amount_cents=500, # AUD $5.00
76
+ description="Property report",
77
+ )
78
+ ```
79
+
80
+ ### Recurring billing — subscription plans
81
+
82
+ Best for services with ongoing access (monthly/yearly).
83
+
84
+ ```python
85
+ from mcp_simplr import MCPPayments, PaymentConfig, PaymentModel, PlanConfig, BillingInterval
86
+
87
+ payments = MCPPayments(PaymentConfig(
88
+ api_key="simplr_owner_...",
89
+ payment_model=PaymentModel.RECURRING,
90
+ currency="aud",
91
+ environment="production",
92
+ plans=[
93
+ PlanConfig(id="basic", name="Basic", amount=999, interval=BillingInterval.MONTH),
94
+ PlanConfig(id="pro", name="Pro", amount=2999, interval=BillingInterval.MONTH),
95
+ ],
96
+ ))
97
+
98
+ # Subscribe a customer to a plan
99
+ await payments.charge(customer_token, plan_id="pro")
100
+
101
+ # Cancel a subscription
102
+ payments.cancel_subscription(customer_token, plan_id="pro")
103
+
104
+ # List available plans
105
+ plans = payments.list_plans()
106
+ ```
107
+
108
+ ### Charge history
109
+
110
+ ```python
111
+ history = await payments.get_charges(customer_token)
112
+ history = await payments.get_charges(customer_token, from_date="2026-06-01", to_date="2026-06-30")
113
+ ```
114
+
115
+ ### Testing
116
+
117
+ Use `MCPPayments.TEST_CUSTOMER_TOKEN` in sandbox mode to test the full charge flow without a real customer:
118
+
119
+ ```python
120
+ await payments.charge(MCPPayments.TEST_CUSTOMER_TOKEN, tokens=500)
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Emailing
126
+
127
+ Send emails to your users from your branded `slug@mcp-simplr.au` address. Subscribe to the email service on the platform website first, then use this client to send.
128
+
129
+ This is a **no-reply, unidirectional** service — emails are sent from your address but replies from users are not forwarded back to you. It is intended for outbound notifications, reports, and updates only.
130
+
131
+ ```python
132
+ from mcp_simplr import MCPEmail, EmailServiceConfig
133
+
134
+ email = MCPEmail(EmailServiceConfig(
135
+ api_key="simplr_owner_...",
136
+ service_slug="property-ai", # → property-ai@mcp-simplr.au
137
+ environment="production",
138
+ ))
139
+
140
+ await email.send(
141
+ to="customer@example.com",
142
+ subject="Your property report is ready",
143
+ body="Hi Sarah, your report for 123 Main St is attached.",
144
+ )
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Auth
150
+
151
+ Verify that a customer token is valid before your MCP tool runs.
152
+
153
+ ```python
154
+ from mcp_simplr import MCPAuth, AuthConfig
155
+
156
+ auth = MCPAuth(AuthConfig(
157
+ api_key="simplr_owner_...",
158
+ service_slug="property-ai",
159
+ environment="production",
160
+ ))
161
+
162
+ # Decorator — verifies token before the function runs
163
+ @auth.require(customer_token_key="customer_token")
164
+ async def search_properties(query: str, customer_token: str) -> str:
165
+ return await do_search(query)
166
+
167
+ # Or verify explicitly — raises AuthenticationError on failure
168
+ customer = await auth.verify(customer_token)
169
+ # {"customer_id": "...", "email": "...", "name": "..."}
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Support
175
+
176
+ Contact us at [contact@mcp-simplr.au](mailto:contact@mcp-simplr.au)
@@ -0,0 +1,161 @@
1
+ # mcp-simplr
2
+
3
+ Python client library for [MCP Simplr](https://mcp-simplr.au) — payments, emailing, and auth for MCP services.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install mcp-simplr
9
+ ```
10
+
11
+ ## Payments
12
+
13
+ Customers register once on the platform and receive a `customer_token`. MCP owners accept this token from their users and pass it to `charge()` — no customer registration code needed on your end.
14
+
15
+ ### Token billing — charge per output token
16
+
17
+ Best for AI tools where cost scales with usage.
18
+
19
+ ```python
20
+ from mcp_simplr import MCPPayments, PaymentConfig, PaymentModel
21
+
22
+ payments = MCPPayments(PaymentConfig(
23
+ api_key="simplr_owner_...",
24
+ payment_model=PaymentModel.TOKEN,
25
+ price_per_token=0.000_5, # AUD per output token
26
+ currency="aud",
27
+ environment="production",
28
+ ))
29
+
30
+ # Charge explicitly
31
+ await payments.charge(customer_token, tokens=500)
32
+
33
+ # Or use the decorator — counts tokens and charges automatically
34
+ @payments.track_usage(customer_id="<customer-token>")
35
+ async def my_tool(query: str) -> str:
36
+ return await run_model(query)
37
+
38
+ # Dynamic customer token from request payload
39
+ @payments.track_usage(customer_id_key="customer_token")
40
+ async def my_tool(query: str, customer_token: str) -> str:
41
+ return await run_model(query)
42
+ ```
43
+
44
+ ### Fixed billing — charge a set amount per call
45
+
46
+ Best for tools with a predictable cost per request.
47
+
48
+ ```python
49
+ from mcp_simplr import MCPPayments, PaymentConfig, PaymentModel
50
+
51
+ payments = MCPPayments(PaymentConfig(
52
+ api_key="simplr_owner_...",
53
+ payment_model=PaymentModel.FIXED,
54
+ currency="aud",
55
+ environment="production",
56
+ ))
57
+
58
+ await payments.charge(
59
+ customer_token,
60
+ amount_cents=500, # AUD $5.00
61
+ description="Property report",
62
+ )
63
+ ```
64
+
65
+ ### Recurring billing — subscription plans
66
+
67
+ Best for services with ongoing access (monthly/yearly).
68
+
69
+ ```python
70
+ from mcp_simplr import MCPPayments, PaymentConfig, PaymentModel, PlanConfig, BillingInterval
71
+
72
+ payments = MCPPayments(PaymentConfig(
73
+ api_key="simplr_owner_...",
74
+ payment_model=PaymentModel.RECURRING,
75
+ currency="aud",
76
+ environment="production",
77
+ plans=[
78
+ PlanConfig(id="basic", name="Basic", amount=999, interval=BillingInterval.MONTH),
79
+ PlanConfig(id="pro", name="Pro", amount=2999, interval=BillingInterval.MONTH),
80
+ ],
81
+ ))
82
+
83
+ # Subscribe a customer to a plan
84
+ await payments.charge(customer_token, plan_id="pro")
85
+
86
+ # Cancel a subscription
87
+ payments.cancel_subscription(customer_token, plan_id="pro")
88
+
89
+ # List available plans
90
+ plans = payments.list_plans()
91
+ ```
92
+
93
+ ### Charge history
94
+
95
+ ```python
96
+ history = await payments.get_charges(customer_token)
97
+ history = await payments.get_charges(customer_token, from_date="2026-06-01", to_date="2026-06-30")
98
+ ```
99
+
100
+ ### Testing
101
+
102
+ Use `MCPPayments.TEST_CUSTOMER_TOKEN` in sandbox mode to test the full charge flow without a real customer:
103
+
104
+ ```python
105
+ await payments.charge(MCPPayments.TEST_CUSTOMER_TOKEN, tokens=500)
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Emailing
111
+
112
+ Send emails to your users from your branded `slug@mcp-simplr.au` address. Subscribe to the email service on the platform website first, then use this client to send.
113
+
114
+ This is a **no-reply, unidirectional** service — emails are sent from your address but replies from users are not forwarded back to you. It is intended for outbound notifications, reports, and updates only.
115
+
116
+ ```python
117
+ from mcp_simplr import MCPEmail, EmailServiceConfig
118
+
119
+ email = MCPEmail(EmailServiceConfig(
120
+ api_key="simplr_owner_...",
121
+ service_slug="property-ai", # → property-ai@mcp-simplr.au
122
+ environment="production",
123
+ ))
124
+
125
+ await email.send(
126
+ to="customer@example.com",
127
+ subject="Your property report is ready",
128
+ body="Hi Sarah, your report for 123 Main St is attached.",
129
+ )
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Auth
135
+
136
+ Verify that a customer token is valid before your MCP tool runs.
137
+
138
+ ```python
139
+ from mcp_simplr import MCPAuth, AuthConfig
140
+
141
+ auth = MCPAuth(AuthConfig(
142
+ api_key="simplr_owner_...",
143
+ service_slug="property-ai",
144
+ environment="production",
145
+ ))
146
+
147
+ # Decorator — verifies token before the function runs
148
+ @auth.require(customer_token_key="customer_token")
149
+ async def search_properties(query: str, customer_token: str) -> str:
150
+ return await do_search(query)
151
+
152
+ # Or verify explicitly — raises AuthenticationError on failure
153
+ customer = await auth.verify(customer_token)
154
+ # {"customer_id": "...", "email": "...", "name": "..."}
155
+ ```
156
+
157
+ ---
158
+
159
+ ## Support
160
+
161
+ Contact us at [contact@mcp-simplr.au](mailto:contact@mcp-simplr.au)
@@ -0,0 +1,50 @@
1
+ from mcp_simplr.payments.client import MCPPayments
2
+ from mcp_simplr.email.client import MCPEmail
3
+ from mcp_simplr.auth.client import MCPAuth
4
+ from mcp_simplr.config import (
5
+ PaymentConfig,
6
+ PlanConfig,
7
+ EmailServiceConfig,
8
+ AuthConfig,
9
+ PaymentModel,
10
+ Environment,
11
+ Currency,
12
+ BillingInterval,
13
+ )
14
+ from mcp_simplr.exceptions import (
15
+ MCPPaymentError,
16
+ ConfigurationError,
17
+ CustomerNotFoundError,
18
+ PaymentFailedError,
19
+ InsufficientPaymentMethodError,
20
+ PlanNotFoundError,
21
+ MCPAuthError,
22
+ AuthenticationError,
23
+ )
24
+
25
+ __all__ = [
26
+ # Payments
27
+ "MCPPayments",
28
+ "PaymentConfig",
29
+ "PlanConfig",
30
+ # Enums
31
+ "PaymentModel",
32
+ "Environment",
33
+ "Currency",
34
+ "BillingInterval",
35
+ # Email service
36
+ "MCPEmail",
37
+ "EmailServiceConfig",
38
+ # Auth + rate limit
39
+ "MCPAuth",
40
+ "AuthConfig",
41
+ # Exceptions
42
+ "MCPPaymentError",
43
+ "ConfigurationError",
44
+ "CustomerNotFoundError",
45
+ "PaymentFailedError",
46
+ "InsufficientPaymentMethodError",
47
+ "PlanNotFoundError",
48
+ "MCPAuthError",
49
+ "AuthenticationError",
50
+ ]
File without changes
@@ -0,0 +1,55 @@
1
+ import httpx
2
+
3
+ from mcp_simplr.exceptions import MCPPaymentError, PaymentFailedError
4
+
5
+
6
+ class APIClient:
7
+ """
8
+ HTTP client that talks to the mcp-payments backend.
9
+ Uses sync httpx for setup calls (register_customer, subscribe)
10
+ and async httpx for per-request charges so the event loop is never blocked.
11
+ """
12
+
13
+ def __init__(self, api_key: str, base_url: str, environment: str = "sandbox"):
14
+ self._headers = {
15
+ "Authorization": f"Bearer {api_key}",
16
+ "X-Stripe-Environment": environment,
17
+ }
18
+ self._base_url = base_url.rstrip("/")
19
+
20
+ # ------------------------------------------------------------------
21
+ # Sync — used at server startup (register_customer, subscribe, cancel)
22
+ # ------------------------------------------------------------------
23
+
24
+ def post(self, path: str, body: dict) -> dict:
25
+ with httpx.Client(headers=self._headers, timeout=30) as client:
26
+ return self._handle(client.post(f"{self._base_url}{path}", json=body))
27
+
28
+ def delete(self, path: str) -> dict:
29
+ with httpx.Client(headers=self._headers, timeout=30) as client:
30
+ return self._handle(client.delete(f"{self._base_url}{path}"))
31
+
32
+ # ------------------------------------------------------------------
33
+ # Async — used on every MCP request (charge)
34
+ # ------------------------------------------------------------------
35
+
36
+ async def apost(self, path: str, body: dict) -> dict:
37
+ async with httpx.AsyncClient(headers=self._headers, timeout=30) as client:
38
+ return self._handle(await client.post(f"{self._base_url}{path}", json=body))
39
+
40
+ async def aget(self, path: str, params: dict | None = None) -> dict:
41
+ async with httpx.AsyncClient(headers=self._headers, timeout=30) as client:
42
+ return self._handle(await client.get(f"{self._base_url}{path}", params=params))
43
+
44
+ # ------------------------------------------------------------------
45
+
46
+ @staticmethod
47
+ def _handle(response: httpx.Response) -> dict:
48
+ try:
49
+ response.raise_for_status()
50
+ return response.json()
51
+ except httpx.HTTPStatusError as e:
52
+ detail = e.response.json().get("detail", str(e)) if e.response.content else str(e)
53
+ raise PaymentFailedError(detail)
54
+ except httpx.RequestError as e:
55
+ raise MCPPaymentError(f"Could not reach mcp-payments backend: {e}")
@@ -0,0 +1,4 @@
1
+ from mcp_simplr.auth.client import MCPAuth
2
+ from mcp_simplr.config import AuthConfig
3
+
4
+ __all__ = ["MCPAuth", "AuthConfig"]
@@ -0,0 +1,84 @@
1
+ import functools
2
+
3
+ import httpx
4
+
5
+ from mcp_simplr.config import AuthConfig
6
+ from mcp_simplr.exceptions import AuthenticationError
7
+
8
+
9
+ class MCPAuth:
10
+ """
11
+ Authentication client for MCP owners.
12
+
13
+ Verifies that a customer_token is valid before your MCP tool runs.
14
+
15
+ Subscription is managed via the platform website — not through this library.
16
+ Once subscribed, use verify() or the @require decorator in every tool.
17
+
18
+ Usage:
19
+ from mcp_simplr import MCPAuth, AuthConfig
20
+
21
+ auth = MCPAuth(AuthConfig(
22
+ api_key="simplr_owner_xxx",
23
+ service_slug="property-ai",
24
+ ))
25
+
26
+ # Decorator — auto-verifies before the function runs
27
+ @auth.require(customer_token_key="customer_token")
28
+ async def my_tool(query: str, customer_token: str) -> str:
29
+ return await do_work(query)
30
+
31
+ # Or explicit — raises AuthenticationError on failure
32
+ customer = await auth.verify(customer_token)
33
+ # customer = {"customer_id": "...", "email": "...", "name": "..."}
34
+ """
35
+
36
+ def __init__(self, config: AuthConfig) -> None:
37
+ self._config = config
38
+ self._headers = {
39
+ "Authorization": f"Bearer {config.api_key}",
40
+ "Content-Type": "application/json",
41
+ }
42
+
43
+ async def verify(self, customer_token: str) -> dict:
44
+ """
45
+ Verify a customer token.
46
+
47
+ Returns {"customer_id": str, "email": str, "name": str | None} on success.
48
+ Raises AuthenticationError if the token is invalid.
49
+ """
50
+ async with httpx.AsyncClient(timeout=10) as client:
51
+ res = await client.post(
52
+ f"{self._config.api_base}/v1/auth-service/verify",
53
+ headers=self._headers,
54
+ json={
55
+ "service_slug": self._config.service_slug,
56
+ "customer_token": customer_token,
57
+ },
58
+ )
59
+
60
+ if res.status_code == 401:
61
+ raise AuthenticationError(res.json().get("detail", "Invalid customer token"))
62
+ res.raise_for_status()
63
+ return res.json()
64
+
65
+ def require(self, customer_token_key: str = "customer_token"):
66
+ """
67
+ Decorator that verifies the customer token before the function runs.
68
+
69
+ The decorated function must accept the token as a keyword argument
70
+ matching customer_token_key (default: "customer_token").
71
+ """
72
+ def decorator(fn):
73
+ @functools.wraps(fn)
74
+ async def wrapper(*args, **kwargs):
75
+ token = kwargs.get(customer_token_key)
76
+ if not token:
77
+ raise AuthenticationError(
78
+ f"Missing '{customer_token_key}' argument — "
79
+ "pass the customer's simplr_ token to this tool."
80
+ )
81
+ await self.verify(token)
82
+ return await fn(*args, **kwargs)
83
+ return wrapper
84
+ return decorator
File without changes
@@ -0,0 +1,32 @@
1
+ _STRIPE_MIN_CENTS = 50
2
+
3
+
4
+ class FixedBilling:
5
+ """
6
+ One-time fixed-price billing. The MCP owner calculates the cost and calls
7
+ charge() explicitly. customer_id is the platform UUID from /v1/customers/register.
8
+ """
9
+
10
+ def __init__(self, config, api_client):
11
+ self.config = config
12
+ self._api = api_client
13
+
14
+ async def charge(self, customer_id: str, amount_cents: int, description: str = "") -> dict | None:
15
+ """
16
+ Charge the customer a fixed amount calculated by the MCP owner.
17
+ Returns the charge result dict, or None if below Stripe's minimum.
18
+ """
19
+ if amount_cents < _STRIPE_MIN_CENTS:
20
+ return None
21
+
22
+ return await self._api.apost(
23
+ "/v1/charge",
24
+ {
25
+ "customer_token": customer_id,
26
+ "amount_cents": amount_cents,
27
+ "currency": self.config.currency,
28
+ "description": description,
29
+ "service_name": self.config.service_name,
30
+ "service_description": self.config.service_description,
31
+ },
32
+ )
@@ -0,0 +1,70 @@
1
+ from mcp_simplr.exceptions import PlanNotFoundError
2
+
3
+
4
+ class RecurringBilling:
5
+ """
6
+ Recurring subscription billing. The MCP owner defines plans and Stripe handles
7
+ automatic charges on each billing cycle. customer_id is the platform UUID
8
+ from /v1/customers/register.
9
+ """
10
+
11
+ def __init__(self, config, api_client):
12
+ self.config = config
13
+ self._api = api_client
14
+ # customer_id -> {plan_id, stripe_sub_id} — tracked in memory for cancel()
15
+ self._subscriptions: dict[str, dict] = {}
16
+
17
+ async def subscribe(self, customer_id: str, plan_id: str) -> str:
18
+ """
19
+ Subscribe a customer to a recurring plan via the backend.
20
+ Returns the Stripe Subscription ID.
21
+ """
22
+ plan = self._resolve_plan(plan_id)
23
+ result = await self._api.apost(
24
+ "/v1/subscriptions",
25
+ {
26
+ "customer_token": customer_id,
27
+ "plan": {
28
+ "id": plan.id,
29
+ "name": plan.name,
30
+ "amount": plan.amount,
31
+ "currency": self.config.currency,
32
+ "interval": plan.interval,
33
+ "description": plan.description,
34
+ },
35
+ },
36
+ )
37
+ self._subscriptions[customer_id] = {
38
+ "plan_id": plan_id,
39
+ "stripe_sub_id": result["stripe_subscription_id"],
40
+ }
41
+ return result["stripe_subscription_id"]
42
+
43
+ def cancel(self, customer_id: str, plan_id: str) -> None:
44
+ sub = self._subscriptions.get(customer_id)
45
+ if not sub or sub["plan_id"] != plan_id:
46
+ raise KeyError(
47
+ f"No active subscription found for customer '{customer_id}' on plan '{plan_id}'. "
48
+ "subscribe() must be called in the same process before cancel()."
49
+ )
50
+ self._api.delete(f"/v1/subscriptions/{sub['stripe_sub_id']}")
51
+ del self._subscriptions[customer_id]
52
+
53
+ def list_plans(self) -> list[dict]:
54
+ return [
55
+ {
56
+ "id": p.id,
57
+ "name": p.name,
58
+ "amount": p.amount,
59
+ "currency": self.config.currency.upper(),
60
+ "interval": p.interval,
61
+ "description": p.description,
62
+ }
63
+ for p in self.config.plans
64
+ ]
65
+
66
+ def _resolve_plan(self, plan_id: str):
67
+ for plan in self.config.plans:
68
+ if plan.id == plan_id:
69
+ return plan
70
+ raise PlanNotFoundError(f"Plan '{plan_id}' not found in config")
@@ -0,0 +1,34 @@
1
+ _STRIPE_MIN_CENTS = 50
2
+
3
+
4
+ class TokenBilling:
5
+ """
6
+ Per-request billing charged by output tokens. Every tracked MCP call immediately
7
+ fires a charge through the platform backend using the customer's platform UUID.
8
+ """
9
+
10
+ def __init__(self, config, api_client):
11
+ self.config = config
12
+ self._api = api_client
13
+
14
+ async def charge_for_request(self, customer_id: str, tokens: int) -> dict | None:
15
+ """
16
+ Calculate cost and immediately charge the customer via the backend.
17
+ customer_id is the platform UUID from /v1/customers/register.
18
+ Returns the charge result dict, or None if below Stripe's minimum.
19
+ """
20
+ amount_cents = int(tokens * self.config.price_per_token * 100)
21
+ if amount_cents < _STRIPE_MIN_CENTS:
22
+ return None
23
+
24
+ return await self._api.apost(
25
+ "/v1/charge",
26
+ {
27
+ "customer_token": customer_id,
28
+ "amount_cents": amount_cents,
29
+ "currency": self.config.currency,
30
+ "description": f"MCP request — {tokens:,} output tokens",
31
+ "service_name": self.config.service_name,
32
+ "service_description": self.config.service_description,
33
+ },
34
+ )