Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ export interface accessControlsLogicActions {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -647,6 +648,7 @@ export interface accessControlsLogicMeta {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -794,6 +796,7 @@ export interface accessControlsLogicMeta {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -913,6 +916,7 @@ export interface accessControlsLogicMeta {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ export interface accessDetailLogicActions {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export interface addObjectOverrideModalLogicActions {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -265,6 +266,7 @@ export interface addObjectOverrideModalLogicActions {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -403,6 +405,7 @@ export interface addObjectOverrideModalLogicActions {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -525,6 +528,7 @@ export interface addObjectOverrideModalLogicActions {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -646,6 +650,7 @@ export interface addObjectOverrideModalLogicActions {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -788,6 +793,7 @@ export interface addObjectOverrideModalLogicMeta {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export interface groupedAccessControlRuleModalLogicValues {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -234,6 +235,7 @@ export interface groupedAccessControlRuleModalLogicValues {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -349,6 +351,7 @@ export interface groupedAccessControlRuleModalLogicValues {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -477,6 +480,7 @@ export interface groupedAccessControlRuleModalLogicValues {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -610,6 +614,7 @@ export interface groupedAccessControlRuleModalLogicActions {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -745,6 +750,7 @@ export interface groupedAccessControlRuleModalLogicActions {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -860,6 +866,7 @@ export interface groupedAccessControlRuleModalLogicActions {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -1014,6 +1021,7 @@ export interface groupedAccessControlRuleModalLogicMeta {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -1131,6 +1139,7 @@ export interface groupedAccessControlRuleModalLogicMeta {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -1247,6 +1256,7 @@ export interface groupedAccessControlRuleModalLogicMeta {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -1363,6 +1373,7 @@ export interface groupedAccessControlRuleModalLogicMeta {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -1480,6 +1491,7 @@ export interface groupedAccessControlRuleModalLogicMeta {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -1596,6 +1608,7 @@ export interface groupedAccessControlRuleModalLogicMeta {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -1723,6 +1736,7 @@ export interface groupedAccessControlRuleModalLogicMeta {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -1839,6 +1853,7 @@ export interface groupedAccessControlRuleModalLogicMeta {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ export interface accessControlLogicMeta {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -621,6 +622,7 @@ export interface accessControlLogicMeta {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down Expand Up @@ -737,6 +739,7 @@ export interface accessControlLogicMeta {
| 'signal_scout'
| 'signal_scout_internal'
| 'signal_scout_report'
| 'signal_scratchpad_internal'
| 'stamphog'
| 'streamlit_app'
| 'subscription'
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/scopes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ export const API_SCOPES_OMITTED_FROM_MODAL: Partial<Record<APIScopeObject, strin
mcp_builtin_agent: 'Internal: identifies a trusted built-in agent credential.',
signal_scout_internal: 'Internal: sandbox-only writes for the headless Signals agent.',
signal_scout_report: 'Internal: sandbox-only writes for the scout report channel.',
signal_scratchpad_internal: 'Internal: sandbox-only writes for the Signals scratchpad.',
// OAUTH_HIDDEN_SCOPE_OBJECTS — pasteable into a PAT, but never advertised via OAuth/CLI/MCP.
batch_import_support: 'OAuth-hidden: staff-only, pasteable into a PAT but not advertised.',
query_performance: 'OAuth-hidden: staff-only, pasteable into a PAT but not advertised.',
Expand Down
1 change: 1 addition & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5913,6 +5913,7 @@ export const API_SCOPE_OBJECTS = [
'signal_scout',
'signal_scout_internal',
'signal_scout_report',
'signal_scratchpad_internal',
'stamphog',
'streamlit_app',
'subscription',
Expand Down
6 changes: 6 additions & 0 deletions posthog/scopes.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"signal_scout",
"signal_scout_internal",
"signal_scout_report",
"signal_scratchpad_internal",
"stamphog",
"streamlit_app",
"subscription",
Expand Down Expand Up @@ -181,6 +182,11 @@
# opted into the report tools (via the `signals_scout_reports` posture) — every
# other scout's token lacks it, so the MCP server strips those tools entirely.
"signal_scout_report",
# Sandbox-only write for the shared scratchpad (remember / forget). Split out from
# `signal_scout_internal` for the same reason as the report channel: the report
# pipeline's research and implementation runs need durable memory, and granting it
# through the scout object would hand them `emit_signal` and `record_output` too.
"signal_scratchpad_internal",
}
)

Expand Down
58 changes: 54 additions & 4 deletions posthog/temporal/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,14 @@
}
)

McpScopePreset = Literal["read_only", "full", "signals_scout", "signals_scout_reports"]
McpScopePreset = Literal[
"read_only",
"full",
"signals_scout",
"signals_scout_reports",
"signals_research",
"signals_implementation",
]
SandboxOAuthApplication = Literal["array", "posthog_ai", "signals"]

# Granted only to sandbox runs a person started by hand (see `interactive_run` in
Expand All @@ -118,13 +125,22 @@
"internal_run:read",
]

# Write access to the shared scratchpad (`remember` / `forget`) and nothing else. Held apart
# from `SCOUT_INTERNAL_SCOPES` so the report pipeline's research and implementation runs can
# persist what they learn without also getting `emit_signal` and `record_output`, which the
# scout object unlocks. Scouts still carry it — it is folded into their posture below.
SCRATCHPAD_INTERNAL_SCOPES: list[str] = [
"signal_scratchpad_internal:write",
]

# Writes for the Signals scout harness — sandbox-only because the scope object is in
# `INTERNAL_API_SCOPE_OBJECTS` and so cannot be minted via the personal API key UI or
# granted through the OAuth consent flow. Reads use the public `signal_scout:read` scope.
# Kept OUT of the global `INTERNAL_SCOPES` so it is added ONLY for the `signals_scout`
# preset — unrelated `full`/`read_only` task tokens must never carry scout write access.
SCOUT_INTERNAL_SCOPES: list[str] = [
"signal_scout_internal:write",
*SCRATCHPAD_INTERNAL_SCOPES,
]


Expand Down Expand Up @@ -182,7 +198,22 @@ def _build_mcp_scopes(action: Literal["read", "write"]) -> list[str]:

PosthogMcpScopes = McpScopePreset | list[str]

MCP_SCOPE_PRESETS = ("read_only", "full", "signals_scout", "signals_scout_reports")
MCP_SCOPE_PRESETS = (
"read_only",
"full",
"signals_scout",
"signals_scout_reports",
"signals_research",
"signals_implementation",
)

# Withheld from `signals_research`, which is otherwise the `read_only` resolution.
# `task:write` reaches every posture through `INTERNAL_SCOPES`, but it is inert wherever the
# MCP server runs in read-only mode, which strips every tool not annotated read-only.
# `signals_research` turns that mode off so its two scratchpad tools survive, and that alone
# would hand the research stage the whole task-write toolset — including setting a report's
# state. The stage reads data and returns findings; the pipeline persists them afterwards.
RESEARCH_WITHHELD_SCOPES: frozenset[str] = frozenset({"task:write"})


def resolve_scopes(
Expand All @@ -191,9 +222,20 @@ def resolve_scopes(
include_internal_scopes: bool = True,
) -> list[str]:
internal = list(INTERNAL_SCOPES) if include_internal_scopes else []
scratchpad = list(SCRATCHPAD_INTERNAL_SCOPES) if include_internal_scopes else []
if isinstance(scopes, str):
if scopes == "full":
resolved = [*MCP_READ_SCOPES, *MCP_WRITE_SCOPES, *internal]
elif scopes == "signals_implementation":
# The self-driving implementation run: `full`, plus durable memory. It already
# writes code and logs its work on the report, so the scratchpad adds reach into
# one more surface rather than a new class of capability.
resolved = [*MCP_READ_SCOPES, *MCP_WRITE_SCOPES, *internal, *scratchpad]
elif scopes == "signals_research":
# The report research run: reads, plus durable memory, and nothing else. See
# `RESEARCH_WITHHELD_SCOPES` for why `task:write` comes back out.
reads = [scope for scope in (*MCP_READ_SCOPES, *internal) if scope not in RESEARCH_WITHHELD_SCOPES]
resolved = [*reads, *scratchpad]
elif scopes in ("signals_scout", "signals_scout_reports"):
# The scout sandbox: reads, the scout's own internal write scope, and a narrow
# allowlist of user-facing writes (`SCOUT_USER_WRITE_SCOPES`) for the durable
Expand Down Expand Up @@ -225,8 +267,16 @@ def has_write_scopes(scopes: PosthogMcpScopes) -> bool:
# scout sandbox — the agent IS allowed to call the write tools its preset exists for
# (remember/forget/emit_finding + the narrow `SCOUT_USER_WRITE_SCOPES`). Read-only mode
# is a tool-annotation filter, not a scope filter, and would strip those tools
# categorically without this opt-out.
return scopes in ("full", "signals_scout", "signals_scout_reports")
# categorically without this opt-out. The two pipeline postures need the same opt-out
# for their scratchpad tools; `signals_research` pays for it by withholding `task:write`
# (see `RESEARCH_WITHHELD_SCOPES`), so turning read-only mode off widens nothing else.
return scopes in (
"full",
"signals_scout",
"signals_scout_reports",
"signals_research",
"signals_implementation",
)
return any(s in MCP_WRITE_SCOPES for s in scopes)


Expand Down
Loading
Loading