Skip to content

feat: MCP server for deterministic workflow orchestration - #1

Open
kmarecki wants to merge 23 commits into
initial-designfrom
feat/mcp-orchestrator
Open

kmarecki wants to merge 23 commits into
initial-designfrom
feat/mcp-orchestrator

Conversation

@kmarecki

Copy link
Copy Markdown
Owner

Adds a deterministic MCP server (zero deps, stdlib-only) with 12 tools for workflow state management. Skills can optionally use MCP tools for enforced phase transitions and auto-chaining after bug logging.

hh79 added 23 commits June 16, 2026 21:44
…manual config

- Wrap hermes mcp add with timeout 10 so it doesn't hang 40s
- On timeout/failure: write config via Python yaml (safe, idempotent)
- Fallback sed: delete only the spec-kit block, not adjacent entries
- Update README: reflect 11 skills, MCP server, uninstall.sh, 5 refs
- Update user-guide: MCP Server section, updated template/CLI tables
hermes mcp add tries to hot-reload into a running Hermes process — it
times out when no session is active (typical during install). Write the
config.yaml entry directly using sed + heredoc instead:

- Remove old spec-kit block precisely (sed N;N;d — 3 lines only)
- Append fresh entry with inline args format (not Python yaml block-style)
- Also clean up uninstall.sh: remove dangerous grep -v 'spec-kit' that
  could corrupt unrelated config keys, replace with same precise sed
…e-delimited JSON, not Content-Length framing

Root cause: Hermes' built-in MCP client (mcp Python SDK) sends messages
as newline-delimited JSON over stdio. The hand-rolled server read using
Content-Length headers (HTTP-style). Protocol mismatch caused the client
to hang forever waiting for a response.

Fix: rewrite server.py using FastMCP (mcp.server.fastmcp) — the official
SDK server class that uses the correct stdio transport. All 12 tools
registered as @mcp.tool() decorated functions. Core state logic unchanged.

Other changes:
- test_server.py: updated to use mcp SDK client instead of raw Content-Length
- mcp-server README: remove 'zero deps' claim, document mcp SDK dependency
- README/user-guide: drop 'pure stdlib' references, note mcp SDK requirement
…cement in test skill

Tool name fix: Hermes adds its own mcp_spec_kit_ prefix to MCP tools.
The old server registered tools as spec_kit_* causing double spec_kit_
(e.g., mcp_spec_kit_spec_kit_log_bug). Now tools are bare (log_bug)
so Hermes renders them as mcp_spec_kit_log_bug — clean, single prefix.

Workflow fix: spec-kit-test.md MCP sync section said 'call it after
each bug to sync state'. The agent treated MCP log_bug as a replacement
for writing bugs.md — it logged via MCP then went straight to fixing
code. Now the skill explicitly says:
  - bugs.md is the PRIMARY bug log (write first)
  - MCP is supplemental sync (call after)
  - Never fix directly — always route through 'bugfix [feature]'
… found by tests

The new test_workflow.py tests the MCP server via the mcp SDK client:
- Happy path: full forward cycle through all 7 phases + close
- Prerequisite enforcement: artifacts validated before phase transitions
- Wrong phase rejection
- Duplicate feature rejection
- Missing feature error handling
- Bug lifecycle: log → plan_ref → resolved → verified → next_actions
- Next actions progression at each phase
- Reopen guard (non-closed features)
- Reopen flow: close → reopen → log bug → advance → close again
- Update artifact status
- Auto-detect from specs/ directory

Bugs found by tests:
1. _advance_phase auto-marking checked 'not in artifacts' but _get_feature
   pre-fills ALL artifact keys with 'absent' — never matched. Fixed: check
   for 'absent' value instead.
2. reopen_feature left phase at 6 (close) instead of resetting to 5 (test).
   Bugfix loop couldn't advance 5->6 to close again. Fixed: set phase to 5.
3. _validate_advance blocked ALL transitions for 'reopened' status. Reopened
   features need to advance through bugfix loop. Fixed: removed the hard block.

test_server.py now delegates to test_workflow.py (no duplication).
…gs found by tests

New test_skills.py validates all spec-kit skill markdown files:
  - Skill inventory: every expected skill file exists, no extras
  - Frontmatter: valid YAML, required fields present
  - Phase coverage: all 7+ phases map to correct skills
  - Trigger descriptions: each skill mentions its 'speckit' trigger
  - Reference files: every referenced file exists in references/
  - Template files: every referenced template exists, no missing
  - Cross-skill links: all spec-kit-* refs point to existing skills
  - Preflight compliance: every phase skill references preflight.md
  - Umbrella coverage: SKILL.md lists all phase skills
  - Reference inventory: no extra/missing reference files
  - Workflow diagram: all phases mentioned in workflow skill

Bugs found and fixed:
  - spec-kit-constitution frontmatter: unescaped colons in
    description broke YAML parsing (mapping values not allowed)
  - spec-kit-workflow frontmatter: same issue
  - Both switched to YAML block scalar (>) to avoid escaping issues

CI: runs both test suites (skills + MCP workflow) on every push.
Before this change, only spec-kit-test referenced the MCP server. Every
other phase (constitution, specify, plan, tasks, implement, summarize)
worked without MCP, so the state machine was never updated during those
phases. Now every phase skill syncs its artifact and advances phase:

  Constitution: update_artifact(constitution.md) + advance 0->1
  Specify:      update_artifact(spec.md)          + advance 1->2
  Plan:         update_artifact(plan.md)          + advance 2->3
  Tasks:        update_artifact(tasks.md)         + advance 3->4
  Implement:    update_artifact(tasks.md)         + advance 4->5
  Test:         log_bug (already had this)
  Summarize:    update_artifact(close.md) + advance 6 + close_feature

Each MCP block is opt-in (only called when mcp_spec_kit_* tools are
available), so skills still work identically without an MCP server.
The MCP server's log_bug tool now checks that bugs.md exists with bug
entries before accepting the call. If the agent tries to call log_bug
without writing bugs.md first, the server rejects it with:

  'specs/[feature]/bugs.md does not exist. You MUST write bugs to
   bugs.md first via spec-kit-test before calling MCP bug tools.'

This prevents the agent from calling mcp_spec_kit_log_bug directly
(bypassing the skill workflow) — it MUST write bugs.md first.

Also re-added the MCP sync instructions to spec-kit-test.md:
write bugs.md => confirm with user => sync to MCP (in that order).
…e of truth

The MCP server had log_bug, set_bug_status, and set_bug_plan_ref tools
that tempted the agent to call them directly instead of writing bugs.md
via spec-kit-test. The filesystem guard (checking bugs.md exists before
accepting log_bug) only caught the first call — once bugs.md existed, the
bypass worked again.

Bug tracking is now purely file-based in bugs.md. The MCP server handles
only phase state and artifact status — things that HAVE no markdown file.
No sync needed, no guard needed, no bypass possible.

Tools remaining (9): init_feature, get_feature_state, get_next_actions,
advance_phase, list_features, reopen_feature, close_feature,
update_artifact, auto_detect_features.
- Remove dead bug branches from get_next_actions (no tool can populate
  the bugs array since adc6b79); fix stale reopen hint
- spec-kit-test.md: drop MCP sync instructions referencing removed tools
- Docs: remove log_bug/set_bug_status/set_bug_plan_ref from all four tool
  tables (README, AGENT.md, user-guide, server README); 12 -> 11 tools;
  fix 'pure Python stdlib' contradiction
- Add spec-kit-mcp-server/requirements.txt pinning mcp<2 (2.x renamed
  FastMCP to MCPServer and breaks server.py); CI and install.sh use it
- _save_state: atomic write via tmp file + replace — a crash mid-write
  no longer silently resets all feature state
- test_skills: new drift guard — every mcp_spec_kit_* doc mention must
  exist in server.py and vice versa; removed tools asserted absent
- Untrack .venv/ (1014 files); ignore __pycache__/.pytest_cache
- AGENT.md rewritten: 3-layer architecture, project-level constitution,
  MCP rules, phase state machine, drift-free tool table (11 tools)
- README/user-guide/server README: bug tracking documented as file-based
  (bugs.md single source of truth), corrected tool lists and counts
- CHANGELOG added
…es make hashes dead data

- history-template.md, history-tracking.md: entry format loses **Commit** line
- preflight.md, auto-commit.md: no COMMIT_HASH capture; rationale documented
  (feature branches are squash-merged, pre-merge hashes don't survive;
  traceability = phase name + timestamp + commit message convention)
- spec-kit-summarize.md: stray $COMMIT_HASH line removed
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.

2 participants