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:
- Human‑in‑the‑loop verification — ask the signed‑in user to approve an action with a face scan on their Pollus app (
verification_request→verification_status). - The Valyd web agent — run a browser/web task on the user's behalf, reusing their secure browser profile (
do_task).
| Endpoint | https://mcp.pollus.tech/verification/mcp |
| Transport | Streamable HTTP |
| Auth | OAuth 2.1 Bearer token (RFC 9728 discovery) |
| Authorization server | https://idp.pollus.tech |
| Required scopes | openid mcp |
Token audience (aud) | https://mcp.pollus.tech |
| The user | The access‑token sub. Tools act on their behalf. |
| Tools | verification_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.
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.):
- The client calls the MCP endpoint with no token and gets 401 with a
WWW-Authenticateheader pointing athttps://mcp.pollus.tech/.well-known/oauth-protected-resource. - 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"] } - The client discovers the IDP, registers (Dynamic Client Registration), and opens a browser. The user logs into Pollus and consents.
- The client gets an access token bound to
resource = https://mcp.pollus.techwith scopemcpand sends it on every call:Authorization: Bearer <access_token>.
Token validation requirements (all must hold)
| Claim | Requirement |
|---|---|
| signature | RS256, verifiable against https://idp.pollus.tech/api/auth/oidc/jwks.json |
iss | https://idp.pollus.tech |
aud | https://mcp.pollus.tech |
scope | must include mcp |
exp / iat / sub | present 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
| Name | Type | Required | Description |
|---|---|---|---|
action_type | string | Yes | Kind of action, e.g. "delete", "payment", "update" |
title | string | Yes | Short title shown on the approval prompt |
description | string | Yes | Context 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
| Name | Type | Required | Description |
|---|---|---|---|
valyd_session_id | string | Yes | The 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
| Name | Type | Required | Description |
|---|---|---|---|
task | string | Yes | What the agent should do, in plain language |
start_url | string | No | Page to open before starting |
user_uuid | string | No | Profile 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)
- Call
verification_requestwithaction_type,title,description. - Poll
verification_status(valyd_session_id)untilstatusis no longerPENDING. - If
APPROVED, perform the action. IfDENIED/DECLINED/EXPIRED, abort and tell the user.
Web task
- Call
do_taskwithtask(and optionalstart_url). The agent reuses the user's browser profile. - Read
responseandsuccess. 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
| Status | Meaning |
|---|---|
PENDING | Waiting for the user to respond on their Pollus app. Keep polling. |
APPROVED | The user approved (face‑verified). Proceed with the action. |
DENIED | The system/policy denied the request. |
DECLINED | The user explicitly declined. Do not proceed. |
EXPIRED | The 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": "..."}.
| Symptom | Cause | Fix |
|---|---|---|
401 with WWW-Authenticate on connect | No / expired token | Let the client run the OAuth login (Claude Code/Cursor: /mcp → Authenticate). For code clients, fetch a fresh token. |
invalid_token | Wrong aud, iss, scope, or signature | Token must have aud=https://mcp.pollus.tech, iss=https://idp.pollus.tech, scope including mcp. |
invalid_scope | A requested scope is not allowed by the IDP | Request only openid mcp. Don't ask for scopes the IDP doesn't grant. |
missing_token | No Authorization: Bearer header | Send the Bearer token on every request. |
do_task returns success: false | The web agent could not complete the task | Read 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.