# Connect your agent and prepare the research

## 1. Start with the owner’s URL and goal

Ask what they want to learn about the supplied URL. Do not assume a property,
environment, persona or plan exists. Suggest the product name from the app and
confirm it conversationally before creating anything.

Ask for the missing pieces in plain words: the app URL, what they want to
learn, and what the person should start with. For example: “Should we explore
what anyone can see without signing in, or start signed in with an account and
existing data?” When no account state is requested, offer a public signed-out
first visit as the default option. Ask whether the person should be signed in
with existing data; do not require familiarity with “situation”. Map the
owner's facts to canonical standing and
situation definitions: standing is role/access, situation is starting history
and account facts. Explain that the situation means “the account and history
the person starts with”. Never silently substitute a public visit for a
signed-in request; if they ask for signed-in data and no hook can supply it,
explain the context naturally and preserve the blocked request (see
[hooks](hooks.md)).

For a signed-in account, before saving the canonical situation, ask the owner
the missing research facts in plain words: whether this is a first use or a
returning visit and the exact prior visits, the role or tier if it matters to
the job, and any existing data, counts, drafts, or statuses that matter to the
job. Do not infer zero visits, an empty draft, counts, or assignments from app
defaults or example data. If a fact is unknown, preserve it as unknown and ask
a follow-up; do not invent a first-visit definition. Confirm a short summary
of the situation with the owner before saving it. This is ordinary onboarding
decision-making, not hook implementation.

Save it with `upsert_situation`. `authState` says whether
the person starts signed in; for a signed-in account also give `supply`
(`provision` for a factory-made account), the owner-confirmed `priorVisits`,
and `resetPolicy` (`fresh_each_run` for a fresh synthetic account). The server
refuses a situation whose fields disagree and names each problem; fix the
input and retry rather than working around it. A missing hook does not
justify saving a requested signed-in situation as anonymous.

```json
{
  "propertyId": "ACTUAL_ID",
  "situationKey": "owner-confirmed-state",
  "label": "Returning member",
  "description": "Owner-confirmed history and existing data",
  "authState": "authenticated",
  "supply": "provision",
  "priorVisits": 2,
  "resetPolicy": "fresh_each_run",
  "facts": {
    "history": "Owner-confirmed history",
    "data": "Owner-confirmed existing data"
  }
}
```

The numbers, keys and facts above are examples to replace with owner-approved
facts; never copy them into research.

## 2. Connect with browser authorization

Use the supplied MCP endpoint (local default `http://127.0.0.1:8788/mcp`).
For OpenCode, merge `mcp.criticalfriends = {"type":"remote","url":"THE_ENDPOINT","oauth":{}}`
into `opencode.json`. Do not add an Authorization header. Ask the owner to complete
the browser sign-in and consent flow; OpenCode manages the tokens. If needed,
`opencode mcp auth criticalfriends` starts browser authorization. Never request a
password, token or API key in chat. The website is invite-only; a new account
requires a valid invitation.

Browser consent applies to one property. The owner may cancel consent. Property-scoped API keys remain
supported for clients without OAuth.

The authorization browser lets the owner select an existing property or name a
new one. The CLI grant applies only to that property. After connecting, read
`get_context` and use the returned property ID. Do not create another property
through this grant. To work on a different property, authorize it separately.

To save an intent, call `upsert_property_intent` with
`{"propertyId":"RETURNED_PROPERTY_ID","title":"Research question","description":"User job, success criteria and constraints"}`.
Omit `id` and `intentId` when creating; use IDs returned by the server for references.

Reconnect the client and list tools. Call `get_context` with
`{"scope":"workspace"}`, then `{"scope":"property","propertyId":"ACTUAL_ID"}`.
Confirm the returned property and accessible environment, rather than treating
a saved configuration as successful authorization. Read
`get_context {"scope":"methodology"}` for research guidance and
`get_context {"scope":"agent_setup"}` for these instructions.

The MCP service does not log in to the application being researched. App-account
access is a separate step through a start hook when needed.

## 3. Prepare a realistic starting point

Read existing intents, personas, standings, situations, devices, hooks and plans
from context. A standing defines role/access; a situation defines starting
history/context. Keep the environment URL separate from property identity.
For an existing property, `upsert_property_environment` takes `propertyId`, `environmentKey`, `label`,
`targetUrl` and optional `kind: "hosted"`.

When the app is running on this machine and has no deployed URL, use
`connect_local_environment` with `propertyId` and `port` instead. Find the
port yourself: it is the one the app's dev server listens on. If nothing is
listening, stop and tell the owner; never start their app for them. It returns
the environment (a private hostname only Critical Friends can reach; nothing
on the machine is exposed publicly) and a `command`. Run that command in the
background on this machine and keep it running for as long as sessions need
the app. To reconnect after a restart, call it again with the returned
`environmentKey`; the hostname stays the same. Sessions against a local
environment refuse to start while it is offline, with a message saying so.
A property holds at most three local environments and an account ten; when
the server says the limit is reached, reconnect one of the keys it names
instead of creating another.
If the server answers that local environments are not configured, ask the
owner for a deployed or preview URL instead.

A public first visit can start without a hook, including when it has a named
standing and situation. Omit `startHookId` when no preparation is needed.
Do not silently substitute an anonymous
visit for a signed-in experience. For account-dependent research, first inspect
the existing setup: read `get_context` with `scope: "setup"`, `propertyId`
and `environmentKey` to see declared hooks and setup routing.

A registered hook is not a working one. `get_context` with `scope: "setup"`
reads what hooks declare they can supply; the client's `validate_setups` hook
is what says whether a setup is supported. Report what has actually been
verified and nothing more.

If the requested state is missing or unsupported, preserve the requested intent
and state as blocked and explain the context naturally. Do not ask the owner to
choose build now or build later, do not offer to implement the hook, and do not
launch a run that is guaranteed to fail. Engineering scheduling is out of band;
offer implementation only if the owner independently and explicitly asks.
Example owner-facing response:

> This study needs a signed-in account with [agreed state]. Critical Friends
> cannot prepare that account because this environment has no account-setup
> endpoint. That endpoint is a small addition to your application that creates
> a safe test account when a run starts. I saved the study setup; your
> application team can use [docs] to add this integration. Once it is connected
> and this starting state is supported, we can run the study.

If an existing hook explicitly rejects the selected state, explain that the
endpoint exists but the selected state is unsupported and the application team
must extend its capability. Keep the technical contract, secrets, files and
JSON steps in the linked [hooks](hooks.md) reference, not in the owner
conversation.
Do not implement hooks automatically just because repository access exists;
only when the user requests code work.

For a signed-in first study created by `/get-started`, open its study page and
use **Connect a test account** after implementing the factory. This binds the
endpoint to the saved plan while preserving `requireAuthenticatedStart`. Store
the factory bearer credential in that property's environment secrets and enter
its variable name in the account connection form. Registering a standalone hook alone does not bind it to the first-study plan.

Prefer the session plan created by `/get-started`. Additional session plans can be saved
with `upsert_session_plan`. Example using identifiers already
present in the property (replace all placeholders):

```json
{
  "path": "YOUR_PROPERTY/first-value",
  "label": "First value for a new member",
  "propertyId": "YOUR_PROPERTY",
  "environmentKey": "YOUR_ENVIRONMENT",
  "personaKey": "YOUR_PERSONA",
  "critiMode": "on_demand",
  "deviceKey": "YOUR_DEVICE",
  "maxSteps": 20,
  "intent": {
    "intentId": "YOUR_INTENT",
    "standingKey": "YOUR_STANDING",
    "situationKey": "YOUR_SITUATION",
    "startHookId": "YOUR_HOOK"
  }
}
```

Omit `startHookId` for public visits that require no factory. Read the existing
plan rather than replacing its access requirements or research intent.

## 4. Launch once, then inspect the evidence

Show the owner the property, environment URL, job, Criti, starting state and
action limit. Use existing authorization if they already asked to launch this
session; otherwise obtain it before calling `start_session` with
`{"sessionPlanPath":"ACTUAL_PLAN_PATH"}`.

Keep the returned session ID. Follow it with `get_session_events`, passing the
last event ID you received; each call waits for the next step, so one call per
step is enough. Stop when the session completes. Use `get_session_report` with the same
session ID for feedback and evidence. Do not interpret an unfinished report as
complete. If the launch response is lost, find the session with
`get_context {"scope":"sessions"}` before starting another.

Read `get_context {"scope":"insights","propertyId":"ACTUAL_ID"}` and lead
with findings, evidence quality, affected perspectives and next action. Link
to `/findings?property=PROPERTY_ID` and `/runs/RUN_ID` in the workspace. If no
finding was synthesized, report the observed result and coverage limitations.
Do not invent findings or label one session a validated pattern.

## Recovery

- 401: reconnect using browser authorization. For an explicitly selected API-key
  connection, check expiry and secret configuration without printing the value.
- Scope denied: verify the chosen property/environment and request an
  appropriately scoped key from its owner. Do not broaden access automatically.
- Unreachable MCP: verify the service address from the agent's machine.
- Provisioning rejected: surface the app's reason, fix the selected setup,
  then recheck. Do not bypass the hook or change research intent to force a run.
- Failed/interrupted session: retrieve status and available evidence before
  offering a new run. Preserve the session ID and saved plan for resuming work.

## Client references

Configuration checked against official documentation on 2026-09-13:
[Codex](https://developers.openai.com/codex/mcp),
[Claude Code](https://code.claude.com/docs/en/mcp),
[Gemini CLI](https://geminicli.com/docs/tools/mcp-server/),
[xAI remote MCP](https://docs.x.ai/developers/tools/remote-mcp).
Consult these for client-version differences. The transferable pattern comes
from [Stripe's MCP and skill onboarding](https://docs.stripe.com/mcp).
