MCP Workspace
September 1, 2026 · View on GitHub
Each test below is self-contained and can be run independently. Execute the steps by calling the MCP tools listed.
If you find any issues — wrong results, confusing errors, broken output formatting — report them.
Section 1: Local file & search tools
Test 1.1: Save / read / append / search / delete
save_file("_test.txt", "line one\nline two\n")read_file("_test.txt")— expect"line one\nline two\n"append_file("_test.txt", "line three\n")read_file("_test.txt")— expect all three linessearch_files(glob="_test.txt")— expect one matchdelete_this_file("_test.txt")search_files(glob="_test.txt")— expect zero matches
Test 1.2: Basic text replacement + already-applied path
save_file("_test.txt", "Hello world\nThis is a test\n")edit_file("_test.txt", old_string="Hello world", new_string="Greetings earth")read_file("_test.txt")— expect"Greetings earth\nThis is a test\n"edit_file("_test.txt", old_string="Hello world", new_string="Greetings earth")— expect"No changes needed - edit already applied"delete_this_file("_test.txt")
Test 1.3: Two sequential edits
save_file("_test.txt", "First section\nSecond section\nThird section\nFourth section\n")edit_file("_test.txt", old_string="First section", new_string="Section one")edit_file("_test.txt", old_string="Third section", new_string="Section three")read_file("_test.txt")— expect both replacements presentdelete_this_file("_test.txt")
Test 1.4: Indentation preserved
save_file("_test.py", "def example():\n # comment\n x = 5\n return x\n")edit_file("_test.py", old_string="# comment", new_string="# returns five")read_file("_test.py")— expect 4-space indentation preserveddelete_this_file("_test.py")
Test 1.5: Failed match
save_file("_test.txt", "Some content\n")edit_file("_test.txt", old_string="does not exist", new_string="x")— expect clean errorread_file("_test.txt")— expect unchangeddelete_this_file("_test.txt")
Test 1.6: Regex special chars treated as literal
save_file("_test.txt", "(parens) [brackets] *stars* ^anchors$\n")edit_file("_test.txt", old_string="(parens) [brackets]", new_string="<angle>")read_file("_test.txt")— expect literal replacement (no regex interpretation)delete_this_file("_test.txt")
Test 1.7: Multi-line edit
save_file("_test.txt", "L1\nL2\nL3\nL4\nL5\n")edit_file("_test.txt", old_string="L2\nL3\nL4", new_string="X\nY\nZ")read_file("_test.txt")— expect"L1\nX\nY\nZ\nL5\n"delete_this_file("_test.txt")
Test 1.8: Markdown bullet indentation
save_file("_test.md", "- top\n- options:\n- a: 1\n- b: 2\n")edit_file("_test.md", old_string="- options:\n- a: 1\n- b: 2", new_string="- options:\n - a: 1\n - b: 2")read_file("_test.md")— expect nested bullets indenteddelete_this_file("_test.md")
Test 1.9: Multi-match safety + replace_all
save_file("_test.txt", "foo bar foo baz foo\n")edit_file("_test.txt", old_string="foo", new_string="FOO")— expect error containing"Multiple matches (3) found for foo"and the hint"Use replace_all=True"edit_file("_test.txt", old_string="foo", new_string="FOO", replace_all=True)read_file("_test.txt")— expect"FOO bar FOO baz FOO\n"delete_this_file("_test.txt")
Test 1.10: move_file
save_file("_a.txt", "content\n")move_file("_a.txt", "_b.txt")read_file("_b.txt")— expect"content\n"search_files(glob="_a.txt")— expect zero matchessave_file("_target.txt", "exists\n")move_file("_b.txt", "_target.txt")— expect error message exactly"Destination already exists"move_file("_does_not_exist.txt", "_x.txt")— expect error message exactly"File not found"- Cleanup both remaining files
Test 1.11: Search, listing, sliced reads, file-size check
list_directory()— expect file list scoped to project, gitignored paths excludedlist_directory(dirs_only=True)— expect only directory entries, each with trailing/search_files(pattern="def main", glob="**/*.py")— expect content matches with file paths and line numberssearch_files(pattern="[unclosed")— invalid regex; expect literal-text fallback with note containing"Pattern treated as literal text (invalid regex)"save_file("_slice.txt", "line one\nline two\nline three\nline four\nline five\n")read_file("_slice.txt", start_line=2, end_line=4)— expect lines 2-4 with line-number prefixes using the Unicode arrow→(U+2192), not:or|(e.g."2→line two\n3→line three\n4→line four\n")delete_this_file("_slice.txt")check_file_size(max_lines=10000)— expect output starting with"File size check passed"and mentioning the total file count. May also surface a"Stale allowlist entries"section — both are normal.
Test 1.12: Reference-project tools
Skip the whole test if get_reference_projects() returns count: 0.
get_reference_projects()— expect dict withcount(int),projects(list of{name, url}objects),usage(str)- Pick a project name from step 1's output.
list_reference_directory(reference_name=<name>)— expect file/dir listing- Pick a known file from step 3 (e.g.
README.md). read_reference_file(reference_name=<name>, file_path=<file>)— expect file contentsearch_reference_files(reference_name=<name>, glob="**/*.py")— expect matches (skip step if project has no Python)
Section 2: Local git tools
(No network. Read-only. Skip Test 2.1 step 8 if the repo has fewer than 2 commits.)
Test 2.1
get_base_branch()— expect a non-empty string (may be the parent branch, not necessarily"main")git(command="status")— expect output containing"On branch"and a working-tree state linegit(command="log", max_lines=100)— expect output starting with"commit "and containing"Author:"git(command="branch", args=["--show-current"])— expect current branch name (non-empty)git(command="rev_parse", args=["HEAD"])— expect a 40-char SHAgit(command="ls_files", args=["src/"])— expect tracked files undersrc/git(command="diff", args=["HEAD", "--stat"], compact=False)— on a clean tree expect literal"No changes found"; otherwise expect a file-change summarygit(command="diff", args=["HEAD~1..HEAD", "--stat"], compact=False)— expect file-change summary for the latest commit (requires ≥2 commits)
Section 3: Network-bound tools (slow, rate-limited)
These hit live APIs. Run only when needed.
Test 3.1
github_issue_list(state="open", max_results=3)— expect lines starting with#(e.g."#200 [open] ...")github_search(query="bug", max_results=3)— expect issue results only (is:issueis the default), no auto-filter note- Pick an issue number from step 1.
github_issue_view(number=<from step 1>)— expect formatted issue bodygithub_search(query="is:pr is:closed", max_results=3)— expect[PR]lines only; pick a closed PR number (or skip if none)github_pr_view(number=<from step 5>)— expect formatted PR body- Pick a reference project
<name>fromget_reference_projects(). Skip steps 8-10 if it returnscount: 0or the chosen project has a nullurl. github_issue_list(reference_name=<name>, state="open", max_results=3)— expect lines starting with#, from the sibling repogithub_issue_view(number=<from step 8>, reference_name=<name>)— expect formatted issue bodygithub_issue_list(reference_name="does_not_exist")— expect exactly"Error: Reference project 'does_not_exist' not found"
Test 3.2
-
check_branch_status(ci_timeout=0, pr_timeout=0)— with polling disabled, returns immediately. Expect output containing each of these line prefixes:"Branch:""Branch Status:""GitHub Label:""Recommendations:"
Additionally, when run on an issue-numbered branch (
<number>-...), expect a"Linked Branch:"line; it is absent onmainand other non-issue branches.
Section 4: GitHub write tools (MUTATING — opt-in)
These tools create and modify real GitHub objects. Run only when you intend to write to the repository. GitHub has no issue-delete API, so the test issue is closed rather than removed.
Test 4.1: Issue create → edit → comment → close
github_label_list()— expect one line per label with name,#colorand descriptiongithub_label_list(search="bug")— expect the filtered subsetgithub_issue_create(title="LLM test - safe to close", body="Created by tests/LLM_Test.md Section 4.")— expectCreated issue #N — <url>, thenLabels: (none)andAssignees: (none)github_issue_comment(number=<N from step 3>, body="Test comment.\nSecond line.")— expectAdded comment to issue #N — <url>github_issue_edit(number=<N>, title="LLM test - edited", add_labels=["<a real label from step 1>"])— expectUpdated issue #N, then aLabels:line containing that labelgithub_issue_edit(number=<N>, remove_labels=["<a label NOT on the issue>"])— expect success, not an error: the no-op removal is filtered outgithub_issue_edit(number=<N>, add_labels=["status-01:created"])— expect an error namingmcp-coder gh-tool set-status, and no change to the issuegithub_issue_edit(number=<N>, add_labels=["definitely-not-a-real-label"])— expect an error naming the unknown labelgithub_issue_edit(number=<N>, state="closed")— expect(state: closed)
Test 4.2: Cross-repo issue create → comment → edit → close
Writes to a sibling repository. Skip the whole test if get_reference_projects() returns
count: 0 or no project has a non-null url.
- Pick a reference project
<name>with a non-nullurl. github_label_list(reference_name=<name>)— expect that repo's labels, one per linegithub_issue_create(title="LLM test - safe to close", body="Created by tests/LLM_Test.md Section 4.", reference_name=<name>)— expectCreated issue #N — <url>, with the URL pointing at the sibling repogithub_issue_comment(number=<N from step 3>, body="Test comment.", reference_name=<name>)— expectAdded comment to issue #N — <url>github_issue_edit(number=<N>, add_labels=["status-01:created"], reference_name=<name>)— expect an error namingmcp-coder gh-tool set-statusand that project's own checkout, and no change to the issuegithub_issue_edit(number=<N>, state="closed", reference_name=<name>)— expect(state: closed)
Test 4.3: PR creation guards (no PR is created)
Only the rejection paths are scripted — creating a real PR needs a real branch.
github_pr_create(title="")— expect an empty-title errorgithub_pr_create(title="x", head="main", base="main")— expect a head/base errorgithub_pr_create(title="x", head="bad~name")— expect an invalid-branch-name error