From 11975a53329989df8d87b72ef4ca8d1c07fd89ae Mon Sep 17 00:00:00 2001 From: mocha06 <52426811+mocha06@users.noreply.github.com> Date: Sat, 18 Jul 2026 15:30:22 -0300 Subject: [PATCH 1/2] feat(mcp): mark pipe/card/field/member/webhook/portal read tools remote-safe (#441) --- .../pipefy_mcp/tools/field_condition_tools.py | 3 +++ .../src/pipefy_mcp/tools/pipe_config_tools.py | 2 ++ .../mcp/src/pipefy_mcp/tools/pipe_tools.py | 3 +++ .../mcp/src/pipefy_mcp/tools/portal_tools.py | 3 +++ .../src/pipefy_mcp/tools/relation_tools.py | 3 +++ .../mcp/src/pipefy_mcp/tools/webhook_tools.py | 4 ++++ .../mcp/tests/tools/test_remote_profile.py | 19 +++++++++++++++++++ 7 files changed, 37 insertions(+) diff --git a/packages/mcp/src/pipefy_mcp/tools/field_condition_tools.py b/packages/mcp/src/pipefy_mcp/tools/field_condition_tools.py index f1c0a98f..32883386 100644 --- a/packages/mcp/src/pipefy_mcp/tools/field_condition_tools.py +++ b/packages/mcp/src/pipefy_mcp/tools/field_condition_tools.py @@ -18,6 +18,7 @@ field_condition_actions_error_message, handle_pipe_config_tool_graphql_error, ) +from pipefy_mcp.tools.remote_profile import REMOTE from pipefy_mcp.tools.tool_context import get_pipefy_client from pipefy_mcp.tools.validation_helpers import ( validate_tool_id, @@ -48,6 +49,7 @@ def register(mcp: FastMCP) -> None: annotations=ToolAnnotations( readOnlyHint=True, ), + meta=REMOTE, ) async def get_field_conditions( ctx: Context[ServerSession, None], @@ -104,6 +106,7 @@ async def get_field_conditions( annotations=ToolAnnotations( readOnlyHint=True, ), + meta=REMOTE, ) async def get_field_condition( ctx: Context[ServerSession, None], diff --git a/packages/mcp/src/pipefy_mcp/tools/pipe_config_tools.py b/packages/mcp/src/pipefy_mcp/tools/pipe_config_tools.py index 84feabf4..dd8ca808 100644 --- a/packages/mcp/src/pipefy_mcp/tools/pipe_config_tools.py +++ b/packages/mcp/src/pipefy_mcp/tools/pipe_config_tools.py @@ -370,6 +370,7 @@ async def clone_pipe( annotations=ToolAnnotations( readOnlyHint=True, ), + meta=REMOTE, ) async def get_phase_allowed_move_targets( phase_id: PipefyId, @@ -423,6 +424,7 @@ async def get_phase_allowed_move_targets( annotations=ToolAnnotations( readOnlyHint=True, ), + meta=REMOTE, ) async def get_phase_cards_count( phase_id: PipefyId, diff --git a/packages/mcp/src/pipefy_mcp/tools/pipe_tools.py b/packages/mcp/src/pipefy_mcp/tools/pipe_tools.py index e594728c..2fcc5fb6 100644 --- a/packages/mcp/src/pipefy_mcp/tools/pipe_tools.py +++ b/packages/mcp/src/pipefy_mcp/tools/pipe_tools.py @@ -326,6 +326,7 @@ async def get_card( annotations=ToolAnnotations( readOnlyHint=True, ), + meta=REMOTE, ) async def get_card_relations( ctx: Context[ServerSession, None], @@ -774,6 +775,7 @@ async def get_pipe( annotations=ToolAnnotations( readOnlyHint=True, ), + meta=REMOTE, ) async def get_labels( ctx: Context[ServerSession, None], @@ -827,6 +829,7 @@ async def get_labels( annotations=ToolAnnotations( readOnlyHint=True, ), + meta=REMOTE, ) async def get_pipe_members( ctx: Context[ServerSession, None], diff --git a/packages/mcp/src/pipefy_mcp/tools/portal_tools.py b/packages/mcp/src/pipefy_mcp/tools/portal_tools.py index c292b74f..e6210c01 100644 --- a/packages/mcp/src/pipefy_mcp/tools/portal_tools.py +++ b/packages/mcp/src/pipefy_mcp/tools/portal_tools.py @@ -32,6 +32,7 @@ validate_sort_page_ids_no_duplicates, validate_tool_ids, ) +from pipefy_mcp.tools.remote_profile import REMOTE from pipefy_mcp.tools.tool_context import get_pipefy_client from pipefy_mcp.tools.validation_helpers import validate_tool_id @@ -45,6 +46,7 @@ def register(mcp: FastMCP) -> None: @mcp.tool( annotations=ToolAnnotations(readOnlyHint=True), + meta=REMOTE, ) async def list_portals( ctx: Context[ServerSession, None], @@ -84,6 +86,7 @@ async def list_portals( @mcp.tool( annotations=ToolAnnotations(readOnlyHint=True), + meta=REMOTE, ) async def get_portal( ctx: Context[ServerSession, None], diff --git a/packages/mcp/src/pipefy_mcp/tools/relation_tools.py b/packages/mcp/src/pipefy_mcp/tools/relation_tools.py index 51c26350..bce7ead9 100644 --- a/packages/mcp/src/pipefy_mcp/tools/relation_tools.py +++ b/packages/mcp/src/pipefy_mcp/tools/relation_tools.py @@ -16,6 +16,7 @@ build_relation_read_success_payload, handle_relation_tool_graphql_error, ) +from pipefy_mcp.tools.remote_profile import REMOTE from pipefy_mcp.tools.tool_context import get_pipefy_client from pipefy_mcp.tools.validation_helpers import ( mutation_error_if_not_optional_dict, @@ -30,6 +31,7 @@ class RelationTools: def register(mcp: FastMCP) -> None: @mcp.tool( annotations=ToolAnnotations(readOnlyHint=True), + meta=REMOTE, ) async def get_pipe_relations(pipe_id: PipefyId, ctx: Context) -> dict[str, Any]: """List pipe relations for a pipe (parent and child links, config, and repo refs). @@ -61,6 +63,7 @@ async def get_pipe_relations(pipe_id: PipefyId, ctx: Context) -> dict[str, Any]: @mcp.tool( annotations=ToolAnnotations(readOnlyHint=True), + meta=REMOTE, ) async def get_table_relations( relation_ids: list[PipefyId], ctx: Context diff --git a/packages/mcp/src/pipefy_mcp/tools/webhook_tools.py b/packages/mcp/src/pipefy_mcp/tools/webhook_tools.py index d97ba77c..94236bb5 100644 --- a/packages/mcp/src/pipefy_mcp/tools/webhook_tools.py +++ b/packages/mcp/src/pipefy_mcp/tools/webhook_tools.py @@ -10,6 +10,7 @@ from pipefy_sdk import PipefyId from pipefy_mcp.tools.destructive_tool_guard import check_destructive_confirmation +from pipefy_mcp.tools.remote_profile import REMOTE from pipefy_mcp.tools.tool_context import get_pipefy_client from pipefy_mcp.tools.validation_helpers import ( mutation_error_if_not_optional_dict, @@ -29,6 +30,7 @@ class WebhookTools: def register(mcp: FastMCP) -> None: @mcp.tool( annotations=ToolAnnotations(readOnlyHint=True), + meta=REMOTE, ) async def get_email_templates( repo_id: PipefyId, @@ -73,6 +75,7 @@ async def get_email_templates( @mcp.tool( annotations=ToolAnnotations(readOnlyHint=True), + meta=REMOTE, ) async def get_card_inbox_emails( card_id: PipefyId, @@ -268,6 +271,7 @@ async def send_email_with_template( @mcp.tool( annotations=ToolAnnotations(readOnlyHint=True), + meta=REMOTE, ) async def get_webhooks( ctx: Context[ServerSession, None], diff --git a/packages/mcp/tests/tools/test_remote_profile.py b/packages/mcp/tests/tools/test_remote_profile.py index 800ce458..8e4a3fb6 100644 --- a/packages/mcp/tests/tools/test_remote_profile.py +++ b/packages/mcp/tests/tools/test_remote_profile.py @@ -92,6 +92,25 @@ "get_pipe_report_columns", "get_pipe_report_filterable_fields", "get_pipe_report_export", + # pipe / card / field / member / webhook / portal reads (#441): read tools + # that reach the API with the request-scoped bearer and are governed by API + # permissions; no filesystem or per-user process-global settings reads. The + # relation reads may hit Pipefy's Internal API, which receives whatever + # credential the caller already carries (nothing service-account-specific). + "get_card_relations", + "get_card_inbox_emails", + "get_field_condition", + "get_field_conditions", + "get_labels", + "get_pipe_members", + "get_pipe_relations", + "get_phase_allowed_move_targets", + "get_phase_cards_count", + "get_table_relations", + "get_webhooks", + "get_email_templates", + "get_portal", + "list_portals", } ) From fb3f8d4bc00d2e7b8c99a493c4f10458c48a7b26 Mon Sep 17 00:00:00 2001 From: mocha06 <52426811+mocha06@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:09:41 -0300 Subject: [PATCH 2/2] docs(mcp): correct #441 seed comment on relation/portal read endpoints --- packages/mcp/tests/tools/test_remote_profile.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/mcp/tests/tools/test_remote_profile.py b/packages/mcp/tests/tools/test_remote_profile.py index 8e4a3fb6..e0ae08a7 100644 --- a/packages/mcp/tests/tools/test_remote_profile.py +++ b/packages/mcp/tests/tools/test_remote_profile.py @@ -95,8 +95,9 @@ # pipe / card / field / member / webhook / portal reads (#441): read tools # that reach the API with the request-scoped bearer and are governed by API # permissions; no filesystem or per-user process-global settings reads. The - # relation reads may hit Pipefy's Internal API, which receives whatever - # credential the caller already carries (nothing service-account-specific). + # relation reads use the public GraphQL API only and the portal reads use + # the Interfaces schema; Pipefy's Internal API is reached only by mutations + # such as delete_card_relation, which stay withheld. "get_card_relations", "get_card_inbox_emails", "get_field_condition",