alix-graphql-mcp-knowledge-base-tool 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,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: alix-graphql-mcp-knowledge-base-tool
3
+ Version: 0.1.0
4
+ Summary: Knowledge Base MCP tool for Alix GraphQL. Fetches institution marshaling plans.
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: mcp>=1.9.0
7
+ Requires-Dist: httpx>=0.27
8
+ Provides-Extra: dev
9
+ Requires-Dist: build; extra == "dev"
10
+ Requires-Dist: twine; extra == "dev"
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "alix-graphql-mcp-knowledge-base-tool"
7
+ version = "0.1.0"
8
+ description = "Knowledge Base MCP tool for Alix GraphQL. Fetches institution marshaling plans."
9
+ requires-python = ">=3.11"
10
+ dependencies = [
11
+ "mcp>=1.9.0",
12
+ "httpx>=0.27",
13
+ ]
14
+
15
+ [project.optional-dependencies]
16
+ dev = ["build", "twine"]
17
+
18
+ [project.scripts]
19
+ alix-graphql-mcp-knowledge-base-tool = "alix_graphql_mcp_knowledge_base_tool.mcp_server:main"
20
+
21
+ [tool.setuptools.packages.find]
22
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,12 @@
1
+ __version__ = "0.1.0"
2
+
3
+ from alix_graphql_mcp_knowledge_base_tool.mcp_server import (
4
+ run_get_institution_marshaling_plan,
5
+ run_get_institution_marshaling_plans,
6
+ )
7
+
8
+ __all__ = [
9
+ "__version__",
10
+ "run_get_institution_marshaling_plan",
11
+ "run_get_institution_marshaling_plans",
12
+ ]
@@ -0,0 +1,6 @@
1
+ """python -m alix_graphql_mcp_knowledge_base_tool -> stdio MCP."""
2
+
3
+ from alix_graphql_mcp_knowledge_base_tool.mcp_server import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,373 @@
1
+ """Knowledge Base MCP server for institution marshaling plans.
2
+
3
+ Env vars:
4
+ ALIX_API_URL required. Base URL (e.g. http://localhost:8080) or full
5
+ /graphql URL.
6
+ ALIX_API_KEY required. Master token sent as the Authorization header.
7
+ ALIX_GRAPHQL_TIMEOUT optional. Request timeout in seconds (default 30).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import os
14
+ from typing import Any, Literal
15
+
16
+ import httpx
17
+ from mcp.server.fastmcp import FastMCP
18
+
19
+ SERVER_NAME: str = "alix-graphql-mcp-knowledge-base-tool"
20
+
21
+ AssetCategory = Literal[
22
+ "BankAccount",
23
+ "Investment",
24
+ "LifeInsurance",
25
+ "PersonalProperty",
26
+ "RealEstate",
27
+ "Retirement",
28
+ "UnclaimedProperty",
29
+ "Vehicle",
30
+ "Business",
31
+ "Cash",
32
+ "MoneyDueToEstate",
33
+ ]
34
+
35
+ AssetSubcategory = Literal[
36
+ "Checking",
37
+ "Savings",
38
+ "CD",
39
+ "MoneyMarket",
40
+ "SafetyDepositBox",
41
+ "Trust",
42
+ "IRA",
43
+ "RothIRA",
44
+ "K401",
45
+ "Roth401K",
46
+ "B457",
47
+ "B403",
48
+ "Pension",
49
+ "Annuity",
50
+ "ESOP",
51
+ "Brokerage",
52
+ "Stocks",
53
+ "HSA",
54
+ "PrimaryResidence",
55
+ "VacationHome",
56
+ "ResidentialIncomeProperty",
57
+ "CommercialProperty",
58
+ "UndevelopedParcel",
59
+ "Car",
60
+ "Truck",
61
+ "RV",
62
+ "Boat",
63
+ "Aircraft",
64
+ "Trailer",
65
+ "Motorcycle",
66
+ "SoleProprietorship",
67
+ "Partnership",
68
+ "LLC",
69
+ "Corporation",
70
+ "SCorporation",
71
+ "Income",
72
+ "Refund",
73
+ "Other",
74
+ ]
75
+
76
+ AssetDesignation = Literal[
77
+ "InTrust",
78
+ "PodTod",
79
+ "Tod",
80
+ "JointOwnership",
81
+ "JointOwnershipWithSurvivorship",
82
+ "JointTenantsWithRightOfSurvivorship",
83
+ "TenancyInCommon",
84
+ "CommunityProperty",
85
+ "InTheEstate",
86
+ "OwnerTransferByOperatingAgreement",
87
+ "Unknown",
88
+ ]
89
+
90
+ _MARSHALING_PLAN_SELECTION = """
91
+ id
92
+ createdAt
93
+ updatedAt
94
+ deletedAt
95
+ institutionId
96
+ assetCategory
97
+ assetDesignation
98
+ assetSubcategory
99
+ contactPhone
100
+ faxNumber
101
+ businessHours
102
+ submissionMethods
103
+ submissionInstructions
104
+ onlinePortalUrl
105
+ typicalProcessingTime
106
+ recommendedFollowUpInterval
107
+ additionalNotes
108
+ metadata
109
+ mailingAddress {
110
+ id
111
+ address
112
+ city
113
+ county
114
+ state
115
+ zip
116
+ country
117
+ }
118
+ steps(orderBy: [{ order: asc }]) {
119
+ id
120
+ order
121
+ title
122
+ description
123
+ config
124
+ }
125
+ forms {
126
+ id
127
+ createdAt
128
+ updatedAt
129
+ name
130
+ description
131
+ isFillable
132
+ fieldMap
133
+ fileId
134
+ }
135
+ requiredDocuments {
136
+ id
137
+ createdAt
138
+ updatedAt
139
+ documentType
140
+ notes
141
+ }
142
+ """
143
+
144
+ _GET_MARSHALING_PLANS_QUERY = f"""
145
+ query GetInstitutionMarshalingPlans($institutionId: String!) {{
146
+ marshalingPlans(
147
+ where: {{
148
+ institutionId: {{ equals: $institutionId }}
149
+ deletedAt: {{ equals: null }}
150
+ }}
151
+ orderBy: [{{ updatedAt: desc }}]
152
+ ) {{
153
+ {_MARSHALING_PLAN_SELECTION}
154
+ }}
155
+ }}
156
+ """
157
+
158
+ _GET_MARSHALING_PLAN_QUERY = f"""
159
+ query GetInstitutionMarshalingPlan($where: MarshalingPlanWhereInput!) {{
160
+ marshalingPlans(
161
+ where: $where
162
+ orderBy: [{{ updatedAt: desc }}]
163
+ take: 2
164
+ ) {{
165
+ {_MARSHALING_PLAN_SELECTION}
166
+ }}
167
+ }}
168
+ """
169
+
170
+
171
+ def _resolve_endpoint(raw: str) -> str:
172
+ url = raw.rstrip("/")
173
+ if url.endswith("/graphql"):
174
+ return url
175
+ return f"{url}/graphql"
176
+
177
+
178
+ def _read_timeout() -> float:
179
+ raw = os.environ.get("ALIX_GRAPHQL_TIMEOUT")
180
+ if not raw:
181
+ return 30.0
182
+ try:
183
+ return float(raw)
184
+ except ValueError:
185
+ return 30.0
186
+
187
+
188
+ def _graphql_request(
189
+ *,
190
+ query: str,
191
+ variables: dict[str, Any],
192
+ operation_name: str,
193
+ ) -> dict[str, Any]:
194
+ api_url_raw = os.environ.get("ALIX_API_URL")
195
+ api_key = os.environ.get("ALIX_API_KEY")
196
+ if not api_url_raw:
197
+ return {"errors": [{"message": "ALIX_API_URL is not set."}]}
198
+ if not api_key:
199
+ return {"errors": [{"message": "ALIX_API_KEY is not set."}]}
200
+
201
+ endpoint = _resolve_endpoint(api_url_raw)
202
+ timeout = _read_timeout()
203
+
204
+ payload: dict[str, Any] = {
205
+ "query": query,
206
+ "variables": variables,
207
+ "operationName": operation_name,
208
+ }
209
+ headers = {
210
+ "Content-Type": "application/json",
211
+ "Authorization": api_key,
212
+ }
213
+
214
+ try:
215
+ with httpx.Client(timeout=timeout) as client:
216
+ response = client.post(endpoint, json=payload, headers=headers)
217
+ except httpx.HTTPError as exc:
218
+ return {"errors": [{"message": f"Transport error: {exc.__class__.__name__}: {exc}"}]}
219
+
220
+ text = response.text
221
+ try:
222
+ body: Any = response.json()
223
+ except ValueError:
224
+ body = None
225
+
226
+ if response.is_error:
227
+ if isinstance(body, dict) and ("errors" in body or "data" in body):
228
+ return {
229
+ "data": body.get("data"),
230
+ "errors": body.get("errors")
231
+ or [{"message": f"HTTP {response.status_code}: {text or response.reason_phrase}"}],
232
+ "http_status": response.status_code,
233
+ }
234
+ return {
235
+ "data": None,
236
+ "errors": [{"message": f"HTTP {response.status_code}: {text or response.reason_phrase}"}],
237
+ "http_status": response.status_code,
238
+ }
239
+
240
+ if not isinstance(body, dict):
241
+ return {
242
+ "data": None,
243
+ "errors": [{"message": f"Non-JSON response from GraphQL endpoint: {text[:500]}"}],
244
+ "http_status": response.status_code,
245
+ }
246
+
247
+ return {
248
+ "data": body.get("data"),
249
+ "errors": body.get("errors"),
250
+ "extensions": body.get("extensions"),
251
+ "http_status": response.status_code,
252
+ }
253
+
254
+
255
+ def _clean_optional_enum(value: str | None) -> str | None:
256
+ if value is None:
257
+ return None
258
+ cleaned = value.strip()
259
+ if not cleaned:
260
+ return None
261
+ return cleaned
262
+
263
+
264
+ def run_get_institution_marshaling_plans(institutionId: str) -> str:
265
+ if not institutionId or not isinstance(institutionId, str):
266
+ return json.dumps({"errors": [{"message": "institutionId is required."}]})
267
+
268
+ result = _graphql_request(
269
+ query=_GET_MARSHALING_PLANS_QUERY,
270
+ variables={"institutionId": institutionId},
271
+ operation_name="GetInstitutionMarshalingPlans",
272
+ )
273
+ return json.dumps(result, default=str)
274
+
275
+
276
+ def run_get_institution_marshaling_plan(
277
+ institutionId: str,
278
+ assetCategory: AssetCategory,
279
+ assetDesignation: AssetDesignation,
280
+ assetSubcategory: AssetSubcategory | None = None,
281
+ ) -> str:
282
+ if not institutionId or not isinstance(institutionId, str):
283
+ return json.dumps({"errors": [{"message": "institutionId is required."}]})
284
+
285
+ where: dict[str, Any] = {
286
+ "institutionId": {"equals": institutionId},
287
+ "deletedAt": {"equals": None},
288
+ "assetCategory": {"equals": assetCategory},
289
+ "assetDesignation": {"equals": assetDesignation},
290
+ }
291
+ cleaned_subcategory = _clean_optional_enum(assetSubcategory)
292
+ if cleaned_subcategory is not None:
293
+ where["assetSubcategory"] = {"equals": cleaned_subcategory}
294
+
295
+ result = _graphql_request(
296
+ query=_GET_MARSHALING_PLAN_QUERY,
297
+ variables={"where": where},
298
+ operation_name="GetInstitutionMarshalingPlan",
299
+ )
300
+
301
+ if result.get("errors"):
302
+ return json.dumps(result, default=str)
303
+
304
+ data = result.get("data") if isinstance(result.get("data"), dict) else {}
305
+ plans = data.get("marshalingPlans") if isinstance(data, dict) else None
306
+
307
+ if not isinstance(plans, list):
308
+ return json.dumps(
309
+ {
310
+ **result,
311
+ "errors": [{"message": "Unexpected GraphQL response: `marshalingPlans` is missing."}],
312
+ },
313
+ default=str,
314
+ )
315
+
316
+ if not plans:
317
+ return json.dumps(
318
+ {
319
+ **result,
320
+ "data": {"marshalingPlan": None},
321
+ },
322
+ default=str,
323
+ )
324
+
325
+ if len(plans) > 1:
326
+ return json.dumps(
327
+ {
328
+ **result,
329
+ "data": {"marshalingPlan": None},
330
+ "errors": [
331
+ {
332
+ "message": (
333
+ "Multiple marshaling plans match this filter. "
334
+ "Provide additional filters: assetCategory, assetDesignation, assetSubcategory."
335
+ )
336
+ }
337
+ ],
338
+ },
339
+ default=str,
340
+ )
341
+
342
+ return json.dumps(
343
+ {
344
+ **result,
345
+ "data": {"marshalingPlan": plans[0]},
346
+ },
347
+ default=str,
348
+ )
349
+
350
+
351
+ def main() -> None:
352
+ mcp = FastMCP(SERVER_NAME)
353
+
354
+ mcp.tool(
355
+ name="getInstitutionMarshalingPlans",
356
+ title="Get institution marshaling plans",
357
+ description=(
358
+ "Fetch all active MarshalingPlan records for an institutionId from the Alix GraphQL API. "
359
+ "Returns raw JSON containing `{data, errors, extensions, http_status}`."
360
+ ),
361
+ )(run_get_institution_marshaling_plans)
362
+
363
+ mcp.tool(
364
+ name="getInstitutionMarshalingPlan",
365
+ title="Get one institution marshaling plan",
366
+ description=(
367
+ "Fetch a single active MarshalingPlan for an institutionId with optional filters "
368
+ "(assetCategory, assetDesignation, assetSubcategory). Returns raw JSON containing "
369
+ "`{data, errors, extensions, http_status}` and normalizes the payload to `data.marshalingPlan`."
370
+ ),
371
+ )(run_get_institution_marshaling_plan)
372
+
373
+ mcp.run()
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: alix-graphql-mcp-knowledge-base-tool
3
+ Version: 0.1.0
4
+ Summary: Knowledge Base MCP tool for Alix GraphQL. Fetches institution marshaling plans.
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: mcp>=1.9.0
7
+ Requires-Dist: httpx>=0.27
8
+ Provides-Extra: dev
9
+ Requires-Dist: build; extra == "dev"
10
+ Requires-Dist: twine; extra == "dev"
@@ -0,0 +1,10 @@
1
+ pyproject.toml
2
+ src/alix_graphql_mcp_knowledge_base_tool/__init__.py
3
+ src/alix_graphql_mcp_knowledge_base_tool/__main__.py
4
+ src/alix_graphql_mcp_knowledge_base_tool/mcp_server.py
5
+ src/alix_graphql_mcp_knowledge_base_tool.egg-info/PKG-INFO
6
+ src/alix_graphql_mcp_knowledge_base_tool.egg-info/SOURCES.txt
7
+ src/alix_graphql_mcp_knowledge_base_tool.egg-info/dependency_links.txt
8
+ src/alix_graphql_mcp_knowledge_base_tool.egg-info/entry_points.txt
9
+ src/alix_graphql_mcp_knowledge_base_tool.egg-info/requires.txt
10
+ src/alix_graphql_mcp_knowledge_base_tool.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ alix-graphql-mcp-knowledge-base-tool = alix_graphql_mcp_knowledge_base_tool.mcp_server:main