Skip to content

feat(mcp): support sse and streamable-http transports#382

Merged
jimisola merged 1 commit into
mainfrom
feat/mcp-transports
May 17, 2026
Merged

feat(mcp): support sse and streamable-http transports#382
jimisola merged 1 commit into
mainfrom
feat/mcp-transports

Conversation

@jimisola

@jimisola jimisola commented May 17, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds --transport {stdio,sse,streamable-http}, --host, and --port flags to reqstool mcp
  • Default remains stdio / 127.0.0.1 / 8000 — no existing usage breaks
  • For streamable-http, sets json_response=True and stateless_http=True on FastMCP settings for plain-fetch HTTP clients
  • No new dependencies: uvicorn and starlette are already transitive deps of mcp>=1.0

Test plan

  • All 14 existing MCP integration tests pass unchanged
  • reqstool mcp --help shows --transport, --host, --port
  • Manual smoke: reqstool mcp --transport sse local -p <path> starts an SSE server on port 8000
  • Manual smoke: reqstool mcp --transport streamable-http --port 9001 local -p <path> responds to POST /mcp

Closes #377

Add --transport, --host, and --port flags to `reqstool mcp`.
Defaults remain stdio / 127.0.0.1 / 8000 so existing usage is
unchanged. For streamable-http, json_response and stateless_http
are set on FastMCP settings for plain fetch compatibility.

Closes #377

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
@jimisola jimisola self-assigned this May 17, 2026
@jimisola jimisola merged commit bf66314 into main May 17, 2026
5 checks passed
@jimisola jimisola deleted the feat/mcp-transports branch May 17, 2026 15:10
jimisola added a commit that referenced this pull request May 25, 2026
…ool-regression monorepo (#391)

* fix(enrich): format multiline Description/Rationale fields with label-only line and indented values

Closes #383

For multiline values `_block_field` now emits the label alone on its
own line followed by each value line indented 4 spaces, instead of
placing the first value line next to the label and aligning
continuation lines with the first character.

Single-line values are unchanged: label and value remain on one line.

Add unit tests for `_block_field` (single-line, multiline, blank-line
preservation) and an integration test fixture `spec_multiline_description`
backed by a new SVC_203 with a GIVEN/WHEN/THEN description in ms-101
test data.

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* feat(mcp): support sse and streamable-http transports (#382)

Add --transport, --host, and --port flags to `reqstool mcp`.
Defaults remain stdio / 127.0.0.1 / 8000 so existing usage is
unchanged. For streamable-http, json_response and stateless_http
are set on FastMCP settings for plain fetch compatibility.

Closes #377

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* fix(tmpdir): replace hardcoded 'can_we_use_urn_here' suffix with location-based identifier (#381)

* fix(tmpdir): replace hardcoded 'can_we_use_urn_here' suffix with location-based identifier

Closes #370

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* test(tmpdir): verify tmpdir suffix uses location type prefix for all location types

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* fix(tmpdir): address full-pr-review findings for suffix robustness and security

- Add LocalMavenLocation/LocalPypiLocation to __extract_location_provenance
- Change unknown location type fallthrough to log warning and return "unknown"
- Apply _SUFFIX_MAX_LEN cap to full suffix string (prefix + uri), not just uri
- Replace consecutive dots (..) with _ to prevent path traversal via mkdir
- Add containment guard in get_suffix_path (resolve-based, is_relative_to)
- Redact Git URL userinfo (credentials) before using as location_uri
- Extract _SUFFIX_MAX_LEN and _UNSAFE_PATH_CHARS as module-level constants
- Deduplicate test capturing closure into shared _capture_suffix_calls()
- Strengthen test assertions: URI fragment presence, safe-char regex, length guard

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* fix(lint): move module-level constants after imports to fix E402

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* refactor(tmpdir): move suffix naming to LocationInterface.tmpdir_key()

Add abstract tmpdir_key() to LocationInterface with make_safe_tmpdir_suffix()
helper in location.py. Each concrete location class implements tmpdir_key()
directly. __parse_source() now calls current_location_handler.current.tmpdir_key()
instead of duplicating sanitization logic in the generator.

Closes finding #7 from PR review.

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

---------

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* fix(enrich): use blockquote prefix for multiline field continuation lines

Spaces at the start of a line are collapsed by Markdown renderers so
4-space indentation had no visual effect. Switch to blockquote (`> `)
prefix which all Markdown renderers preserve and render with a visible
left bar. Blank lines within a multiline value emit a bare `>` to keep
the blockquote context continuous.

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* fix(enrich): terminate blockquote with paragraph break before next field

Without a blank line after the last > line, Markdown renderers
continue the blockquote and swallow subsequent field labels. Add a
paragraph-break sentinel ("") to the end of multiline _block_field
results and emit it as a bare newline in enrich_text so each
blockquote is closed before the next **Label**: line.

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* fix(enrich): drop blockquote prefix — use plain label-on-own-line for multiline fields

Blockquote rendering is renderer-dependent and OpenSpecUI does not
break the blockquote context on blank lines, causing subsequent field
labels to be swallowed inside the block. Use a plain label-on-own-line
format instead: the label is emitted alone on its line and each value
line follows without any Markdown prefix, which works correctly in all
renderers.

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* fix(enrich): indent multiline field values with U+00A0 for CommonMark-safe indentation

U+00A0 (non-breaking space) is not treated as whitespace by CommonMark
parsers, so it survives as visible indentation in react-markdown where
regular ASCII spaces are stripped.

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* test(regression): add cross-ecosystem integration tests against reqstool-regression monorepo

Closes reqstool-client#386 (local + git axes).

Adds tests/integration/reqstool/model_generators/test_regression_monorepo.py:

- test_ecosystem_git_location[python/java/typescript]: parameterised over
  all three ecosystem wrappers via GitLocation. Verifies the wrapper URN,
  parent URN (reqstool-regression), grandparent URNs (regression-base-a/b),
  and that REQ_B02 is excluded by the parent import filter.

- test_parent_entry_aggregates_all_ecosystems: entry point is the parent
  layer (fixtures/parent). Verifies all three ecosystem URNs are walked
  via implementations.local — the "count grows with each new ecosystem"
  regression signal.

All tests gated on GITHUB_TOKEN; marked @pytest.mark.integration.

Also updates tests/fixtures/reqstool-regression-python/README.md to remove
the stale "becomes a git submodule" guidance.

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* style: apply black formatting to test_regression_monorepo

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* fix(test): address full-pr-review findings in test_regression_monorepo

Fixes applied:
- #1/#2: REQ_B02 assertion was vacuously true (UrnId vs string); replace
  with string comparison; add REQ_B01 positive assertion; document that
  REQ_B02 filter exclusion lives at the DB layer (DatabaseFilterProcessor),
  not at the raw-dataset level
- #2: Ecosystem URN check changed from substring match to exact key lookup
- #3: Replace per-function @pytest.mark.integration and @pytest.mark.skipif
  with module-level pytestmark
- #4: Ecosystem names in parent test now derived from ECOSYSTEMS constant
- #6: Move UrnId import to module top (then removed as unused after #1 fix)
- #7: Extract _GITHUB_TOKEN_ENV constant; use everywhere
- #8: Extract _COMMON_URNS frozenset; use issubset() in both tests
- #9: Document intentional unpinned 'main' branch with inline comment

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* fix(test): address round-2 review findings in test_regression_monorepo

- #1: Consolidate GitLocation construction into _make_generator(path)
  — eliminates the two-site duplication and adds a _all_req_ids helper
- #2: _make_generator now returns (gen, holder); both tests assert
  holder.get_no_of_errors() == 0 so semantic errors are not silently swallowed
- #3: test_parent_entry_aggregates_all_ecosystems gains REQ_B01 assertion
  to verify base-b data was actually aggregated, not just keyed
- #4: skipif guard tightened to os.getenv(..., "").strip() so whitespace-
  only token values are treated as absent
- #5: all_req_ids extracted to _all_req_ids() helper with clarifying docstring;
  comment in test makes scope explicit
- #6: README bare #386 reference replaced with full GitHub URL

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* fix(test): use holder.get_errors() in validation assertion message

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* test(regression): add explicit structural assertions per req/svc/mvr

Add test_regression_structure.py with field-level assertions for all
entities in the regression fixture: significance, lifecycle state,
categories, implementation type, verification type, requirement_ids,
and svc_ids. Covers all six URNs (parent, base-a, base-b, python, java,
typescript) with ~40 test functions, sharing a single module-scoped git
clone.

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* style: apply black formatting to test_regression_structure

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* test(regression): rewrite structural tests to use SQLite pipeline end-to-end

Replace raw-dataset assertions with build_database() + RequirementsRepository
queries so the full pipeline is exercised: parse → populate → filter →
lifecycle validation. Key behavioral differences now captured:
- REQ_B02 excluded by parent import filter (base-b count 1, not 2)
- Ecosystem requirement rows absent from DB (implementation-chain filtering)
- SVC requirement_id links to filtered ecosystem reqs cascade-deleted

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* refactor(test): address full-pr-review findings in regression tests

- Extract shared constants (URL, branch, token env, ecosystem lists) to
  _regression_shared.py — single source of truth for both test files
- Add conftest.py with pytestmark (integration + skipif) and a
  module-scoped shared_tmpdir fixture; removes duplication from both
  test files and makes the monorepo tests share one git checkout
- Fix _all_req_ids: rename loop var req → urn_id, remove redundant or []
- Move validation-error assert into _make_generator; callers no longer
  need to check holder separately
- Add repo.get_initial_urn() guard in fixture to catch wrong entry point
- Add test_base_b_in_db guard so test_base_b_no_mvrs cannot pass as a
  false positive when the URN is absent from the DB
- Add SVC existence check before len(requirement_ids)==0 in svc_l02 test
- Add inline comments on count assertions citing source YAML files
- Add cross-reference comment from monorepo test to structure test for
  REQ_B02 filter coverage

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

* docs(test): document SHA-pinning strategy for regression repo branch

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>

---------

Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(mcp): support multiple transports (sse, streamable-http)

1 participant