Repository navigation
Expand file tree
/
Copy pathpyproject.toml
More file actions
162 lines (154 loc) · 9.11 KB
/
Copy pathpyproject.toml
File metadata and controls
162 lines (154 loc) · 9.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
[project]
name = "comfy-mcp"
version = "0.10.0"
description = "MCP server for ComfyUI — a thin wrapper over comfy-cli."
readme = "README.md"
requires-python = ">=3.10"
# PEP 639 SPDX expression. `LicenseRef-Comfy-Commercial` is the SPDX form for a
# custom licence that has no registered identifier — the terms live in LICENSE
# and are obtained from licensing@comfy.org.
license = "AGPL-3.0-or-later OR LicenseRef-Comfy-Commercial"
license-files = ["LICENSE", "NOTICE"]
authors = [{ name = "Comfy Org" }]
dependencies = [
# >= 2 because that is where `mcp.server.mcpserver` exists: the SDK's 2.0.0
# DELETED `mcp.server.fastmcp` outright — no shim, no deprecation window —
# and this server's single SDK import (`from mcp.server.mcpserver import
# Context, Image, MCPServer`, server.py) resolves on 2.x only. On 1.x it is
# an ImportError that takes the whole suite down at conftest collection
# rather than at any one test. (Historical floors this supersedes: >= 1.10
# for elicitation, >= 1.14 to survive `ctx: Context | None` tool parameters.)
#
# < 3 for the same reason the `< 2` cap existed before this port: a major
# release must arrive as a reviewable Dependabot PR, not as unrelated PRs'
# CI turning red hours after a release nobody here asked for.
#
# `mcp-types` is deliberately NOT declared, and the two facts behind that
# only look like a contradiction: 2.x split the wire types out into a
# standalone `mcp-types` distribution, AND kept `mcp.types` inside the `mcp`
# distribution as a module that mirrors it (every name is the same object),
# which is the spelling `server.py` imports. The version is bounded either
# way — `mcp` 2.0.0 requires `mcp-types==2.0.0`, an EXACT pin, so a breaking
# `mcp-types` major cannot arrive except via an `mcp` release, which this
# `< 3` ceiling already gates into a reviewable Dependabot PR.
#
# `requires-python = ">=3.10"` above still holds: `mcp` 2.0.0 and
# `mcp-types` 2.0.0 both declare `Requires-Python: >=3.10`, so the major
# bump did not raise the floor out from under the 3.10 CI leg.
"mcp>=2,<3",
# Already an mcp dependency; declared explicitly because this server now
# imports it directly for the elicitation response schema.
"pydantic>=2",
# Already an mcp dependency (the SDK's own tool dispatcher uses it to run a
# `def` tool off the event loop); declared explicitly because `job()` now
# imports `anyio.to_thread` directly to off-load its blocking branches the
# same way, rather than wedging the loop for up to an hour (`action="wait"`).
# Floor matches what `mcp` itself requires.
"anyio>=4.9",
]
# comfy-cli is intentionally NOT a declared dependency: this server shells out
# to whatever `comfy` binary PATH/COMFY_BIN resolves to (which may differ from a
# pip-installed one), and the envelope/discover contract it wraps is carried by a
# specific comfy-cli build rather than a hard-pinnable PyPI release. Declaring it
# would install a second copy into THIS environment that the tools may never
# call, while doing nothing about the one they do — and not a small one: pip
# resolves comfy-cli into ~40 further distributions (GitPython, cookiecutter,
# uv, analytics clients), all of which a declaration would force on every
# install of this server, including the ones already driving a `comfy` on PATH.
# Compatibility is enforced at RUNTIME instead, against the binary actually
# invoked: `_check_comfy_version` holds a hard `>= _MIN_COMFY_CLI` floor before
# the first call, `_unwrap_envelope` asserts the envelope schema major, and
# `server_info` reports both plus the optional COMFY_CLI_MIN_VERSION. See
# src/comfy_mcp/server.py.
#
# The cost is real and is paid in the DOCS, not here: `pip install comfy-mcp`
# alone yields a server that completes the MCP handshake and then errors on every
# tool call until comfy-cli is installed. So the README quickstart installs both
# in one command (`pip install comfy-mcp "comfy-cli>=1.14.0"`) and says why, and
# tests/test_packaging.py pins that pairing — undeclared HERE requires documented
# THERE. Reverse this decision by all means, but reverse all of it together:
# this comment, that test, the README quickstart AND prerequisites, and the
# one-liner in AGENTS.md ("Toolchain") that sends contributors back here.
# The PyPI sidebar renders these, and it renders NOTHING without them: through
# 0.10.0 the published metadata carried no `project_urls` and an empty
# `Home-page`, so someone who found the package on PyPI had no route back to the
# repository, the docs, or the issue tracker. `Homepage` also backfills the
# legacy `Home-page` field that `pip show` prints.
#
# Keep every entry a page that is maintained OUTSIDE this file — the repo root,
# the docs site, the issue tracker — rather than a deep link into a document
# whose headings we would then have to keep in sync with a URL nobody here
# tests. `Documentation` is the docs site's MCP page (its "Local Comfy MCP
# Connection" section covers this package), which is the same page README.md
# points readers at.
[project.urls]
Homepage = "https://github.com/Comfy-Org/comfy-mcp"
Repository = "https://github.com/Comfy-Org/comfy-mcp"
Documentation = "https://docs.comfy.org/agent-tools/mcp"
Issues = "https://github.com/Comfy-Org/comfy-mcp/issues"
[project.optional-dependencies]
# The ruff floor is 0.16, not 0.15, because `[tool.ruff.lint] select` below now
# enables RUF100 (unused-`noqa`) — and 0.16 narrowed BLE001 to stop flagging a
# blind `except` that logs its exception. Under 0.15 those handlers still need a
# `# noqa: BLE001`; under 0.16 that same directive is an unused-directive error.
# A range spanning that change cannot be green on both, so pin past it: a lint
# toolchain whose verdicts differ by patch release is the exact problem the
# pinned rule selection exists to prevent.
dev = ["pytest>=8", "ruff>=0.16,<0.18"]
[project.scripts]
comfy-mcp = "comfy_mcp.server:main"
[build-system]
requires = ["setuptools>=77"]
build-backend = "setuptools.build_meta"
[tool.setuptools.packages.find]
where = ["src"]
[tool.pytest.ini_options]
# e2e needs a live local ComfyUI; cli_contract needs a real (unmocked)
# comfy-cli binary. Deselect both from plain `pytest` so the unit suite never
# probes the network or shells out to a real binary. Run them explicitly:
# `pytest -m e2e` / `pytest -m cli_contract` (a command-line -m comes after
# addopts, so it overrides this default).
addopts = "-m 'not e2e and not cli_contract'"
markers = [
"e2e: live end-to-end smoke test; needs a running local ComfyUI + comfy-cli (deselected by default; run with -m e2e)",
"cli_contract: live contract check against an installed comfy-cli binary (no ComfyUI needed)",
]
[tool.ruff]
target-version = "py310"
[tool.ruff.lint]
# Pin the rule selection EXPLICITLY rather than inheriting ruff's defaults.
#
# ruff 0.16 expanded its default selection from 59 rules to 413. With no
# `select` this repo silently inherited all of them, so a routine dev-dependency
# bump turned a green tree red with 60 findings — 52 of them stale `# noqa: ARG0xx`
# directives for a rule that was never enabled, plus 8 genuine ones.
#
# The first four are ruff's historical defaults: the set this codebase was
# written and reviewed against. Widening them is a deliberate decision, not
# something a linter release should make on our behalf — so widen here, in a PR,
# on purpose. What follows is that deliberate widening, one rule set at a time:
#
# ASYNC — blocking calls inside `async def`. This server's whole job is shelling
# out from async tools, so a blocking spawn or read on the event loop is
# the highest-value defect class ruff can find here.
# BLE — blind `except Exception`. Every remaining site now states its reason:
# the handlers that log the exception satisfy the rule outright (0.16
# exempts those), and the few that genuinely must swallow carry an
# explicit `# noqa: BLE001 - <reason>`. The rule now forces the NEXT
# blind catch to argue for itself the same way.
# S110 — `try`/`except`/`pass`. Selected as a single rule rather than all of
# `S` (flake8-bandit): the broader set flags `subprocess` use itself,
# which is this repo's entire reason to exist.
# I — import sorting. Cosmetic and auto-fixable; on so it cannot drift.
# RUF100 — unused/non-enabled `# noqa`. This is the rule that would have PREVENTED
# the mess this selection was written for: ~50 `# noqa: ARG0xx`
# directives had accumulated for `flake8-unused-arguments`, a rule this
# repo has never enabled, and nothing flagged them until a linter
# release changed its defaults. With RUF100 on, a directive that
# suppresses nothing is an error the day it is written.
#
# Deliberately NOT adopted: ARG (flake8-unused-arguments). The tests are largely
# interface-mirroring fakes whose unused parameters exist precisely BECAUSE the
# real signature has them, so the rule reports 306 sites here and finds no
# defects. The ~50 stale directives were removed rather than completed.
select = ["E4", "E7", "E9", "F", "ASYNC", "BLE", "S110", "I", "RUF100"]