# Workspace MCP mode

Self-hosted workspace MCP mode routes targets to the repo that owns them, fans out across repos on four tools, and adds three workspace-only cross-repo tools.

> **Note**
>
> This page describes **self-hosted** workspace mode, where one MCP
> server is started over a workspace directory. Hosted Repowise has its
> own workspace endpoint at `https://api.repowise.dev/mcp/w/{workspace_id}`
> with a different federation rule: there is no default repo, every
> federatable tool takes `repo`, and omitting it answers across every
> member. See
> [Connecting your IDE](https://docs.repowise.dev/hosted/connecting-ide#across-a-whole-workspace).

In workspace mode, every one of the
[core MCP tools](https://docs.repowise.dev/mcp/overview) takes an extra `repo` parameter, and
three workspace-only tools appear for questions that have no single-repo
equivalent. The default repo (set by `repowise workspace set-default`)
is targeted when `repo` is omitted, just like single-repo mode.

## Four ways to address a workspace

| How | Behavior |
|---|---|
| `repo` omitted | `get_context`, `get_symbol`, `get_risk` and `get_health` route each target to the repo that owns it. Other tools target the default repo. |
| `"<alias>:<target>"` in a target | Names the repo outright, for example `web:src/app.ts::App`. |
| `repo="backend"` | Targets the repo with that alias. |
| `repo="all"` | Fans the query out across every repo, on the four tools that support it. |

## Routing targets to the repo that owns them

With `repo` omitted, a path or `path::Name` target goes to the default
repo when the file exists there, else to the one other repo whose
checkout has it. A path found in several other repos returns its
`candidates`. A path in no repo gets the default repo's usual not-found
answer plus a `hint` naming `search_codebase(repo="all")` and the
`<alias>:<path>` form. Routed results carry `repo`.

`get_context` and `get_symbol` route a bare symbol name (`render`,
`Client.send`) the same way, through the workspace symbol index: the
default repo keeps a name it has, one other repo that has it gets the
call, and several return `candidates`. A `repowise#<ref>` omission ref
is found in whichever repo holds it.

| Tool | Batch spanning repos |
|---|---|
| `get_context` | One call answers across repos |
| `get_symbol` | One id per call |
| `get_risk`, `get_health` | One repo per call; the response names the per-repo calls to make |

Routing reads `.repowise-workspace/workspace.db`, a store derived from
each repo's own index. It is refreshed after every workspace update and
is always safe to delete: it is rebuilt the next time it is needed.

```python
get_context(targets=["src/app.ts::App", "api/handlers.py"])  # each routed to its repo
get_context(targets=["web:src/app.ts::App"])                  # repo named outright
get_symbol("Client.send")                                     # bare name, routed
```

`repo="all"` is supported by four tools: `search_codebase` (RRF merge
of the per-repo results), `get_dead_code` (merged, sorted),
`get_overview` (a workspace aggregate, not a ranked merge), and `get_why`
in its workspace search mode.

These calls query up to 4 repos at a time and give each 60 seconds.
A repo that fails to open, errors or times out is listed in
`repos_failed` with its reason while the other repos still answer. A
repo whose index is behind its checkout is listed in `repos_stale` with
`indexed_commit` and `live_head`, and its rows carry `stale: true`.
Both keys appear only when non-empty.

| Variable | Default | Meaning |
|---|---|---|
| `REPOWISE_WORKSPACE_CONCURRENCY` | `4` | Repos queried at a time |
| `REPOWISE_WORKSPACE_REPO_TIMEOUT_S` | `60` | Seconds each repo gets |

Every other tool returns an error for `repo="all"` and asks you to
name one repo. That includes `get_answer`, `get_context`,
`get_symbol`, `get_risk`, `get_change_risk`, `get_dependency_path`,
`get_execution_flows`, and `get_why`'s reference-lookup and
health-dashboard modes. They each resolve one thing in one repo, so
there is nothing coherent to merge.

## Examples

```python
# Get an architecture summary of the whole workspace
get_overview(repo="all")

# Search every repo at once
search_codebase("authentication flow", repo="all")

# Risk assessment in a specific repo
get_risk(targets=["src/auth/service.py"], repo="backend")

# Decision archaeology across repos
get_why("why caching?", repo="all")
```

## How federated queries work

Each tool implements `repo="all"` slightly differently:

- **`get_overview(repo="all")`**, returns workspace-level totals
  (repos, files, symbols), per-repo dependency graph, and cross-repo
  topology / contract links.
- **`search_codebase(repo="all")`**, runs the search in each repo and
  merges results with Reciprocal Rank Fusion (k=60).
- **`get_dead_code(repo="all")`**, concatenates each repo's findings and
  sorts the union by confidence, then by lines, and applies a cross-repo
  confidence adjustment when a "dead" symbol has external consumers.
- **`get_why(repo="all")`**, keyword search across every repo's
  decision records (capped at 15 merged results).
- **`get_context`** and **`get_risk`**, when the target lives in repo A
  but has cross-repo co-change partners or contract consumers in repos B
  and C, append those signals to the response.

## Cross-repo directives in `get_risk`

When you pass `changed_files` in workspace mode, the
[`get_risk`](https://docs.repowise.dev/mcp/get-risk) `directive` block carries the changed
repo's cross-repo fallout in addition to the in-repo blast radius:

- `will_break_consumers`, services in *other* repos that depend on this
  one (structural impact), each with `repo`, `service`, `distance`,
  `score`, and the edge kinds carrying the impact.
- `missing_cross_repo_cochanges`, services in other repos that
  historically co-change with this one but aren't in the diff.
- `breaking_changes`, provider contracts in this repo that changed
  *incompatibly* since the last index (a removed route or field, a type
  or field-number change, a newly-required field), each with the
  impacted consumers it endangers. See
  [Breaking-change guard](https://docs.repowise.dev/multi-repo/breaking-changes).
- `conformance_violations` and `dependency_cycles`, the declared
  dependency-rule breaches and circular dependencies the diff's repo
  participates in. See
  [Architecture conformance](https://docs.repowise.dev/multi-repo/conformance).

## Workspace-only tools

These three tools only exist when the server runs over a workspace. They
read the [system graph](https://docs.repowise.dev/multi-repo/system-map) directly.

### `get_blast_radius`

Cross-repo downstream impact: if you change this service, what breaks
across the other repos? See
[Cross-repo blast radius](https://docs.repowise.dev/multi-repo/blast-radius).

| Parameter | Type | Required | Description |
|---|---|---|---|
| `targets` | string[] | Yes | Node ids (`repo` or `repo::service/path`), repo aliases, or the symbol id of a published symbol |
| `max_depth` | int | No | Reachability depth (1-8, default 3) |
| `include_behavioral` | bool | No | Include co-change (behavioral) edges (default true) |

**Returns:** the impacted services ranked by impact `score`, each with
`distance` (hops), `structural` (a real dependency vs co-change only),
and the edge kinds that carried the impact; plus `impacted_repos`,
`structural_count` / `behavioral_count`, `total_impacted`, and any
`unresolved_targets`.

**When to use:** before changing a high-fan-out provider, to see who
consumes it across repo boundaries. Structural impact (`will break`)
outweighs behavioral co-change (`may drift`).

```python
get_blast_radius(targets=["backend"])
get_blast_radius(targets=["mono::services/auth"], max_depth=2, include_behavioral=False)
```

Contract link rows in `get_blast_radius`, `get_change_risk` and `get_risk`
carry the link's `strategy` and the far end's contract `origin` (omitted
when the stored artifact predates them). `get_risk` and `get_change_risk`
add the graph evidence state with reason codes when it is short of
`measured`, and count HTTP calls read but not linked apart from calls
served inside their own service.

### `get_conformance`

**Opt-in**, off by default alongside `generate_refactoring_code` to
keep the default tool surface lean. Enable it with `repowise mcp --tools +get_conformance` (default set plus
this tool) if your agent needs it. Architecture governance: does the live
system graph obey the declared dependency rules, and are there circular
service dependencies? See [Architecture conformance](https://docs.repowise.dev/multi-repo/conformance).

| Parameter | Type | Required | Description |
|---|---|---|---|
| `repo` | string | No | Limit findings to those involving this repo alias |

**Returns:** `violations` (each with the offending `source`/`target`
services, the `rule_source`/`rule_target` matchers that fired, and the
`edge_kind`), `cycles` (each with the participating `nodes` and
`length`), and the `violation_count` / `cycle_count` / `rules_evaluated`
rollups.

**When to use:** before a refactor that changes service boundaries, or
to audit whether the live architecture still matches the intended one.
Rules are declared under `conformance:` in `.repowise-workspace.yaml`.

```python
get_conformance()
get_conformance(repo="frontend")
```

### `get_architecture`

The one evaluative read of the whole system: how coupled is it, where is
the architectural core, and a single 1-10 architecture score.
Deterministic, structural edges only (co-change excluded). See
[Architecture metrics](https://docs.repowise.dev/multi-repo/architecture-metrics).

**Parameters:** none.

**Returns:** `score` (1-10), `architecture_type` (`core-periphery` or
`hierarchical`), `propagation_cost_pct` (share of other services the
average service reaches), `core_size` / `core_ratio` / `core_members`
(the largest cyclic group), `cycle_count`, `conformance_violations`, a
`role_breakdown` (count of Core / Shared / Control / Peripheral
services), and a one-line `summary`.

**When to use:** before a cross-service refactor, or to gauge and
compare overall system structure over time.

```python
get_architecture()
```

## Setting the default

```bash
repowise workspace set-default backend
```

The default determines what `repo`-less queries hit. Set it to whichever
repo your team works in most often.

## Single endpoint, no extra setup

`repowise init .` at the workspace root writes a single `.mcp.json` that
points at the workspace MCP server. Your editor doesn't need to know
which repo it's targeting, the tools handle routing.

The server lazy-loads each repo's index on first query and evicts with
an LRU policy once more than 5 repos are loaded at once, to bound
memory in large workspaces. The default repo is never evicted.

> **Note**
>
> The MCP transport's per-call token budget still applies in workspace
> mode. `repo="all"` results are aggregated and capped; if you need
> deeper paging, narrow to a specific repo with `repo="<alias>"`.
