jev-mcp-python 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.
Files changed (65) hide show
  1. jev_mcp/__init__.py +1 -0
  2. jev_mcp/__main__.py +3 -0
  3. jev_mcp/domain/__init__.py +32 -0
  4. jev_mcp/domain/answers.py +25 -0
  5. jev_mcp/domain/json.py +49 -0
  6. jev_mcp/domain/questions.py +75 -0
  7. jev_mcp/domain/usage.py +16 -0
  8. jev_mcp/errors.py +59 -0
  9. jev_mcp/extract/__init__.py +1 -0
  10. jev_mcp/extract/candidates.py +75 -0
  11. jev_mcp/extract/dialect.py +400 -0
  12. jev_mcp/extract/executor.py +118 -0
  13. jev_mcp/extract/worker.py +198 -0
  14. jev_mcp/ids.py +49 -0
  15. jev_mcp/limits.py +218 -0
  16. jev_mcp/policy/__init__.py +98 -0
  17. jev_mcp/policy/actions.py +41 -0
  18. jev_mcp/policy/claims.py +103 -0
  19. jev_mcp/policy/extract.py +73 -0
  20. jev_mcp/policy/ranking.py +41 -0
  21. jev_mcp/policy/review.py +73 -0
  22. jev_mcp/policy/screen.py +48 -0
  23. jev_mcp/policy/thresholds.py +74 -0
  24. jev_mcp/providers/__init__.py +26 -0
  25. jev_mcp/providers/base.py +236 -0
  26. jev_mcp/providers/cloudflare.py +59 -0
  27. jev_mcp/providers/compatible.py +43 -0
  28. jev_mcp/providers/openrouter.py +47 -0
  29. jev_mcp/providers/resolver.py +106 -0
  30. jev_mcp/providers/typesafe.py +127 -0
  31. jev_mcp/py.typed +0 -0
  32. jev_mcp/serialize.py +199 -0
  33. jev_mcp/server.py +176 -0
  34. jev_mcp/settings.py +73 -0
  35. jev_mcp/stdio.py +99 -0
  36. jev_mcp/telemetry.py +223 -0
  37. jev_mcp/text.py +42 -0
  38. jev_mcp/tools/__init__.py +20 -0
  39. jev_mcp/tools/arguments.py +447 -0
  40. jev_mcp/tools/base.py +153 -0
  41. jev_mcp/tools/classify.py +187 -0
  42. jev_mcp/tools/common.py +96 -0
  43. jev_mcp/tools/compare.py +143 -0
  44. jev_mcp/tools/decide.py +206 -0
  45. jev_mcp/tools/extract.py +262 -0
  46. jev_mcp/tools/find.py +113 -0
  47. jev_mcp/tools/gate.py +236 -0
  48. jev_mcp/tools/observed.py +69 -0
  49. jev_mcp/tools/rerank.py +139 -0
  50. jev_mcp/tools/review.py +236 -0
  51. jev_mcp/tools/screen.py +126 -0
  52. jev_mcp/tools/toolset.py +92 -0
  53. jev_mcp/tools/verify.py +141 -0
  54. jev_mcp/validation/__init__.py +25 -0
  55. jev_mcp/validation/caps.py +93 -0
  56. jev_mcp/validation/choice.py +65 -0
  57. jev_mcp/validation/extract.py +48 -0
  58. jev_mcp/validation/noul.py +15 -0
  59. jev_mcp/validation/numbers.py +21 -0
  60. jev_mcp/validation/score.py +20 -0
  61. jev_mcp_python-0.1.0.dist-info/METADATA +18 -0
  62. jev_mcp_python-0.1.0.dist-info/RECORD +65 -0
  63. jev_mcp_python-0.1.0.dist-info/WHEEL +4 -0
  64. jev_mcp_python-0.1.0.dist-info/entry_points.txt +2 -0
  65. jev_mcp_python-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,141 @@
1
+ """jev_verify: check claims against evidence (`index.ts:121-238`)."""
2
+
3
+ from typing import Any
4
+
5
+ from jev_mcp.domain import ChoiceQuestion, Question, is_json_object
6
+ from jev_mcp.ids import ensure_unique_ids
7
+ from jev_mcp.limits import VERIFY
8
+ from jev_mcp.policy import DEFAULT_AUTO_ACCEPT
9
+ from jev_mcp.tools.base import JevTool, Runtime, ToolResult, caller_actions, define, frame, headline
10
+ from jev_mcp.tools.common import EVIDENCE_SCHEMA, evidence_items
11
+ from jev_mcp.tools.observed import validate_choice, verify_action
12
+
13
+ RELATION_TO_VERDICT = {"supports": "verified", "contradicts": "contradicted", "says_nothing": "unsupported"}
14
+ """`RELATION_TO_VERDICT` (`lib.ts:53-57`)."""
15
+
16
+ RELATION_CRITERIA = {
17
+ "supports": "The evidence states the claim or directly implies that it is true",
18
+ "contradicts": "The evidence states the opposite of the claim or implies that it is false",
19
+ "says_nothing": "The evidence does not address what the claim asserts, either way",
20
+ }
21
+ NO_SOURCE = "No single evidence item contains the content the claim depends on"
22
+
23
+ DEFINITION = define(
24
+ "jev_verify",
25
+ "Verify claims against evidence",
26
+ "Check each claim against provided evidence text with TypeSafe Jev. Returns per claim: verdict (verified | "
27
+ "contradicted | unsupported), full probability distribution, confidence, and whether the verdict stands on its "
28
+ "own (auto) or needs human review. Pattern: docs.typesafe.ai/cookbooks/citation_check. Pass reports, PR "
29
+ "descriptions, or agent briefs as claims and their cited sources, diffs, or documents as evidence.",
30
+ {
31
+ "type": "object",
32
+ "properties": {
33
+ "claims": {
34
+ "type": "array",
35
+ "items": {"type": "string"},
36
+ "minItems": VERIFY.claims_min,
37
+ "description": "Claims to verify, e.g. individual factual statements from a report.",
38
+ },
39
+ "evidence": EVIDENCE_SCHEMA,
40
+ "auto_accept": {
41
+ "type": "number",
42
+ "minimum": 0,
43
+ "maximum": 1,
44
+ "description": "Verdicts at or above this confidence stand automatically; below it they are flagged "
45
+ "'review'. Default 0.8.",
46
+ },
47
+ },
48
+ "required": ["claims", "evidence"],
49
+ "additionalProperties": False,
50
+ },
51
+ )
52
+
53
+
54
+ async def handle(args: dict[str, Any], runtime: Runtime) -> ToolResult:
55
+ auto_accept: float = args.get("auto_accept", DEFAULT_AUTO_ACCEPT)
56
+ evidence = ensure_unique_ids(evidence_items(args["evidence"]), "evidence").items
57
+ claims = ensure_unique_ids([{"text": text} for text in args["claims"]], "claim").items
58
+
59
+ questions: dict[str, Question] = {}
60
+ for claim in claims:
61
+ claim_id, text = claim["id"], claim["text"]
62
+ questions[f"relation_{claim_id}"] = ChoiceQuestion(
63
+ f"How does the evidence relate to claim `{claim_id}` ({text})?", RELATION_CRITERIA
64
+ )
65
+ if len(evidence) > 1:
66
+ criteria: dict[str, str | None] = {str(item["id"]): None for item in evidence}
67
+ criteria["none"] = NO_SOURCE
68
+ questions[f"source_{claim_id}"] = ChoiceQuestion(
69
+ f"Which evidence item does claim `{claim_id}` ({text}) rest on?", criteria
70
+ )
71
+
72
+ state = {
73
+ "purpose": "Verify each claim in claims against the evidence in evidence.",
74
+ "claims": claims,
75
+ "evidence": evidence,
76
+ }
77
+ evaluation = await runtime.ask(state, questions)
78
+ answers = evaluation.answers
79
+ source_keys = [*(str(item["id"]) for item in evidence), "none"]
80
+
81
+ results: list[dict[str, object]] = []
82
+ for claim in claims:
83
+ relation = answers.get(f"relation_{claim['id']}")
84
+ validated = validate_choice(relation, RELATION_TO_VERDICT)
85
+ # source_* is auxiliary and asked only for several evidence items: its absence never
86
+ # invalidates the relation, but a present source must name a supplied id or "none".
87
+ source = validate_choice(answers.get(f"source_{claim['id']}"), source_keys)
88
+ # Q4 (ADR-0012): a present but malformed confidence invalidates the relation.
89
+ if (
90
+ validated is None
91
+ or not is_json_object(relation)
92
+ or (relation.get("confidence") is not None and validated.confidence is None)
93
+ ):
94
+ results.append(
95
+ {
96
+ "id": claim["id"],
97
+ "claim": claim["text"],
98
+ "verdict": "unknown",
99
+ "probabilities": None,
100
+ "confidence": None,
101
+ "status": "invalid_response",
102
+ "action": "review",
103
+ "supporting_evidence": None,
104
+ }
105
+ )
106
+ continue
107
+ confidence = validated.confidence
108
+ results.append(
109
+ {
110
+ "id": claim["id"],
111
+ "claim": claim["text"],
112
+ "verdict": RELATION_TO_VERDICT[validated.choice],
113
+ "probabilities": validated.probabilities,
114
+ "confidence": confidence,
115
+ "action": "review" if confidence is None else verify_action(confidence, auto_accept),
116
+ "supporting_evidence": source.choice if source is not None and source.choice != "none" else None,
117
+ }
118
+ )
119
+
120
+ item_actions = caller_actions(r["action"] for r in results)
121
+ return ToolResult(
122
+ frame(
123
+ "jev_verify",
124
+ evaluation,
125
+ {
126
+ "auto_accept": auto_accept,
127
+ "summary": {
128
+ "verified": sum(1 for r in results if r["verdict"] == "verified"),
129
+ "contradicted": sum(1 for r in results if r["verdict"] == "contradicted"),
130
+ "unsupported": sum(1 for r in results if r["verdict"] == "unsupported"),
131
+ "needs_review": sum(1 for r in results if r["action"] == "review"),
132
+ },
133
+ "results": results,
134
+ },
135
+ ),
136
+ action=headline(item_actions),
137
+ item_actions=item_actions,
138
+ )
139
+
140
+
141
+ TOOL = JevTool(DEFINITION, handle)
@@ -0,0 +1,25 @@
1
+ """Per-question answer validation. Validation rejects; policy decides (ADR-0002)."""
2
+
3
+ from jev_mcp.validation.choice import (
4
+ ARGMAX_TOLERANCE,
5
+ PROBABILITY_SUM_TOLERANCE,
6
+ margin,
7
+ top_probability,
8
+ validate_choice,
9
+ )
10
+ from jev_mcp.validation.extract import EXTRACT_SUM_TOLERANCE, validate_extract_choice
11
+ from jev_mcp.validation.noul import validate_noul
12
+ from jev_mcp.validation.score import MAX_SCORE, validate_score
13
+
14
+ __all__ = [
15
+ "ARGMAX_TOLERANCE",
16
+ "EXTRACT_SUM_TOLERANCE",
17
+ "MAX_SCORE",
18
+ "PROBABILITY_SUM_TOLERANCE",
19
+ "margin",
20
+ "top_probability",
21
+ "validate_choice",
22
+ "validate_extract_choice",
23
+ "validate_noul",
24
+ "validate_score",
25
+ ]
@@ -0,0 +1,93 @@
1
+ """Applying a cap: truncation, budget errors and their frozen texts (ADR-0014). Values live in
2
+ `limits.py`, measurement in `text.py`; the reject behavior is each tool's schema. Every budget here is
3
+ a strict greater-than check — the manifest's `>8000 → error` means 8000 is allowed — and every error
4
+ string is byte-locked by parity fixtures.
5
+
6
+ A tool cuts through one `CapLedger` per call, so a cut and its record cannot drift apart.
7
+ """
8
+
9
+ from dataclasses import dataclass
10
+ from typing import Literal
11
+
12
+ from jev_mcp.serialize import js_number_to_locale_string_en_us
13
+ from jev_mcp.text import length, truncate
14
+
15
+
16
+ def exceeds(total: int, cap: int) -> bool:
17
+ """Strictly over budget: the cap itself is allowed (manifest `caps.$comment`)."""
18
+ return total > cap
19
+
20
+
21
+ @dataclass(frozen=True, slots=True)
22
+ class CappedText:
23
+ """A text cut to its cap, and whether the cut dropped anything."""
24
+
25
+ value: str
26
+ truncated: bool
27
+
28
+
29
+ def cap_text(text: str, cap: int) -> CappedText:
30
+ """`truncate(text, cap)` with its flag: truncated exactly when `text` is strictly over `cap` UTF-16 units."""
31
+ return CappedText(truncate(text, cap), exceeds(length(text), cap))
32
+
33
+
34
+ type CapScope = Literal["context", "item"]
35
+ """Who reads a cut. `context`: a document the judgment is made over (review and gate inputs, gate
36
+ claims and evidence, jev_extract's candidate universe), so Policy never returns auto over it.
37
+ `item`: a candidate's or class's own text (jev_find, jev_rerank, jev_classify), telemetry only — the
38
+ reference returns auto over cut item text."""
39
+
40
+
41
+ class CapLedger:
42
+ """One call's cuts: `text` cuts as `truncate` does and records the scope of every cut it makes."""
43
+
44
+ __slots__ = ("_scopes",)
45
+
46
+ def __init__(self) -> None:
47
+ self._scopes: set[CapScope] = set()
48
+
49
+ def text(self, value: str, cap: int, scope: CapScope) -> str:
50
+ capped = cap_text(value, cap)
51
+ if capped.truncated:
52
+ self._scopes.add(scope)
53
+ return capped.value
54
+
55
+ def note(self, scope: CapScope) -> None:
56
+ """Record a cut made elsewhere, such as candidates the regex executor capped or skipped."""
57
+ self._scopes.add(scope)
58
+
59
+ @property
60
+ def context_cut(self) -> bool:
61
+ """Whether a document the judgment is made over was cut: the only cut Policy reads."""
62
+ return "context" in self._scopes
63
+
64
+ @property
65
+ def scopes(self) -> frozenset[CapScope]:
66
+ """Every scope cut so far, for `ToolResult.truncated`."""
67
+ return frozenset(self._scopes)
68
+
69
+
70
+ def candidate_budget_error(total: int, cap: int, remedy: str) -> str:
71
+ """jev_rerank and jev_extract share this frozen scaffold; only the remedy sentence differs."""
72
+ return f"Batch too large: {total} candidate characters exceeds the {cap} character budget. {remedy}"
73
+
74
+
75
+ def classify_budget_error(items: int, classes: int, cap: int) -> str:
76
+ """jev_classify's item-class pair budget: the cap renders as `toLocaleString('en-US')` digits."""
77
+ return (
78
+ f"Batch too large: {items} items x {classes} classes exceeds the "
79
+ f"{js_number_to_locale_string_en_us(cap)} item-class budget. Split the batch."
80
+ )
81
+
82
+
83
+ def gate_evidence_items_error(cap: int) -> str:
84
+ """jev_gate's item-count budget (`isError` result, not a thrown error)."""
85
+ return f"evidence exceeds {cap} items; split the gate or trim the evidence."
86
+
87
+
88
+ def gate_evidence_aggregate_error(cap: int) -> str:
89
+ """jev_gate's aggregate budget: the cap renders as `toLocaleString('en-US')` digits (ADR-0014)."""
90
+ return (
91
+ f"evidence exceeds the {js_number_to_locale_string_en_us(cap)}-character aggregate budget; "
92
+ "split the gate or trim the evidence."
93
+ )
@@ -0,0 +1,65 @@
1
+ """Choice answer validation (`validateChoiceAnswer`, `index.ts:1174-1198`), `margin` (`lib.ts:120-125`), and
2
+ `top_probability`."""
3
+
4
+ import math
5
+ from collections.abc import Iterable, Mapping
6
+
7
+ from jev_mcp.domain.answers import ChoiceAnswer, RawAnswer
8
+ from jev_mcp.domain.json import as_number, is_json_object
9
+ from jev_mcp.serialize import js_key_order
10
+ from jev_mcp.validation.numbers import confidence_of
11
+
12
+ PROBABILITY_SUM_TOLERANCE = 0.01 + 1e-12
13
+ """`lib.ts:11`. Kept as the expression: a 0.99 sum is 0.010000000000000009 from 1 and must pass."""
14
+
15
+ ARGMAX_TOLERANCE = 1e-9
16
+ """The choice may trail the top probability by at most this much and still count as tied."""
17
+
18
+
19
+ def validate_choice(answer: RawAnswer, expected_keys: Iterable[str]) -> ChoiceAnswer | None:
20
+ """The answer if it is a well-formed Choice over exactly `expected_keys`, else `None`.
21
+
22
+ Well-formed: an object whose `choice` is an expected key and whose `probabilities` object has
23
+ exactly the expected keys, each a finite number in [0, 1], summing to 1 within
24
+ `PROBABILITY_SUM_TOLERANCE`, with the choice's probability within `ARGMAX_TOLERANCE` of the
25
+ maximum. A malformed or out-of-range `confidence` becomes `None` and does not reject the answer.
26
+ """
27
+ if not is_json_object(answer):
28
+ return None
29
+ choice = answer.get("choice")
30
+ raw_probabilities = answer.get("probabilities")
31
+ if not isinstance(choice, str) or not is_json_object(raw_probabilities):
32
+ return None
33
+ expected = set(expected_keys)
34
+ keys = list(raw_probabilities)
35
+ if choice not in expected or len(keys) != len(expected) or not all(key in expected for key in keys):
36
+ return None
37
+ probabilities: dict[str, float] = {}
38
+ for key in keys:
39
+ value = as_number(raw_probabilities[key])
40
+ if value is None or not math.isfinite(value) or value < 0 or value > 1:
41
+ return None
42
+ probabilities[key] = value
43
+ # JS sums Object.values left to right in JS property order, one float64 add at a time. Python's
44
+ # sum() compensates float error since 3.12, so fold by hand.
45
+ total = 0.0
46
+ for key in js_key_order(keys):
47
+ total += probabilities[key]
48
+ if abs(total - 1) > PROBABILITY_SUM_TOLERANCE:
49
+ return None
50
+ if probabilities[choice] < max(probabilities.values()) - ARGMAX_TOLERANCE:
51
+ return None
52
+ return ChoiceAnswer(choice=choice, probabilities=probabilities, confidence=confidence_of(answer))
53
+
54
+
55
+ def margin(probabilities: Mapping[str, float] | None) -> float:
56
+ """Top probability minus the runner-up; 0 when there is no runner-up (`marginOf`)."""
57
+ ranked = sorted((probabilities or {}).values(), reverse=True)
58
+ if len(ranked) < 2:
59
+ return 0
60
+ return ranked[0] - ranked[1]
61
+
62
+
63
+ def top_probability(answer: ChoiceAnswer) -> float:
64
+ """The chosen label's probability, which policy gates: within `ARGMAX_TOLERANCE` of the maximum."""
65
+ return answer.probabilities[answer.choice]
@@ -0,0 +1,48 @@
1
+ """jev_extract's own inline Choice validation (`index.ts:1047-1061`), kept apart from the shared one (Q2, ADR-0012).
2
+
3
+ It differs from `validate_choice` in two observable ways: the sum tolerance is a bare `<= 0.01`, so a
4
+ 0.99 sum (0.010000000000000009 from 1) fails here and passes there; and `probabilities` that are not
5
+ an object count as `{}` rather than rejecting outright, which rejects all the same because the
6
+ expected key set is never empty.
7
+ """
8
+
9
+ import math
10
+ from collections.abc import Iterable
11
+
12
+ from jev_mcp.domain.answers import ChoiceAnswer, RawAnswer
13
+ from jev_mcp.domain.json import as_number, is_json_object
14
+ from jev_mcp.serialize import js_key_order
15
+ from jev_mcp.validation.choice import ARGMAX_TOLERANCE
16
+ from jev_mcp.validation.numbers import confidence_of
17
+
18
+ EXTRACT_SUM_TOLERANCE = 0.01
19
+
20
+
21
+ def validate_extract_choice(answer: RawAnswer, expected_keys: Iterable[str]) -> ChoiceAnswer | None:
22
+ """The answer if its choice is expected and its probabilities are exactly the expected keys, each a
23
+ finite number in [0, 1], summing to 1 within `<= 0.01`, with the choice at the maximum; else `None`."""
24
+ if not is_json_object(answer):
25
+ return None
26
+ choice = answer.get("choice")
27
+ raw = answer.get("probabilities")
28
+ raw_probabilities: dict[str, object] = raw if is_json_object(raw) else {}
29
+ expected = set(expected_keys)
30
+ keys = list(raw_probabilities)
31
+ if not isinstance(choice, str) or choice not in expected or len(keys) != len(expected):
32
+ return None
33
+ if not all(key in expected for key in keys):
34
+ return None
35
+ probabilities: dict[str, float] = {}
36
+ for key in keys:
37
+ value = as_number(raw_probabilities[key])
38
+ if value is None or not math.isfinite(value) or value < 0 or value > 1:
39
+ return None
40
+ probabilities[key] = value
41
+ total = 0.0
42
+ for key in js_key_order(keys):
43
+ total += probabilities[key]
44
+ if not abs(total - 1) <= EXTRACT_SUM_TOLERANCE:
45
+ return None
46
+ if not probabilities[choice] >= max(probabilities.values()) - ARGMAX_TOLERANCE:
47
+ return None
48
+ return ChoiceAnswer(choice=choice, probabilities=probabilities, confidence=confidence_of(answer))
@@ -0,0 +1,15 @@
1
+ """Noul answer validation (`validateNoulAnswer`, `index.ts:1200-1205`)."""
2
+
3
+ from jev_mcp.domain.answers import RawAnswer
4
+ from jev_mcp.domain.json import is_json_object
5
+ from jev_mcp.validation.numbers import unit_interval
6
+
7
+
8
+ def validate_noul(answer: RawAnswer) -> float | None:
9
+ """The `noul` probability if it is a finite number in [0, 1], else `None`.
10
+
11
+ `0.0` is a valid answer: test the result with `is None`, never for truthiness.
12
+ """
13
+ if not is_json_object(answer):
14
+ return None
15
+ return unit_interval(answer.get("noul"))
@@ -0,0 +1,21 @@
1
+ """Semantic number validators: unit intervals and confidence (ADR-0002, ADR-0019).
2
+
3
+ Value-shape primitives (`as_number`, `is_json_object`) live in `domain/json.py`.
4
+ """
5
+
6
+ import math
7
+
8
+ from jev_mcp.domain.json import as_number
9
+
10
+
11
+ def unit_interval(value: object) -> float | None:
12
+ """`value` when it is a finite number in [0, 1], else `None`. `-0.0` passes, as `-0 >= 0` in JS."""
13
+ number = as_number(value)
14
+ if number is None or not math.isfinite(number) or number < 0 or number > 1:
15
+ return None
16
+ return number
17
+
18
+
19
+ def confidence_of(answer: dict[str, object]) -> float | None:
20
+ """A finite confidence in [0, 1], else `None`: unknown, never zero."""
21
+ return unit_interval(answer.get("confidence"))
@@ -0,0 +1,20 @@
1
+ """Score answer validation (`validateScoreAnswer`, `index.ts:1159-1168`)."""
2
+
3
+ import math
4
+
5
+ from jev_mcp.domain.answers import RawAnswer, ScoreAnswer
6
+ from jev_mcp.domain.json import as_number, is_json_object
7
+ from jev_mcp.validation.numbers import confidence_of
8
+
9
+ MAX_SCORE = 2
10
+ """Hardcoded in `validateScoreAnswer` (`index.ts:1161`), independent of the question's rubric length."""
11
+
12
+
13
+ def validate_score(answer: RawAnswer) -> ScoreAnswer | None:
14
+ """The answer if `score` is a finite number in [0, 2], else `None`. A bad confidence becomes `None`."""
15
+ if not is_json_object(answer):
16
+ return None
17
+ score = as_number(answer.get("score"))
18
+ if score is None or not math.isfinite(score) or score < 0 or score > MAX_SCORE:
19
+ return None
20
+ return ScoreAnswer(score=score, confidence=confidence_of(answer))
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.5
2
+ Name: jev-mcp-python
3
+ Version: 0.1.0
4
+ Summary: Behavior-compatible Python MCP server exposing TypeSafe's Jev judgment tools.
5
+ Author: Mohamed Elkholy
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Requires-Python: >=3.12
10
+ Requires-Dist: httpx
11
+ Requires-Dist: mcp<3,>=2.2
12
+ Requires-Dist: pydantic-settings
13
+ Requires-Dist: pydantic>=2
14
+ Provides-Extra: telemetry
15
+ Requires-Dist: opentelemetry-sdk; extra == 'telemetry'
16
+ Requires-Dist: prometheus-client; extra == 'telemetry'
17
+ Provides-Extra: typesafe
18
+ Requires-Dist: typesafe-sdk; extra == 'typesafe'
@@ -0,0 +1,65 @@
1
+ jev_mcp/__init__.py,sha256=Y3OqXXAVz5Aghh4oB41l8AdJBH2YLXg-xvXUtFmW9Bk,100
2
+ jev_mcp/__main__.py,sha256=S6Z_k4U5SvGR6tFlZIwQ1Sfcd9hHheFXTNWNtA7VuFI,40
3
+ jev_mcp/errors.py,sha256=PrKsvmgvvZ6DwY74-HT8YVZgsd9LdoyAfJA9dDTPazM,2229
4
+ jev_mcp/ids.py,sha256=rC0hTVsfkwcvirh7ezFzm5xHuFtOcMwMFWRuW5VCAz4,1837
5
+ jev_mcp/limits.py,sha256=Xbb6HTHVLdQfRnizZRkLz04meEsgPFF-FnhP6HAO5FU,6283
6
+ jev_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ jev_mcp/serialize.py,sha256=iFyPxMOVIqF4_U6VPNaxXn6SmvZ9pTTufM6BAiAMe8M,8469
8
+ jev_mcp/server.py,sha256=5yeSUjZaDo1VF6i1waFQ8TCSeK8wskRrqRss9m595Jc,7009
9
+ jev_mcp/settings.py,sha256=YJ0jMvvpTk9HBXiEHiwW4DieWTfas93GZUDUUObrcYo,3924
10
+ jev_mcp/stdio.py,sha256=wGjaV9putJo1WW_p8NLLXRzd9weMT6hfGixKjHUReDI,4447
11
+ jev_mcp/telemetry.py,sha256=W6HDPfe17PYgurOH7ki67qHxai12HPIYPoBcPA837xQ,9575
12
+ jev_mcp/text.py,sha256=GwdEv9uuEC5sI1Q66GilES68f8tMwCi1qwXizDBESwE,1667
13
+ jev_mcp/domain/__init__.py,sha256=8jKsnNNSbjvxyAkEptH9DCHNXwvGfFAdpciDH41_EoY,747
14
+ jev_mcp/domain/answers.py,sha256=yz-8Lbk616O0m__LkpWABcWH1GXEpFUuzhuFh43asQg,786
15
+ jev_mcp/domain/json.py,sha256=W-Q3QgDPdd8HnGiETJt1Cse4D4CwygMDBqVl1x3aGu8,1958
16
+ jev_mcp/domain/questions.py,sha256=zfzDWzmznhzeQmTAcGkX4gI8dQFDpDARHM5crlhVtYU,2641
17
+ jev_mcp/domain/usage.py,sha256=ER9eLP6lvGYufH9Bz_QQVhUNbbNdh5fImV_PBjWrxPU,489
18
+ jev_mcp/extract/__init__.py,sha256=MYqBzKQXDHpbZs4HmuGr-hxjrjngSkYB793jNdmsZ9M,91
19
+ jev_mcp/extract/candidates.py,sha256=fM4yGEGmBJmHZ4BVReYu8e1Cn8YvmApVZEWePWY4vuI,3190
20
+ jev_mcp/extract/dialect.py,sha256=Uccbzk0DfL3SwVOgZ_dbY57UX94Kp36zXGuf0UitjY4,15596
21
+ jev_mcp/extract/executor.py,sha256=C5WXzX4CNt-LEVkbRCTUu-88zoue3JgChzCasl-608g,4284
22
+ jev_mcp/extract/worker.py,sha256=TVVJZHL7zL5jgqHlOCl7gUNz3xYnZ9sFBHWKJzw0OHI,8100
23
+ jev_mcp/policy/__init__.py,sha256=QS7YjLQBlbNaCHI4GvME48_fkpyTxXKBYVYPJTHL0-E,2691
24
+ jev_mcp/policy/actions.py,sha256=smQ2FspoNWCRhvLIY-znj-PwYnKt5uguBwBgYmVcArA,1699
25
+ jev_mcp/policy/claims.py,sha256=0Id-vBL4-EenADnnyBGwIdWvnBHa-AYtkZmb9sGBhHI,3882
26
+ jev_mcp/policy/extract.py,sha256=2vN4dKPbWHKbSeEesy1hP1JMlHgnOcNo7vPtTau3G0g,2829
27
+ jev_mcp/policy/ranking.py,sha256=AX6XCQhHKV-XpL9eO8wU2YC98pm1K4hVn8QX6Kzks10,1904
28
+ jev_mcp/policy/review.py,sha256=pLmeHKIN0_7HTnPu099nww5hMF-LiQ-RiYDQm_YvQAs,2809
29
+ jev_mcp/policy/screen.py,sha256=4sDq58tFtgEqBw7VpVTT3R7SD03wl9OXMttPRIFHEb4,2042
30
+ jev_mcp/policy/thresholds.py,sha256=am5XDxbQfQlE6OHRNJ4456IEiUqMEH-kiIl0T-HtXrU,2500
31
+ jev_mcp/providers/__init__.py,sha256=PRxY1Sm9RjtxrPeGXSH3leSjnKHI8OD15I3wsq2qie0,662
32
+ jev_mcp/providers/base.py,sha256=uy1igied3xAnI-xhVihf-kBCoT4vLgt9fm6rArz_Ggo,10024
33
+ jev_mcp/providers/cloudflare.py,sha256=03uwX34SeZ6MX289nO28i5hCqFbWScY9ydKZkBy8nQ8,2877
34
+ jev_mcp/providers/compatible.py,sha256=JfTJscq2H-tXDAMGnmVFyYR_wCJHr5xdiM5nDPtsEo8,1474
35
+ jev_mcp/providers/openrouter.py,sha256=5m6CVYrk7X7YFVJ4NtUiITnTm6rDbXIMCRtypz7H6Vg,2000
36
+ jev_mcp/providers/resolver.py,sha256=qK0IihOQNev0CDLninWh8tmyIeU5zTcE0VxQtxOqDHU,4666
37
+ jev_mcp/providers/typesafe.py,sha256=WOTOThtjHdY24ZIYpKjQMfCn9ym6bjCyERj_EB1y28c,4815
38
+ jev_mcp/tools/__init__.py,sha256=otMXydCjjQsFyVBhJomdzmZGeL2OumLp2qI7F1PowcA,622
39
+ jev_mcp/tools/arguments.py,sha256=xvfnAUTv90XSjLBo9hBTv32umAXTvgAYLn79NTVh-HA,19016
40
+ jev_mcp/tools/base.py,sha256=yd89fkqwkxasGG-tzhoFAQ0awR-9vn0di_OYZLxldjo,6309
41
+ jev_mcp/tools/classify.py,sha256=it4GEZYQZ3Dk37BSjn10f6PbU1KqsgwqRgB4d3-nDNI,8352
42
+ jev_mcp/tools/common.py,sha256=iS5SHYdYGwC7OA16ld-AzOm7kNmTqm7UGaM0sBbYGXM,3597
43
+ jev_mcp/tools/compare.py,sha256=TGKDd_je8YHCjkiGUYaxwvmOf9YSkcqkYGEM4ReBjFg,6316
44
+ jev_mcp/tools/decide.py,sha256=55IKurxrinRX8jMaXTNcKJ6G_J9Jth0WgiyOSk4RVeg,9096
45
+ jev_mcp/tools/extract.py,sha256=CwVgW-oUEOEEuGrA39IRz0UDpxqC7ZWsWseOosKWxQ0,11802
46
+ jev_mcp/tools/find.py,sha256=WhVpozOTynf1MVU7DyokZDQC-kzX33s5PMPdvEoa6wk,4309
47
+ jev_mcp/tools/gate.py,sha256=sDvuq1eEKF_xEYWlRqIMqh7KRhus927KQD1jfrGng7s,9708
48
+ jev_mcp/tools/observed.py,sha256=K8rwrNYZ-PvOs-tqRscI0T7A-qxPtdDcGxOPkJw7duQ,3005
49
+ jev_mcp/tools/rerank.py,sha256=4cFPL_XN9jzlAS43LFKzxh4JOzVoPX1slbnqn5YXsx4,5692
50
+ jev_mcp/tools/review.py,sha256=vAxniHy8tJIuKOhanZP9_hMa3IqNvPkzufnaMzxwp8M,9153
51
+ jev_mcp/tools/screen.py,sha256=qoj5qDTCzFQ3YWGjWPTLeqGN15qhDu2q8voNv8f0tOA,5418
52
+ jev_mcp/tools/toolset.py,sha256=_Xt-IstwSdeKSu9nUV1kW9FSc6-Bz9BHj9PSLHsxJD8,4097
53
+ jev_mcp/tools/verify.py,sha256=Jucai4bXJM7SZjnsEqr05e-SU9mN4RK9oy6ww0-D1HU,6125
54
+ jev_mcp/validation/__init__.py,sha256=7ArxeBS3nB4NkBV-tybk37im6wjF8DT4Y0CdybJDC2E,686
55
+ jev_mcp/validation/caps.py,sha256=0-c6luzDPJvqZlbj-jH7oiCyBZ5xTq6ZOvPscxDa4Xs,3670
56
+ jev_mcp/validation/choice.py,sha256=XxyKd_a5lSLe6v3_mPkCtkErn_UeuZRxZx03t2AwuRA,2945
57
+ jev_mcp/validation/extract.py,sha256=u09dkE0niM0SAGmKxv0fbzIj_bxWI516rctdrH5d3Ts,2188
58
+ jev_mcp/validation/noul.py,sha256=uLOYxcJ8vYjbIJeac45DGFugURKhZ1aKNTbwznqmleY,548
59
+ jev_mcp/validation/numbers.py,sha256=CBGn8LxC0pe9T238tC6NTMYs8c4C0SLLF9PcMiEyO8M,719
60
+ jev_mcp/validation/score.py,sha256=sEWc_PvNe-ItXgNvLBCa3BkgPzK_u_dDc43MSUyHrgU,823
61
+ jev_mcp_python-0.1.0.dist-info/METADATA,sha256=v9-KBzdcZM_xkUh5k46TbOnFQoC_I7KjsEXo_8gtfHI,608
62
+ jev_mcp_python-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
63
+ jev_mcp_python-0.1.0.dist-info/entry_points.txt,sha256=mXeDrPdSj93_9s9tKe3IfpLKNooMUDxl0ow5jvxZkUA,55
64
+ jev_mcp_python-0.1.0.dist-info/licenses/LICENSE,sha256=YkwtlAJeUBMPltHLdl_UZphqiHjjPhAWOa-b6AFnIwc,1435
65
+ jev_mcp_python-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ jev-mcp-python = jev_mcp.server:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mohamed Elkholy⁠​‌​​‌‌​‌​‌‌​‌‌‌‌​‌‌​‌​​​​‌‌​​​​‌​‌‌​‌‌​‌​‌‌​​‌​‌​‌‌​​‌​​​​‌​​​​​​‌​​​‌​‌​‌‌​‌‌​​​‌‌​‌​‌‌​‌‌​‌​​​​‌‌​‌‌‌‌​‌‌​‌‌​​​‌‌‌‌​​‌
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.