LogoSkills

mcp-toolkit:scenario

Run a YAML-defined scenario that orchestrates dynamic tool calls + Marionette + pixel-loop in one flow.

ํ•ญ๋ชฉ๋‚ด์šฉ
Invoke/mcp-toolkit:scenario
Categorycocode-flutter
Complexityhigh
MCP Serversmcp_flutter, marionette, maestro, dart

/mcp-toolkit scenario โ€” ์‹œ๋‚˜๋ฆฌ์˜ค ํ•œ ๋ฒˆ์— ์ž๋™ ์‹คํ–‰#

ํ•œ๋งˆ๋””๋กœ#

์—ฌ๋Ÿฌ ๋‹จ๊ณ„์˜ ์•ฑ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐ๋ณธ(YAML ํŒŒ์ผ) ํ•˜๋‚˜์— ์ ์–ด๋‘๋ฉด, ์ˆœ์„œ๋Œ€๋กœ ์ž๋™์œผ๋กœ ์‹คํ–‰ํ•ด ์ฃผ๋Š” ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ์—ฐ๊ทน ๋Œ€๋ณธ์ฒ˜๋Ÿผ "์ค€๋น„ โ†’ ๋ณธ ์—ฐ๊ธฐ โ†’ ์ •๋ฆฌ"๋ฅผ ๋ฏธ๋ฆฌ ์ ์–ด ๋‘๋ฉด, ๋ฐฐ์šฐ(์•ฑ) ๋Œ€์‹  ์ž๋™์œผ๋กœ ๋ฌด๋Œ€๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋กํ•ด ์ค๋‹ˆ๋‹ค.

๋ˆ„๊ฐ€ยท์–ธ์ œ ์“ฐ๋‚˜์š”#

  • ์•ฑ์˜ ๋ณต์žกํ•œ ํ๋ฆ„์„ ์ž๋™์œผ๋กœ ๊ฒ€์ฆํ•˜๊ณ  ์‹ถ์€ ๊ฐœ๋ฐœ์ž/QA ๋‹ด๋‹น์ž
  • ์˜ˆ: "๋กœ๊ทธ์ธ โ†’ ๋ฐ์ดํ„ฐ ์ค€๋น„ โ†’ ํ™”๋ฉด์—์„œ ๋‹น๊ฒจ์„œ ์ƒˆ๋กœ๊ณ ์นจ โ†’ ๊ฒฐ๊ณผ๊ฐ€ ๋งž๋Š”์ง€ ํ™•์ธ โ†’ ํ™”๋ฉด ์บก์ฒ˜"์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ ๋™์ž‘์ด ์„ž์ธ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๋ฐ˜๋ณต ์‹คํ–‰ํ•ด์•ผ ํ•  ๋•Œ
  • ํŠนํžˆ ์•ฑ ๋‚ด๋ถ€ ์ƒํƒœ ํ™•์ธ + ํ™”๋ฉด ํ„ฐ์น˜ + ํ™”๋ฉด ์ด๋ฏธ์ง€ ๋น„๊ต๊ฐ€ ํ•œ๊บผ๋ฒˆ์— ํ•„์š”ํ•œ ๊ฒฝ์šฐ(cocode์˜ ๋Œ€ํ‘œ์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€)์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ‘‰ ์ด๋Ÿด ๋• ์“ฐ์ง€ ๋งˆ์„ธ์š”: ๋‹จ์ˆœํžˆ ํ™”๋ฉด์„ ํ„ฐ์น˜ํ•˜๊ณ  ์Šคํฌ๋ฆฐ์ƒท๋งŒ ์ฐ๋Š” ์ •๋„๋ผ๋ฉด, ๋” ๊ฐ€๋ฒผ์šด cc-flutter-marionette:sweep๋‚˜ cc-flutter-pixel-loop:loop๋ฅผ ์“ฐ๋Š” ๊ฒŒ ๋‚ซ์Šต๋‹ˆ๋‹ค.

๋ฌด์—‡์„ ํ•ด์ฃผ๋‚˜์š”#

๊ฐ๋ณธ๋Œ€๋กœ ์‹คํ–‰ํ•œ ๋’ค, ๊ฒฐ๊ณผ๋ฅผ ํด๋” ํ•˜๋‚˜์— ์ •๋ฆฌํ•ด ์ค๋‹ˆ๋‹ค. ํด๋” ์•ˆ์—๋Š”:

  • scenario.yaml โ€” ์‹ค์ œ๋กœ ์‹คํ–‰ํ•œ ๊ฐ๋ณธ ์‚ฌ๋ณธ
  • steps/ โ€” ๊ฐ ๋‹จ๊ณ„๋ณ„๋กœ ๋ฌด์—‡์„ ํ–ˆ๊ณ  ์–ด๋–ค ๊ฐ’์ด ๋Œ์•„์™”๋Š”์ง€(01.json ๋“ฑ)
  • screens/ โ€” ์‹œ๋‚˜๋ฆฌ์˜ค ์ค‘ ์ฐ์€ ํ™”๋ฉด ์บก์ฒ˜ ์ด๋ฏธ์ง€
  • bloc-dumps/ โ€” ์•ฑ ๋‚ด๋ถ€ ์ƒํƒœ ๊ธฐ๋ก
  • teardown.json โ€” ๋งˆ๋ฌด๋ฆฌ ์ •๋ฆฌ ๋‹จ๊ณ„ ๊ธฐ๋ก
  • summary.md โ€” ์„ฑ๊ณต/์‹คํŒจ ์š”์•ฝ(๊ฑธ๋ฆฐ ์‹œ๊ฐ„ ํฌํ•จ)

์–ด๋–ป๊ฒŒ ์“ฐ๋‚˜์š”#

# ๊ฐ๋ณธ ํŒŒ์ผ์„ ์ง€์ •ํ•ด ์‹คํ–‰
/mcp-toolkit scenario --file test/scenarios/feed_pull_to_refresh.yaml

# ๊ฒฐ๊ณผ ์ €์žฅ ํด๋”๋ฅผ ์ง์ ‘ ์ง€์ • (์ƒ๋žตํ•˜๋ฉด ์ž๋™ ํด๋”์— ์ €์žฅ)
/mcp-toolkit scenario --file ... --output .claude/docs/mcp-toolkit/my-run/

# ์‹ค์ œ ์‹คํ–‰ ์—†์ด ๊ฐ๋ณธ ๋ฌธ๋ฒ•์ด ๋งž๋Š”์ง€๋งŒ ๊ฒ€์‚ฌ (์˜ˆํ–‰์—ฐ์Šต)
/mcp-toolkit scenario --file ... --dry-run
  • --file (ํ•„์ˆ˜): ์‹คํ–‰ํ•  ๊ฐ๋ณธ YAML ํŒŒ์ผ ๊ฒฝ๋กœ
  • --output (์„ ํƒ): ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•  ํด๋”. ์•ˆ ์ ์œผ๋ฉด .claude/docs/mcp-toolkit/<์‹œ๋‚˜๋ฆฌ์˜ค์ด๋ฆ„>-<์‹œ๊ฐ>/์— ์ž๋™ ์ €์žฅ
  • --dry-run (์„ ํƒ): ์ง„์งœ๋กœ ๋Œ๋ฆฌ์ง€ ์•Š๊ณ  ๊ฐ๋ณธ์ด ์˜ฌ๋ฐ”๋ฅธ์ง€๋งŒ ํ™•์ธ

์•ˆ์—์„œ ๋ฌด์Šจ ์ผ์ด ๋ฒŒ์–ด์ง€๋‚˜์š”#

์ด ๋„๊ตฌ๋Š” ํ•œ ๋ฒˆ ์ญ‰ ์‹คํ–‰ํ•˜๊ณ  ๋๋‚˜๋Š” ์ •ํ•ด์ง„ ์ˆœ์„œ์˜ ์ž๋™ ์žฌ์ƒ์ž…๋‹ˆ๋‹ค(๊ฐ™์€ ๊ฑธ ๋ฐ˜๋ณตํ•˜๋Š” ๋ฃจํ”„๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค).

  1. ์ค€๋น„(setup) โ€” ๋กœ๊ทธ์ธ, ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”, ํ…Œ์ŠคํŠธ์šฉ ๋ฐ์ดํ„ฐ ์‹ฌ๊ธฐ, ํ™”๋ฉด ์ด๋™ ๋“ฑ ์‚ฌ์ „ ์ค€๋น„๋ฅผ ํ•œ ๋ฒˆ ํ•ฉ๋‹ˆ๋‹ค.
  2. ๋ณธ ํ๋ฆ„(steps) โ€” ๊ฐ๋ณธ์— ์ ํžŒ ๋™์ž‘์„ ์ฐจ๋ก€๋กœ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์•ฑ ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ฑฐ๋‚˜(expect๋กœ "์ด๋Ÿฐ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์™€์•ผ ํ•œ๋‹ค" ๊ฒ€์ฆ), ํ™”๋ฉด์„ ํ„ฐ์น˜/์Šคํฌ๋กคํ•˜๊ฑฐ๋‚˜, ํ™”๋ฉด์„ ์บก์ฒ˜ํ•ฉ๋‹ˆ๋‹ค.
  3. ์ •๋ฆฌ(teardown) โ€” ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” ๋“ฑ ๋’ท์ •๋ฆฌ๋ฅผ ํ•ฉ๋‹ˆ๋‹ค. ์ค‘๊ฐ„์— ์‹คํŒจํ•˜๋”๋ผ๋„ ์ด ์ •๋ฆฌ ๋‹จ๊ณ„๋Š” ํ•ญ์ƒ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
  4. ์‚ฌํ›„ ํ™•์ธ(asserts) โ€” ๋งˆ์ง€๋ง‰์œผ๋กœ ์ตœ์ข… ์ƒํƒœ๊ฐ€ ๊ธฐ๋Œ€๋Œ€๋กœ์ธ์ง€ ํ•œ ๋ฒˆ ๋” ์ ๊ฒ€ํ•ฉ๋‹ˆ๋‹ค.

์‹คํŒจ๋Š” ์ด๋Ÿฐ ๊ฒฝ์šฐ์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค: ๋„๊ตฌ ํ˜ธ์ถœ ์ž์ฒด๊ฐ€ ์˜ค๋ฅ˜์ผ ๋•Œ, expect๋กœ ๊ธฐ๋Œ€ํ•œ ๊ฐ’๊ณผ ์‹ค์ œ๊ฐ€ ๋‹ค๋ฅผ ๋•Œ, ์™ธ๋ถ€ ๋ช…๋ น์ด ์‹คํŒจ๋ฅผ ๋Œ๋ ค์ค„ ๋•Œ.


โš™๏ธ ์ƒ์„ธ ์˜ต์…˜ยท์‹คํ–‰ ๋ช…์„ธ (๊ฐœ๋ฐœ์ž / AI ์—์ด์ „ํŠธ์šฉ)

Parameters#

ParameterRequiredDescription
--fileโœ…Path to a scenario YAML
--outputโŒOutput dir (default .claude/docs/mcp-toolkit/<scenario-name>-<ts>/)
--dry-runโŒValidate the YAML without executing

YAML Schema#

name:  < scenario-id >              # required, snake_case
description:  < free text > 

 # Optional preconditions โ€” run once
setup:
  - tool:  < name >                # dynamic tool from mcp_flutter
    args: { ... }
  - external:  < plugin:command > 
     args: { ... }

# Main flow
steps:
  - tool:  < name > 
     args: { ... }
    expect: { ... }           # optional assertion on the tool ' s return
  - external:  < plugin:command > 
     args: { ... }

# Teardown โ€” runs even on failure
teardown:
  - tool:  < name > 
     args: { ... }

# Post-conditions
asserts:
  - tool: bloc.dump_state
    args: { type: FeedBloc }
    expect: { status: loaded, posts: { length: 10 } }

tool: vs external:#

  • tool: โ€” dynamic tool from mcp_flutter (the in-app tools you registered).
  • external: โ€” call into another plugin's command. Currently supported:
    • cc-flutter-marionette:tap, enter_text, scroll_to, take_screenshots, get_interactive_elements, hot_reload
    • cc-flutter-pixel-loop:capture, compare
    • cc-jaspr-web:audit, match, regression โ€” when the scenario covers a Web/Jaspr surface

Example Scenario#

# test/scenarios/feed_pull_to_refresh.yaml
name: feed_pull_to_refresh
description: Verify pull-to-refresh on Feed correctly hits the server and updates state.

setup:
  - tool: auth.impersonate
    args: { persona:  " alice "   }
  - tool: db.reset
  - tool: db.seed_fixture
    args: { name:  " feed_with_3 "   }
  - tool: nav.go_named
    args: { name:  " feed "   }

steps:
  # Confirm initial state has 3 posts
  - tool: bloc.dump_state
    args: { type:  " FeedBloc "   }
    expect: { status:  " loaded " , posts: { length: 3 } }

  - external: cc-flutter-pixel-loop:capture
    args: { route:  " /feed " , label:  " before-refresh "   }

  # Seed more posts on the backend
  - tool: db.seed_fixture
    args: { name:  " feed_with_10 "   }

  # User pulls to refresh
  - external: cc-flutter-marionette:scroll_to
    args: { key:  " feed_top "   }
  - external: cc-flutter-marionette:swipe   # if pull gesture supported
    args: { direction:  " down " , from_key:  " feed_top "   }

  - tool: bloc.dump_state
    args: { type:  " FeedBloc "   }
    expect: { status:  " loaded " , posts: { length: 10 } }

  - external: cc-flutter-pixel-loop:capture
    args: { route:  " /feed " , label:  " after-refresh "   }

teardown:
  - tool: db.reset
  - tool: auth.impersonate
    args: { persona:  " default "   }

asserts:
  # Final state was already asserted in steps; nothing extra here.

Convergence#

The scenario runner does not loop. It is a deterministic playback. Failures collected:

  • Tool error (non-2xx style response).
  • expect: mismatch.
  • external: command returned a failure summary.

On any failure, teardown still runs.

Output#

.claude/docs/mcp-toolkit/ < scenario-name > - < ts > /
โ”œโ”€โ”€ scenario.yaml          # copy of the run scenario
โ”œโ”€โ”€ steps/
โ”‚   โ”œโ”€โ”€ 01.json            # each step ' s args + return
โ”‚   โ””โ”€โ”€ ...
โ”œโ”€โ”€ screens/               # any pixel-loop captures forwarded here
โ”œโ”€โ”€ bloc-dumps/
โ”œโ”€โ”€ teardown.json
โ””โ”€โ”€ summary.md             # pass/fail summary with timings

When to use vs when to avoid#

  • Use: complex flows that mix domain state + UI taps + visual diffs (the cocode bread-and-butter case).
  • Avoid for: pure tap-and-screenshot flows โ€” those belong in cc-flutter-marionette:sweep or cc-flutter-pixel-loop:loop.