pipefy-mcp-server 0.3.0a1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. pipefy_mcp_server-0.3.0a1/.gitignore +56 -0
  2. pipefy_mcp_server-0.3.0a1/AGENTS.md +169 -0
  3. pipefy_mcp_server-0.3.0a1/CLAUDE.md +1 -0
  4. pipefy_mcp_server-0.3.0a1/PKG-INFO +141 -0
  5. pipefy_mcp_server-0.3.0a1/README.md +127 -0
  6. pipefy_mcp_server-0.3.0a1/pyproject.toml +69 -0
  7. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/__init__.py +16 -0
  8. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/_docs.py +7 -0
  9. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/auth/__init__.py +19 -0
  10. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/auth/request_identity.py +94 -0
  11. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/auth/resource_server.py +175 -0
  12. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/core/fastmcp_tool_lifecycle.py +46 -0
  13. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/core/runtime.py +164 -0
  14. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/main.py +100 -0
  15. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/server.py +178 -0
  16. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/settings.py +288 -0
  17. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/__init__.py +5 -0
  18. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/ai_agent_tools.py +649 -0
  19. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/ai_automation_tools.py +435 -0
  20. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/ai_tool_helpers.py +370 -0
  21. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/attachment_tool_helpers.py +137 -0
  22. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/attachment_tools.py +219 -0
  23. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/automation_tool_helpers.py +194 -0
  24. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/automation_tools.py +676 -0
  25. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/behavior_placeholder_interpolation.py +23 -0
  26. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/destructive_tool_guard.py +100 -0
  27. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/field_condition_tools.py +482 -0
  28. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/graphql_error_helpers.py +590 -0
  29. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/introspection_tool_helpers.py +53 -0
  30. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/introspection_tools.py +190 -0
  31. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/mcp_capabilities.py +36 -0
  32. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/member_tool_helpers.py +86 -0
  33. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/member_tools.py +247 -0
  34. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/observability_tool_helpers.py +101 -0
  35. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/observability_tools.py +535 -0
  36. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/organization_tools.py +43 -0
  37. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/pagination_helpers.py +94 -0
  38. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/phase_transition_helpers.py +84 -0
  39. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/pipe_config_tool_helpers.py +629 -0
  40. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/pipe_config_tools.py +1297 -0
  41. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/pipe_tool_helpers.py +521 -0
  42. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/pipe_tools.py +1336 -0
  43. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/portal_tool_helpers.py +264 -0
  44. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/portal_tools.py +1032 -0
  45. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/registry.py +284 -0
  46. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/relation_tool_helpers.py +101 -0
  47. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/relation_tools.py +328 -0
  48. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/remote_profile.py +31 -0
  49. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/report_tool_helpers.py +103 -0
  50. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/report_tools.py +857 -0
  51. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/table_tool_helpers.py +243 -0
  52. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/table_tools.py +1060 -0
  53. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/tool_context.py +23 -0
  54. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/tool_error_envelope.py +132 -0
  55. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/transition_hints.py +35 -0
  56. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/validation_envelope.py +179 -0
  57. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/validation_helpers.py +130 -0
  58. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/webhook_tool_helpers.py +72 -0
  59. pipefy_mcp_server-0.3.0a1/src/pipefy_mcp/tools/webhook_tools.py +510 -0
  60. pipefy_mcp_server-0.3.0a1/tests/_rs_fixtures.py +30 -0
  61. pipefy_mcp_server-0.3.0a1/tests/auth/__init__.py +0 -0
  62. pipefy_mcp_server-0.3.0a1/tests/auth/test_request_identity.py +183 -0
  63. pipefy_mcp_server-0.3.0a1/tests/auth/test_resource_server.py +244 -0
  64. pipefy_mcp_server-0.3.0a1/tests/conftest.py +32 -0
  65. pipefy_mcp_server-0.3.0a1/tests/core/test_runtime.py +198 -0
  66. pipefy_mcp_server-0.3.0a1/tests/test_main.py +177 -0
  67. pipefy_mcp_server-0.3.0a1/tests/test_server.py +429 -0
  68. pipefy_mcp_server-0.3.0a1/tests/test_settings.py +203 -0
  69. pipefy_mcp_server-0.3.0a1/tests/tools/__init__.py +0 -0
  70. pipefy_mcp_server-0.3.0a1/tests/tools/conftest.py +115 -0
  71. pipefy_mcp_server-0.3.0a1/tests/tools/test_ai_agent_tools.py +2355 -0
  72. pipefy_mcp_server-0.3.0a1/tests/tools/test_ai_automation_tools.py +1494 -0
  73. pipefy_mcp_server-0.3.0a1/tests/tools/test_ai_automation_tools_live.py +105 -0
  74. pipefy_mcp_server-0.3.0a1/tests/tools/test_ai_tool_helpers.py +772 -0
  75. pipefy_mcp_server-0.3.0a1/tests/tools/test_ai_tool_helpers_slug_resolution.py +546 -0
  76. pipefy_mcp_server-0.3.0a1/tests/tools/test_attachment_tool_helpers.py +127 -0
  77. pipefy_mcp_server-0.3.0a1/tests/tools/test_attachment_tools.py +640 -0
  78. pipefy_mcp_server-0.3.0a1/tests/tools/test_attachment_tools_live.py +230 -0
  79. pipefy_mcp_server-0.3.0a1/tests/tools/test_automation_tools.py +1117 -0
  80. pipefy_mcp_server-0.3.0a1/tests/tools/test_behavior_placeholder_interpolation.py +323 -0
  81. pipefy_mcp_server-0.3.0a1/tests/tools/test_destructive_preview_dependents.py +124 -0
  82. pipefy_mcp_server-0.3.0a1/tests/tools/test_destructive_tool_guard.py +111 -0
  83. pipefy_mcp_server-0.3.0a1/tests/tools/test_docstring_discovery_hints.py +174 -0
  84. pipefy_mcp_server-0.3.0a1/tests/tools/test_field_condition_tools.py +203 -0
  85. pipefy_mcp_server-0.3.0a1/tests/tools/test_field_conditions_tools_live.py +154 -0
  86. pipefy_mcp_server-0.3.0a1/tests/tools/test_graphql_error_enrichment.py +306 -0
  87. pipefy_mcp_server-0.3.0a1/tests/tools/test_graphql_error_helpers.py +158 -0
  88. pipefy_mcp_server-0.3.0a1/tests/tools/test_introspection_scenarios.py +173 -0
  89. pipefy_mcp_server-0.3.0a1/tests/tools/test_introspection_tools.py +407 -0
  90. pipefy_mcp_server-0.3.0a1/tests/tools/test_introspection_tools_live.py +111 -0
  91. pipefy_mcp_server-0.3.0a1/tests/tools/test_mcp_capabilities.py +47 -0
  92. pipefy_mcp_server-0.3.0a1/tests/tools/test_member_tools.py +498 -0
  93. pipefy_mcp_server-0.3.0a1/tests/tools/test_observability_tools.py +1344 -0
  94. pipefy_mcp_server-0.3.0a1/tests/tools/test_organization_tools.py +124 -0
  95. pipefy_mcp_server-0.3.0a1/tests/tools/test_pagination_helpers.py +111 -0
  96. pipefy_mcp_server-0.3.0a1/tests/tools/test_phase_transition_helpers.py +403 -0
  97. pipefy_mcp_server-0.3.0a1/tests/tools/test_pipe_config_mcp_signoff_live.py +190 -0
  98. pipefy_mcp_server-0.3.0a1/tests/tools/test_pipe_config_tool_helpers.py +171 -0
  99. pipefy_mcp_server-0.3.0a1/tests/tools/test_pipe_config_tools.py +2816 -0
  100. pipefy_mcp_server-0.3.0a1/tests/tools/test_pipe_config_tools_live.py +142 -0
  101. pipefy_mcp_server-0.3.0a1/tests/tools/test_pipe_tool_helpers.py +684 -0
  102. pipefy_mcp_server-0.3.0a1/tests/tools/test_pipe_tools.py +2900 -0
  103. pipefy_mcp_server-0.3.0a1/tests/tools/test_portal_tool_helpers.py +86 -0
  104. pipefy_mcp_server-0.3.0a1/tests/tools/test_portal_tools.py +2343 -0
  105. pipefy_mcp_server-0.3.0a1/tests/tools/test_registry.py +172 -0
  106. pipefy_mcp_server-0.3.0a1/tests/tools/test_relation_tools.py +432 -0
  107. pipefy_mcp_server-0.3.0a1/tests/tools/test_remote_profile.py +124 -0
  108. pipefy_mcp_server-0.3.0a1/tests/tools/test_report_tool_helpers.py +60 -0
  109. pipefy_mcp_server-0.3.0a1/tests/tools/test_report_tools.py +1752 -0
  110. pipefy_mcp_server-0.3.0a1/tests/tools/test_table_tools.py +1737 -0
  111. pipefy_mcp_server-0.3.0a1/tests/tools/test_tool_context.py +50 -0
  112. pipefy_mcp_server-0.3.0a1/tests/tools/test_tool_error_envelope.py +22 -0
  113. pipefy_mcp_server-0.3.0a1/tests/tools/test_tool_success_envelope.py +33 -0
  114. pipefy_mcp_server-0.3.0a1/tests/tools/test_validation_envelope.py +158 -0
  115. pipefy_mcp_server-0.3.0a1/tests/tools/test_validation_helpers.py +127 -0
  116. pipefy_mcp_server-0.3.0a1/tests/tools/test_webhook_tools.py +1096 -0
@@ -0,0 +1,56 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Ruff stuff:
10
+ .ruff_cache/
11
+
12
+ # Unit test / coverage reports
13
+ htmlcov/
14
+ .tox/
15
+ .nox/
16
+ .coverage
17
+ .coverage.*
18
+ .cache
19
+ nosetests.xml
20
+ coverage.xml
21
+ *.cover
22
+ *.py,cover
23
+ .hypothesis/
24
+ .pytest_cache/
25
+ cover/
26
+
27
+ # Environments
28
+ .env
29
+ .venv
30
+ env/
31
+ venv/
32
+ ENV/
33
+ env.bak/
34
+ venv.bak/
35
+ .mise.toml
36
+
37
+ # Ralph Wiggum (local autonomous runs; state must not land in git)
38
+ .ralph/
39
+ RALPH_TASK.md
40
+
41
+ # IDEs and editors
42
+ .cursor/
43
+ .claude/
44
+ # Cursor MCP workspace config (machine-specific paths or local server entries).
45
+ # Carve-out: repo-root .mcp.json is the Claude Code plugin's MCP server config
46
+ # (auto-discovered at plugin root) and is intentionally tracked.
47
+ .mcp.json
48
+ !/.mcp.json
49
+
50
+ # Tracked release tooling only (ignore ad-hoc local scripts and smoke artifacts)
51
+ scripts/*
52
+ !scripts/bump_version.py
53
+
54
+ # macOS
55
+ .DS_Store
56
+ **/.DS_Store
@@ -0,0 +1,169 @@
1
+ # MCP package conventions
2
+
3
+ Scoped to `packages/mcp/`. Repo-wide guidance lives in `../../AGENTS.md`.
4
+
5
+ ## Distribution model
6
+
7
+ This MCP server is distributed as code that runs in the user's environment as
8
+ a subprocess of the agent runtime (Claude Code, Claude Desktop, etc.). The
9
+ trust boundary is the user; the server has the same filesystem and network
10
+ access the user already has.
11
+
12
+ Implications for tool design:
13
+
14
+ - Local filesystem inputs (`file_path`) are first-class. There is no
15
+ path-traversal threat surface beyond what the user can already access.
16
+ - SSRF guards, redirect loops, and download size caps that defend a hosted
17
+ server are not appropriate here. They add maintenance cost without buying
18
+ a security boundary.
19
+
20
+ A hosted/remote distribution profile is in progress. It runs the server as a
21
+ multi-user HTTP service. Tool exposure there is **default-deny**: only tools
22
+ explicitly marked remote-safe are registered; everything else is withheld. The
23
+ marker is described below.
24
+
25
+ ## Transport profiles
26
+
27
+ `pipefy-mcp-server` launches with two orthogonal flags:
28
+
29
+ - **`--profile {local|remote}`** (default `local`, env `PIPEFY_MCP_PROFILE`).
30
+ - `local`: registers every tool and acts as the one credential resolved at
31
+ startup. The installed-subprocess case.
32
+ - `remote`: exposes ONLY the default-deny remote-safe tool surface and, when a
33
+ resource-server URL is configured, validates an inbound bearer per request.
34
+ - **`--transport {stdio|http}`** (env `PIPEFY_MCP_TRANSPORT`). Left unset it follows
35
+ the profile: `local` speaks stdio, `remote` serves over Streamable HTTP. Set it
36
+ explicitly to run `local` over loopback HTTP. `remote` over stdio is rejected: a
37
+ per-request bearer has no stdio equivalent. The pair is resolved (and validated)
38
+ once, at startup, by `resolve_mcp_settings`.
39
+
40
+ Bind host/port come from `PIPEFY_MCP_HOST` / `PIPEFY_MCP_PORT` (defaults
41
+ `127.0.0.1:8000`), overridable with `--host` / `--port`, and matter only over HTTP.
42
+
43
+ Under `remote` the server acts on behalf of each caller: it validates the inbound
44
+ bearer per request and the one shared client reads that validated bearer per call,
45
+ so concurrent callers each act as themselves rather than as a single identity
46
+ resolved at startup. `local` runs as the one credential resolved at startup. The
47
+ transport still binds loopback-only until the DNS-rebinding host/Origin allowlist
48
+ lands.
49
+
50
+ **Resource-server profile.** Config is split by domain. *Token validation* is an
51
+ auth concern and lives in `pipefy_auth.JwtValidationSettings` (`settings.jwt`,
52
+ env `PIPEFY_JWT_*`): `ISSUER_URL` (an override; absent it, the inbound issuer
53
+ defaults to the one this process logs into, the `OidcClient` issuer, since in a
54
+ single-realm deployment they are the same IdP), optional `AUDIENCE` /
55
+ `VERIFY_AUDIENCE` (off by default, the same-audience interim), and `JWKS_URI`.
56
+ *Resource identity* is MCP-specific and stays in `pipefy_mcp.ResourceServerSettings`
57
+ (`settings.rs`, env `PIPEFY_MCP_RS_*`): `RESOURCE_SERVER_URL` (this server's public
58
+ canonical URL, e.g. `https://host/mcp`) and `REQUIRED_SCOPES`. The shared
59
+ `PIPEFY_ALLOW_INSECURE_URLS` covers both. The profile activates when
60
+ `RESOURCE_SERVER_URL` is set (the one value that cannot default); there is no
61
+ separate enable flag, just the `remote` profile plus this URL. Set
62
+ `RESOURCE_SERVER_URL` with the stored-session login disabled and no `ISSUER_URL`
63
+ override and startup fails (no issuer to validate against).
64
+
65
+ The JWKS/RS256 validation lives in `pipefy_auth` (`JwtValidator`); the MCP adapter
66
+ `auth/resource_server.py` (`JwtTokenVerifier`) maps validated claims onto the
67
+ SDK's `AccessToken`. FastMCP serves the RFC 9728 protected-resource metadata and
68
+ the `401` + `WWW-Authenticate` challenge; `build_resource_server_auth` (same
69
+ module) resolves the inbound issuer and pairs the verifier with `AuthSettings`.
70
+ The runtime (`McpRuntime.for_profile`) calls it for the `remote` profile and holds
71
+ the pair as `inbound_auth`, which `server.py` wires into the app.
72
+
73
+ **Loopback bind.** `_assert_safe_http_bind` restricts the HTTP transport to a
74
+ loopback bind, unconditionally for now. Per-request on-behalf-of identity (each
75
+ call runs as the validated caller, not a single startup identity) means inbound
76
+ identity is not the constraint; the constraint is DNS-rebinding protection, the
77
+ configurable host / Origin allowlist for a proxied deployment. Off-loopback binding
78
+ stays off until that lands (see `experiments/hosted-obo/RFC-OUTLINE.md`). The
79
+ attachment tools' local `file_path` inputs also still assume a loopback peer that
80
+ shares the client's disk (remote-safe file inputs are separate follow-up work).
81
+
82
+ ## Tool registration
83
+
84
+ Tools are registered **once, at construction** (via `_register_pipefy_tools` in
85
+ `server.py`, reached through `build_pipefy_mcp_server`, which both transports use),
86
+ not inside the FastMCP `lifespan`. The lifespan owns resources only: it yields
87
+ the already-wired app-scoped runtime as the request `lifespan_context`. This
88
+ follows the FastMCP contract, where the lifespan can run per session (per request
89
+ under Streamable HTTP) and so must not mutate the tool table.
90
+
91
+ Tools take no client at registration. Each tool function declares a
92
+ `ctx: Context` parameter (FastMCP injects it and keeps it out of the tool's
93
+ input schema) and resolves the live client per request with
94
+ `get_pipefy_client(ctx)` (`tools/tool_context.py`), which reads
95
+ `ctx.request_context.lifespan_context.pipefy_client`. Because the client is
96
+ looked up per call rather than captured at registration, tools observe whatever
97
+ the runtime holds without re-registering; under the hosted profile the one shared
98
+ client applies each request's identity itself (via its request-context bearer), so
99
+ identity is per-request even though the client is stable. That is why there is no
100
+ repeat-visit bookkeeping: registration never repeats.
101
+
102
+ When adding a tool, give it a `ctx: Context` parameter and start its body with
103
+ `client = get_pipefy_client(ctx)`; do not pass a client through `register`.
104
+
105
+ Both transports launch through the single `run_server` entry point, which resolves
106
+ the profile/transport once (via `resolve_mcp_settings`) and builds the same app
107
+ through `build_pipefy_mcp_server` (same runtime-bound lifespan, same
108
+ `_register_pipefy_tools`), differing only in the transport `run` and HTTP's bind
109
+ concerns. `build_pipefy_mcp_server` constructs one app-scoped `McpRuntime` via
110
+ `McpRuntime.for_profile` (`core/runtime.py`) and binds the lifespan to it.
111
+ `for_profile` is the composition root's one build step: the `remote` profile picks
112
+ a per-request identity and builds the inbound resource-server `(verifier, auth)`
113
+ pair (failing fast when that profile has no resource server); every other profile
114
+ resolves the one startup credential and fails fast when none is configured
115
+ (`StartupIdentity.from_configured_credential`). So a missing credential (or, under
116
+ `remote`, a missing resource server) surfaces when the server is built at startup,
117
+ not on the first tool call. The runtime wires its shared client to whichever
118
+ identity's `httpx.Auth` it was handed and exposes the inbound pair as
119
+ `inbound_auth`, which `build_pipefy_mcp_server` reads into FastMCP. (This also
120
+ means `build_pipefy_mcp_server` resolves the credential, so the live integration
121
+ tests that build the app at import skip themselves when no creds are configured.) Wiring the client at construction is safe off the event
122
+ loop: `PipefyClient` construction does no network I/O and binds nothing to a
123
+ running loop, because its executors open a fresh per-request transport at call
124
+ time; the client built at startup works on whatever loop later serves requests.
125
+ Streamable HTTP re-entering the lifespan per session just yields the same
126
+ already-wired runtime, so there is nothing to rebuild. The runtime holds no
127
+ per-request state: both identity variants carry an `httpx.Auth` the shared client
128
+ applies to every outbound call. `StartupIdentity` carries the one credential
129
+ resolved at startup (stdio/local), while `RequestScopedIdentity` carries a
130
+ `RequestContextBearerAuth` that reads each caller's validated bearer from the
131
+ request context, so one client serves every concurrent caller as themselves.
132
+
133
+ ## Remote-profile tool marker
134
+
135
+ Under the `remote` profile (`--profile remote` / `PIPEFY_MCP_PROFILE=remote`), the
136
+ server exposes ONLY tools whose registration carries `meta=REMOTE`. Any unmarked
137
+ tool is implicitly withheld (default-deny). Under `local` (the default), all tools
138
+ register and the marker is inert.
139
+
140
+ The marker is a single co-located source of truth on the `@mcp.tool` decorator:
141
+
142
+ ```python
143
+ from pipefy_mcp.tools.remote_profile import REMOTE
144
+
145
+ @mcp.tool(annotations=ToolAnnotations(readOnlyHint=True), meta=REMOTE)
146
+ async def get_organization(...): ...
147
+ ```
148
+
149
+ `ToolRegistry.apply_remote_profile()` reads it back via `is_remote_tool` and
150
+ removes every unmarked Pipefy tool at registration time (before the server
151
+ serves anything). The marker is greppable (`rg "meta=REMOTE" packages/mcp`) and
152
+ machine-enforced, unlike the comment-only `GATED:` convention it replaces.
153
+
154
+ Inclusion criteria for marking a tool remote-safe: it reaches the API with the
155
+ request-scoped bearer and is fully governed by API permissions; it does NOT read
156
+ the local filesystem; it does NOT read process-global settings for a per-user
157
+ decision. Opting a tool in is a deliberate, reviewed change (it shifts the
158
+ `REMOTE_SEED` drift guard in `tests/tools/test_remote_profile.py`).
159
+
160
+ ### Exposure vs input restriction
161
+
162
+ The `meta` marker expresses **exposure** only: whether a tool is available in the
163
+ remote profile. The retired `GATED:` convention could also express *input*
164
+ restriction within an exposed tool. Where a remotely-exposed tool needs restricted
165
+ inputs (for example the attachment tools, which would accept a `file_url` rather
166
+ than a local `file_path`), enforce that in the tool body gated on
167
+ `settings.mcp.profile == "remote"` at call time, not via the marker. A tool whose
168
+ exclusion deserves a reason gets a plain code comment stating why; the exclusion
169
+ itself needs no annotation.
@@ -0,0 +1 @@
1
+ AGENTS.md
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: pipefy-mcp-server
3
+ Version: 0.3.0a1
4
+ Summary: MCP server that exposes Pipefy's GraphQL API.
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: httpx>=0.27.0
7
+ Requires-Dist: mcp[cli]>=1.25.0
8
+ Requires-Dist: pipefy
9
+ Requires-Dist: pipefy-auth
10
+ Requires-Dist: pipefy-infra
11
+ Requires-Dist: pydantic-settings>=2.8.1
12
+ Requires-Dist: pydantic<3,>=2.13.4
13
+ Description-Content-Type: text/markdown
14
+
15
+ # pipefy-mcp-server
16
+
17
+ MCP server for Pipefy — **152 tools** for AI agents (Cursor, Claude Desktop, Claude Code, Codex, and any MCP-compatible client). Depends on [`pipefy`](../sdk/README.md) for all GraphQL and API logic.
18
+
19
+ ## Install (pre-launch, v0.1 → v0.5)
20
+
21
+ ```sh
22
+ uvx \
23
+ --with "pipefy @ git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/sdk" \
24
+ --with "pipefy-auth @ git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/auth" \
25
+ --from "git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/mcp" \
26
+ --refresh pipefy-mcp-server
27
+ ```
28
+
29
+ The `--with pipefy` / `pipefy-auth` flags are required pre-1.0 because the workspace members are not yet on PyPI. At v1.0 this collapses to `uvx pipefy-mcp-server`.
30
+
31
+ For per-client wiring (Claude Code / Cursor / Claude Desktop / Codex), see [root `README.md#installation`](../../README.md#installation).
32
+
33
+ ## Configuration
34
+
35
+ Set the following environment variables (or add them to a `.env` file in the working directory, or pin them in `~/.config/pipefy/config.toml`):
36
+
37
+ ```env
38
+ PIPEFY_SERVICE_ACCOUNT_CLIENT_ID=your_client_id
39
+ PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET=your_client_secret
40
+ # Non-prod environments only:
41
+ # PIPEFY_BASE_URL=https://<your-api-host>
42
+ # PIPEFY_AUTH_URL=https://<your-signin-host>/realms/<realm>
43
+ ```
44
+
45
+ `PIPEFY_BASE_URL` defaults to `https://app.pipefy.com` (drives the four API endpoints) and `PIPEFY_AUTH_URL` defaults to `https://signin.pipefy.com/realms/pipefy` (the OIDC issuer). Set them only for non-prod environments.
46
+
47
+ Full reference (every `PIPEFY_*` variable, validation rules, TOML schema, precedence chain): [`docs/config.md`](../../docs/config.md).
48
+
49
+ ## Edge cases and alternative wiring
50
+
51
+ ### macOS keychain `errSecParam (-25244)`
52
+
53
+ `pipefy auth login` may exit with `errSecParam (-25244)` at the final keychain-write step even though OAuth itself succeeded. The cause is not yet reliably diagnosed — direct `keyring.set_password` calls from the same uv-tool-installed Python succeed under repro testing, so this is likely a transient `Security.framework` condition rather than a deterministic per-binary ACL problem. If it occurs, retry the slash command (Claude Code) or `pipefy auth login` (terminal) first; as a fallback, run `pipefy auth login` once from a regular Terminal.app session and approve any macOS keychain dialog that appears. [Issue #235](https://github.com/pipefy/ai-toolkit/issues/235) tracks platform-aware error messaging.
54
+
55
+ ### Claude Code: `claude mcp add` (per-project terminal flow)
56
+
57
+ Useful when you want to wire the server without editing `~/.claude.json` by hand:
58
+
59
+ ```bash
60
+ claude mcp add --scope project pipefy \
61
+ -- uvx \
62
+ --with "pipefy @ git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/sdk" \
63
+ --with "pipefy-auth @ git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/auth" \
64
+ --from "git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/mcp" \
65
+ pipefy-mcp-server
66
+ ```
67
+
68
+ Then (repeat for each key you need):
69
+
70
+ ```bash
71
+ claude mcp add-env pipefy PIPEFY_SERVICE_ACCOUNT_CLIENT_ID <YOUR_CLIENT_ID>
72
+ claude mcp add-env pipefy PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET <YOUR_CLIENT_SECRET>
73
+ # Non-prod environments only:
74
+ # claude mcp add-env pipefy PIPEFY_BASE_URL https://<your-api-host>
75
+ # claude mcp add-env pipefy PIPEFY_AUTH_URL https://<your-signin-host>/realms/<realm>
76
+ ```
77
+
78
+ ### Claude Code: settings edit (post-plugin install)
79
+
80
+ The plugin's `.mcp.json` ships `command` + `args` only. To set the `env` block, edit the `pipefy` server entry in `~/.claude.json` (or the Claude Code settings UI):
81
+
82
+ ```json
83
+ {
84
+ "mcpServers": {
85
+ "pipefy": {
86
+ "env": {
87
+ "PIPEFY_SERVICE_ACCOUNT_CLIENT_ID": "<CLIENT_ID>",
88
+ "PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET": "<CLIENT_SECRET>"
89
+ }
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ A live MCP server picks up rotated credentials on its next tool call; if the server failed to start because credentials were missing, restart it (or restart Claude Code) after login completes.
96
+
97
+ ### Local-clone alternative (contributors)
98
+
99
+ If you have a clone of this repo and want the MCP server to use it directly (without `uvx` fetching from git), launch via `uv run` from the clone:
100
+
101
+ ```json
102
+ {
103
+ "mcpServers": {
104
+ "pipefy": {
105
+ "command": "uv",
106
+ "args": [
107
+ "run",
108
+ "--directory",
109
+ "/absolute/path/to/pipefy-mcp-server",
110
+ "pipefy-mcp-server"
111
+ ],
112
+ "env": {
113
+ "PIPEFY_SERVICE_ACCOUNT_CLIENT_ID": "<CLIENT_ID>",
114
+ "PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET": "<CLIENT_SECRET>"
115
+ }
116
+ }
117
+ }
118
+ }
119
+ ```
120
+
121
+ This form also works as a per-project `.mcp.json` if your team shares a clone. Committing `.mcp.json` without secrets (placeholders or env injection) keeps team setups consistent.
122
+
123
+ ### Legacy environment variables
124
+
125
+ `PIPEFY_OAUTH_CLIENT` and `PIPEFY_OAUTH_SECRET` still resolve to the new `PIPEFY_SERVICE_ACCOUNT_*` names with a one-shot stderr deprecation warning. The aliases will be removed in a later `0.2.0-beta.x` release. The `PIPEFY_OAUTH_URL` alias was dropped — set `PIPEFY_BASE_URL` instead. Migration notes: [`docs/MIGRATION.md#service-account-env-var-rename`](../../docs/MIGRATION.md#service-account-env-var-rename).
126
+
127
+ ## Tools
128
+
129
+ **152 tools** across ten domains (including **Portals**) — see the root [`README.md`](../../README.md#mcp-server) for the full table with per-area links. Deep reference: [`docs/mcp/tools/`](../../docs/mcp/tools/cross-cutting.md) (start with [`cross-cutting.md`](../../docs/mcp/tools/cross-cutting.md)); portals: [`portal.md`](../../docs/mcp/tools/portal.md).
130
+
131
+ ## Development
132
+
133
+ From the **repository root**:
134
+
135
+ ```bash
136
+ uv sync
137
+ uv run pytest packages/mcp/tests # MCP tests in isolation
138
+ uv run ruff check packages/mcp/src # lint
139
+ ```
140
+
141
+ See the root [`README.md`](../../README.md) and [`AGENTS.md`](../../AGENTS.md) for contributor guidance.
@@ -0,0 +1,127 @@
1
+ # pipefy-mcp-server
2
+
3
+ MCP server for Pipefy — **152 tools** for AI agents (Cursor, Claude Desktop, Claude Code, Codex, and any MCP-compatible client). Depends on [`pipefy`](../sdk/README.md) for all GraphQL and API logic.
4
+
5
+ ## Install (pre-launch, v0.1 → v0.5)
6
+
7
+ ```sh
8
+ uvx \
9
+ --with "pipefy @ git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/sdk" \
10
+ --with "pipefy-auth @ git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/auth" \
11
+ --from "git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/mcp" \
12
+ --refresh pipefy-mcp-server
13
+ ```
14
+
15
+ The `--with pipefy` / `pipefy-auth` flags are required pre-1.0 because the workspace members are not yet on PyPI. At v1.0 this collapses to `uvx pipefy-mcp-server`.
16
+
17
+ For per-client wiring (Claude Code / Cursor / Claude Desktop / Codex), see [root `README.md#installation`](../../README.md#installation).
18
+
19
+ ## Configuration
20
+
21
+ Set the following environment variables (or add them to a `.env` file in the working directory, or pin them in `~/.config/pipefy/config.toml`):
22
+
23
+ ```env
24
+ PIPEFY_SERVICE_ACCOUNT_CLIENT_ID=your_client_id
25
+ PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET=your_client_secret
26
+ # Non-prod environments only:
27
+ # PIPEFY_BASE_URL=https://<your-api-host>
28
+ # PIPEFY_AUTH_URL=https://<your-signin-host>/realms/<realm>
29
+ ```
30
+
31
+ `PIPEFY_BASE_URL` defaults to `https://app.pipefy.com` (drives the four API endpoints) and `PIPEFY_AUTH_URL` defaults to `https://signin.pipefy.com/realms/pipefy` (the OIDC issuer). Set them only for non-prod environments.
32
+
33
+ Full reference (every `PIPEFY_*` variable, validation rules, TOML schema, precedence chain): [`docs/config.md`](../../docs/config.md).
34
+
35
+ ## Edge cases and alternative wiring
36
+
37
+ ### macOS keychain `errSecParam (-25244)`
38
+
39
+ `pipefy auth login` may exit with `errSecParam (-25244)` at the final keychain-write step even though OAuth itself succeeded. The cause is not yet reliably diagnosed — direct `keyring.set_password` calls from the same uv-tool-installed Python succeed under repro testing, so this is likely a transient `Security.framework` condition rather than a deterministic per-binary ACL problem. If it occurs, retry the slash command (Claude Code) or `pipefy auth login` (terminal) first; as a fallback, run `pipefy auth login` once from a regular Terminal.app session and approve any macOS keychain dialog that appears. [Issue #235](https://github.com/pipefy/ai-toolkit/issues/235) tracks platform-aware error messaging.
40
+
41
+ ### Claude Code: `claude mcp add` (per-project terminal flow)
42
+
43
+ Useful when you want to wire the server without editing `~/.claude.json` by hand:
44
+
45
+ ```bash
46
+ claude mcp add --scope project pipefy \
47
+ -- uvx \
48
+ --with "pipefy @ git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/sdk" \
49
+ --with "pipefy-auth @ git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/auth" \
50
+ --from "git+https://github.com/pipefy/ai-toolkit@latest#subdirectory=packages/mcp" \
51
+ pipefy-mcp-server
52
+ ```
53
+
54
+ Then (repeat for each key you need):
55
+
56
+ ```bash
57
+ claude mcp add-env pipefy PIPEFY_SERVICE_ACCOUNT_CLIENT_ID <YOUR_CLIENT_ID>
58
+ claude mcp add-env pipefy PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET <YOUR_CLIENT_SECRET>
59
+ # Non-prod environments only:
60
+ # claude mcp add-env pipefy PIPEFY_BASE_URL https://<your-api-host>
61
+ # claude mcp add-env pipefy PIPEFY_AUTH_URL https://<your-signin-host>/realms/<realm>
62
+ ```
63
+
64
+ ### Claude Code: settings edit (post-plugin install)
65
+
66
+ The plugin's `.mcp.json` ships `command` + `args` only. To set the `env` block, edit the `pipefy` server entry in `~/.claude.json` (or the Claude Code settings UI):
67
+
68
+ ```json
69
+ {
70
+ "mcpServers": {
71
+ "pipefy": {
72
+ "env": {
73
+ "PIPEFY_SERVICE_ACCOUNT_CLIENT_ID": "<CLIENT_ID>",
74
+ "PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET": "<CLIENT_SECRET>"
75
+ }
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ A live MCP server picks up rotated credentials on its next tool call; if the server failed to start because credentials were missing, restart it (or restart Claude Code) after login completes.
82
+
83
+ ### Local-clone alternative (contributors)
84
+
85
+ If you have a clone of this repo and want the MCP server to use it directly (without `uvx` fetching from git), launch via `uv run` from the clone:
86
+
87
+ ```json
88
+ {
89
+ "mcpServers": {
90
+ "pipefy": {
91
+ "command": "uv",
92
+ "args": [
93
+ "run",
94
+ "--directory",
95
+ "/absolute/path/to/pipefy-mcp-server",
96
+ "pipefy-mcp-server"
97
+ ],
98
+ "env": {
99
+ "PIPEFY_SERVICE_ACCOUNT_CLIENT_ID": "<CLIENT_ID>",
100
+ "PIPEFY_SERVICE_ACCOUNT_CLIENT_SECRET": "<CLIENT_SECRET>"
101
+ }
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ This form also works as a per-project `.mcp.json` if your team shares a clone. Committing `.mcp.json` without secrets (placeholders or env injection) keeps team setups consistent.
108
+
109
+ ### Legacy environment variables
110
+
111
+ `PIPEFY_OAUTH_CLIENT` and `PIPEFY_OAUTH_SECRET` still resolve to the new `PIPEFY_SERVICE_ACCOUNT_*` names with a one-shot stderr deprecation warning. The aliases will be removed in a later `0.2.0-beta.x` release. The `PIPEFY_OAUTH_URL` alias was dropped — set `PIPEFY_BASE_URL` instead. Migration notes: [`docs/MIGRATION.md#service-account-env-var-rename`](../../docs/MIGRATION.md#service-account-env-var-rename).
112
+
113
+ ## Tools
114
+
115
+ **152 tools** across ten domains (including **Portals**) — see the root [`README.md`](../../README.md#mcp-server) for the full table with per-area links. Deep reference: [`docs/mcp/tools/`](../../docs/mcp/tools/cross-cutting.md) (start with [`cross-cutting.md`](../../docs/mcp/tools/cross-cutting.md)); portals: [`portal.md`](../../docs/mcp/tools/portal.md).
116
+
117
+ ## Development
118
+
119
+ From the **repository root**:
120
+
121
+ ```bash
122
+ uv sync
123
+ uv run pytest packages/mcp/tests # MCP tests in isolation
124
+ uv run ruff check packages/mcp/src # lint
125
+ ```
126
+
127
+ See the root [`README.md`](../../README.md) and [`AGENTS.md`](../../AGENTS.md) for contributor guidance.
@@ -0,0 +1,69 @@
1
+ [project]
2
+ name = "pipefy-mcp-server"
3
+ dynamic = ["version"]
4
+ description = "MCP server that exposes Pipefy's GraphQL API."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "httpx>=0.27.0",
9
+ "mcp[cli]>=1.25.0",
10
+ # Workspace resolves versions in dev; PyPI releases stage these wheels alongside CLI/MCP (see release workflow).
11
+ "pipefy-auth",
12
+ "pipefy-infra",
13
+ "pipefy",
14
+ "pydantic>=2.13.4,<3",
15
+ "pydantic-settings>=2.8.1",
16
+ ]
17
+
18
+ [tool.uv.sources]
19
+ pipefy-auth = { workspace = true }
20
+ pipefy-infra = { workspace = true }
21
+ pipefy = { workspace = true }
22
+
23
+ [project.scripts]
24
+ pipefy-mcp-server = "pipefy_mcp.main:main"
25
+
26
+ [dependency-groups]
27
+ dev = [
28
+ "aiohttp>=3.11.16",
29
+ "pytest>=8.3.5",
30
+ "pytest-asyncio>=0.26.0",
31
+ "pytest-cov>=6.1.1",
32
+ "pytest-mock>=3.14.0",
33
+ "respx>=0.22.0",
34
+ "ruff>=0.11.4",
35
+ ]
36
+
37
+ [build-system]
38
+ requires = ["hatchling"]
39
+ build-backend = "hatchling.build"
40
+
41
+ [tool.hatch.build.targets.wheel]
42
+ packages = ["src/pipefy_mcp"]
43
+
44
+ [tool.hatch.version]
45
+ path = "src/pipefy_mcp/__init__.py"
46
+
47
+ [tool.pytest.ini_options]
48
+ testpaths = ["tests"]
49
+ # `src` is the package; `../../packages/sdk/tests` supplies the `_shared` test bundle (live creds, fixtures) without putting the whole repo on sys.path.
50
+ pythonpath = ["src", "tests", "../../packages/sdk/tests"]
51
+ python_files = ["test_*.py"]
52
+ python_classes = ["Test*"]
53
+ python_functions = ["test_*"]
54
+ addopts = "-v --no-header --tb=short"
55
+ asyncio_mode = "auto"
56
+ asyncio_default_fixture_loop_scope = "module"
57
+ markers = [
58
+ "unit: marks a test as a unit test",
59
+ "integration: marks tests that call the live Pipefy GraphQL API (PIPEFY_* env)",
60
+ ]
61
+
62
+ [tool.ruff]
63
+ extend = "../../pyproject.toml"
64
+ lint.extend-select = ["TID251"]
65
+
66
+ [tool.ruff.lint.flake8-tidy-imports.banned-api]
67
+ "pipefy_cli".msg = "pipefy_mcp must not import the CLI package; keep MCP independent of pipefy-cli."
68
+ "pipefy_sdk.services".msg = "Import from pipefy_sdk or pipefy_sdk.ai_* helpers only; pipefy_sdk.services is private to the SDK."
69
+ "pipefy_sdk.queries".msg = "Import from pipefy_sdk only; pipefy_sdk.queries is private to the SDK."
@@ -0,0 +1,16 @@
1
+ """Pipefy MCP Server package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ __version__ = "0.3.0-alpha.1"
8
+ version = __version__
9
+
10
+ m = re.match(r"^(\d+)\.(\d+)\.(\d+)", __version__)
11
+ if not m:
12
+ msg = f"__version__ must start with MAJOR.MINOR.PATCH, got {__version__!r}"
13
+ raise ValueError(msg)
14
+ version_tuple = (int(m[1]), int(m[2]), int(m[3]))
15
+
16
+ __all__ = ["__version__", "version", "version_tuple"]
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from pipefy_mcp import __version__
4
+
5
+ DOCS_SETUP_REF = (
6
+ f"https://github.com/pipefy/ai-toolkit/blob/v{__version__}/README.md#installation"
7
+ )
@@ -0,0 +1,19 @@
1
+ """Resource-server auth for the HTTP transport: inbound bearer validation."""
2
+
3
+ from pipefy_mcp.auth.request_identity import (
4
+ RequestContextBearerAuth,
5
+ require_request_bearer,
6
+ )
7
+ from pipefy_mcp.auth.resource_server import (
8
+ JwtTokenVerifier,
9
+ ResourceServerAuth,
10
+ build_resource_server_auth,
11
+ )
12
+
13
+ __all__ = [
14
+ "JwtTokenVerifier",
15
+ "RequestContextBearerAuth",
16
+ "ResourceServerAuth",
17
+ "build_resource_server_auth",
18
+ "require_request_bearer",
19
+ ]