Skip to content

detect declines a workspace and init writes a manifest for it anyway — with a source type it guessed and did not flag #441

Closed

Description

@pengfei-threemoonslab

Summary

detect declines a workspace outright. init — the command the control loop routes to from verify --preview — writes a manifest for it anyway, and the manifest it writes declares a source type it did not infer and did not flag for review.

A user following the documented loop on a Python MCP server ends up committing an artifact that detect had just said was pointless, describing an OpenAPI spec that does not exist in the repository.

Reproduction

awslabs/mcp#4489 — a FastMCP Python MCP server under src/billing-cost-management-mcp-server. CLI 0.16.0b7 at fc96bad9.

Step 1 — preview routes to init. Scope resolution works correctly here, picking the right sub-project out of a monorepo with dozens of servers:

control.next_action.command:
  shipgate init --workspace …/src/billing-cost-management-mcp-server --write --json
why: "Every changed path that carries a capability surface belongs to
      src/billing-cost-management-mcp-server…"

Step 2 — detect on that exact path declines it:

is_agent_project : false
frameworks       : []
suggested_sources: []
next_action      : "Stop: Python project with no agent framework, prompts, or tool
                    surface — not a Shipgate target."

Step 3 — init on that exact path writes a manifest:

created: true    manifest_status: written    agent_name: None
placeholders: ['agent.name', 'agent.declared_purpose[0]',
               'tool_sources[0].id', 'tool_sources[0].path']
tool_sources:
  - id: CHANGE_ME
    type: openapi
    path: CHANGE_ME.yaml

Three distinct defects

1. Two commands answer one question differently. detect says "not a Shipgate target"; init proceeds. Because verify --preview routes to init and not to detect, a user following control.allowed_next_commands never sees the refusal. The product leads them to create the artifact its own discovery command says has no purpose.

2. type is a silent guess where id and path are flagged. Both CHANGE_ME fields appear in placeholders[]; type: openapi appears in neither, so nothing tells the reader it needs review. Filling in the two flagged blanks yields a schema-valid manifest declaring an OpenAPI source for a FastMCP Python server — the exact outcome quickstart warns about: "the manifest validates but downstream consumers see meaningless defaults."

The fallback is deliberate and its reasoning is sound — cli/discovery/template.py:94-102:

# Schema requires ≥ 1 of tool_sources / openai_api / anthropic /
# google_adk / langchain / crewai. Empty workspace → emit a
# CHANGE_ME stub identical to the legacy --minimal template's
# fallback so users get an actionable starting point.

Satisfying the schema is the right instinct. Picking one of six possible types and not marking it as a guess is the defect: the stub is "actionable" only if the reader knows to question a field the tool did not question.

3. has_tools_dir: false with a tools/ directory present. The server's tools live at awslabs/billing_cost_management_mcp_server/tools/; the conventional-dir check looks only at the workspace root, so the one structural signal this repository does offer is missed. Contributing cause to defect 1's verdict, minor on its own.

Why this class matters more than one repository

Four vendor MCP servers walked, three languages:

repo language registration detect sees it?
mongodb-mcp-server TypeScript static toolName = "aggregate" no
mcp-grafana Go mcpgrafana.MustTool("update_incident", …) no
awslabs/mcp Python @server.tool(name='budgets', …) no
github-mcp-server Go generates .snap JSON yes

Three of four land in exactly this path — detect declines, init writes a stub anyway. That is the default experience for an MCP server today (#431), which makes this the shape most first-time adopters of that class will hit.

Expected

  • init on a workspace detect declines either refuses with the same reason, or writes and states plainly that discovery found no tool surface and the source block is a scaffold rather than an inference.
  • Any field the tool chose without evidence is marked as needing review — either type: CHANGE_ME with the accepted values listed (as the declaration questionnaire already does for effect), or a comment saying which types are available and that this one was not inferred.
  • detect and init reach the same conclusion about whether a workspace is a target, or the disagreement is stated where the reader is.

Acceptance

  • The reproduction above produces either a refusal or a manifest whose type is visibly unresolved.
  • A workspace detect accepts is unaffected (regression: the ADK and MCP samples).
  • placeholders[] includes every field the template chose without evidence.
  • A nested tools/ directory contributes its conventional-dir signal.

Found 2026-08-24 walking awslabs/mcp#4489. Related: #431 (why three of four MCP servers reach this path), #328 (product defect vs. user configuration error in adopter-facing output), and cause 2 of #440 (two surfaces answering one question with no precedence).

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Picked up inside the current milestone window; keep about 15 openarea:initinit / detect / manifest generationbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions