Valyd MCP

Connect any AI agent to Valyd verification and the Valyd web agent over the Model Context Protocol — Streamable HTTP, OAuth 2.1.

Overview

One hosted server gives your agent two capabilities:

Endpointhttps://mcp.pollus.tech/verification/mcp
TransportStreamable HTTP
AuthOAuth 2.1 Bearer token (RFC 9728 discovery)
Authorization serverhttps://idp.pollus.tech
Required scopesopenid mcp
Token audience (aud)https://mcp.pollus.tech
The userThe access‑token sub. Tools act on their behalf.
Toolsverification_request, verification_status, do_task

There is nothing to register per request and no shared secret to paste. A modern MCP client performs the OAuth login once; after that, every tool call carries the user's token automatically.

Base URL & endpoint

Configure your MCP client with this endpoint. Use no trailing slash.

https://mcp.pollus.tech/verification/mcp

Production base URL: https://mcp.pollus.tech. The OAuth authorization server (IDP) is https://idp.pollus.tech.

Authentication (OAuth 2.1)

This server does not issue tokens — it only validates them. The flow is standards‑based (OAuth 2.1 + PKCE, RFC 9728 Protected Resource Metadata, RFC 8707 resource indicators), so any OAuth‑capable MCP client connects with zero custom code.

How a client connects (automatic in Claude Code, Cursor, etc.):

  1. The client calls the MCP endpoint with no token and gets 401 with a WWW-Authenticate header pointing at https://mcp.pollus.tech/.well-known/oauth-protected-resource.
  2. That metadata names the authorization server and scopes:
    {
      "resource": "https://mcp.pollus.tech",
      "authorization_servers": ["https://idp.pollus.tech"],
      "scopes_supported": ["openid", "mcp"],
      "bearer_methods_supported": ["header"]
    }
  3. The client discovers the IDP, registers (Dynamic Client Registration), and opens a browser. The user logs into Pollus and consents.
  4. The client gets an access token bound to resource = https://mcp.pollus.tech with scope mcp and sends it on every call: Authorization: Bearer <access_token>.

Token validation requirements (all must hold)

ClaimRequirement
signatureRS256, verifiable against https://idp.pollus.tech/api/auth/oidc/jwks.json
isshttps://idp.pollus.tech
audhttps://mcp.pollus.tech
scopemust include mcp
exp / iat / subpresent and not expired

Code‑first clients (LangChain, OpenAI SDK, scripts): obtain a user access token by running the OAuth 2.1 authorization‑code + PKCE flow against https://idp.pollus.tech with scope=openid mcp and the resource indicator resource=https://mcp.pollus.tech, then pass it as the Authorization: Bearer header. The token is user‑bound, so it must come from a user login (not client‑credentials).

Tools

The user is the OAuth token sub — no user id is passed. On failure every tool returns {"status": "error", "message": "..."}.

verification_request

Ask the signed‑in user to approve a sensitive action with a face scan on their Pollus app. Call before anything risky (delete, payment, sharing data).

Arguments

NameTypeRequiredDescription
action_typestringYesKind of action, e.g. "delete", "payment", "update"
titlestringYesShort title shown on the approval prompt
descriptionstringYesContext the user reads before deciding

Returns — status is PENDING until the user responds, then poll verification_status

{
  "valyd_session_id": "uuid-string",
  "status": "PENDING",
  "expires_at": "2026-06-24T09:10:46+00:00"
}

verification_status

Check the result of a verification started by verification_request. Poll until status is no longer PENDING.

Arguments

NameTypeRequiredDescription
valyd_session_idstringYesThe id returned by verification_request

Returns — status is one of PENDING / APPROVED / DENIED / DECLINED / EXPIRED (see Status reference)

{
  "valyd_session_id": "uuid-string",
  "status": "APPROVED",
  "result": "...",
  "assurance_level": "high",
  "expires_at": "2026-06-24T09:10:46+00:00"
}

do_task

Run a web/browser task for the signed‑in user via the Valyd agent — open sites, fill forms, complete actions. The browser profile persists per user across calls. May take several minutes.

Arguments

NameTypeRequiredDescription
taskstringYesWhat the agent should do, in plain language
start_urlstringNoPage to open before starting
user_uuidstringNoProfile override; defaults to the signed‑in user

Returns

{
  "uuid": "user-or-profile-uuid",
  "response": "what the agent did / found",
  "success": true
}

If the agent needs a login, card, or personal detail, it fetches it securely from Valyd (the user approves on their phone) — secrets are never returned to the calling agent in plain text.

Integrations

The endpoint and auth are identical for every client. Interactive clients (Claude Code, Cursor, Claude Desktop) handle the OAuth login for you. Code‑first clients (LangChain, OpenAI SDK) take a Bearer token you obtained from the IDP.

Claude Code

Add the server (HTTP transport), then authenticate from inside Claude Code:

claude mcp add --transport http valyd https://mcp.pollus.tech/verification/mcp

Then run /mcp in Claude Code and choose Authenticate for valyd — a browser opens for the Pollus login. After that, the tools are available.

Equivalent .mcp.json (project‑scoped)

{
  "mcpServers": {
    "valyd": {
      "type": "http",
      "url": "https://mcp.pollus.tech/verification/mcp"
    }
  }
}

No secrets go in this file. Claude Code performs the OAuth flow and stores the token. Re‑run /mcp → Authenticate if the token expires.

Codex (OpenAI Codex CLI)

Add the server to ~/.codex/config.toml as a remote (streamable HTTP) MCP server:

[mcp_servers.valyd]
url = "https://mcp.pollus.tech/verification/mcp"

Then sign in so Codex runs the OAuth flow and caches the token:

codex mcp login valyd

If your Codex build does not yet support interactive OAuth login, supply a token from the IDP via an environment variable instead:

[mcp_servers.valyd]
url = "https://mcp.pollus.tech/verification/mcp"
bearer_token_env_var = "VALYD_MCP_TOKEN"
export VALYD_MCP_TOKEN="<access_token from idp.pollus.tech>"

Cursor / Claude Desktop

Add a remote MCP server in the client's MCP config (Cursor: .cursor/mcp.json or Settings → Tools & MCP; Claude Desktop: its mcpServers config):

{
  "mcpServers": {
    "valyd": {
      "url": "https://mcp.pollus.tech/verification/mcp"
    }
  }
}

Restart the client; it will prompt you to authorize with Pollus on first use. Use no trailing slash on the URL.

LangChain

Use langchain-mcp-adapters. Obtain a user token from the IDP and pass it as a header.

pip install langchain-mcp-adapters langgraph langchain-openai
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent

TOKEN = "<access_token from idp.pollus.tech, scope: openid mcp>"

async def main():
    client = MultiServerMCPClient(
        {
            "valyd": {
                "transport": "streamable_http",
                "url": "https://mcp.pollus.tech/verification/mcp",
                "headers": {"Authorization": f"Bearer {TOKEN}"},
            }
        }
    )
    tools = await client.get_tools()  # verification_request, verification_status, do_task

    agent = create_react_agent("openai:gpt-4o", tools)
    result = await agent.ainvoke(
        {"messages": "Ask the user to approve deleting the prod database, then tell me the outcome."}
    )
    print(result["messages"][-1].content)

asyncio.run(main())

OpenAI Agents SDK

Use the OpenAI Agents SDK with a streamable‑HTTP MCP server.

pip install openai-agents
import asyncio
from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHttp

TOKEN = "<access_token from idp.pollus.tech, scope: openid mcp>"

async def main():
    async with MCPServerStreamableHttp(
        name="valyd",
        params={
            "url": "https://mcp.pollus.tech/verification/mcp",
            "headers": {"Authorization": f"Bearer {TOKEN}"},
        },
    ) as valyd:
        agent = Agent(
            name="Assistant",
            instructions="Use Valyd tools for approvals and web tasks.",
            mcp_servers=[valyd],
        )
        result = await Runner.run(agent, "Book a table for two on the user's behalf at example.com.")
        print(result.final_output)

asyncio.run(main())

Alternative — OpenAI Responses API (hosted MCP tool)

from openai import OpenAI

client = OpenAI()
resp = client.responses.create(
    model="gpt-4o",
    tools=[
        {
            "type": "mcp",
            "server_label": "valyd",
            "server_url": "https://mcp.pollus.tech/verification/mcp",
            "headers": {"Authorization": "Bearer <access_token>"},
            "require_approval": "never",
        }
    ],
    input="Ask the user to approve the payment, then proceed if approved.",
)
print(resp.output_text)

Agent flow

Verification (human‑in‑the‑loop)

  1. Call verification_request with action_type, title, description.
  2. Poll verification_status(valyd_session_id) until status is no longer PENDING.
  3. If APPROVED, perform the action. If DENIED/DECLINED/EXPIRED, abort and tell the user.

Web task

  1. Call do_task with task (and optional start_url). The agent reuses the user's browser profile.
  2. Read response and success. If the agent needed a sensitive credential, it was fetched securely from Valyd with the user's phone approval — not returned to you in plain text.

You can combine them: gate a do_task behind a verification_request approval for sensitive actions.

Status reference

StatusMeaning
PENDINGWaiting for the user to respond on their Pollus app. Keep polling.
APPROVEDThe user approved (face‑verified). Proceed with the action.
DENIEDThe system/policy denied the request.
DECLINEDThe user explicitly declined. Do not proceed.
EXPIREDThe request timed out (expires_at passed) without a decision.

When present, assurance_level (e.g. high) describes how strongly the user was verified.

Common errors

Every tool returns errors as {"status": "error", "message": "..."}.

SymptomCauseFix
401 with WWW-Authenticate on connectNo / expired tokenLet the client run the OAuth login (Claude Code/Cursor: /mcp → Authenticate). For code clients, fetch a fresh token.
invalid_tokenWrong aud, iss, scope, or signatureToken must have aud=https://mcp.pollus.tech, iss=https://idp.pollus.tech, scope including mcp.
invalid_scopeA requested scope is not allowed by the IDPRequest only openid mcp. Don't ask for scopes the IDP doesn't grant.
missing_tokenNo Authorization: Bearer headerSend the Bearer token on every request.
do_task returns success: falseThe web agent could not complete the taskRead response for the reason; refine the task or start_url and retry.

The legacy X-MCP-Client-Id / X-MCP-Client-Secret / X-MCP-Webhook-Url header scheme and the user_id tool parameter are removed. Authentication is OAuth 2.1 Bearer only, and the user comes from the token.