agent-artifact-kit 0.1.0__py3-none-any.whl

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,225 @@
1
+ Metadata-Version: 2.5
2
+ Name: agent-artifact-kit
3
+ Version: 0.1.0
4
+ Summary: Shared artifact-creation harness for agentic workflows: renders docx/pptx/xlsx/pdf or delivers arbitrary files, with pluggable destinations and verified writes. Installs as agent-artifact-kit, imports as artifactkit.
5
+ Project-URL: Repository, https://github.com/drimal/artifactkit
6
+ Project-URL: Issues, https://github.com/drimal/artifactkit/issues
7
+ Author: Dipak Rimal
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: agents,artifacts,docx,mcp,pdf,pptx,s3,strands,xlsx
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Office/Business
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: openpyxl>=3.1.0
23
+ Requires-Dist: pypdf>=4.0.0
24
+ Requires-Dist: python-docx>=1.1.0
25
+ Requires-Dist: python-pptx>=1.0.0
26
+ Requires-Dist: reportlab>=4.2.0
27
+ Provides-Extra: all
28
+ Requires-Dist: boto3>=1.34.0; extra == 'all'
29
+ Requires-Dist: mcp<2.0.0,>=1.0.0; extra == 'all'
30
+ Requires-Dist: strands-agents; extra == 'all'
31
+ Provides-Extra: dev
32
+ Requires-Dist: build; extra == 'dev'
33
+ Requires-Dist: pytest-cov; extra == 'dev'
34
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
35
+ Requires-Dist: twine; extra == 'dev'
36
+ Provides-Extra: docs
37
+ Requires-Dist: mkdocs; extra == 'docs'
38
+ Requires-Dist: mkdocs-material; extra == 'docs'
39
+ Requires-Dist: mkdocstrings[python]; extra == 'docs'
40
+ Requires-Dist: ruff; extra == 'docs'
41
+ Provides-Extra: mcp
42
+ Requires-Dist: mcp<2.0.0,>=1.0.0; extra == 'mcp'
43
+ Provides-Extra: s3
44
+ Requires-Dist: boto3>=1.34.0; extra == 's3'
45
+ Provides-Extra: strands
46
+ Requires-Dist: strands-agents; extra == 'strands'
47
+ Description-Content-Type: text/markdown
48
+
49
+ # artifactkit
50
+
51
+ Installs as `agent-artifact-kit`, imports as `artifactkit`
52
+ (same split as `beautifulsoup4` → `bs4`: the PyPI name is descriptive
53
+ for discoverability, the import name is short because that's what you
54
+ actually type in code).
55
+
56
+ Generic artifact creation harness for agentic workflows. Agents build a
57
+ declarative content spec; artifactkit renders it to docx/pptx/xlsx/pdf,
58
+ writes it to a destination (local disk or S3), verifies the write, and
59
+ returns the location. No agent workflow reimplements this on its own.
60
+
61
+ ## Install
62
+
63
+ ```
64
+ pip install agent-artifact-kit[s3] # local + S3
65
+ pip install agent-artifact-kit[all] # + Strands and MCP adapters
66
+ ```
67
+
68
+ All code below imports the package as `artifactkit` regardless of
69
+ which extras you installed.
70
+
71
+ ## Development
72
+
73
+ ```bash
74
+ pip install -e ".[dev]"
75
+ pytest tests/ -v
76
+ pytest tests/ --cov=artifactkit --cov-report=term-missing # 92% with all extras installed
77
+ ```
78
+
79
+ See `PUBLISHING.md` for how to build and release to PyPI, and
80
+ `mkdocs.yml` / `docs/` for the full documentation site (`pip install
81
+ -e ".[docs]"` then `mkdocs serve`).
82
+
83
+ ## Observability
84
+
85
+ Every call is logged (structured, correlated by an `operation_id`
86
+ returned on the result) and, if you supply a `MetricsSink`, measured.
87
+ See `docs/observability.md` for the exact log events, fields, and
88
+ metric names, or wire in your own sink:
89
+
90
+ ```python
91
+ from artifactkit import ArtifactService, MetricsSink
92
+
93
+ class MySink:
94
+ def increment(self, name, tags=None): ...
95
+ def timing(self, name, duration_ms, tags=None): ...
96
+
97
+ service = ArtifactService(metrics=MySink())
98
+ ```
99
+
100
+ ## Plain Python
101
+
102
+ ```python
103
+ from artifactkit import (
104
+ ArtifactService, ArtifactFormat, DocumentSpec, Heading, Paragraph,
105
+ destination_from_uri,
106
+ )
107
+
108
+ spec = DocumentSpec(
109
+ title="Q3 Summary",
110
+ blocks=(
111
+ Heading("Overview", level=1),
112
+ Paragraph.of("Revenue grew 12% quarter over quarter."),
113
+ ),
114
+ base_filename="q3_summary", # optional — bare name, no extension
115
+ )
116
+
117
+ service = ArtifactService()
118
+ result = service.create(
119
+ spec,
120
+ ArtifactFormat.DOCX,
121
+ destination=destination_from_uri("s3://reports-bucket/q3"),
122
+ include_presigned_url=True,
123
+ # filename omitted: resolves to "q3_summary.docx" from spec.base_filename,
124
+ # adapted to whatever format is requested. Pass filename= explicitly to
125
+ # override it for this call without changing the spec.
126
+ )
127
+ print(result.location, result.presigned_url)
128
+ ```
129
+
130
+ ## Strands agent
131
+
132
+ ```python
133
+ from strands import Agent
134
+ from artifactkit.adapters.strands_tools import create_docx, create_pptx, create_xlsx, create_pdf
135
+
136
+ agent = Agent(tools=[create_docx, create_pptx, create_xlsx, create_pdf])
137
+ ```
138
+
139
+ ## MCP server
140
+
141
+ ```
142
+ python -m artifactkit.adapters.mcp_server
143
+ ```
144
+
145
+ Any MCP-capable agent can then call `create_docx` / `create_pptx` /
146
+ `create_xlsx` / `create_pdf` with a JSON spec, a `destination_uri`, and
147
+ a `filename`.
148
+
149
+ ## Spec shape
150
+
151
+ See `artifactkit/core/parsing.py` for the exact dict shape each tool
152
+ expects — it's the single source of truth both adapters parse against.
153
+
154
+ ## Styling
155
+
156
+ Bare output from python-docx/pptx/openpyxl looks exactly like what it
157
+ is: unstyled. Set `theme` on any structured spec for a named preset
158
+ (`Theme.VIBRANT`, `Theme.CORPORATE`, `Theme.MINIMAL`) that each backend
159
+ applies in whatever way fits its format — colored headings and an
160
+ accent rule in a doc, a gradient hero slide with a decorative shape in
161
+ a deck, a native Excel Table with banded rows and a colored tab in a
162
+ workbook. See `docs/quickstart.md` for examples of all three.
163
+
164
+ ## Arbitrary files (code, images, anything else)
165
+
166
+ `create()` is for structured content that needs rendering (docx/pptx/xlsx/pdf).
167
+ For content you already have, code files, images, data files, whatever,
168
+ use `create_files()` instead. It skips rendering entirely and goes
169
+ straight to write + verify. If you hand it more than 5 files, it
170
+ bundles them into a single `.zip` automatically rather than doing one
171
+ write per file.
172
+
173
+ Each `RawFile` takes its content one of two ways:
174
+
175
+ ```python
176
+ from artifactkit import ArtifactService, RawFile, FileBundleSpec, destination_from_uri
177
+
178
+ # In-memory bytes (e.g. code the agent just wrote)
179
+ RawFile("main.py", content=b"print('hello')\n")
180
+
181
+ # A path already on disk (e.g. output from code that ran, a generated
182
+ # image, anything already written somewhere) -- read lazily at write
183
+ # time, never loaded into memory until it's actually needed.
184
+ RawFile("chart.png", source_path="/tmp/agent_run/chart.png")
185
+ ```
186
+
187
+ If a piece of code produced a whole directory of output, skip
188
+ building `RawFile`s by hand:
189
+
190
+ ```python
191
+ bundle = FileBundleSpec.from_directory("/tmp/agent_run", bundle_filename="run_output")
192
+ service = ArtifactService()
193
+ result = service.create_files(bundle, destination_from_uri("s3://reports-bucket/runs"))
194
+ # 5 or fewer files in the directory -> delivered individually
195
+ # 6+ -> bundled into run_output.zip
196
+ ```
197
+
198
+ Via Strands or MCP, this is the `deliver_files` tool. Each file entry
199
+ needs exactly one of `content_base64` (for content the agent is
200
+ holding directly) or `source_path` (for content already on disk,
201
+ avoids hauling large payloads through the model's context just to
202
+ hand them back to this tool).
203
+
204
+ **artifactkit never executes code or generates content.** It only
205
+ delivers bytes that already exist. If `app.py` needs to run and
206
+ produce files, that execution happens entirely outside this library,
207
+ with whatever sandboxed runtime the agent already has, and
208
+ `create_files()` picks up only once the output exists on disk.
209
+
210
+ ## Design notes
211
+
212
+ - Render, validate, and write/verify are three separate stages. Only
213
+ the write/verify stage retries; render and validate failures are
214
+ deterministic and retrying wastes time.
215
+ - `ArtifactValidationError` (bad spec/render) and `ArtifactWriteError`
216
+ (write couldn't be verified) are distinct exception types so a
217
+ calling agent can choose the right remediation.
218
+ - Destinations are pluggable via the `OutputDestination` protocol.
219
+ Adding GCS/Azure support means one new class, no changes to backends
220
+ or the service.
221
+ - `create()` and `create_files()` share the same write/verify/retry/
222
+ presign machinery (`ArtifactService._write_verified`), the only
223
+ difference is whether there's a render+validate step first. Arbitrary
224
+ files skip it because there's no format-specific structure to check
225
+ on content the caller already fully formed.
@@ -0,0 +1,23 @@
1
+ artifactkit/__init__.py,sha256=3FPygYTi1Tu8hWcITwYckBqyTZLd7ehrd_59_eOSkrQ,2193
2
+ artifactkit/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ artifactkit/adapters/mcp_server.py,sha256=2ukZKZarL4lLhg3uX4l3H8mL3zpVGOmRWX0O_AfTYkk,6194
4
+ artifactkit/adapters/strands_tools.py,sha256=liUv_tNbF8aLTfBolXJt4U9Ii8es_L9zc39wXWRTbLs,7081
5
+ artifactkit/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ artifactkit/backends/docx_backend.py,sha256=mk9I0lv9Wd3cQyiC-dSWM0dYe3AeZtjNCThhdr1jZEU,8074
7
+ artifactkit/backends/pdf_backend.py,sha256=Ln3q5Icn-xX0QBItI-20wf-2kRkVvm2RykhIwYP7Vxc,4657
8
+ artifactkit/backends/pptx_backend.py,sha256=_04-R7mwJqaebG3zZ4OiSwihhEZ24XW0VCxs1eWrU4w,10754
9
+ artifactkit/backends/xlsx_backend.py,sha256=8CABxJqaaDrfm2GdaC5VmYtRrOvrqA3GOqXZ4kZZ56w,6887
10
+ artifactkit/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ artifactkit/core/backend.py,sha256=Lj2Q_U6gqHFSrwcDyZmS36YujpHaiRvQclNEP8e1RZg,1294
12
+ artifactkit/core/destinations.py,sha256=xfTauiy325XSlkFc2erUsLH05GHptoRYmk17hmLEBeU,6632
13
+ artifactkit/core/errors.py,sha256=jKVauleNpXsKOvcF8FncQhqTQyf3fpBZm4guADOlgNA,1302
14
+ artifactkit/core/models.py,sha256=DFZxzFq0ia_lCY4pgYDGicaG2KlCNSyoYODDLBOe14k,15971
15
+ artifactkit/core/observability.py,sha256=Tuq3e_38TcP1RqN5KbV0twgRUFI_8iipC33urJF_uUA,3679
16
+ artifactkit/core/parsing.py,sha256=1hFFCmeOcYX0cfWzeuDIrTDt8Qy2RF9TD0Aq_xyMYi4,5869
17
+ artifactkit/core/registry.py,sha256=grcwYC60n6_ZbibIFxzCznPO__i8zBSvCoKSQzGA40U,1890
18
+ artifactkit/core/retry.py,sha256=jIKmEtUU7SytYbn6jAnvJDhqrk-pGqP7Yv526CfC5-Y,1850
19
+ artifactkit/core/service.py,sha256=2YFoqLniDif-XGeGStqBu8wnix36PC9onlyuMAZerQc,18983
20
+ agent_artifact_kit-0.1.0.dist-info/METADATA,sha256=NB-E-ldoX4ZUcnI_tAON1xvTbKMXlBj-bODBRjUjJW8,8373
21
+ agent_artifact_kit-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
22
+ agent_artifact_kit-0.1.0.dist-info/licenses/LICENSE,sha256=_qbBF4PexKUumWJrB_w2myr_zAeNLbbymtVKiD4iR54,1068
23
+ agent_artifact_kit-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dipak Rimal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,89 @@
1
+ """artifactkit: a shared artifact-creation harness for agentic workflows.
2
+
3
+ Public API surface — everything an agent or a plain-Python caller
4
+ needs is importable from the top level:
5
+
6
+ from artifactkit import (
7
+ ArtifactService, ArtifactFormat, ArtifactResult,
8
+ DocumentSpec, PresentationSpec, WorkbookSpec,
9
+ Heading, Paragraph, ListBlock, Table, Image, PageBreak, TextRun, TextStyle,
10
+ destination_from_uri,
11
+ ArtifactError, ArtifactValidationError, ArtifactWriteError,
12
+ )
13
+ """
14
+
15
+ from artifactkit.core.destinations import (
16
+ LocalDirectoryDestination,
17
+ OutputDestination,
18
+ S3Destination,
19
+ destination_from_uri,
20
+ )
21
+ from artifactkit.core.errors import (
22
+ ArtifactError,
23
+ ArtifactValidationError,
24
+ ArtifactWriteError,
25
+ DestinationError,
26
+ UnsupportedFormatError,
27
+ )
28
+ from artifactkit.core.models import (
29
+ ArtifactFormat,
30
+ ArtifactSpec,
31
+ DocumentSpec,
32
+ FileBundleSpec,
33
+ Heading,
34
+ Image,
35
+ ListBlock,
36
+ PageBreak,
37
+ Paragraph,
38
+ PresentationSpec,
39
+ Theme,
40
+ RawFile,
41
+ Sheet,
42
+ Slide,
43
+ Table,
44
+ TextRun,
45
+ TextStyle,
46
+ WorkbookSpec,
47
+ )
48
+ from artifactkit.core.observability import LoggingMetricsSink, MetricsSink, NoOpMetricsSink
49
+ from artifactkit.core.retry import RetryPolicy
50
+ from artifactkit.core.service import ArtifactResult, ArtifactService, FileDeliveryResult
51
+
52
+ __version__ = "0.1.0"
53
+
54
+ __all__ = [
55
+ "ArtifactService",
56
+ "ArtifactResult",
57
+ "ArtifactFormat",
58
+ "ArtifactSpec",
59
+ "RetryPolicy",
60
+ "DocumentSpec",
61
+ "Heading",
62
+ "Paragraph",
63
+ "ListBlock",
64
+ "Table",
65
+ "Image",
66
+ "PageBreak",
67
+ "TextRun",
68
+ "TextStyle",
69
+ "PresentationSpec",
70
+ "Theme",
71
+ "Slide",
72
+ "WorkbookSpec",
73
+ "Sheet",
74
+ "RawFile",
75
+ "FileBundleSpec",
76
+ "FileDeliveryResult",
77
+ "MetricsSink",
78
+ "NoOpMetricsSink",
79
+ "LoggingMetricsSink",
80
+ "OutputDestination",
81
+ "LocalDirectoryDestination",
82
+ "S3Destination",
83
+ "destination_from_uri",
84
+ "ArtifactError",
85
+ "ArtifactValidationError",
86
+ "ArtifactWriteError",
87
+ "UnsupportedFormatError",
88
+ "DestinationError",
89
+ ]
File without changes
@@ -0,0 +1,170 @@
1
+ """FastMCP server exposing artifactkit as MCP tools.
2
+
3
+ Deliberately duplicates the tool signatures in strands_tools.py rather
4
+ than sharing decorated functions: @tool (Strands) and @mcp.tool
5
+ (FastMCP) wrap differently, and trying to share one decorated function
6
+ across both frameworks is exactly the kind of cross-framework coupling
7
+ this harness exists to avoid at the call site. The logic underneath -
8
+ parse spec, resolve destination, call ArtifactService - is shared via
9
+ core/, which is the actual point of de-duplication.
10
+
11
+ Run standalone with: python -m artifactkit.adapters.mcp_server
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from mcp.server.fastmcp import FastMCP
17
+
18
+ from artifactkit.core.destinations import destination_from_uri
19
+ from artifactkit.core.errors import ArtifactError
20
+ from artifactkit.core.models import ArtifactFormat
21
+ from artifactkit.core.parsing import (
22
+ parse_document_spec,
23
+ parse_file_bundle_spec,
24
+ parse_presentation_spec,
25
+ parse_workbook_spec,
26
+ )
27
+ from artifactkit.core.service import ArtifactResult, ArtifactService, FileDeliveryResult
28
+
29
+ mcp = FastMCP("artifactkit")
30
+ _service = ArtifactService()
31
+
32
+
33
+ def _result_to_dict(result: ArtifactResult) -> dict:
34
+ return {
35
+ "location": result.location,
36
+ "format": result.format.value,
37
+ "size_bytes": result.size_bytes,
38
+ "write_attempts": result.write_attempts,
39
+ "shareable_url": result.presigned_url,
40
+ "shareable_url_expires_at": (
41
+ result.presigned_expires_at.isoformat() if result.presigned_expires_at else None
42
+ ),
43
+ }
44
+
45
+
46
+ def _delivery_result_to_dict(result: FileDeliveryResult) -> dict:
47
+ return {
48
+ "locations": list(result.locations),
49
+ "bundled": result.bundled,
50
+ "file_count": result.file_count,
51
+ "total_size_bytes": result.total_size_bytes,
52
+ "write_attempts": result.write_attempts,
53
+ "shareable_url": result.presigned_url,
54
+ "shareable_url_expires_at": (
55
+ result.presigned_expires_at.isoformat() if result.presigned_expires_at else None
56
+ ),
57
+ }
58
+
59
+
60
+ @mcp.tool()
61
+ def create_docx(
62
+ spec: dict, destination_uri: str, filename: str | None = None, include_shareable_link: bool = False
63
+ ) -> dict:
64
+ """Create a Word document from a structured content spec. See
65
+ parse_document_spec for the expected spec shape."""
66
+ try:
67
+ result = _service.create(
68
+ parse_document_spec(spec),
69
+ ArtifactFormat.DOCX,
70
+ destination_from_uri(destination_uri),
71
+ filename=filename,
72
+ include_presigned_url=include_shareable_link,
73
+ )
74
+ return _result_to_dict(result)
75
+ except ArtifactError as exc:
76
+ return {"error": str(exc), "error_type": type(exc).__name__}
77
+
78
+
79
+ @mcp.tool()
80
+ def create_pptx(
81
+ spec: dict, destination_uri: str, filename: str | None = None, include_shareable_link: bool = False
82
+ ) -> dict:
83
+ """Create a PowerPoint deck from a structured content spec. See
84
+ parse_presentation_spec for the expected spec shape."""
85
+ try:
86
+ result = _service.create(
87
+ parse_presentation_spec(spec),
88
+ ArtifactFormat.PPTX,
89
+ destination_from_uri(destination_uri),
90
+ filename=filename,
91
+ include_presigned_url=include_shareable_link,
92
+ )
93
+ return _result_to_dict(result)
94
+ except ArtifactError as exc:
95
+ return {"error": str(exc), "error_type": type(exc).__name__}
96
+
97
+
98
+ @mcp.tool()
99
+ def create_xlsx(
100
+ spec: dict, destination_uri: str, filename: str | None = None, include_shareable_link: bool = False
101
+ ) -> dict:
102
+ """Create an Excel workbook from a structured content spec. See
103
+ parse_workbook_spec for the expected spec shape."""
104
+ try:
105
+ result = _service.create(
106
+ parse_workbook_spec(spec),
107
+ ArtifactFormat.XLSX,
108
+ destination_from_uri(destination_uri),
109
+ filename=filename,
110
+ include_presigned_url=include_shareable_link,
111
+ )
112
+ return _result_to_dict(result)
113
+ except ArtifactError as exc:
114
+ return {"error": str(exc), "error_type": type(exc).__name__}
115
+
116
+
117
+ @mcp.tool()
118
+ def create_pdf(
119
+ spec: dict, destination_uri: str, filename: str | None = None, include_shareable_link: bool = False
120
+ ) -> dict:
121
+ """Create a PDF from a structured content spec. Uses the same spec
122
+ shape as create_docx."""
123
+ try:
124
+ result = _service.create(
125
+ parse_document_spec(spec),
126
+ ArtifactFormat.PDF,
127
+ destination_from_uri(destination_uri),
128
+ filename=filename,
129
+ include_presigned_url=include_shareable_link,
130
+ )
131
+ return _result_to_dict(result)
132
+ except ArtifactError as exc:
133
+ return {"error": str(exc), "error_type": type(exc).__name__}
134
+
135
+
136
+ @mcp.tool()
137
+ def deliver_files(
138
+ files: list[dict],
139
+ destination_uri: str,
140
+ bundle_filename: str | None = None,
141
+ zip_threshold: int = 5,
142
+ include_shareable_link: bool = False,
143
+ ) -> dict:
144
+ """Deliver an arbitrary set of files (code, images, data, anything
145
+ with content already in hand) to destination_uri. No rendering
146
+ happens; each file's content is written as-is.
147
+
148
+ If more than zip_threshold files are given (default 5), they are
149
+ bundled into a single .zip and delivered as one artifact instead
150
+ of one write per file.
151
+
152
+ files shape: [{"filename": "app.py", "content_base64": "..."}, ...]
153
+ Each entry needs exactly one of content_base64 (base64-encoded
154
+ bytes) or source_path (a path already on disk).
155
+ """
156
+ try:
157
+ bundle_spec = parse_file_bundle_spec({"files": files, "bundle_filename": bundle_filename})
158
+ result = _service.create_files(
159
+ bundle_spec,
160
+ destination_from_uri(destination_uri),
161
+ zip_threshold=zip_threshold,
162
+ include_presigned_url=include_shareable_link,
163
+ )
164
+ return _delivery_result_to_dict(result)
165
+ except ArtifactError as exc:
166
+ return {"error": str(exc), "error_type": type(exc).__name__}
167
+
168
+
169
+ if __name__ == "__main__":
170
+ mcp.run()