Skip to content

docs: target-architecture north-star for the monorepo#395

Draft
gbrlcustodio wants to merge 4 commits into
devfrom
docs/target-architecture
Draft

docs: target-architecture north-star for the monorepo#395
gbrlcustodio wants to merge 4 commits into
devfrom
docs/target-architecture

Conversation

@gbrlcustodio

@gbrlcustodio gbrlcustodio commented Jul 11, 2026

Copy link
Copy Markdown
Member

What

Adds docs/target-architecture.md, the repo-wide north-star for the monorepo, and wires it into the doc map.

The doc describes the end-state the packages converge on, not the current code, as a small set of invariants plus the target directory structure per package:

  • App packages (pipefy-mcp-server, pipefy-cli) are thin delivery adapters over the pipefy SDK. They own no domain tier and expose Pipefy surfaces but own none of them.
  • The pipefy SDK is the gateway and the only place the Pipefy domain is modeled, grouped by Pipefy bounded context.
  • Owned logic in the apps is a cross-cutting policy layer (safety, exposure, telemetry, response shaping), perpendicular to Pipefy's seams.

The rules answer two questions: where a file lives (concern folders, delivery-role surface folder, center + wiring/, type ownership) and how it behaves (parse at the boundary, effects built once in wiring/, published-contract DAG with telemetry as a sink, one response-shaping seam over the SDK's typed models).

A load-bearing point: an app owns its delivery concerns (auth, telemetry, response, wiring at the package root) but only carries Pipefy surfaces, which group under one delivery-role folder (tools/ on the server, commands/ on the CLI). A cards/ folder at an app root would be a hollow domain slot that invites SDK domain logic to leak upward and would mirror the SDK's context taxonomy as app structure.

Meta-tool surface (server)

The MCP server exposes a fixed meta-tool surface (search_tools, describe_tool, execute_tool, get_tool_categories) over a hidden, validated tool catalog, plus a raw-GraphQL escape hatch (search_schema, introspect_*, execute_graphql). Only those appear in tools/list; the real tool bodies are reached by name through execute_tool. This keeps the exposed tool-list small (a context-budget concern) and makes the catalog a runtime artifact rather than a protocol dump.

Consequences captured in the doc: dispatch/ is an owned delivery concern and tools/ becomes the hidden catalog (WHERE-2); the argument-parse boundary moves into the dispatcher, against a catalog schema derived from each body's typed signature (HOW-1); response shaping happens at the dispatch boundary (HOW-4); telemetry observes each dispatched body rather than the meta-tool boundary; and remote default-deny is enforced in dispatch. The CLI has no such surface (a CLI is discovered by --help), so it keeps direct command exposure. That server/CLI asymmetry is deliberate and stated in the doc.

Why here vs architecture.md

docs/architecture.md keeps the rules enforced today and the machinery that enforces them (import-linter contract set, ruff TID251). target-architecture.md holds only the end-goal; each invariant moves into architecture.md as the code comes to obey it. The migration is incremental PRs, not a plan recorded in this doc.

Changes

  • New: docs/target-architecture.md.
  • docs/architecture.md: two pointers to the target doc.
  • AGENTS.md, docs/README.md: index entries.

Testing

Docs-only; no code paths touched. Verified the doc is free of em/en dashes and curly quotes, headings are sentence case, and prose is single-line per paragraph.

Add docs/target-architecture.md: the repo-wide end-state the monorepo
converges on, as a small set of invariants (where a file lives, how it
behaves) plus the target directory structure per package. The app
packages are thin delivery adapters over the pipefy SDK gateway; they
own their delivery concerns but only expose Pipefy surfaces under a
delivery-role folder (tools/, commands/), never as root-level domain
folders.

Reference it from docs/architecture.md (which keeps the rules enforced
today and the enforcement machinery), and add index entries in AGENTS.md
and docs/README.md.
@gbrlcustodio
gbrlcustodio force-pushed the docs/target-architecture branch from ecbcfae to 4273ff6 Compare July 11, 2026 22:00
…escape hatch

The MCP server exposes a fixed meta-tool surface (search/describe/execute/
categories) over a hidden, validated tool catalog, plus a raw-GraphQL escape
hatch; only those appear in tools/list. This splits the exposed protocol
surface (dispatch/) from the Pipefy catalog (tools/), moves the argument-parse
boundary into the dispatcher (schema derived from the body signature), and
enforces remote default-deny in dispatch. The CLI keeps direct command
exposure. Update the Decisions, WHERE-2, HOW-1/HOW-4, the server tree, the CLI
note, and the toolsets.py relationship accordingly.
@gbrlcustodio

Copy link
Copy Markdown
Member Author

suggestion (non-blocking): mcp_extensions/ names the wrong layer and groups on a mechanical axis

Two things make the name misfit its contents.

First, terminology collision. "Extension" is already loaded in MCP: the protocol has capabilities and capability negotiation, so mcp_extensions/ invites the reader to expect protocol-level additions and instead find Python-side supplements to the FastMCP/mcp SDK (a tool-call middleware seam, a private-_tool_manager tool-removal helper, a capability accessor). The name points at the protocol layer; the contents live at the SDK-binding layer.

Second, and more fundamental, the folder groups by "code that touches the MCP SDK", which is a technical dimension rather than a concern. That is the same shape as utils/ or adapters/, which rule 1 here disfavors ("named by concern ... never port/adapter/utils/middleware/common", no name overloaded toward a wrong dominant meaning). The trouble naming the folder is the tell: it is a bucket.

Each member has an owning concern to go to, which lets the bucket dissolve (consistent with "cross-cutting code lives in its concern, never a pattern bucket"):

  • the tool-call middleware seam belongs to the tool-call boundary it wraps (dispatch/), which is what telemetry and the validation envelope hook into;
  • tool removal by name (over the private _tool_manager) belongs with the catalog/registry that withholds non-remote-safe tools under the remote profile (wiring/catalog.py);
  • the capability accessor (supports_elicitation) belongs with the tool bodies that elicit, or as a small center-level compat helper;
  • lifespan.py is the clearest misfit: it supplies no SDK API, it fills the SDK's existing lifespan= hook with our runtime, so it is composition and belongs in wiring/.

If the grouping is deliberate, containing FastMCP private-API coupling (_tool_manager, request_handlers) in one blast radius so SDK churn hits a single place is a legitimate goal. In that case name the folder for that intent (the vendor-SDK coupling it isolates), not "extensions", and accept that it is a technical grouping chosen on purpose.

@gbrlcustodio

Copy link
Copy Markdown
Member Author

Addressed in 226a13e, going with dissolving the bucket plus a narrower replacement rather than a rename.

  • mcp_extensions/ is gone. lifespan moved to wiring/ (it fills the public lifespan= hook, so it is composition, not SDK coupling), and the supports_elicitation accessor stays with its caller under tools/ (public API, no special home).
  • The genuinely fragile private-API coupling (request_handlers, _tool_manager) now lives in vendor/, one module per vendor (fastmcp.py), reframed as an owned anti-corruption layer rather than a "touches the SDK" bucket.
  • New rule 5 requires all private-vendor-API access to route through vendor/ shims, so registry.py's direct _tool_manager.list_tools() goes behind a shim too. A TID251 ban on each vendor's private symbols outside vendor/ makes the folder the complete, enforced upgrade-audit surface, which a folder alone could never guarantee.

@gbrlcustodio gbrlcustodio self-assigned this Jul 13, 2026
@gbrlcustodio gbrlcustodio added documentation Improvements or additions to documentation refactor labels Jul 13, 2026
Add an Architectural style section as the reading lens: this is an integration and capability-provider system, so system-scale domain/application/infrastructure layering leaves empty boxes. Name the fitting patterns instead (strategic DDD, SDK as a client-library gateway, MCP server as a microkernel, package-by-feature and concern), and state that the use-case layer is externalized to the caller.

Reorganize Decisions around whose use case: a gateway operation is one of Pipefy's own use cases seen from outside, so the SDK exposes Pipefy's use cases and never the consumer's. Fold the composition, sharing, and exposure points into the boundary rule and the two-hop exposure chain.

Recast the SDK-internal roles note as the worked example of splitting a package into model, repository, and service, with Pipefy as the authority for the advisory logic the SDK mirrors. Align stray system-scale layer language.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation refactor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant