testmcpy-oauth-probe 0.1.0__tar.gz
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.
- testmcpy_oauth_probe-0.1.0/PKG-INFO +66 -0
- testmcpy_oauth_probe-0.1.0/README.md +44 -0
- testmcpy_oauth_probe-0.1.0/pyproject.toml +48 -0
- testmcpy_oauth_probe-0.1.0/setup.cfg +4 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe/__init__.py +44 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe/__main__.py +3 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe/cli.py +152 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe/config.py +426 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe/discovery.py +733 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe/mcp.py +332 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe/models.py +231 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe/oauth.py +583 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe/reporters.py +117 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe/runner.py +243 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe/schemas/oauth-smoke-report-v1.schema.json +78 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe/schemas/oauth-smoke-v1.schema.json +468 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe/secrets.py +118 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe/transport.py +225 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe.egg-info/PKG-INFO +66 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe.egg-info/SOURCES.txt +22 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe.egg-info/dependency_links.txt +1 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe.egg-info/entry_points.txt +2 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe.egg-info/requires.txt +2 -0
- testmcpy_oauth_probe-0.1.0/testmcpy_oauth_probe.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: testmcpy-oauth-probe
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Headless, vendor-neutral OAuth and MCP interoperability probe
|
|
5
|
+
Author: Amin Ghadersohi
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/preset-io/testmcpy
|
|
8
|
+
Project-URL: Repository, https://github.com/preset-io/testmcpy
|
|
9
|
+
Project-URL: Issues, https://github.com/preset-io/testmcpy/issues
|
|
10
|
+
Keywords: oauth,mcp,ci,smoke-test,interoperability
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Testing
|
|
18
|
+
Requires-Python: <3.13,>=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: httpx<1.0.0,>=0.27.0
|
|
21
|
+
Requires-Dist: pyyaml<7.0,>=6.0
|
|
22
|
+
|
|
23
|
+
# testmcpy-oauth-probe
|
|
24
|
+
|
|
25
|
+
The independently installable, headless distribution of testmcpy's OAuth/MCP
|
|
26
|
+
interoperability probe. It depends on **HTTPX and PyYAML only** — installing it
|
|
27
|
+
into a release pipeline will not pull in an agent runtime, a web server or an
|
|
28
|
+
ORM, and so cannot conflict with whatever that pipeline already pins.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install "testmcpy-oauth-probe==0.1.0"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This distribution owns the `testmcpy-oauth` console script. The main `testmcpy`
|
|
35
|
+
wheel depends on this one and exposes the same core as `testmcpy auth`; it does
|
|
36
|
+
not ship a second copy of the package, so installing both is safe.
|
|
37
|
+
|
|
38
|
+
It reports observable interoperability and configured policy outcomes. It is
|
|
39
|
+
not a formal OAuth or MCP compliance certification.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
testmcpy-oauth validate --config auth-smoke.yaml
|
|
43
|
+
testmcpy-oauth schema --kind report > oauth-smoke-report-v1.schema.json
|
|
44
|
+
testmcpy-oauth check --config auth-smoke.yaml --format json --output report.json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`--config` and `--manifest` are accepted interchangeably.
|
|
48
|
+
|
|
49
|
+
`--service`, `--region`, `--revision` and `--deployment-id` are report labels
|
|
50
|
+
recorded in the `correlation` block. They are never compared against the
|
|
51
|
+
deployed target; the probe does not assert the running revision.
|
|
52
|
+
|
|
53
|
+
## Versioning
|
|
54
|
+
|
|
55
|
+
Versioned independently of `testmcpy`, because consumers pin it into release
|
|
56
|
+
pipelines and rely on its dependency closure staying at two libraries. The
|
|
57
|
+
manifest and report schemas carry their own version strings
|
|
58
|
+
(`testmcpy.io/oauth-smoke/v1`, `testmcpy.io/oauth-smoke-report/v1`) and do not
|
|
59
|
+
move with this package's version.
|
|
60
|
+
|
|
61
|
+
## Provenance
|
|
62
|
+
|
|
63
|
+
CI builds publish a `testmcpy-oauth-probe-<full Git SHA>` artifact containing
|
|
64
|
+
the wheel, source archive, `SOURCE_COMMIT`, and `SHA256SUMS`. Pin the full
|
|
65
|
+
commit/artifact name, confirm `SOURCE_COMMIT`, and verify the checksums before
|
|
66
|
+
installation.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# testmcpy-oauth-probe
|
|
2
|
+
|
|
3
|
+
The independently installable, headless distribution of testmcpy's OAuth/MCP
|
|
4
|
+
interoperability probe. It depends on **HTTPX and PyYAML only** — installing it
|
|
5
|
+
into a release pipeline will not pull in an agent runtime, a web server or an
|
|
6
|
+
ORM, and so cannot conflict with whatever that pipeline already pins.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install "testmcpy-oauth-probe==0.1.0"
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
This distribution owns the `testmcpy-oauth` console script. The main `testmcpy`
|
|
13
|
+
wheel depends on this one and exposes the same core as `testmcpy auth`; it does
|
|
14
|
+
not ship a second copy of the package, so installing both is safe.
|
|
15
|
+
|
|
16
|
+
It reports observable interoperability and configured policy outcomes. It is
|
|
17
|
+
not a formal OAuth or MCP compliance certification.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
testmcpy-oauth validate --config auth-smoke.yaml
|
|
21
|
+
testmcpy-oauth schema --kind report > oauth-smoke-report-v1.schema.json
|
|
22
|
+
testmcpy-oauth check --config auth-smoke.yaml --format json --output report.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`--config` and `--manifest` are accepted interchangeably.
|
|
26
|
+
|
|
27
|
+
`--service`, `--region`, `--revision` and `--deployment-id` are report labels
|
|
28
|
+
recorded in the `correlation` block. They are never compared against the
|
|
29
|
+
deployed target; the probe does not assert the running revision.
|
|
30
|
+
|
|
31
|
+
## Versioning
|
|
32
|
+
|
|
33
|
+
Versioned independently of `testmcpy`, because consumers pin it into release
|
|
34
|
+
pipelines and rely on its dependency closure staying at two libraries. The
|
|
35
|
+
manifest and report schemas carry their own version strings
|
|
36
|
+
(`testmcpy.io/oauth-smoke/v1`, `testmcpy.io/oauth-smoke-report/v1`) and do not
|
|
37
|
+
move with this package's version.
|
|
38
|
+
|
|
39
|
+
## Provenance
|
|
40
|
+
|
|
41
|
+
CI builds publish a `testmcpy-oauth-probe-<full Git SHA>` artifact containing
|
|
42
|
+
the wheel, source archive, `SOURCE_COMMIT`, and `SHA256SUMS`. Pin the full
|
|
43
|
+
commit/artifact name, confirm `SOURCE_COMMIT`, and verify the checksums before
|
|
44
|
+
installation.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "testmcpy-oauth-probe"
|
|
7
|
+
# Versioned independently of `testmcpy`. This distribution is release-pipeline
|
|
8
|
+
# infrastructure: consumers pin it exactly and expect its dependency closure to
|
|
9
|
+
# stay at two libraries, so it must not inherit testmcpy's release cadence.
|
|
10
|
+
version = "0.1.0"
|
|
11
|
+
description = "Headless, vendor-neutral OAuth and MCP interoperability probe"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
authors = [{name = "Amin Ghadersohi"}]
|
|
14
|
+
requires-python = ">=3.10,<3.13"
|
|
15
|
+
license = "Apache-2.0"
|
|
16
|
+
keywords = ["oauth", "mcp", "ci", "smoke-test", "interoperability"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: Software Development :: Testing",
|
|
25
|
+
]
|
|
26
|
+
# Two libraries, deliberately. Consumers install this into release pipelines
|
|
27
|
+
# that pin conflicting versions of the agent/server stack (sqlalchemy, fastmcp),
|
|
28
|
+
# so anything added here has to survive being resolved against someone else's
|
|
29
|
+
# lockfile. `unit_tests/test_oauth_probe_packaging.py` enforces the closure.
|
|
30
|
+
dependencies = [
|
|
31
|
+
"httpx>=0.27.0,<1.0.0",
|
|
32
|
+
"pyyaml>=6.0,<7.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
testmcpy-oauth = "testmcpy_oauth_probe.cli:main"
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://github.com/preset-io/testmcpy"
|
|
40
|
+
Repository = "https://github.com/preset-io/testmcpy"
|
|
41
|
+
Issues = "https://github.com/preset-io/testmcpy/issues"
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.packages.find]
|
|
44
|
+
where = ["."]
|
|
45
|
+
include = ["testmcpy_oauth_probe*"]
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.package-data]
|
|
48
|
+
testmcpy_oauth_probe = ["schemas/*.json"]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Typed public API for the headless OAuth/MCP probe."""
|
|
2
|
+
|
|
3
|
+
from testmcpy_oauth_probe.config import (
|
|
4
|
+
ConfigError,
|
|
5
|
+
load_manifest,
|
|
6
|
+
loads_manifest,
|
|
7
|
+
manifest_json_schema,
|
|
8
|
+
report_json_schema,
|
|
9
|
+
)
|
|
10
|
+
from testmcpy_oauth_probe.models import (
|
|
11
|
+
CONFIG_SCHEMA,
|
|
12
|
+
REPORT_SCHEMA,
|
|
13
|
+
AuthFlow,
|
|
14
|
+
CapabilityPolicy,
|
|
15
|
+
CheckResult,
|
|
16
|
+
CheckStatus,
|
|
17
|
+
Manifest,
|
|
18
|
+
ProbeReport,
|
|
19
|
+
RunReport,
|
|
20
|
+
TargetConfig,
|
|
21
|
+
)
|
|
22
|
+
from testmcpy_oauth_probe.runner import ProbeRunner, run_manifest
|
|
23
|
+
from testmcpy_oauth_probe.transport import HttpTransport
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"CONFIG_SCHEMA",
|
|
27
|
+
"REPORT_SCHEMA",
|
|
28
|
+
"AuthFlow",
|
|
29
|
+
"CapabilityPolicy",
|
|
30
|
+
"CheckResult",
|
|
31
|
+
"CheckStatus",
|
|
32
|
+
"ConfigError",
|
|
33
|
+
"Manifest",
|
|
34
|
+
"HttpTransport",
|
|
35
|
+
"ProbeReport",
|
|
36
|
+
"ProbeRunner",
|
|
37
|
+
"RunReport",
|
|
38
|
+
"TargetConfig",
|
|
39
|
+
"load_manifest",
|
|
40
|
+
"loads_manifest",
|
|
41
|
+
"manifest_json_schema",
|
|
42
|
+
"report_json_schema",
|
|
43
|
+
"run_manifest",
|
|
44
|
+
]
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Minimal argparse CLI, independent of testmcpy's UI/LLM dependencies."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import asyncio
|
|
7
|
+
import sys
|
|
8
|
+
from collections.abc import Sequence
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from testmcpy_oauth_probe.config import (
|
|
12
|
+
ConfigError,
|
|
13
|
+
dump_manifest_schema,
|
|
14
|
+
dump_report_schema,
|
|
15
|
+
load_manifest,
|
|
16
|
+
loads_manifest,
|
|
17
|
+
)
|
|
18
|
+
from testmcpy_oauth_probe.models import CONFIG_SCHEMA, Correlation
|
|
19
|
+
from testmcpy_oauth_probe.reporters import to_human, to_json, to_jsonl, to_junit
|
|
20
|
+
from testmcpy_oauth_probe.runner import ProbeRunner
|
|
21
|
+
|
|
22
|
+
# `--config` stays first so it remains the name in `--help` output and in every
|
|
23
|
+
# existing pipeline; `--manifest` is an accepted alias. argparse stores both
|
|
24
|
+
# under `dest="config"`.
|
|
25
|
+
_MANIFEST_FLAGS = ("--config", "--manifest")
|
|
26
|
+
_MANIFEST_KWARGS = {
|
|
27
|
+
"dest": "config",
|
|
28
|
+
"metavar": "PATH",
|
|
29
|
+
"help": "Path to the versioned probe manifest (YAML or JSON)",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
34
|
+
parser = argparse.ArgumentParser(
|
|
35
|
+
prog="testmcpy-oauth",
|
|
36
|
+
description="Vendor-neutral OAuth/MCP interoperability probe (not formal certification)",
|
|
37
|
+
)
|
|
38
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
39
|
+
validate = subparsers.add_parser("validate", help="Validate a manifest without network access")
|
|
40
|
+
# Everything else — the docs, the schema name, the Auth Smoke page, this
|
|
41
|
+
# tool's own help text — calls the file a manifest, so `--manifest` is the
|
|
42
|
+
# flag people reach for first. Accept both rather than make them guess.
|
|
43
|
+
validate.add_argument(*_MANIFEST_FLAGS, required=True, **_MANIFEST_KWARGS)
|
|
44
|
+
schema = subparsers.add_parser("schema", help="Print a versioned JSON Schema")
|
|
45
|
+
schema.add_argument("--kind", choices=("manifest", "report"), default="manifest")
|
|
46
|
+
schema.set_defaults(command="schema")
|
|
47
|
+
check = subparsers.add_parser("check", help="Run configured targets headlessly")
|
|
48
|
+
check.add_argument(*_MANIFEST_FLAGS, required=True, **_MANIFEST_KWARGS)
|
|
49
|
+
check.add_argument("--target", action="append", dest="targets")
|
|
50
|
+
check.add_argument("--profile")
|
|
51
|
+
check.add_argument("--format", choices=("human", "json", "jsonl"), default="human")
|
|
52
|
+
check.add_argument("--output", default="-")
|
|
53
|
+
check.add_argument("--junit")
|
|
54
|
+
check.add_argument("--run-id")
|
|
55
|
+
check.add_argument("--service", help="Report label only: recorded in correlation.service.")
|
|
56
|
+
check.add_argument("--region", help="Report label only: recorded in correlation.region.")
|
|
57
|
+
check.add_argument(
|
|
58
|
+
"--revision",
|
|
59
|
+
help=(
|
|
60
|
+
"Report label only: recorded in correlation.revision and never "
|
|
61
|
+
"compared against the deployed revision. This is not a revision assertion."
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
check.add_argument(
|
|
65
|
+
"--deployment-id", help="Report label only: recorded in correlation.deployment_id."
|
|
66
|
+
)
|
|
67
|
+
discover = subparsers.add_parser("discover", help="Run discovery without credentials")
|
|
68
|
+
discover.add_argument("--url", required=True)
|
|
69
|
+
discover.add_argument("--format", choices=("human", "json", "jsonl"), default="human")
|
|
70
|
+
return parser
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _write(path: str, content: str) -> None:
|
|
74
|
+
if path == "-":
|
|
75
|
+
sys.stdout.write(content)
|
|
76
|
+
else:
|
|
77
|
+
Path(path).write_text(content, encoding="utf-8")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _render(report_format: str, report: object) -> str:
|
|
81
|
+
from testmcpy_oauth_probe.models import RunReport
|
|
82
|
+
|
|
83
|
+
assert isinstance(report, RunReport)
|
|
84
|
+
if report_format == "json":
|
|
85
|
+
return to_json(report)
|
|
86
|
+
if report_format == "jsonl":
|
|
87
|
+
return to_jsonl(report)
|
|
88
|
+
return to_human(report)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
async def _run(args: argparse.Namespace) -> int:
|
|
92
|
+
if args.command == "discover":
|
|
93
|
+
manifest = loads_manifest(
|
|
94
|
+
f"""schema: {CONFIG_SCHEMA}
|
|
95
|
+
targets:
|
|
96
|
+
discovery:
|
|
97
|
+
mcp_url: {args.url!r}
|
|
98
|
+
oauth:
|
|
99
|
+
flow: none
|
|
100
|
+
# `discover` is advertised as read-only reconnaissance. error_probe
|
|
101
|
+
# defaults to true and now applies to every flow, so opt out explicitly
|
|
102
|
+
# rather than POST an unsupported grant to someone's token endpoint.
|
|
103
|
+
error_probe: false
|
|
104
|
+
"""
|
|
105
|
+
)
|
|
106
|
+
report = await ProbeRunner().run_manifest(manifest)
|
|
107
|
+
_write("-", _render(args.format, report))
|
|
108
|
+
return report.exit_code
|
|
109
|
+
manifest = load_manifest(args.config)
|
|
110
|
+
report = await ProbeRunner().run_manifest(
|
|
111
|
+
manifest,
|
|
112
|
+
target_ids=args.targets,
|
|
113
|
+
profile=args.profile,
|
|
114
|
+
run_id=args.run_id,
|
|
115
|
+
correlation_override=Correlation(
|
|
116
|
+
service=args.service,
|
|
117
|
+
region=args.region,
|
|
118
|
+
revision=args.revision,
|
|
119
|
+
deployment_id=args.deployment_id,
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
_write(args.output, _render(args.format, report))
|
|
123
|
+
if args.junit:
|
|
124
|
+
_write(args.junit, to_junit(report))
|
|
125
|
+
return report.exit_code
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
129
|
+
parser = build_parser()
|
|
130
|
+
args = parser.parse_args(argv)
|
|
131
|
+
try:
|
|
132
|
+
if args.command == "schema":
|
|
133
|
+
print(dump_report_schema() if args.kind == "report" else dump_manifest_schema())
|
|
134
|
+
return 0
|
|
135
|
+
if args.command == "validate":
|
|
136
|
+
manifest = load_manifest(args.config)
|
|
137
|
+
print(
|
|
138
|
+
f"valid {manifest.schema} manifest: {len(manifest.targets)} target(s), "
|
|
139
|
+
f"{len(manifest.profiles)} profile(s)"
|
|
140
|
+
)
|
|
141
|
+
return 0
|
|
142
|
+
return asyncio.run(_run(args))
|
|
143
|
+
except (ConfigError, ValueError) as exc:
|
|
144
|
+
print(f"configuration error: {exc}", file=sys.stderr)
|
|
145
|
+
return 2
|
|
146
|
+
except KeyboardInterrupt:
|
|
147
|
+
print("interrupted", file=sys.stderr)
|
|
148
|
+
return 2
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
if __name__ == "__main__":
|
|
152
|
+
raise SystemExit(main())
|