Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions packages/mcp/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,16 @@ are separate follow-up work).
bind-safety interlock: it checks the inbound request's `Host` / `Origin`, not the
bind interface. FastMCP auto-enables a loopback-only allowlist on the `127.0.0.1`
construction host, so behind a proxy that forwards the public `Host` it answers
`421 Misdirected Request`. `core/transport_security.py:build_transport_security`
`421 Misdirected Request`. `transport_security.py:build_transport_security`
widens it by deriving the allowed host from `resource_server_url` (the public origin
the `remote` profile already declares) plus loopback, and `build_pipefy_mcp_server`
passes the result to FastMCP. `PIPEFY_MCP_ALLOWED_HOSTS` / `PIPEFY_MCP_ALLOWED_ORIGINS`
(JSON) extend it for extra hostnames or a stricter Origin posture. Unset (no
resource-server URL and no override) leaves FastMCP's loopback-only default in force,
so the local subprocess case is unaffected. Being configuration derived at
composition (mirroring `build_resource_server_auth`), it lives in the composition
tier, not in `settings.py`, which keeps the mcp SDK out of the config boundary.
so the local subprocess case is unaffected. Transport security is its own concern
module (a peer of `auth/`, consumed by the composition root); being configuration
derived at composition (mirroring `build_resource_server_auth`), it stays out of
`settings.py`, which keeps the mcp SDK out of the config boundary.

## Tool registration

Expand Down
3 changes: 2 additions & 1 deletion packages/mcp/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,13 @@ lint.extend-select = ["TID251"]
root_package = "pipefy_mcp"

[[tool.importlinter.contracts]]
name = "Inward-only layers: composition root > adapters > core > auth > settings"
name = "Inward-only layers: composition root > adapters > core > transport_security > auth > settings"
type = "layers"
layers = [
"pipefy_mcp.server",
"pipefy_mcp.tools",
"pipefy_mcp.core",
"pipefy_mcp.transport_security",
"pipefy_mcp.auth",
"pipefy_mcp.settings",
]
Expand Down
14 changes: 7 additions & 7 deletions packages/mcp/src/pipefy_mcp/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
"""Identity for the HTTP transport: inbound bearer validation and session identity."""
"""Identity for the HTTP transport: inbound bearer validation and outbound session identity."""

from pipefy_mcp.auth.request_identity import require_request_bearer
from pipefy_mcp.auth.inbound_identity import require_request_bearer
from pipefy_mcp.auth.outbound_identity import (
AuthSource,
RequestScopedIdentity,
StartupIdentity,
)
from pipefy_mcp.auth.resource_server import (
JwtTokenVerifier,
ResourceServer,
ResourceServerAuth,
build_resource_server_auth,
)
from pipefy_mcp.auth.session_identity import (
AuthSource,
RequestScopedIdentity,
StartupIdentity,
)

__all__ = [
"AuthSource",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
that caller rather than as one identity resolved at startup. This module reads
the caller's validated bearer off the request the tool handler received, so the
runtime can snapshot it into a per-session credential (see
:meth:`pipefy_mcp.auth.session_identity.RequestScopedIdentity.resolve`).
:meth:`pipefy_mcp.auth.outbound_identity.RequestScopedIdentity.resolve`).

The bearer comes from the request the handler passes in
(``ctx.request_context.request``), not from ``AuthContextMiddleware``'s
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Outbound identity for a request's SDK session: who each session acts as.

The counterpart to :mod:`pipefy_mcp.auth.request_identity` (which extracts the
The counterpart to :mod:`pipefy_mcp.auth.inbound_identity` (which extracts the
*inbound* bearer a caller presents): these types resolve the *outbound*
``httpx.Auth`` the per-request SDK session binds. The two profiles pick a
different variant at the composition root, and both speak one ``resolve`` contract
Expand All @@ -22,7 +22,7 @@
from starlette.requests import Request

from pipefy_mcp._docs import DOCS_SETUP_REF
from pipefy_mcp.auth.request_identity import require_request_bearer
from pipefy_mcp.auth.inbound_identity import require_request_bearer
from pipefy_mcp.settings import Settings


Expand Down
2 changes: 1 addition & 1 deletion packages/mcp/src/pipefy_mcp/core/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
StartupIdentity,
build_resource_server_auth,
)
from pipefy_mcp.core.transport_security import build_transport_security
from pipefy_mcp.settings import ResourceServerSettings, Settings
from pipefy_mcp.transport_security import build_transport_security


def _resource_server(rs: ResourceServerSettings) -> ResourceServer | None:
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp/src/pipefy_mcp/core/tool_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from mcp import types
from mcp.server.lowlevel.server import request_ctx

from pipefy_mcp.auth.request_identity import CallerIdentity, caller_identity
from pipefy_mcp.auth.inbound_identity import CallerIdentity, caller_identity
from pipefy_mcp.core.tool_error_envelope import tool_error

if TYPE_CHECKING:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from mcp.server.auth.provider import AccessToken
from starlette.requests import Request

from pipefy_mcp.auth.request_identity import (
from pipefy_mcp.auth.inbound_identity import (
CallerIdentity,
caller_identity,
require_request_bearer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import pytest
from mcp import UrlElicitationRequiredError, types

from pipefy_mcp.auth.request_identity import CallerIdentity
from pipefy_mcp.auth.inbound_identity import CallerIdentity
from pipefy_mcp.core.tool_middleware import ToolCallContext
from pipefy_mcp.observability.tool_log_middleware import tool_log_middleware

Expand Down
2 changes: 1 addition & 1 deletion packages/mcp/tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from pipefy_sdk import PipefySettings

from pipefy_mcp.core.tool_middleware import ToolCallContext, short_circuit_error
from pipefy_mcp.core.transport_security import build_transport_security
from pipefy_mcp.observability.tool_log_middleware import tool_log_middleware
from pipefy_mcp.server import (
_make_lifespan,
Expand All @@ -25,6 +24,7 @@
)
from pipefy_mcp.settings import McpSettings, Settings, resolve_mcp_settings
from pipefy_mcp.tools.registry import PIPEFY_TOOL_NAMES
from pipefy_mcp.transport_security import build_transport_security

_MINIMAL_PIPEFY_SETTINGS = Settings(
pipefy=PipefySettings(base_url="https://api.pipefy.com"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import pytest

from pipefy_mcp.auth import ResourceServer
from pipefy_mcp.core.transport_security import build_transport_security
from pipefy_mcp.settings import McpSettings
from pipefy_mcp.transport_security import build_transport_security

_LOOPBACK_FORMS = {
"127.0.0.1",
Expand Down