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.
- agent_artifact_kit-0.1.0.dist-info/METADATA +225 -0
- agent_artifact_kit-0.1.0.dist-info/RECORD +23 -0
- agent_artifact_kit-0.1.0.dist-info/WHEEL +4 -0
- agent_artifact_kit-0.1.0.dist-info/licenses/LICENSE +21 -0
- artifactkit/__init__.py +89 -0
- artifactkit/adapters/__init__.py +0 -0
- artifactkit/adapters/mcp_server.py +170 -0
- artifactkit/adapters/strands_tools.py +202 -0
- artifactkit/backends/__init__.py +0 -0
- artifactkit/backends/docx_backend.py +189 -0
- artifactkit/backends/pdf_backend.py +121 -0
- artifactkit/backends/pptx_backend.py +254 -0
- artifactkit/backends/xlsx_backend.py +159 -0
- artifactkit/core/__init__.py +0 -0
- artifactkit/core/backend.py +35 -0
- artifactkit/core/destinations.py +153 -0
- artifactkit/core/errors.py +36 -0
- artifactkit/core/models.py +416 -0
- artifactkit/core/observability.py +90 -0
- artifactkit/core/parsing.py +177 -0
- artifactkit/core/registry.py +46 -0
- artifactkit/core/retry.py +54 -0
- artifactkit/core/service.py +459 -0
|
@@ -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,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.
|
artifactkit/__init__.py
ADDED
|
@@ -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()
|