← all docs · netdocs-browse7 September 2026

NetDocuments browse + ids — implementation plan

Status: In progress · Author: Fable (lane FEAT netdocs browse ids) · Date: 2026-09-07
Repo: north-os — packages/connector-netdocs-mcp, packages/agent-runtime, apps/mcp-server, packages/analytics
Related: specs/2026-08-17-netdocuments-mcp-federation-design (§7 layer 2 revised by this lane) · triage docs/reviews/2026-09-07-partner-netdocs-gating-triage lane L5 · PR #216 (source of the cherry-picked pieces, untouched)

1. Why

The partner's session showed the federation's structural limits: no folder browsing, no matter or document ids. Search is keyword-only and the model cannot walk a cabinet → workspace → folder tree or resolve a matter name to a workspace. This lane adds two read-only tools on the existing netdocs_mcp connection, using NetDocuments' REST API with the token the federation already leases (spec §3.2 P3 verified that the MCP-obtained token answers GET /v1/user/cabinets).

2. Scope and cap

3. Design

3.1 REST layer — packages/connector-netdocs-mcp/src/rest.ts

3.2 Tools — packages/agent-runtime/src/tools/netdocs.ts

Both tools reuse runToolBoundaryprepareOperationrunWithRecoverysettledToolResult exactly like netDocsSearchTool; NetDocsOperation and the analytics netdocs.search_settled.operation enum gain browse and workspace_lookup.

3.3 MCP surface — apps/mcp-server/src/tools/netdocs-browse.ts, netdocs-workspace-lookup.ts

Same shape as netdocs-search.ts: family: "netdocs", readOnlyHint: true, openWorldHint: true, redactArgs to counts and booleans only, handler = assertMcpToolAccess + invokeNetDocsTool. Registered in NETDOCS_MCP_TOOL_DEFS and the read catalogue; the existing family gating (pilot, posture, kill switch, syncNetDocs) then applies unchanged.

3.4 Prose

The four descriptions live in packages/connector-netdocs-mcp/src/browse-copy.ts (Fable worker), exported constants imported by both surfaces. MCP descriptions must satisfy the catalogue test: mention federation, personal and firm-wide, and end with NETDOCS_UNTRUSTED_DATA_NOTICE.

4. Guard tests (each with its mutation clause)

  1. Method pin (test/rest.test.ts): a recording fetchImpl sees every request of all three operations with method === "GET" and no body. Mutation: change the literal to POST → red.
  2. Single HTTP site (test/surface-guards.test.ts): source scan of src/*.ts: method: occurs only in rest.ts and only as "GET"; the package's public exports are exactly the approved names (no generic request function leaks). Mutation: export restGet or add method: "POST" anywhere → red.
  3. Family membership, product surface (netdocs-capability.test.ts): netDocsTools names include both new tools; toolsWithNetDocsCapability(base, undefined) exposes neither; the default toolbelt never contains them. Mutation: drop one from netDocsTools → red.
  4. Family membership, MCP surface (netdocs-tools.test.ts): NETDOCS_MCP_TOOL_NAMES includes north__netdocs_browse and north__netdocs_workspace_lookup; the existing remove/restore, pilot and kill-switch loops then cover both. Mutation: unregister one def → red.
  5. Sub-call cap: a lookup across 8 cabinets issues at most 10 GETs and reports truncated. Mutation: raise the cabinet cap → red.
  6. Token custody: a 401 on a REST read triggers the same refresh path as search (one refresh, then re-mark), asserted through the existing netdocs.test.ts connectors-api mock. Mutation: bypass runWithRecovery → red.

5. Assumption ledger

#AssumptionStatusFalsifier
B-1The MCP-leased token is accepted by the REST API for cabinets, container and attribute readsVerified for /v1/user/info and /v1/user/cabinets (spec P3, 2026-08-17); container and attributes not probedOne browse call on preprod after deploy; 401/403 on either path flips it
B-2/v2/container/{id} returns Results + SkipToken with v1 standardAttributes or v2 DocId/Attributes items (PR #216 walk, verified live there for the walk)Carried over, UNVERIFIED-LIVE in this repoSame probe; a shape refusal renders as provider_format_unsupported, never as absence
B-3The cabinet's wsAttrNum lookup table lists workspaces with an id usable as a container id (PR #216 A-26)UnverifiedLookup then browse of a returned id; a 404 on browse flips it and the tool degrades to matches without folders
B-4The document id netdocs_fetch accepts equals the REST item's envId (v1) / EnvId (v2) when present, else its idUnverifiedBrowse a folder, fetch one returned id; the description tells the model which to try first

6. Workers and file boundaries

6b. Decisions taken during implementation and review (2026-09-07)

Four departures from §3, each made deliberately and each carried through the dual review.

Size. The finished implementation is about 840 lines of product code against the 500-line cap set before the work. Nothing was cut to fit: the overage is the transport's own safety work (redaction, host containment, deadline and byte caps, error mapping) plus two tools rather than one. Recorded here and in the pull request rather than absorbed silently.

Filed as follow-ups, not fixed here (pre-existing, outside this diff): the provider-catalogue check accepts a tool that merely omits readOnlyHint instead of requiring it to be true; and an already-open MCP session's catalogue is not re-gated on the kill switch or pilot removal, only on authorization posture.

7. Review and gates

Fresh omp (gpt-5.6-sol, high) and fresh Fable reviewers, no implementation context, mutation clause on every assertion the diff adds or touches, deletion audit as its own pass; two rounds then BLOCKED. Gates: root lint and typecheck, suites of connector-netdocs-mcp, agent-runtime, mcp-server, analytics.

Shortcuts named: no separate cross-model plan review (the dispatcher's brief fixed the design; the dual code review is the check); no live NetDocuments probe from this lane (preprod is read-only for lanes and no synthetic NetDocuments account exists), so B-1…B-4 stay open until the first deployed call.