goliath-utils-docs 0.1.1__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,48 @@
1
+ Metadata-Version: 2.4
2
+ Name: goliath-utils-docs
3
+ Version: 0.1.1
4
+ Summary: MCP server exposing goliath-utils API documentation to Claude Code
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: mcp>=1.0
8
+ Requires-Dist: goliath-utils>=0.1.3
9
+
10
+ # goliath-utils-docs
11
+
12
+ MCP server that exposes [goliath-utils](https://pypi.org/project/goliath-utils/) API documentation to Claude Code.
13
+
14
+ When configured, Claude Code gains access to three tools:
15
+
16
+ - **list_available_utils** — overview of all modules, classes, and functions
17
+ - **get_module_docs** — full API reference for a specific module
18
+ - **search_utils** — search across all utility APIs by keyword
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install goliath-utils-docs
24
+ goliath-utils-docs install
25
+ ```
26
+
27
+ This registers the MCP server in your Claude Code settings. Restart Claude Code to activate.
28
+
29
+ ## Uninstall
30
+
31
+ ```bash
32
+ goliath-utils-docs uninstall
33
+ ```
34
+
35
+ ## Manual configuration
36
+
37
+ Add to `~/.claude/settings.json`:
38
+
39
+ ```json
40
+ {
41
+ "mcpServers": {
42
+ "goliath-utils-docs": {
43
+ "command": "uvx",
44
+ "args": ["goliath-utils-docs", "serve"]
45
+ }
46
+ }
47
+ }
48
+ ```
@@ -0,0 +1,39 @@
1
+ # goliath-utils-docs
2
+
3
+ MCP server that exposes [goliath-utils](https://pypi.org/project/goliath-utils/) API documentation to Claude Code.
4
+
5
+ When configured, Claude Code gains access to three tools:
6
+
7
+ - **list_available_utils** — overview of all modules, classes, and functions
8
+ - **get_module_docs** — full API reference for a specific module
9
+ - **search_utils** — search across all utility APIs by keyword
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pip install goliath-utils-docs
15
+ goliath-utils-docs install
16
+ ```
17
+
18
+ This registers the MCP server in your Claude Code settings. Restart Claude Code to activate.
19
+
20
+ ## Uninstall
21
+
22
+ ```bash
23
+ goliath-utils-docs uninstall
24
+ ```
25
+
26
+ ## Manual configuration
27
+
28
+ Add to `~/.claude/settings.json`:
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "goliath-utils-docs": {
34
+ "command": "uvx",
35
+ "args": ["goliath-utils-docs", "serve"]
36
+ }
37
+ }
38
+ }
39
+ ```
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "goliath-utils-docs"
7
+ version = "0.1.1"
8
+ description = "MCP server exposing goliath-utils API documentation to Claude Code"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ dependencies = [
12
+ "mcp>=1.0",
13
+ "goliath-utils>=0.1.3",
14
+ ]
15
+
16
+ [project.scripts]
17
+ goliath-utils-docs = "goliath_utils_docs.cli:main"
18
+
19
+ [tool.setuptools]
20
+ package-dir = {"" = "src"}
21
+
22
+ [tool.setuptools.package-data]
23
+ goliath_utils_docs = ["supplements.json"]
24
+
25
+ [tool.setuptools.packages.find]
26
+ where = ["src"]
27
+
28
+ [dependency-groups]
29
+ dev = [
30
+ "build>=1.4.2",
31
+ "twine>=6.2.0",
32
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ __version__ = "0.1.1"
@@ -0,0 +1,5 @@
1
+ import asyncio
2
+
3
+ from .server import main
4
+
5
+ asyncio.run(main())
@@ -0,0 +1,99 @@
1
+ """CLI for installing/uninstalling the MCP server and running it directly."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ SERVER_NAME = "goliath-utils-docs"
11
+ MCP_CONFIG = {
12
+ "command": "uvx",
13
+ "args": ["goliath-utils-docs", "serve"],
14
+ }
15
+
16
+
17
+ def _global_settings_path() -> Path:
18
+ return Path.home() / ".claude" / "settings.json"
19
+
20
+
21
+ def _project_settings_path() -> Path:
22
+ # Walk up to find a git root
23
+ cwd = Path.cwd()
24
+ for parent in [cwd, *cwd.parents]:
25
+ if (parent / ".git").exists():
26
+ return parent / ".claude" / "settings.json"
27
+ return cwd / ".claude" / "settings.json"
28
+
29
+
30
+ def _read_settings(path: Path) -> dict:
31
+ if path.exists():
32
+ return json.loads(path.read_text(encoding="utf-8"))
33
+ return {}
34
+
35
+
36
+ def _write_settings(path: Path, settings: dict) -> None:
37
+ path.parent.mkdir(parents=True, exist_ok=True)
38
+ path.write_text(json.dumps(settings, indent=2) + "\n", encoding="utf-8")
39
+
40
+
41
+ def install(project: bool = False) -> None:
42
+ """Register the MCP server in Claude Code settings."""
43
+ path = _project_settings_path() if project else _global_settings_path()
44
+ settings = _read_settings(path)
45
+ settings.setdefault("mcpServers", {})
46
+ settings["mcpServers"][SERVER_NAME] = MCP_CONFIG
47
+ _write_settings(path, settings)
48
+ scope = "project" if project else "global"
49
+ print(f"Installed {SERVER_NAME} in {scope} settings: {path}")
50
+ print("Restart Claude Code to activate.")
51
+
52
+
53
+ def uninstall(project: bool = False) -> None:
54
+ """Remove the MCP server from Claude Code settings."""
55
+ path = _project_settings_path() if project else _global_settings_path()
56
+ settings = _read_settings(path)
57
+ servers = settings.get("mcpServers", {})
58
+ if SERVER_NAME in servers:
59
+ del servers[SERVER_NAME]
60
+ _write_settings(path, settings)
61
+ scope = "project" if project else "global"
62
+ print(f"Removed {SERVER_NAME} from {scope} settings: {path}")
63
+ else:
64
+ print(f"{SERVER_NAME} not found in settings.")
65
+
66
+
67
+ def serve() -> None:
68
+ """Run the MCP server (stdio transport)."""
69
+ from .server import main as server_main
70
+ asyncio.run(server_main())
71
+
72
+
73
+ def main() -> None:
74
+ args = sys.argv[1:]
75
+
76
+ if not args or args[0] in ("-h", "--help"):
77
+ print("Usage: goliath-utils-docs <command>")
78
+ print()
79
+ print("Commands:")
80
+ print(" install Register MCP server in Claude Code settings")
81
+ print(" uninstall Remove MCP server from Claude Code settings")
82
+ print(" serve Run the MCP server directly")
83
+ print()
84
+ print("Options:")
85
+ print(" --project Use project-level settings instead of global")
86
+ return
87
+
88
+ command = args[0]
89
+ project = "--project" in args
90
+
91
+ if command == "install":
92
+ install(project=project)
93
+ elif command == "uninstall":
94
+ uninstall(project=project)
95
+ elif command == "serve":
96
+ serve()
97
+ else:
98
+ print(f"Unknown command: {command}")
99
+ sys.exit(1)
@@ -0,0 +1,435 @@
1
+ """Runtime introspection engine for goliath_utils.
2
+
3
+ Imports goliath_utils submodules and extracts structured documentation
4
+ from type annotations, docstrings, and supplemental descriptions.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import importlib
10
+ import importlib.resources
11
+ import inspect
12
+ import json
13
+ from dataclasses import dataclass, field
14
+ from typing import Any
15
+
16
+ MODULES = {
17
+ "atrax": "goliath_utils.atrax",
18
+ "s3": "goliath_utils.s3",
19
+ "sns": "goliath_utils.sns",
20
+ "secrets": "goliath_utils.secrets",
21
+ "helpers": "goliath_utils.helpers",
22
+ }
23
+
24
+ MODULE_DESCRIPTIONS = {
25
+ "atrax": "Pure-Python pandas alternative (Series, DataSet) with zero external dependencies",
26
+ "s3": "Lightweight S3 client using httpx and AWS SigV4 signing (no boto3)",
27
+ "sns": "Client for the Goliath notification/SNS API",
28
+ "secrets": "Client for the Goliath secrets management API",
29
+ "helpers": "S3 I/O helpers (CSV, JSON, Parquet), event parsing, and database insertion",
30
+ }
31
+
32
+
33
+ @dataclass
34
+ class ParamDoc:
35
+ name: str
36
+ annotation: str
37
+ default: str | None
38
+ kind: str
39
+
40
+
41
+ @dataclass
42
+ class MethodDoc:
43
+ name: str
44
+ signature: str
45
+ docstring: str | None
46
+ params: list[ParamDoc]
47
+ return_annotation: str
48
+ is_property: bool = False
49
+
50
+
51
+ @dataclass
52
+ class ClassDoc:
53
+ name: str
54
+ docstring: str | None
55
+ methods: list[MethodDoc]
56
+ bases: list[str]
57
+
58
+
59
+ @dataclass
60
+ class FunctionDoc:
61
+ name: str
62
+ signature: str
63
+ docstring: str | None
64
+ params: list[ParamDoc]
65
+ return_annotation: str
66
+
67
+
68
+ @dataclass
69
+ class ModuleDoc:
70
+ name: str
71
+ full_path: str
72
+ description: str | None
73
+ docstring: str | None
74
+ classes: list[ClassDoc]
75
+ functions: list[FunctionDoc]
76
+
77
+
78
+ def _load_supplements() -> dict[str, str]:
79
+ """Load supplemental one-liner docs from supplements.json."""
80
+ ref = importlib.resources.files("goliath_utils_docs").joinpath("supplements.json")
81
+ text = ref.read_text(encoding="utf-8")
82
+ return json.loads(text)
83
+
84
+
85
+ def _format_annotation(ann: Any) -> str:
86
+ """Convert a type annotation to a readable string."""
87
+ if ann is inspect.Parameter.empty or ann is inspect.Signature.empty:
88
+ return ""
89
+ if isinstance(ann, str):
90
+ return ann
91
+ if hasattr(ann, "__name__"):
92
+ return ann.__name__
93
+ return str(ann).replace("typing.", "")
94
+
95
+
96
+ def _extract_params(sig: inspect.Signature) -> list[ParamDoc]:
97
+ """Extract parameter documentation from a signature."""
98
+ params = []
99
+ for name, param in sig.parameters.items():
100
+ if name == "self":
101
+ continue
102
+ kind_map = {
103
+ inspect.Parameter.POSITIONAL_ONLY: "positional",
104
+ inspect.Parameter.POSITIONAL_OR_KEYWORD: "keyword",
105
+ inspect.Parameter.KEYWORD_ONLY: "keyword_only",
106
+ inspect.Parameter.VAR_POSITIONAL: "var_positional",
107
+ inspect.Parameter.VAR_KEYWORD: "var_keyword",
108
+ }
109
+ default = None
110
+ if param.default is not inspect.Parameter.empty:
111
+ default = repr(param.default)
112
+ params.append(ParamDoc(
113
+ name=name,
114
+ annotation=_format_annotation(param.annotation),
115
+ default=default,
116
+ kind=kind_map.get(param.kind, "unknown"),
117
+ ))
118
+ return params
119
+
120
+
121
+ def _format_signature(obj: Any) -> str:
122
+ """Get a clean signature string for a callable."""
123
+ try:
124
+ sig = inspect.signature(obj)
125
+ parts = []
126
+ for name, param in sig.parameters.items():
127
+ if name == "self":
128
+ continue
129
+ ann = _format_annotation(param.annotation)
130
+ default = ""
131
+ if param.default is not inspect.Parameter.empty:
132
+ default = f" = {param.default!r}"
133
+ if param.kind == inspect.Parameter.VAR_POSITIONAL:
134
+ part = f"*{name}"
135
+ elif param.kind == inspect.Parameter.VAR_KEYWORD:
136
+ part = f"**{name}"
137
+ elif param.kind == inspect.Parameter.KEYWORD_ONLY:
138
+ part = name
139
+ else:
140
+ part = name
141
+ if ann:
142
+ part = f"{part}: {ann}"
143
+ part += default
144
+ parts.append(part)
145
+
146
+ ret = _format_annotation(sig.return_annotation)
147
+ ret_str = f" -> {ret}" if ret else ""
148
+ return f"({', '.join(parts)}){ret_str}"
149
+ except (ValueError, TypeError):
150
+ return "(...)"
151
+
152
+
153
+ def _is_public_method(name: str) -> bool:
154
+ """Check if a name is a public method we should document."""
155
+ if name.startswith("__") and name.endswith("__"):
156
+ return name == "__init__"
157
+ return not name.startswith("_")
158
+
159
+
160
+ def _extract_class(cls: type, supplements: dict[str, str]) -> ClassDoc:
161
+ """Extract documentation for a class."""
162
+ class_name = cls.__name__
163
+ methods = []
164
+
165
+ for name in sorted(cls.__dict__):
166
+ if not _is_public_method(name):
167
+ continue
168
+
169
+ attr = cls.__dict__[name]
170
+ is_prop = isinstance(attr, property)
171
+
172
+ if is_prop:
173
+ docstring = inspect.getdoc(attr.fget) if attr.fget else None
174
+ try:
175
+ sig = inspect.signature(attr.fget) if attr.fget else None
176
+ ret = _format_annotation(sig.return_annotation) if sig else ""
177
+ except (ValueError, TypeError):
178
+ ret = ""
179
+ methods.append(MethodDoc(
180
+ name=name,
181
+ signature=f"-> {ret}" if ret else "(property)",
182
+ docstring=supplements.get(f"{class_name}.{name}") or docstring,
183
+ params=[],
184
+ return_annotation=ret,
185
+ is_property=True,
186
+ ))
187
+ elif callable(attr) or isinstance(attr, (staticmethod, classmethod)):
188
+ actual = attr
189
+ if isinstance(attr, staticmethod):
190
+ actual = attr.__func__
191
+ elif isinstance(attr, classmethod):
192
+ actual = attr.__func__
193
+
194
+ # Use getattr on __doc__ directly to avoid inheriting generic docstrings
195
+ raw_doc = getattr(actual, "__doc__", None)
196
+ docstring = inspect.cleandoc(raw_doc) if raw_doc else None
197
+ sig_str = _format_signature(actual)
198
+ try:
199
+ sig = inspect.signature(actual)
200
+ params = _extract_params(sig)
201
+ ret = _format_annotation(sig.return_annotation)
202
+ except (ValueError, TypeError):
203
+ params = []
204
+ ret = ""
205
+
206
+ supplement = supplements.get(f"{class_name}.{name}")
207
+ methods.append(MethodDoc(
208
+ name=name,
209
+ signature=sig_str,
210
+ docstring=supplement or docstring,
211
+ params=params,
212
+ return_annotation=ret,
213
+ is_property=False,
214
+ ))
215
+
216
+ bases = [b.__name__ for b in cls.__bases__ if b is not object]
217
+
218
+ return ClassDoc(
219
+ name=class_name,
220
+ docstring=inspect.getdoc(cls),
221
+ methods=methods,
222
+ bases=bases,
223
+ )
224
+
225
+
226
+ def _extract_function(name: str, func: Any, supplements: dict[str, str]) -> FunctionDoc:
227
+ """Extract documentation for a module-level function."""
228
+ docstring = inspect.getdoc(func)
229
+ sig_str = _format_signature(func)
230
+ try:
231
+ sig = inspect.signature(func)
232
+ params = _extract_params(sig)
233
+ ret = _format_annotation(sig.return_annotation)
234
+ except (ValueError, TypeError):
235
+ params = []
236
+ ret = ""
237
+
238
+ return FunctionDoc(
239
+ name=name,
240
+ signature=sig_str,
241
+ docstring=docstring or supplements.get(name),
242
+ params=params,
243
+ return_annotation=ret,
244
+ )
245
+
246
+
247
+ def extract_module(short_name: str) -> ModuleDoc:
248
+ """Extract documentation for a single goliath_utils submodule."""
249
+ full_path = MODULES[short_name]
250
+ mod = importlib.import_module(full_path)
251
+ supplements = _load_supplements()
252
+
253
+ exported = getattr(mod, "__all__", None)
254
+ if exported is None:
255
+ exported = [n for n in dir(mod) if not n.startswith("_")]
256
+
257
+ classes = []
258
+ functions = []
259
+
260
+ for name in exported:
261
+ obj = getattr(mod, name)
262
+ if inspect.isclass(obj):
263
+ # Skip pure exception classes (just document their names)
264
+ if issubclass(obj, Exception) and obj is not Exception:
265
+ classes.append(ClassDoc(
266
+ name=name,
267
+ docstring=inspect.getdoc(obj),
268
+ methods=[],
269
+ bases=[b.__name__ for b in obj.__bases__ if b is not object],
270
+ ))
271
+ else:
272
+ classes.append(_extract_class(obj, supplements))
273
+ elif inspect.isfunction(obj):
274
+ functions.append(_extract_function(name, obj, supplements))
275
+
276
+ return ModuleDoc(
277
+ name=short_name,
278
+ full_path=full_path,
279
+ description=MODULE_DESCRIPTIONS.get(short_name),
280
+ docstring=inspect.getdoc(mod),
281
+ classes=classes,
282
+ functions=functions,
283
+ )
284
+
285
+
286
+ def extract_all() -> dict[str, ModuleDoc]:
287
+ """Extract documentation for all goliath_utils submodules."""
288
+ result = {}
289
+ for short_name in MODULES:
290
+ try:
291
+ result[short_name] = extract_module(short_name)
292
+ except ImportError:
293
+ continue
294
+ return result
295
+
296
+
297
+ def format_module_docs(doc: ModuleDoc) -> str:
298
+ """Format a ModuleDoc into readable markdown."""
299
+ lines = [f"# {doc.name}", ""]
300
+ if doc.description:
301
+ lines.append(doc.description)
302
+ lines.append("")
303
+ if doc.docstring:
304
+ lines.append(doc.docstring)
305
+ lines.append("")
306
+ lines.append(f"**Import**: `from goliath_utils.{doc.name} import ...`")
307
+ lines.append("")
308
+
309
+ for cls in doc.classes:
310
+ lines.append(f"## {cls.name}")
311
+ if cls.bases:
312
+ lines.append(f"Inherits from: {', '.join(cls.bases)}")
313
+ if cls.docstring:
314
+ lines.append("")
315
+ lines.append(cls.docstring)
316
+ lines.append("")
317
+
318
+ if not cls.methods:
319
+ lines.append("*(Exception class — raise/catch only)*")
320
+ lines.append("")
321
+ continue
322
+
323
+ for method in cls.methods:
324
+ if method.is_property:
325
+ lines.append(f"### `{cls.name}.{method.name}` (property)")
326
+ if method.return_annotation:
327
+ lines.append(f" Type: `{method.return_annotation}`")
328
+ else:
329
+ lines.append(f"### `{cls.name}.{method.name}{method.signature}`")
330
+
331
+ if method.docstring:
332
+ lines.append(f" {method.docstring}")
333
+ lines.append("")
334
+
335
+ for func in doc.functions:
336
+ lines.append(f"## `{func.name}{func.signature}`")
337
+ if func.docstring:
338
+ lines.append("")
339
+ lines.append(func.docstring)
340
+ lines.append("")
341
+
342
+ return "\n".join(lines)
343
+
344
+
345
+ def format_overview(docs: dict[str, ModuleDoc]) -> str:
346
+ """Format all modules into a high-level overview."""
347
+ lines = ["# goliath-utils API Overview", ""]
348
+ lines.append("A collection of pure-Python utilities for the Goliath platform.")
349
+ lines.append("")
350
+ lines.append("**Install**: `pip install goliath-utils`")
351
+ lines.append("")
352
+
353
+ for name, doc in docs.items():
354
+ lines.append(f"## {name}")
355
+ if doc.description:
356
+ lines.append(doc.description)
357
+ lines.append("")
358
+
359
+ for cls in doc.classes:
360
+ method_count = len([m for m in cls.methods if not m.is_property])
361
+ prop_count = len([m for m in cls.methods if m.is_property])
362
+ if cls.methods:
363
+ parts = []
364
+ if method_count:
365
+ parts.append(f"{method_count} methods")
366
+ if prop_count:
367
+ parts.append(f"{prop_count} properties")
368
+ lines.append(f"- **{cls.name}** ({', '.join(parts)})")
369
+ else:
370
+ lines.append(f"- **{cls.name}** (exception)")
371
+ if cls.docstring:
372
+ first_line = cls.docstring.split("\n")[0]
373
+ lines.append(f" {first_line}")
374
+
375
+ for func in doc.functions:
376
+ lines.append(f"- **{func.name}()**")
377
+ if func.docstring:
378
+ first_line = func.docstring.split("\n")[0]
379
+ lines.append(f" {first_line}")
380
+
381
+ lines.append("")
382
+
383
+ return "\n".join(lines)
384
+
385
+
386
+ def search_docs(docs: dict[str, ModuleDoc], query: str, max_results: int = 10) -> str:
387
+ """Search across all documentation for matching methods/functions."""
388
+ terms = query.lower().split()
389
+ results: list[tuple[float, str]] = []
390
+
391
+ for mod_name, doc in docs.items():
392
+ for cls in doc.classes:
393
+ for method in cls.methods:
394
+ score = _score_match(terms, method.name, method.docstring, method.signature)
395
+ if score > 0:
396
+ desc = method.docstring or "(no description)"
397
+ if method.is_property:
398
+ entry = f"**{cls.name}.{method.name}** (property)\n {desc}"
399
+ else:
400
+ entry = f"**{cls.name}.{method.name}**`{method.signature}`\n {desc}"
401
+ entry = f"[{mod_name}] {entry}"
402
+ results.append((score, entry))
403
+
404
+ for func in doc.functions:
405
+ score = _score_match(terms, func.name, func.docstring, func.signature)
406
+ if score > 0:
407
+ desc = func.docstring or "(no description)"
408
+ entry = f"[{mod_name}] **{func.name}**`{func.signature}`\n {desc}"
409
+ results.append((score, entry))
410
+
411
+ results.sort(key=lambda x: -x[0])
412
+ top = results[:max_results]
413
+
414
+ if not top:
415
+ return f"No results found for: {query}"
416
+
417
+ lines = [f"# Search results for: {query}", ""]
418
+ for _, entry in top:
419
+ lines.append(f"- {entry}")
420
+ lines.append("")
421
+ return "\n".join(lines)
422
+
423
+
424
+ def _score_match(terms: list[str], name: str, docstring: str | None, signature: str) -> float:
425
+ """Score how well a method matches search terms."""
426
+ text = f"{name} {docstring or ''} {signature}".lower()
427
+ score = 0.0
428
+ for term in terms:
429
+ if term in name.lower():
430
+ score += 3.0 # name match is strongest
431
+ elif docstring and term in docstring.lower():
432
+ score += 1.5
433
+ elif term in signature.lower():
434
+ score += 1.0
435
+ return score
@@ -0,0 +1,117 @@
1
+ """MCP server exposing goliath-utils documentation to Claude Code."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from mcp.server import Server
6
+ from mcp.server.stdio import stdio_server
7
+ from mcp.types import TextContent, Tool
8
+
9
+ from .introspect import (
10
+ ModuleDoc,
11
+ extract_all,
12
+ extract_module,
13
+ format_module_docs,
14
+ format_overview,
15
+ search_docs,
16
+ MODULES,
17
+ )
18
+
19
+ app = Server("goliath-utils-docs")
20
+
21
+ _docs: dict[str, ModuleDoc] | None = None
22
+
23
+
24
+ def _get_docs() -> dict[str, ModuleDoc]:
25
+ global _docs
26
+ if _docs is None:
27
+ _docs = extract_all()
28
+ return _docs
29
+
30
+
31
+ @app.list_tools()
32
+ async def list_tools() -> list[Tool]:
33
+ return [
34
+ Tool(
35
+ name="list_available_utils",
36
+ description=(
37
+ "List all available goliath-utils modules, classes, and functions "
38
+ "with a high-level overview. Call this first to understand what "
39
+ "utilities are available."
40
+ ),
41
+ inputSchema={"type": "object", "properties": {}},
42
+ ),
43
+ Tool(
44
+ name="get_module_docs",
45
+ description=(
46
+ "Get full API documentation for a specific goliath-utils module. "
47
+ "Returns all classes, methods with signatures and descriptions. "
48
+ "Modules: atrax, s3, sns, secrets, helpers."
49
+ ),
50
+ inputSchema={
51
+ "type": "object",
52
+ "properties": {
53
+ "module": {
54
+ "type": "string",
55
+ "description": "Module name: atrax, s3, sns, secrets, or helpers",
56
+ "enum": list(MODULES.keys()),
57
+ },
58
+ },
59
+ "required": ["module"],
60
+ },
61
+ ),
62
+ Tool(
63
+ name="search_utils",
64
+ description=(
65
+ "Search across all goliath-utils APIs by keyword. "
66
+ "Returns matching methods and functions with their signatures "
67
+ "and descriptions. Useful when you know what you want to do "
68
+ "but not which module or method to use."
69
+ ),
70
+ inputSchema={
71
+ "type": "object",
72
+ "properties": {
73
+ "query": {
74
+ "type": "string",
75
+ "description": "Search terms (e.g., 'groupby aggregate', 'upload file s3', 'merge join')",
76
+ },
77
+ },
78
+ "required": ["query"],
79
+ },
80
+ ),
81
+ ]
82
+
83
+
84
+ @app.call_tool()
85
+ async def call_tool(name: str, arguments: dict) -> list[TextContent]:
86
+ docs = _get_docs()
87
+
88
+ if name == "list_available_utils":
89
+ text = format_overview(docs)
90
+ return [TextContent(type="text", text=text)]
91
+
92
+ elif name == "get_module_docs":
93
+ module = arguments.get("module", "")
94
+ if module not in MODULES:
95
+ return [TextContent(
96
+ type="text",
97
+ text=f"Unknown module: {module}. Available: {', '.join(MODULES.keys())}",
98
+ )]
99
+ doc = docs.get(module)
100
+ if doc is None:
101
+ return [TextContent(type="text", text=f"Module {module} could not be loaded.")]
102
+ text = format_module_docs(doc)
103
+ return [TextContent(type="text", text=text)]
104
+
105
+ elif name == "search_utils":
106
+ query = arguments.get("query", "")
107
+ if not query.strip():
108
+ return [TextContent(type="text", text="Please provide a search query.")]
109
+ text = search_docs(docs, query)
110
+ return [TextContent(type="text", text=text)]
111
+
112
+ return [TextContent(type="text", text=f"Unknown tool: {name}")]
113
+
114
+
115
+ async def main():
116
+ async with stdio_server() as (read, write):
117
+ await app.run(read, write, app.create_initialization_options())
@@ -0,0 +1,118 @@
1
+ {
2
+ "Series.head": "Return the first n elements (default 5).",
3
+ "Series.tail": "Return the last n elements (default 5).",
4
+ "Series.values": "Return the underlying data as a plain list.",
5
+ "Series.index": "Return the index labels as a list.",
6
+ "Series.shape": "Return a tuple with the number of elements.",
7
+ "Series.size": "Return the number of elements.",
8
+ "Series.empty": "True if the Series has no elements.",
9
+ "Series.loc": "Label-based indexer. Supports scalar, list, slice, and boolean Series keys.",
10
+ "Series.iloc": "Position-based indexer. Supports int, list of ints, and slice keys.",
11
+ "Series.str": "Accessor for vectorized string operations (lower, upper, contains, replace, etc.).",
12
+ "Series.__init__": "Construct from a list, dict, scalar (with index), or another Series. Optionally specify dtype and name.",
13
+ "Series.sum": "Return the sum of non-NA values. Returns 0 for empty Series.",
14
+ "Series.mean": "Return the mean of non-NA values. Returns NA for empty Series.",
15
+ "Series.median": "Return the median of non-NA values. Returns NA for empty Series.",
16
+ "Series.min": "Return the minimum non-NA value. Returns NA for empty Series.",
17
+ "Series.max": "Return the maximum non-NA value. Returns NA for empty Series.",
18
+ "Series.std": "Return the standard deviation. ddof=1 (sample) by default.",
19
+ "Series.var": "Return the variance. ddof=1 (sample) by default.",
20
+ "Series.quantile": "Return value at the given quantile (0-1). Pass a list for multiple quantiles, returns a Series.",
21
+ "Series.count": "Return the number of non-NA values.",
22
+ "Series.nunique": "Return the number of unique non-NA values.",
23
+ "Series.value_counts": "Return a Series of unique value counts, sorted descending.",
24
+ "Series.any": "Return True if any non-NA value is truthy.",
25
+ "Series.all": "Return True if all non-NA values are truthy (or Series is empty).",
26
+ "Series.apply": "Apply a function element-wise. NA values propagate.",
27
+ "Series.map": "Map values using a dict or function. Unmapped dict keys become NA.",
28
+ "Series.sort_values": "Sort by values. ascending=True by default. NA values go to the end.",
29
+ "Series.sort_index": "Sort by index labels. ascending=True by default.",
30
+ "Series.rank": "Rank values. method='average' by default. Options: average, min, max, first, dense.",
31
+ "Series.drop_duplicates": "Remove duplicate values. keep='first' (default), 'last', or False (drop all duplicates).",
32
+ "Series.fillna": "Replace NA values with the given value.",
33
+ "Series.dropna": "Remove NA values, returning a shorter Series.",
34
+ "Series.astype": "Cast values to the specified type (e.g., int, float, str).",
35
+ "Series.rename": "Return a copy with a new name.",
36
+ "Series.reset_index": "Reset to integer index. drop=False returns (old_index_series, value_series). drop=True returns just the value Series.",
37
+ "Series.copy": "Return a deep copy of the Series.",
38
+ "Series.items": "Iterate over (index_label, value) pairs.",
39
+
40
+ "DataSet.__init__": "Construct from a dict of lists/Series, a list of dicts, or another DataSet. Optionally specify index and columns.",
41
+ "DataSet.columns": "Return the column names as a list.",
42
+ "DataSet.index": "Return the index labels as a list.",
43
+ "DataSet.shape": "Return (n_rows, n_cols) tuple.",
44
+ "DataSet.size": "Return total number of elements (rows * cols).",
45
+ "DataSet.empty": "True if the DataSet has no rows.",
46
+ "DataSet.dtypes": "Return a Series of column dtypes.",
47
+ "DataSet.values": "Return data as a list of lists (one inner list per row).",
48
+ "DataSet.loc": "Label-based indexer. Supports ds.loc[row] and ds.loc[row, col].",
49
+ "DataSet.iloc": "Position-based indexer. Supports ds.iloc[row] and ds.iloc[row, col].",
50
+ "DataSet.T": "Transpose the DataSet (swap rows and columns).",
51
+ "DataSet.sum": "Sum per column (axis=0) or per row (axis=1). Returns a Series.",
52
+ "DataSet.mean": "Mean per column (axis=0) or per row (axis=1). Returns a Series.",
53
+ "DataSet.median": "Median per column (axis=0) or per row (axis=1). Returns a Series.",
54
+ "DataSet.min": "Min per column (axis=0) or per row (axis=1). Returns a Series.",
55
+ "DataSet.max": "Max per column (axis=0) or per row (axis=1). Returns a Series.",
56
+ "DataSet.std": "Standard deviation per column/row. ddof=1 by default.",
57
+ "DataSet.var": "Variance per column/row. ddof=1 by default.",
58
+ "DataSet.count": "Count non-NA values per column (axis=0) or per row (axis=1).",
59
+ "DataSet.nunique": "Count unique non-NA values per column or row.",
60
+ "DataSet.any": "True per column/row if any value is truthy.",
61
+ "DataSet.all": "True per column/row if all values are truthy.",
62
+ "DataSet.describe": "Generate summary statistics (count, mean, std, min, 25%, 50%, 75%, max) for numeric columns.",
63
+ "DataSet.sort_values": "Sort rows by one or more columns. by accepts str or list[str]. ascending=True by default.",
64
+ "DataSet.sort_index": "Sort rows by index labels. ascending=True by default.",
65
+ "DataSet.fillna": "Replace NA values with the given value across all columns.",
66
+ "DataSet.dropna": "Remove rows (axis=0) or columns (axis=1) with NA values. how='any' drops if any NA, how='all' drops if all NA.",
67
+ "DataSet.apply": "Apply a function per column (axis=0) returning a Series, or per row (axis=1) returning a DataSet.",
68
+ "DataSet.astype": "Cast columns. Pass a single type for all columns, or a dict mapping column names to types.",
69
+ "DataSet.rename": "Rename columns using a dict mapping old names to new names.",
70
+ "DataSet.reset_index": "Reset to integer index. drop=True discards old index, drop=False (default) adds it as an 'index' column.",
71
+ "DataSet.drop": "Drop columns by name. Accepts a single column name or a list.",
72
+ "DataSet.select_dtypes": "Select columns by dtype. include accepts a type or list of types.",
73
+ "DataSet.copy": "Return a deep copy of the DataSet.",
74
+ "DataSet.head": "Return the first n rows (default 5).",
75
+ "DataSet.tail": "Return the last n rows (default 5).",
76
+ "DataSet.to_dict": "Convert to dict. orient='dict' (default) returns {col: {idx: val}}, orient='records' returns list of row dicts.",
77
+ "DataSet.to_series": "Convert a single-column DataSet to a Series. Raises if more than one column.",
78
+ "DataSet.equals": "Return True if two DataSets have identical data, index, and columns.",
79
+ "DataSet.merge": "Join two DataSets. on= for shared key, or left_on=/right_on= for different keys. how='inner' (default), 'left', 'right', 'outer'. suffixes=('_x','_y') for conflicting column names.",
80
+ "DataSet.groupby": "Group by one or more columns. Returns a _GroupBy object with sum/mean/median/min/max/std/var/count/agg methods.",
81
+ "DataSet.melt": "Unpivot from wide to long format. id_vars= columns to keep, value_vars= columns to melt, var_name/value_name for output column names.",
82
+ "DataSet.pivot": "Pivot from long to wide format. index= row labels, columns= new column headers, values= cell values.",
83
+ "DataSet.items": "Iterate over (column_name, Series) pairs.",
84
+ "DataSet.iterrows": "Iterate over (index_label, row_Series) pairs.",
85
+ "DataSet.itertuples": "Iterate over rows as tuples. First element is the index label.",
86
+
87
+ "_GroupBy.sum": "Sum each group's non-key columns.",
88
+ "_GroupBy.mean": "Mean of each group's non-key columns.",
89
+ "_GroupBy.median": "Median of each group's non-key columns.",
90
+ "_GroupBy.min": "Min of each group's non-key columns.",
91
+ "_GroupBy.max": "Max of each group's non-key columns.",
92
+ "_GroupBy.std": "Standard deviation of each group. ddof=1 by default.",
93
+ "_GroupBy.var": "Variance of each group. ddof=1 by default.",
94
+ "_GroupBy.count": "Count non-NA values in each group.",
95
+ "_GroupBy.agg": "Aggregate with a string method name, callable, or dict mapping columns to methods/callables.",
96
+
97
+ "StringAccessor.lower": "Convert strings to lowercase.",
98
+ "StringAccessor.upper": "Convert strings to uppercase.",
99
+ "StringAccessor.strip": "Strip leading and trailing whitespace.",
100
+ "StringAccessor.contains": "Test if pattern is contained in each string. Returns a boolean Series.",
101
+ "StringAccessor.replace": "Replace occurrences of pat with repl in each string.",
102
+ "StringAccessor.len": "Return the length of each string as an integer Series.",
103
+ "StringAccessor.startswith": "Test if each string starts with prefix. Returns a boolean Series.",
104
+ "StringAccessor.endswith": "Test if each string ends with suffix. Returns a boolean Series.",
105
+ "StringAccessor.split": "Split each string by separator. Returns a Series of lists.",
106
+
107
+ "S3Client.__init__": "Create an S3 client. Reads credentials from args or env vars: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION.",
108
+ "S3Client.create_bucket": "Create a new S3 bucket.",
109
+ "S3Client.delete_bucket": "Delete an empty S3 bucket.",
110
+ "S3Client.delete_object": "Delete an object from S3.",
111
+ "S3Client.copy_object": "Copy an object from src_bucket/src_key to dest_bucket/dest_key.",
112
+ "S3Client.move_object": "Move an object (copy + delete source).",
113
+ "S3Client.upload_file": "Upload a local file to S3.",
114
+ "S3Client.download_file": "Download an S3 object to a local file path.",
115
+ "S3Client.list_objects": "List object keys in a bucket, optionally filtered by prefix. Handles pagination automatically.",
116
+
117
+ "is_na": "Test if a value is NA (the Atrax missing-value sentinel). Returns True for NA, False otherwise."
118
+ }
@@ -0,0 +1,48 @@
1
+ Metadata-Version: 2.4
2
+ Name: goliath-utils-docs
3
+ Version: 0.1.1
4
+ Summary: MCP server exposing goliath-utils API documentation to Claude Code
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: mcp>=1.0
8
+ Requires-Dist: goliath-utils>=0.1.3
9
+
10
+ # goliath-utils-docs
11
+
12
+ MCP server that exposes [goliath-utils](https://pypi.org/project/goliath-utils/) API documentation to Claude Code.
13
+
14
+ When configured, Claude Code gains access to three tools:
15
+
16
+ - **list_available_utils** — overview of all modules, classes, and functions
17
+ - **get_module_docs** — full API reference for a specific module
18
+ - **search_utils** — search across all utility APIs by keyword
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install goliath-utils-docs
24
+ goliath-utils-docs install
25
+ ```
26
+
27
+ This registers the MCP server in your Claude Code settings. Restart Claude Code to activate.
28
+
29
+ ## Uninstall
30
+
31
+ ```bash
32
+ goliath-utils-docs uninstall
33
+ ```
34
+
35
+ ## Manual configuration
36
+
37
+ Add to `~/.claude/settings.json`:
38
+
39
+ ```json
40
+ {
41
+ "mcpServers": {
42
+ "goliath-utils-docs": {
43
+ "command": "uvx",
44
+ "args": ["goliath-utils-docs", "serve"]
45
+ }
46
+ }
47
+ }
48
+ ```
@@ -0,0 +1,14 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/goliath_utils_docs/__init__.py
4
+ src/goliath_utils_docs/__main__.py
5
+ src/goliath_utils_docs/cli.py
6
+ src/goliath_utils_docs/introspect.py
7
+ src/goliath_utils_docs/server.py
8
+ src/goliath_utils_docs/supplements.json
9
+ src/goliath_utils_docs.egg-info/PKG-INFO
10
+ src/goliath_utils_docs.egg-info/SOURCES.txt
11
+ src/goliath_utils_docs.egg-info/dependency_links.txt
12
+ src/goliath_utils_docs.egg-info/entry_points.txt
13
+ src/goliath_utils_docs.egg-info/requires.txt
14
+ src/goliath_utils_docs.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ goliath-utils-docs = goliath_utils_docs.cli:main
@@ -0,0 +1,2 @@
1
+ mcp>=1.0
2
+ goliath-utils>=0.1.3
@@ -0,0 +1 @@
1
+ goliath_utils_docs