# Packaging is OPTIONAL. Users may simply download perplexity_agent_mcp.py and # run it with python3; this file exists so `uvx --from git+... ` also works. # flit_core is used because it resolves to exactly one package with zero # transitive dependencies (hatchling pulls five), and because it reads the # version and description straight out of the module — so they cannot drift. # setuptools, not flit_core. flit was the original choice because it resolves # to exactly one package with zero transitive dependencies — but it builds # exactly ONE module per distribution, and this project now ships two: the MCP # server and the optional `llm` plugin adapter. setuptools is the only backend # that keeps the property flit was chosen for (still one package, still zero # transitive dependencies) while shipping both. hatchling would have cost five. [build-system] requires = ["setuptools>=77"] build-backend = "setuptools.build_meta" [project] name = "perplexity-agent-mcp" requires-python = ">=3.10" dependencies = [] dynamic = ["version"] description = "Zero-dependency MCP server for Perplexity's Agent API." readme = "README.md" license = { file = "LICENSE" } authors = [{ name = "Constantin Gonzalez", email = "constantin@glez.de" }] keywords = ["mcp", "perplexity", "model-context-protocol", "research"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Topic :: Software Development :: Libraries", ] [project.urls] Homepage = "https://github.com/zalez/perplexity-agent-mcp" Issues = "https://github.com/zalez/perplexity-agent-mcp/issues" Changelog = "https://github.com/zalez/perplexity-agent-mcp/blob/main/CHANGELOG.md" Security = "https://github.com/zalez/perplexity-agent-mcp/blob/main/SECURITY.md" "Release notes" = "https://github.com/zalez/perplexity-agent-mcp/releases" [project.scripts] perplexity-agent-mcp = "perplexity_agent_mcp:main" [tool.setuptools] # One module, no package directory: the server stays a single file you can # read end to end. The `llm` adapter is a separate distribution built from # llm-plugin/ — see that directory's pyproject.toml. py-modules = ["perplexity_agent_mcp"] [tool.setuptools.dynamic] # Still one source of truth for the version: the module's own __version__. # setuptools AST-parses this rather than importing it. version = { attr = "perplexity_agent_mcp.__version__" } [tool.ruff] line-length = 100 target-version = "py310" [tool.ruff.lint] select = ["E", "F", "W", "I", "N", "UP", "B", "A", "C4", "S", "RUF", "ANN"] # ANN401: we annotate `object` rather than `Any` throughout, so the few places # a truly dynamic type is unavoidable would otherwise need a per-line noqa. # S310 (urllib with a non-literal URL) and S311 (non-crypto random, used only # for retry jitter) are suppressed at their single call sites with noqa, so a # reviewer sees the justification next to the code rather than in this file. ignore = ["ANN401"] [tool.ruff.lint.isort] # Both modules are this project's own code. ruff infers first-party from the # project layout, and `perplexity_agent_llm` stopped being inferable when it # moved into llm-plugin/ to become its own distribution — so say it outright # rather than let import grouping depend on where a file happens to sit. known-first-party = ["perplexity_agent_mcp", "perplexity_agent_llm"] [tool.ruff.lint.per-file-ignores] # S101/ANN: asserts and loose annotations are fine in tests. # S603: every subprocess-driven protocol test calls subprocess.run() with a # fixed argv ([sys.executable, str(SERVER)]) — never shell=True, never # attacker-controlled input. Bandit's "audit every subprocess call" warning # is a structural false positive for this whole file's testing strategy # (tests/test_mcp_protocol.py drives the server as a real subprocess # deliberately, to catch stdout/framing bugs in-process tests would miss), # not a one-off worth a noqa at each of its several call sites. "tests/*" = ["S101", "ANN", "S603"] [tool.mypy] python_version = "3.10" strict = true warn_unreachable = true # tomllib is stdlib from 3.11. The import below is guarded by a skipIf and only # executes on interpreters that have it, but mypy checks it against the 3.10 # floor declared above and cannot see the guard. Depending on mypy's import # resolution path, the same root cause surfaces as either import-not-found # (module absent for the target Python version) or import-untyped (module # found on the host interpreter but with no stub certified for that version) — # both are disabled here rather than just one so the outcome doesn't depend on # which path mypy happens to take. # `llm` is an optional extra. Whole-tree `mypy --strict .` must stay clean on a # checkout that has not installed it, which is the normal state for anyone # using only the MCP server. # With `llm` absent it resolves to Any, so --strict objects to subclassing it # and to its untyped decorator. Those two checks are meaningless without the # package and meaningful with it, so they are relaxed HERE only and the real # check runs in CI's `llm-adapter` job, which installs the extra first. [[tool.mypy.overrides]] module = "perplexity_agent_llm" disallow_subclassing_any = false disallow_untyped_decorators = false [[tool.mypy.overrides]] module = ["llm", "llm.*", "pydantic", "pydantic.*"] ignore_missing_imports = true # Both of these read pyproject.toml with `tomllib`, which is stdlib from 3.11 # and ships no type stubs. The imports are guarded by skipIf and only execute # on interpreters that have it; mypy checks against the 3.10 floor and cannot # see the guard. [[tool.mypy.overrides]] module = ["tests.test_no_dependencies", "tests.test_docs_version_pins"] disable_error_code = ["import-not-found", "import-untyped"]