# MCP Server Test Script

After restarting the MCP server (or restarting Claude Desktop), paste each prompt below into Claude Desktop one at a time. Expected behavior is described after each prompt.

## Test 1: Auth check

Prompt:

> Are you connected to Brightspace?

Expected: Claude calls `auth_status`. Response shows `"authenticated": true`. If false, run `bs_auth()` in an interactive R session first.

## Test 2: Dataset discovery

Prompt:

> What datasets are available? List them for me.

Expected: Claude calls `list_datasets`. You see a list of all BDS dataset names with descriptions. Should be dozens of datasets.

## Test 3: Keyword search

Prompt:

> Find any datasets related to grades.

Expected: Claude calls `search_datasets` with keyword “grade” (or similar). Returns a filtered list – should include “Grade Results” and possibly others.

## Test 4: Dataset description with column stats

Prompt:

> Describe the Users dataset. What columns does it have?

Expected: Claude calls `describe_dataset` with name “Users”. Response should show:

- Row count and column count
- Per-column summaries (not sample rows):
  - Numeric columns show min/max/mean
  - Character columns show n_unique and top 3 values
  - Date columns show min/max date range
- Footer suggesting `execute_r`

## Test 5: Simple execute_r – scalar result

Prompt:

> How many users are there in total?

Expected: Claude calls `execute_r` with something like:

    nrow(bs_get_dataset("Users"))

Returns a single number. No raw data transfer.

## Test 6: execute_r – data frame result

Prompt:

> Show me the top 10 most common role names in the User Enrollments dataset.

Expected: Claude calls `execute_r` with a dplyr pipeline like:

    bs_get_dataset("User Enrollments") %>%
      count(role_name, sort = TRUE) %>%
      head(10)

Returns a compact text table (not JSON, not thousands of rows).

## Test 7: execute_r – persistent workspace

Prompt:

> Now filter those enrollments to just Students and tell me how many there are.

Expected: Claude uses variables from the previous call (or re-loads and filters). The key test is that Claude can reference or build on prior work. Should return a count.

## Test 8: Interactive Chart.js chart

Prompt:

> Create an interactive bar chart showing enrollment counts by role.

Expected: Claude should:

1. Call `execute_r` to compute the counts (e.g., `count(role_name, sort = TRUE)`)
2. Build a self-contained HTML string with Chart.js from CDN
3. Write it with `writeLines()` to the output directory
4. Call `browseURL()` to open it

The response should include the HTML file path. Opening it shows an interactive bar chart with tooltips and hover effects.

## Test 8b: Static ggplot fallback

Prompt:

> Use ggplot to create a bar chart of enrollment counts by role. Return the plot object.

Expected: Claude calls `execute_r` with ggplot code (no `ggsave()`). The server detects the ggplot object and saves it as PNG + HTML viewer. Response should include:

1. A text line like “Plot saved: Enrollments by Role (1 layer)”
2. File paths for the PNG and HTML viewer in the output directory

This tests the static chart fallback for when Chart.js is not suitable.

## Test 9: get_data_summary – basic

Prompt:

> Give me a quick summary of the Grade Results dataset.

Expected: Claude calls `get_data_summary` with dataset “Grade Results”. Returns row/column counts and per-column statistics. Footer suggests `execute_r` for custom analysis.

## Test 10: get_data_summary – with filter

Prompt:

> Summarize the User Enrollments dataset, but only for the “Student” role.

Expected: Claude calls `get_data_summary` with:

- dataset: “User Enrollments”
- filter_by: `{"role_name": "Student"}`

Returns stats for the filtered subset only. Row count should be less than the full dataset.

## Test 11: get_data_summary – with group_by

Prompt:

> Break down User Enrollments by role_name. How many of each role are there?

Expected: Claude calls `get_data_summary` with:

- dataset: “User Enrollments”
- group_by: `["role_name"]`

Returns group counts sorted by frequency (Student, Instructor, etc.). May also show numeric column means per group.

## Test 12: execute_r – join and analyze

Prompt:

> Join the Users and User Enrollments datasets. How many courses is each user enrolled in on average?

Expected: Claude calls `execute_r` with something like:

    users <- bs_get_dataset("Users")
    enrollments <- bs_get_dataset("User Enrollments")
    joined <- bs_join(users, enrollments)
    joined %>%
      group_by(user_id) %>%
      summarise(n_courses = n_distinct(org_unit_id)) %>%
      summarise(
        mean_courses = mean(n_courses),
        median_courses = median(n_courses)
      )

Returns a small summary table.

## Test 13: execute_r – error handling

Prompt:

> Run this R code: `nonexistent_function(123)`

Expected: Claude calls `execute_r`. The result should have `isError: true` with a message like “could not find function ‘nonexistent_function’”. Claude should explain the error gracefully.

## Test 14: Multi-step analysis (integration test)

Prompt:

> I want a complete analysis of grade performance. First describe the Grade Results dataset so I understand the columns, then show me the distribution of final grades as a histogram, and finally give me the mean grade broken down by org_unit_id (show the top 10).

Expected: Claude makes 3+ tool calls in sequence:

1. `describe_dataset` or `execute_r` to explore columns
2. `execute_r` with ggplot histogram – inline image appears
3. `execute_r` with grouped summary – text table of top 10

This tests the full workflow: discover, visualize, summarize.

## Test 15: Removed tools are gone

Prompt:

> Use the get_dataset tool to download the Users table.

Expected: Claude should NOT have access to `get_dataset` (it was removed). Instead it should either:

- Use `execute_r` to load the data: `bs_get_dataset("Users") %>% head(20)`
- Or use `get_data_summary` for stats
- If it tries `get_dataset`, the server returns “Unknown tool” error

## Test 16: list_schemas

Prompt:

> What schemas are registered and what are their key columns?

Expected: Claude calls `list_schemas`. Returns a list of schema names with their key columns (the foreign keys used by `bs_join()`).

## Troubleshooting

Server won’t start:

- Check `claude_desktop_config.json` has the right path to `server.R`
- Ensure `cwd` points to the directory with `config.yml`
- Run `Rscript /path/to/server.R` manually to see stderr errors

Auth fails:

- Open R interactively, run `library(brightspaceR); bs_auth()` to get a token
- The token is cached; the MCP server reuses it

Plots don’t render:

- Ensure the `png()` graphics device is available (it should be on all standard R installs)
- Check stderr for errors from `grDevices::png()`

Results are truncated:

- This is intentional at ~800KB. Use `head()` or `filter()` in `execute_r` to narrow results
- The truncation message tells you this