[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] # The PyPI distribution name only. The product, the console script, the MCP # server name, the config directory and the import package are all still # `gsc-mcp` / `gsc_mcp` -- none of those move, and paths.APP_NAME especially # must not, or every existing install loses its config directory. # # The plain name was unavailable: PyPI `gsc-mcp` was taken on 2026-04-25 by an # unrelated Search Console MCP server, and `google-search-console-mcp` by # another. `indexer` is the honest differentiator rather than a workaround -- # neither of those drives Request Indexing in a browser, which is the part of # this that cannot be done through an API at all. name = "gsc-indexer-mcp" # Read from src/gsc_mcp/__init__.py rather than written here, so the wheel's # version and the version the MCP handshake reports have one source. See # [tool.hatch.version] at the foot of this file. dynamic = ["version"] description = "Google Search Console MCP server: index status, discovery, request indexing, sitemap submission, and search analytics for Claude and other AI agents" readme = "README.md" requires-python = ">=3.11" license = { file = "LICENSE" } authors = [{ name = "Shahid Ali" }] keywords = [ "mcp", "model-context-protocol", "mcp-server", "google-search-console", "search-console", "gsc", "seo", "indexing", "url-inspection", "sitemap", "search-analytics", "claude", "ai-agents", ] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "License :: Other/Proprietary License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Internet :: WWW/HTTP :: Indexing/Search", "Topic :: Software Development :: Libraries :: Python Modules", ] dependencies = [ "requests>=2.31", # >=2.0: this server is written against mcp 2.0's MCPServer # (mcp.server.mcpserver) and its Client-based test transport; 1.x # spelled both differently (FastMCP, mcp.shared.memory), so there is no # version range that satisfies both shapes. <3: the 1.x -> 2.0 break # was real, so the next major is assumed breaking until read. "mcp>=2.0,<3", # >=12: the release that introduced the THREADING (sync) implementation # the bridge server is built on. Everything bridge.py touches is present # in 12.0 as shipped -- serve(), Server.serve_forever(), Server.shutdown(), # Server.socket, and Connection.recv(timeout=...). The bridge deliberately # does not depend on Server.shutdown() closing established connections: # on 13.0-16.x it only closes the LISTENING socket (verified in review), # so the read loop polls with a bounded recv() instead and teardown works # the same on every release from 12 up. # <18: an upper bound because nothing above 17 has been tried here. 17.0 # already changed shutdown()'s signature and semantics -- the code # survives that one, but a public package should not silently adopt a # major it has never been run against. "websockets>=12,<18", # Sitemaps are remote, third-party XML. defusedxml is the entity-expansion # and external-entity defence; there is deliberately no stdlib fallback, # because silently downgrading to an unsafe parser is worse than a # missing dependency. Pure-Python and stable; no upper bound needed. "defusedxml>=0.7", ] [project.scripts] # Two names for one entry point, on purpose. `gsc-mcp` is what every doc and # every MCP client config says, and it stays. But a different project owns # `gsc-mcp` on PyPI, and if it also installs a `gsc-mcp` command then whichever # package a user installed last silently wins the name. The distribution-named # alias is the one that cannot be taken from under us -- prefer it in anything # where the collision would be confusing. gsc-mcp = "gsc_mcp.server:main" gsc-indexer-mcp = "gsc_mcp.server:main" [project.urls] Homepage = "https://www.shahidali.co/tools/gsc-mcp/" Documentation = "https://www.shahidali.co/tools/gsc-mcp/" Repository = "https://github.com/Mrshahidali420/google-search-console-mcp" Issues = "https://github.com/Mrshahidali420/google-search-console-mcp/issues" Changelog = "https://github.com/Mrshahidali420/google-search-console-mcp/blob/main/CHANGELOG.md" [project.optional-dependencies] # pytest 9.1 is the floor, not 8.x: the runlog tests rely on caplog attaching # to a non-propagating logger, which this package's runlog deliberately sets. dev = ["pytest>=9.1", "pytest-cov>=5.0"] [tool.hatch.version] path = "src/gsc_mcp/__init__.py" [tool.hatch.build.targets.wheel] # The vendored browser extension (src/gsc_mcp/extension/) ships as package # data. Hatchling carries non-Python files inside a listed package by default, # so no force-include entry is needed: a wheel built from this config was # unpacked and confirmed to contain all ten extension files under # gsc_mcp/extension/. Re-check that if the build backend is ever changed -- # a package-data glob that silently matches nothing fails at install time, # not in CI. packages = ["src/gsc_core", "src/gsc_mcp"] # _embedded.py carries the OAuth client CI injects at build time (decision # D1). It is gitignored so the secret never reaches this public repo, and # hatchling excludes VCS-ignored files from builds by default — which would # silently ship a wheel WITHOUT the client while every check still passed. # This re-includes it. The release workflow unpacks the built wheel and # fails if the client is not in it, because "silently absent" is the one # outcome this entry exists to prevent and a config line is not proof. artifacts = ["src/gsc_mcp/_embedded.py"] [tool.pytest.ini_options] testpaths = ["tests"] pythonpath = ["tests"]