qcoder 0.5.0a6__tar.gz → 0.5.0a8__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.
Files changed (73) hide show
  1. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/CHANGELOG.md +10 -0
  2. {qcoder-0.5.0a6/src/qcoder.egg-info → qcoder-0.5.0a8}/PKG-INFO +6 -2
  3. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/README.md +5 -1
  4. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/pyproject.toml +1 -1
  5. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/__init__.py +1 -1
  6. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/cli.py +12 -3
  7. qcoder-0.5.0a8/src/qcoder/context_bridge_mcp.py +498 -0
  8. {qcoder-0.5.0a6 → qcoder-0.5.0a8/src/qcoder.egg-info}/PKG-INFO +6 -2
  9. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder.egg-info/SOURCES.txt +1 -0
  10. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/LICENSE +0 -0
  11. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/MANIFEST.in +0 -0
  12. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/NOTICE +0 -0
  13. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/setup.cfg +0 -0
  14. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/__main__.py +0 -0
  15. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/core/__init__.py +0 -0
  16. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/core/context.py +0 -0
  17. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/core/qasm2/__init__.py +0 -0
  18. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/core/qasm2/adjoint_eligibility.py +0 -0
  19. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/core/qasm2/mirror_build.py +0 -0
  20. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/core/run_config.py +0 -0
  21. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/core/schema.py +0 -0
  22. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/core/share_safe.py +0 -0
  23. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/context/__init__.py +0 -0
  24. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/context/bundle.py +0 -0
  25. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/context/markdown.py +0 -0
  26. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/adapters/__init__.py +0 -0
  27. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/adapters/cirq_intake.py +0 -0
  28. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/adapters/pennylane_intake.py +0 -0
  29. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/adapters/qiskit_intake.py +0 -0
  30. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/extractor.py +0 -0
  31. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/features/compute_v0.py +0 -0
  32. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/features/glossary_v0.py +0 -0
  33. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/features/schema_v0.py +0 -0
  34. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/ir.py +0 -0
  35. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/labeling.py +0 -0
  36. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/parsers/__init__.py +0 -0
  37. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/qasm2_regex_parser.py +0 -0
  38. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/reps/cut_profile.py +0 -0
  39. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/reps/depth.py +0 -0
  40. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/reps/entangling_layers.py +0 -0
  41. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/reps/gate_set_stats.py +0 -0
  42. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/reps/interaction_graph.py +0 -0
  43. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/reps/interaction_graph_metrics.py +0 -0
  44. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/feature_extraction/reps/spans.py +0 -0
  45. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/guidance/__init__.py +0 -0
  46. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/guidance/model_pack.py +0 -0
  47. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/guidance/resource.py +0 -0
  48. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/guidance/structural_scores.py +0 -0
  49. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/profiles/__init__.py +0 -0
  50. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/profiles/feature_profiles_v0.py +0 -0
  51. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/review/__init__.py +0 -0
  52. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/review/bundle.py +0 -0
  53. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/review/counts_v0.py +0 -0
  54. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/review/markdown.py +0 -0
  55. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/engines/review/qiskit_counts.py +0 -0
  56. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/explorer/__init__.py +0 -0
  57. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/explorer/derived_evidence.py +0 -0
  58. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/model_packs/__init__.py +0 -0
  59. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/model_packs/resource_guidance_local_v0.json +0 -0
  60. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/pipelines/analyze.py +0 -0
  61. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/pipelines/batch.py +0 -0
  62. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/pipelines/context.py +0 -0
  63. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/pipelines/review.py +0 -0
  64. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/pro_preview/__init__.py +0 -0
  65. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/pro_preview/client.py +0 -0
  66. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/pro_preview/config.py +0 -0
  67. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/pro_preview/errors.py +0 -0
  68. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/pro_preview/manifest.py +0 -0
  69. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder/tools/batch.py +0 -0
  70. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder.egg-info/dependency_links.txt +0 -0
  71. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder.egg-info/entry_points.txt +0 -0
  72. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder.egg-info/requires.txt +0 -0
  73. {qcoder-0.5.0a6 → qcoder-0.5.0a8}/src/qcoder.egg-info/top_level.txt +0 -0
@@ -1,6 +1,16 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## 0.5.0a8
5
+
6
+ - Add `qcoder context-bridge mcp serve`, a stdio Context Bridge MCP adapter for eligible Explorer users.
7
+ - Add `qcoder context-bridge mcp smoke --token-file <path> --json` for sanitized install verification.
8
+ - Expose only the approved Context Bridge tools: guided evidence context, prompt context, Evidence Context Pack, Context Session Card, and Run Readiness Card.
9
+ - Read Context Bridge tokens from a local token file only; token values are not printed and are not required in IDE config.
10
+ - Reject raw QASM, raw counts, paths, unsupported artifact lookup, unknown tools, and non-current-context inputs before forwarding.
11
+ - Preserve qCoder OSS local commands, Explorer evidence commands, and archived Pro shell boundaries.
12
+
13
+
4
14
  ## 0.5.0a6
5
15
 
6
16
  - Add share-safe / redacted artifact mode for artifacts intended for ChatGPT, Cursor, email, GitHub issues, or support threads.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qcoder
3
- Version: 0.5.0a6
3
+ Version: 0.5.0a8
4
4
  Summary: Quantum circuit analysis and structured feature extraction tools.
5
5
  Author-email: Quantum Ready Solutions <support@qcoder.ai>
6
6
  Maintainer-email: Quantum Ready Solutions <support@qcoder.ai>
@@ -41,15 +41,17 @@ The current public local path is **qCoder OSS**. OSS commands run locally and do
41
41
  - `qcoder context`
42
42
  - `qcoder review`
43
43
  - `qcoder explorer` (Explorer Beta account-backed status/demo/evidence checks)
44
+ - `qcoder context-bridge` (token-backed Context Bridge MCP adapter for eligible Explorer users)
44
45
  - `qcoder student` (temporary compatibility alias for Explorer Beta)
45
46
  - `qcoder pro` (archived pilot/bootstrap client contract; non-confidential local plumbing only, not a current public product path)
46
47
 
47
48
  ## Current product boundaries
48
49
 
49
- Public `qcoder` ships **OSS local commands** plus Explorer Beta compatibility commands. Pro is not launched and is not a current public product path.
50
+ Public `qcoder` ships **OSS local commands**, Explorer Beta compatibility commands, and the Context Bridge adapter command for eligible Explorer users. Pro is not launched and is not a current public product path.
50
51
 
51
52
  - **OSS commands** (`analyze`, `batch`, `context`, `review`) are Apache-2.0, local-first/offline, and useful without an account or token. They do not upload data, call a qCoder hosted service, or run QPU/simulator jobs.
52
53
  - **Explorer Beta commands** (`qcoder explorer status`, `qcoder explorer demo`, `qcoder explorer evidence`) are account-backed checks for Explorer Beta status, built-in guided evidence samples, and derived-context guided evidence for user-owned OpenQASM 2 artifacts. The older `qcoder student ...` commands remain available as beta compatibility aliases.
54
+ - **Context Bridge adapter commands** (`qcoder context-bridge mcp serve`, `qcoder context-bridge mcp smoke`) are for eligible Explorer users with support-managed Context Bridge token access. They expose bounded current-evidence context tools to configured MCP clients and read tokens from a local token file.
53
55
  - Explorer Beta custom evidence uses locally derived qCoder context/features. The CLI may read QASM locally, but the hosted request must not include raw QASM, raw source text, local paths, operation lists, raw counts, notebooks, prompts, tokens, auth headers, or cookies.
54
56
  - Explorer Beta custom evidence is stateless in this v0 slice; it does not create persistent Explorer history.
55
57
  - **`qcoder pro` bootstrap/workflow commands** are archived pilot/client-contract surfaces. They are not a Pro purchase path, not a current public signup path, and not generally available hosted Pro.
@@ -96,6 +98,8 @@ qcoder explorer evidence
96
98
  qcoder explorer evidence --qasm path/to/circuit.qasm
97
99
  qcoder explorer evidence --context-json preflight.context.json
98
100
  qcoder explorer evidence --qasm path/to/circuit.qasm --out-json explorer.json --out-md explorer.md --share-safe
101
+ qcoder context-bridge mcp serve --help
102
+ qcoder context-bridge mcp smoke --token-file ~/.qcoder/context-bridge/token.txt --json
99
103
  qcoder pro --help
100
104
  ```
101
105
 
@@ -11,15 +11,17 @@ The current public local path is **qCoder OSS**. OSS commands run locally and do
11
11
  - `qcoder context`
12
12
  - `qcoder review`
13
13
  - `qcoder explorer` (Explorer Beta account-backed status/demo/evidence checks)
14
+ - `qcoder context-bridge` (token-backed Context Bridge MCP adapter for eligible Explorer users)
14
15
  - `qcoder student` (temporary compatibility alias for Explorer Beta)
15
16
  - `qcoder pro` (archived pilot/bootstrap client contract; non-confidential local plumbing only, not a current public product path)
16
17
 
17
18
  ## Current product boundaries
18
19
 
19
- Public `qcoder` ships **OSS local commands** plus Explorer Beta compatibility commands. Pro is not launched and is not a current public product path.
20
+ Public `qcoder` ships **OSS local commands**, Explorer Beta compatibility commands, and the Context Bridge adapter command for eligible Explorer users. Pro is not launched and is not a current public product path.
20
21
 
21
22
  - **OSS commands** (`analyze`, `batch`, `context`, `review`) are Apache-2.0, local-first/offline, and useful without an account or token. They do not upload data, call a qCoder hosted service, or run QPU/simulator jobs.
22
23
  - **Explorer Beta commands** (`qcoder explorer status`, `qcoder explorer demo`, `qcoder explorer evidence`) are account-backed checks for Explorer Beta status, built-in guided evidence samples, and derived-context guided evidence for user-owned OpenQASM 2 artifacts. The older `qcoder student ...` commands remain available as beta compatibility aliases.
24
+ - **Context Bridge adapter commands** (`qcoder context-bridge mcp serve`, `qcoder context-bridge mcp smoke`) are for eligible Explorer users with support-managed Context Bridge token access. They expose bounded current-evidence context tools to configured MCP clients and read tokens from a local token file.
23
25
  - Explorer Beta custom evidence uses locally derived qCoder context/features. The CLI may read QASM locally, but the hosted request must not include raw QASM, raw source text, local paths, operation lists, raw counts, notebooks, prompts, tokens, auth headers, or cookies.
24
26
  - Explorer Beta custom evidence is stateless in this v0 slice; it does not create persistent Explorer history.
25
27
  - **`qcoder pro` bootstrap/workflow commands** are archived pilot/client-contract surfaces. They are not a Pro purchase path, not a current public signup path, and not generally available hosted Pro.
@@ -66,6 +68,8 @@ qcoder explorer evidence
66
68
  qcoder explorer evidence --qasm path/to/circuit.qasm
67
69
  qcoder explorer evidence --context-json preflight.context.json
68
70
  qcoder explorer evidence --qasm path/to/circuit.qasm --out-json explorer.json --out-md explorer.md --share-safe
71
+ qcoder context-bridge mcp serve --help
72
+ qcoder context-bridge mcp smoke --token-file ~/.qcoder/context-bridge/token.txt --json
69
73
  qcoder pro --help
70
74
  ```
71
75
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qcoder"
7
- version = "0.5.0a6"
7
+ version = "0.5.0a8"
8
8
  description = "Quantum circuit analysis and structured feature extraction tools."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  __all__ = []
2
- __version__ = "0.5.0a6"
2
+ __version__ = "0.5.0a8"
3
3
  file = __file__
@@ -819,6 +819,12 @@ def _cmd_student(argv: list[str]) -> int:
819
819
  return _cmd_explorer(argv, compatibility_alias=True)
820
820
 
821
821
 
822
+ def _cmd_context_bridge(argv: list[str]) -> int:
823
+ from qcoder.context_bridge_mcp import main as context_bridge_main
824
+
825
+ return context_bridge_main(argv)
826
+
827
+
822
828
  def _cmd_pro(argv: list[str]) -> int:
823
829
  p = argparse.ArgumentParser(
824
830
  prog="qcoder pro",
@@ -1200,15 +1206,16 @@ def _cmd_pro(argv: list[str]) -> int:
1200
1206
 
1201
1207
  def _print_root_help() -> None:
1202
1208
  print(
1203
- "usage: qcoder [--version | -V] [-h] {analyze,batch,context,review,explorer,pro,student} ...\n\n"
1209
+ "usage: qcoder [--version | -V] [-h] {analyze,batch,context,review,explorer,context-bridge,pro,student} ...\n\n"
1204
1210
  "Quantum circuit analysis CLI.\n\n"
1205
1211
  "positional arguments:\n"
1206
- " {analyze,batch,context,review,explorer,pro,student} subcommand\n\n"
1212
+ " {analyze,batch,context,review,explorer,context-bridge,pro,student} subcommand\n\n"
1207
1213
  " analyze Analyze a QASM file (feature extraction + metadata + run config).\n"
1208
1214
  " batch Batch extract a directory to JSONL (requires --out).\n"
1209
1215
  " context Build deterministic preflight context artifacts.\n"
1210
1216
  " review Build deterministic execution review artifacts from counts.\n"
1211
1217
  " explorer Explorer Beta status/demo/evidence checks.\n"
1218
+ " context-bridge Run the Context Bridge MCP adapter for eligible Explorer users.\n"
1212
1219
  " pro Archived Pro client-contract shell (not current public product).\n"
1213
1220
  " student Compatibility alias for Explorer Beta checks.\n\n"
1214
1221
  "Run `qcoder <subcommand> --help` for subcommand options.",
@@ -1241,13 +1248,15 @@ def main(argv: list[str] | None = None) -> int:
1241
1248
  return _cmd_review(rest)
1242
1249
  if cmd == "explorer":
1243
1250
  return _cmd_explorer(rest)
1251
+ if cmd == "context-bridge":
1252
+ return _cmd_context_bridge(rest)
1244
1253
  if cmd == "pro":
1245
1254
  return _cmd_pro(rest)
1246
1255
  if cmd == "student":
1247
1256
  return _cmd_student(rest)
1248
1257
 
1249
1258
  print(
1250
- f"qcoder: unknown subcommand {cmd!r} (expected analyze, batch, context, review, explorer, pro, or student)",
1259
+ f"qcoder: unknown subcommand {cmd!r} (expected analyze, batch, context, review, explorer, context-bridge, pro, or student)",
1251
1260
  file=sys.stderr,
1252
1261
  )
1253
1262
  print("Run `qcoder --help` for usage.", file=sys.stderr)
@@ -0,0 +1,498 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ from pathlib import Path
7
+ import stat
8
+ import sys
9
+ from typing import Any, Callable
10
+ import urllib.error
11
+ import urllib.request
12
+
13
+ DEFAULT_BASE_URL = "https://preview-api.qcoder.ai"
14
+ ROUTE_PATH = "/v0/internal/hosted-mcp/context"
15
+ EXPECTED_TOOLS = (
16
+ "get_guided_evidence_context",
17
+ "create_prompt_context",
18
+ "create_evidence_context_pack",
19
+ "create_context_session_card",
20
+ "create_run_readiness_card",
21
+ )
22
+ DEFAULT_ARTIFACT_KIND = "share_safe_evidence_summary"
23
+ MAX_ARTIFACT_TEXT_CHARS = 20_000
24
+ FORBIDDEN_TEXT_MARKERS = (
25
+ "openqasm",
26
+ "qreg ",
27
+ "creg ",
28
+ "counts=",
29
+ '"counts"',
30
+ "'counts'",
31
+ "/home/",
32
+ "\\users\\",
33
+ "c:\\",
34
+ "../",
35
+ "repo_path",
36
+ "file_path",
37
+ "raw_qasm",
38
+ "raw_counts",
39
+ "raw_source",
40
+ "notebook",
41
+ ".ipynb",
42
+ )
43
+
44
+
45
+ def default_token_file() -> Path:
46
+ return Path.home() / ".qcoder" / "context-bridge" / "token.txt"
47
+
48
+
49
+ def safe_error(error_category: str, *, status_category: str = "adapter_rejected") -> dict[str, Any]:
50
+ return {
51
+ "ok": False,
52
+ "error_category": error_category,
53
+ "status_category": status_category,
54
+ "retention": "process_and_discard",
55
+ "retained_artifacts": [],
56
+ "token_printed": False,
57
+ "raw_payload_printed": False,
58
+ "raw_response_printed": False,
59
+ }
60
+
61
+
62
+ def validate_token_file(token_file: str | Path) -> tuple[bool, str, str]:
63
+ path = Path(token_file)
64
+ if not path.is_file():
65
+ return False, "token_file_missing", ""
66
+ try:
67
+ mode = stat.S_IMODE(path.stat().st_mode)
68
+ except OSError:
69
+ return False, "token_file_unreadable", ""
70
+ if os.name != "nt" and mode & 0o077:
71
+ return False, "token_file_permissions_unsafe", ""
72
+ try:
73
+ token = path.read_text(encoding="utf-8").strip()
74
+ except OSError:
75
+ return False, "token_file_unreadable", ""
76
+ if not token:
77
+ return False, "token_file_empty", ""
78
+ if "\n" in token or "\r" in token:
79
+ return False, "token_file_malformed", ""
80
+ return True, "ok", token
81
+
82
+
83
+ def validate_artifact_text(artifact_text: object) -> str:
84
+ if not isinstance(artifact_text, str) or not artifact_text.strip():
85
+ return "artifact_text_missing"
86
+ if len(artifact_text) > MAX_ARTIFACT_TEXT_CHARS:
87
+ return "artifact_text_too_large"
88
+ lowered = artifact_text.lower()
89
+ if any(marker in lowered for marker in FORBIDDEN_TEXT_MARKERS):
90
+ return "forbidden_input_value"
91
+ return "ok"
92
+
93
+
94
+ def decode_json(raw: bytes) -> dict[str, Any]:
95
+ try:
96
+ decoded = json.loads(raw.decode("utf-8"))
97
+ except Exception:
98
+ return {"ok": False, "error_category": "non_json_response"}
99
+ return decoded if isinstance(decoded, dict) else {"ok": False, "error_category": "non_object_response"}
100
+
101
+
102
+ def post_context_bridge(
103
+ *,
104
+ base_url: str,
105
+ token_file: str | Path,
106
+ tool_name: str,
107
+ artifact_text: object,
108
+ artifact_kind: str = DEFAULT_ARTIFACT_KIND,
109
+ client_context: dict[str, Any] | None = None,
110
+ opener: Callable[..., Any] | None = None,
111
+ ) -> dict[str, Any]:
112
+ if tool_name not in EXPECTED_TOOLS:
113
+ return safe_error("unknown_tool")
114
+ if artifact_kind != DEFAULT_ARTIFACT_KIND:
115
+ return safe_error("unsupported_artifact_kind")
116
+ text_validation = validate_artifact_text(artifact_text)
117
+ if text_validation != "ok":
118
+ return safe_error(text_validation)
119
+ token_ok, token_category, token = validate_token_file(token_file)
120
+ if not token_ok:
121
+ return safe_error(token_category, status_category="auth_preflight_failed")
122
+
123
+ body = {
124
+ "tool_name": tool_name,
125
+ "artifact_kind": artifact_kind,
126
+ "artifact_text": artifact_text,
127
+ "client_context": {
128
+ "client_version": "qcoder-context-bridge-mcp-adapter",
129
+ **(client_context or {}),
130
+ },
131
+ }
132
+ data = json.dumps(body, sort_keys=True, separators=(",", ":")).encode("utf-8")
133
+ request = urllib.request.Request(
134
+ base_url.rstrip("/") + ROUTE_PATH,
135
+ data=data,
136
+ headers={
137
+ "content-type": "application/json",
138
+ "authorization": f"Bearer {token}",
139
+ },
140
+ method="POST",
141
+ )
142
+ urlopen = opener or urllib.request.urlopen
143
+ try:
144
+ with urlopen(request, timeout=20) as response:
145
+ status = int(response.status)
146
+ payload = decode_json(response.read())
147
+ except urllib.error.HTTPError as exc:
148
+ status = int(exc.code)
149
+ payload = decode_json(exc.read())
150
+ except Exception:
151
+ return safe_error("context_bridge_unreachable", status_category="network_error")
152
+
153
+ payload.setdefault("adapter_status_category", "success_2xx" if 200 <= status < 300 else f"http_{status}")
154
+ payload.setdefault("token_printed", False)
155
+ payload.setdefault("raw_payload_printed", False)
156
+ payload.setdefault("raw_response_printed", False)
157
+ return payload
158
+
159
+
160
+ def tool_descriptors() -> list[dict[str, Any]]:
161
+ schema = {
162
+ "type": "object",
163
+ "properties": {
164
+ "artifact_text": {
165
+ "type": "string",
166
+ "description": "Share-safe current qCoder evidence summary. Raw circuits, counts, paths, notebooks, and source files are rejected.",
167
+ },
168
+ "artifact_kind": {
169
+ "type": "string",
170
+ "enum": [DEFAULT_ARTIFACT_KIND],
171
+ "default": DEFAULT_ARTIFACT_KIND,
172
+ },
173
+ "client_context": {
174
+ "type": "object",
175
+ "additionalProperties": True,
176
+ "description": "Optional client metadata without secrets, paths, or raw artifacts.",
177
+ },
178
+ },
179
+ "required": ["artifact_text"],
180
+ "additionalProperties": False,
181
+ }
182
+ descriptions = {
183
+ "get_guided_evidence_context": "Create bounded assistant context from share-safe current qCoder evidence.",
184
+ "create_prompt_context": "Create a share-safe prompt context from current qCoder evidence.",
185
+ "create_evidence_context_pack": "Create a current-evidence context packet with evidence limits and next-step framing.",
186
+ "create_context_session_card": "Create a current-session context card without memory or history.",
187
+ "create_run_readiness_card": "Create a bounded readiness card for the next development check.",
188
+ }
189
+ return [
190
+ {"name": name, "description": descriptions[name], "inputSchema": schema}
191
+ for name in EXPECTED_TOOLS
192
+ ]
193
+
194
+
195
+ def _jsonrpc_result(message_id: object, result: dict[str, Any]) -> dict[str, Any]:
196
+ return {"jsonrpc": "2.0", "id": message_id, "result": result}
197
+
198
+
199
+ def _jsonrpc_error(message_id: object, code: int, message: str) -> dict[str, Any]:
200
+ return {"jsonrpc": "2.0", "id": message_id, "error": {"code": code, "message": message}}
201
+
202
+
203
+ def handle_jsonrpc_message(
204
+ message: dict[str, Any],
205
+ *,
206
+ base_url: str,
207
+ token_file: str | Path,
208
+ opener: Callable[..., Any] | None = None,
209
+ ) -> dict[str, Any] | None:
210
+ method = message.get("method")
211
+ message_id = message.get("id")
212
+ if method == "notifications/initialized":
213
+ return None
214
+ if method == "initialize":
215
+ return _jsonrpc_result(
216
+ message_id,
217
+ {
218
+ "protocolVersion": "2024-11-05",
219
+ "capabilities": {"tools": {"listChanged": False}, "resources": {}, "prompts": {}},
220
+ "serverInfo": {"name": "qcoder-context-bridge", "version": "1.0.0"},
221
+ },
222
+ )
223
+ if method == "tools/list":
224
+ return _jsonrpc_result(message_id, {"tools": tool_descriptors()})
225
+ if method == "prompts/list":
226
+ return _jsonrpc_result(message_id, {"prompts": []})
227
+ if method == "resources/list":
228
+ return _jsonrpc_result(message_id, {"resources": []})
229
+ if method == "tools/call":
230
+ params = message.get("params") if isinstance(message.get("params"), dict) else {}
231
+ tool_name = params.get("name")
232
+ arguments = params.get("arguments") if isinstance(params.get("arguments"), dict) else {}
233
+ payload = post_context_bridge(
234
+ base_url=base_url,
235
+ token_file=token_file,
236
+ tool_name=str(tool_name or ""),
237
+ artifact_text=arguments.get("artifact_text"),
238
+ artifact_kind=str(arguments.get("artifact_kind") or DEFAULT_ARTIFACT_KIND),
239
+ client_context=arguments.get("client_context")
240
+ if isinstance(arguments.get("client_context"), dict)
241
+ else None,
242
+ opener=opener,
243
+ )
244
+ return _jsonrpc_result(
245
+ message_id,
246
+ {
247
+ "content": [{"type": "text", "text": json.dumps(payload, sort_keys=True)}],
248
+ "structuredContent": payload,
249
+ "isError": payload.get("ok") is False,
250
+ },
251
+ )
252
+ return _jsonrpc_error(message_id, -32601, "method_not_supported")
253
+
254
+
255
+ def serve_stdio(*, base_url: str, token_file: str | Path) -> int:
256
+ for raw_line in sys.stdin:
257
+ line = raw_line.strip()
258
+ if not line:
259
+ continue
260
+ try:
261
+ message = json.loads(line)
262
+ except json.JSONDecodeError:
263
+ response = _jsonrpc_error(None, -32700, "parse_error")
264
+ else:
265
+ if not isinstance(message, dict):
266
+ response = _jsonrpc_error(None, -32600, "invalid_request")
267
+ else:
268
+ response = handle_jsonrpc_message(message, base_url=base_url, token_file=token_file)
269
+ if response is None:
270
+ continue
271
+ print(json.dumps(response, sort_keys=True), flush=True)
272
+ return 0
273
+
274
+
275
+ def _case_summary(*, payload: dict[str, Any], expected_success: bool) -> dict[str, Any]:
276
+ serialized = json.dumps(payload, sort_keys=True)
277
+ ok_value = payload.get("ok")
278
+ retained = payload.get("retained_artifacts", [])
279
+ status_category = str(payload.get("adapter_status_category") or payload.get("status_category") or "missing")
280
+ success = ok_value is True or status_category == "success_2xx"
281
+ return {
282
+ "expected_outcome_met": success if expected_success else not success,
283
+ "ok_category": "true" if ok_value is True else "false" if ok_value is False else "missing",
284
+ "status_category": status_category,
285
+ "error_category": str(payload.get("error_category") or ""),
286
+ "tool_name_category": payload.get("tool_name")
287
+ if payload.get("tool_name") in EXPECTED_TOOLS
288
+ else "other_or_missing",
289
+ "context_status_category": str(payload.get("context_status") or "missing"),
290
+ "retention_category": str(payload.get("retention") or "missing"),
291
+ "retained_artifacts_empty_or_absent": retained in ([], None),
292
+ "raw_payload_echo_absent": "QCODER_CONTEXT_BRIDGE_SMOKE_MARKER" not in serialized,
293
+ "token_printed": False,
294
+ "raw_response_printed": False,
295
+ }
296
+
297
+
298
+ def run_smoke(*, base_url: str, token_file: str | Path) -> dict[str, Any]:
299
+ token_ok, token_category, _ = validate_token_file(token_file)
300
+ if not token_ok:
301
+ return {
302
+ "ok": False,
303
+ "metadata_only": True,
304
+ "token_file_category": token_category,
305
+ "token_printed": False,
306
+ "raw_token_printed": False,
307
+ "instruction_category": "create_local_chmod_600_token_file",
308
+ }
309
+ safe_text = (
310
+ "Share-safe current qCoder evidence summary. "
311
+ "Small Bell-state style circuit workflow. Evidence summary says the user prepared "
312
+ "a two-qubit entanglement example and wants bounded assistant context. "
313
+ "No raw QASM, no raw counts, no file paths, no backend identifiers, and no source code are included. "
314
+ "QCODER_CONTEXT_BRIDGE_SMOKE_MARKER"
315
+ )
316
+ cases = {
317
+ "guided_context_allowed": _case_summary(
318
+ payload=post_context_bridge(
319
+ base_url=base_url,
320
+ token_file=token_file,
321
+ tool_name="get_guided_evidence_context",
322
+ artifact_text=safe_text,
323
+ ),
324
+ expected_success=True,
325
+ ),
326
+ "prompt_context_allowed": _case_summary(
327
+ payload=post_context_bridge(
328
+ base_url=base_url,
329
+ token_file=token_file,
330
+ tool_name="create_prompt_context",
331
+ artifact_text=safe_text,
332
+ ),
333
+ expected_success=True,
334
+ ),
335
+ "evidence_context_pack_allowed": _case_summary(
336
+ payload=post_context_bridge(
337
+ base_url=base_url,
338
+ token_file=token_file,
339
+ tool_name="create_evidence_context_pack",
340
+ artifact_text=safe_text,
341
+ ),
342
+ expected_success=True,
343
+ ),
344
+ "context_session_card_allowed": _case_summary(
345
+ payload=post_context_bridge(
346
+ base_url=base_url,
347
+ token_file=token_file,
348
+ tool_name="create_context_session_card",
349
+ artifact_text=safe_text,
350
+ ),
351
+ expected_success=True,
352
+ ),
353
+ "run_readiness_card_allowed": _case_summary(
354
+ payload=post_context_bridge(
355
+ base_url=base_url,
356
+ token_file=token_file,
357
+ tool_name="create_run_readiness_card",
358
+ artifact_text=safe_text,
359
+ ),
360
+ expected_success=True,
361
+ ),
362
+ "raw_qasm_rejected": _case_summary(
363
+ payload=post_context_bridge(
364
+ base_url=base_url,
365
+ token_file=token_file,
366
+ tool_name="get_guided_evidence_context",
367
+ artifact_text="OPENQASM 2.0; qreg q[1];",
368
+ ),
369
+ expected_success=False,
370
+ ),
371
+ "repo_path_rejected": _case_summary(
372
+ payload=post_context_bridge(
373
+ base_url=base_url,
374
+ token_file=token_file,
375
+ tool_name="get_guided_evidence_context",
376
+ artifact_text="/home/private/project/source.py",
377
+ ),
378
+ expected_success=False,
379
+ ),
380
+ "artifact_lookup_rejected": _case_summary(
381
+ payload=post_context_bridge(
382
+ base_url=base_url,
383
+ token_file=token_file,
384
+ tool_name="get_guided_evidence_context",
385
+ artifact_text="artifact lookup request",
386
+ artifact_kind="server_artifact_id",
387
+ ),
388
+ expected_success=False,
389
+ ),
390
+ "unknown_tool_rejected": _case_summary(
391
+ payload=post_context_bridge(
392
+ base_url=base_url,
393
+ token_file=token_file,
394
+ tool_name="suggest_next_checks",
395
+ artifact_text=safe_text,
396
+ ),
397
+ expected_success=False,
398
+ ),
399
+ }
400
+ approved = [
401
+ "guided_context_allowed",
402
+ "prompt_context_allowed",
403
+ "evidence_context_pack_allowed",
404
+ "context_session_card_allowed",
405
+ "run_readiness_card_allowed",
406
+ ]
407
+ unsafe = ["raw_qasm_rejected", "repo_path_rejected", "artifact_lookup_rejected", "unknown_tool_rejected"]
408
+ result = {
409
+ "ok": True,
410
+ "metadata_only": True,
411
+ "client_category": "qCoder Context Bridge MCP adapter",
412
+ "token_source_category": "local_chmod_600_file",
413
+ "tools_visible": list(EXPECTED_TOOLS),
414
+ "tools_exact": True,
415
+ "approved_tool_calls_passed": all(cases[name]["expected_outcome_met"] for name in approved),
416
+ "unsafe_calls_rejected": all(cases[name]["expected_outcome_met"] for name in unsafe),
417
+ "token_printed": False,
418
+ "raw_payload_echo": "no" if all(case["raw_payload_echo_absent"] for case in cases.values()) else "yes",
419
+ "retention_category": "process_and_discard_or_rejected",
420
+ "retained_artifacts_empty": "yes"
421
+ if all(case["retained_artifacts_empty_or_absent"] for case in cases.values())
422
+ else "no",
423
+ "payment_auth_billing_mutation": "no",
424
+ "public_claim_created": "no",
425
+ "source_modified": "no",
426
+ "cases": cases,
427
+ }
428
+ result["all_expected_outcomes_met"] = (
429
+ result["approved_tool_calls_passed"]
430
+ and result["unsafe_calls_rejected"]
431
+ and result["raw_payload_echo"] == "no"
432
+ and result["retained_artifacts_empty"] == "yes"
433
+ )
434
+ result["ok"] = bool(result["all_expected_outcomes_met"])
435
+ return result
436
+
437
+
438
+ def build_parser() -> argparse.ArgumentParser:
439
+ parser = argparse.ArgumentParser(
440
+ prog="qcoder context-bridge",
441
+ description="qCoder Context Bridge adapter tools for eligible Explorer users.",
442
+ )
443
+ sub = parser.add_subparsers(dest="context_bridge_command")
444
+ mcp = sub.add_parser("mcp", help="Run or smoke-test the local Context Bridge MCP adapter.")
445
+ mcp_sub = mcp.add_subparsers(dest="mcp_command")
446
+
447
+ serve = mcp_sub.add_parser("serve", help="Run the local stdio MCP adapter.")
448
+ serve.add_argument(
449
+ "--token-file",
450
+ default=os.getenv("QCODER_CONTEXT_BRIDGE_TOKEN_FILE", str(default_token_file())),
451
+ help="Path to a local Context Bridge token file. The token value is never printed.",
452
+ )
453
+ serve.add_argument(
454
+ "--base-url",
455
+ default=os.getenv("QCODER_CONTEXT_BRIDGE_BASE_URL", DEFAULT_BASE_URL),
456
+ help="Context Bridge service base URL.",
457
+ )
458
+ serve.set_defaults(context_bridge_command="mcp", mcp_command="serve")
459
+
460
+ smoke = mcp_sub.add_parser("smoke", help="Run a sanitized adapter install smoke.")
461
+ smoke.add_argument(
462
+ "--token-file",
463
+ default=os.getenv("QCODER_CONTEXT_BRIDGE_TOKEN_FILE", str(default_token_file())),
464
+ help="Path to a local Context Bridge token file. The token value is never printed.",
465
+ )
466
+ smoke.add_argument(
467
+ "--base-url",
468
+ default=os.getenv("QCODER_CONTEXT_BRIDGE_BASE_URL", DEFAULT_BASE_URL),
469
+ help="Context Bridge service base URL.",
470
+ )
471
+ smoke.add_argument("--json", action="store_true", help="Emit sanitized JSON result.")
472
+ smoke.set_defaults(context_bridge_command="mcp", mcp_command="smoke")
473
+ return parser
474
+
475
+
476
+ def main(argv: list[str] | None = None) -> int:
477
+ parser = build_parser()
478
+ args = parser.parse_args(argv)
479
+ if args.context_bridge_command is None or getattr(args, "mcp_command", None) is None:
480
+ parser.print_help()
481
+ return 0
482
+ if args.mcp_command == "serve":
483
+ return serve_stdio(base_url=args.base_url, token_file=args.token_file)
484
+ if args.mcp_command == "smoke":
485
+ result = run_smoke(base_url=args.base_url, token_file=args.token_file)
486
+ if args.json:
487
+ print(json.dumps(result, indent=2, sort_keys=True))
488
+ else:
489
+ print(f"qCoder Context Bridge adapter smoke: {'PASS' if result.get('ok') else 'CHECK'}")
490
+ print(f" token_file: {result.get('token_file_category', 'present_safe')}")
491
+ print(f" tools_exact: {result.get('tools_exact', False)}")
492
+ print(f" approved_tool_calls: {result.get('approved_tool_calls_passed', False)}")
493
+ print(f" unsafe_calls_rejected: {result.get('unsafe_calls_rejected', False)}")
494
+ print(f" token_printed: {result.get('token_printed', False)}")
495
+ return 0 if result.get("ok") else 1
496
+ parser.print_help()
497
+ return 0
498
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qcoder
3
- Version: 0.5.0a6
3
+ Version: 0.5.0a8
4
4
  Summary: Quantum circuit analysis and structured feature extraction tools.
5
5
  Author-email: Quantum Ready Solutions <support@qcoder.ai>
6
6
  Maintainer-email: Quantum Ready Solutions <support@qcoder.ai>
@@ -41,15 +41,17 @@ The current public local path is **qCoder OSS**. OSS commands run locally and do
41
41
  - `qcoder context`
42
42
  - `qcoder review`
43
43
  - `qcoder explorer` (Explorer Beta account-backed status/demo/evidence checks)
44
+ - `qcoder context-bridge` (token-backed Context Bridge MCP adapter for eligible Explorer users)
44
45
  - `qcoder student` (temporary compatibility alias for Explorer Beta)
45
46
  - `qcoder pro` (archived pilot/bootstrap client contract; non-confidential local plumbing only, not a current public product path)
46
47
 
47
48
  ## Current product boundaries
48
49
 
49
- Public `qcoder` ships **OSS local commands** plus Explorer Beta compatibility commands. Pro is not launched and is not a current public product path.
50
+ Public `qcoder` ships **OSS local commands**, Explorer Beta compatibility commands, and the Context Bridge adapter command for eligible Explorer users. Pro is not launched and is not a current public product path.
50
51
 
51
52
  - **OSS commands** (`analyze`, `batch`, `context`, `review`) are Apache-2.0, local-first/offline, and useful without an account or token. They do not upload data, call a qCoder hosted service, or run QPU/simulator jobs.
52
53
  - **Explorer Beta commands** (`qcoder explorer status`, `qcoder explorer demo`, `qcoder explorer evidence`) are account-backed checks for Explorer Beta status, built-in guided evidence samples, and derived-context guided evidence for user-owned OpenQASM 2 artifacts. The older `qcoder student ...` commands remain available as beta compatibility aliases.
54
+ - **Context Bridge adapter commands** (`qcoder context-bridge mcp serve`, `qcoder context-bridge mcp smoke`) are for eligible Explorer users with support-managed Context Bridge token access. They expose bounded current-evidence context tools to configured MCP clients and read tokens from a local token file.
53
55
  - Explorer Beta custom evidence uses locally derived qCoder context/features. The CLI may read QASM locally, but the hosted request must not include raw QASM, raw source text, local paths, operation lists, raw counts, notebooks, prompts, tokens, auth headers, or cookies.
54
56
  - Explorer Beta custom evidence is stateless in this v0 slice; it does not create persistent Explorer history.
55
57
  - **`qcoder pro` bootstrap/workflow commands** are archived pilot/client-contract surfaces. They are not a Pro purchase path, not a current public signup path, and not generally available hosted Pro.
@@ -96,6 +98,8 @@ qcoder explorer evidence
96
98
  qcoder explorer evidence --qasm path/to/circuit.qasm
97
99
  qcoder explorer evidence --context-json preflight.context.json
98
100
  qcoder explorer evidence --qasm path/to/circuit.qasm --out-json explorer.json --out-md explorer.md --share-safe
101
+ qcoder context-bridge mcp serve --help
102
+ qcoder context-bridge mcp smoke --token-file ~/.qcoder/context-bridge/token.txt --json
99
103
  qcoder pro --help
100
104
  ```
101
105
 
@@ -7,6 +7,7 @@ pyproject.toml
7
7
  src/qcoder/__init__.py
8
8
  src/qcoder/__main__.py
9
9
  src/qcoder/cli.py
10
+ src/qcoder/context_bridge_mcp.py
10
11
  src/qcoder.egg-info/PKG-INFO
11
12
  src/qcoder.egg-info/SOURCES.txt
12
13
  src/qcoder.egg-info/dependency_links.txt
File without changes
File without changes
File without changes
File without changes