Visa MCP STAGING

Book an India visa from your own agent

A remote MCP server that exposes Naytive's Visit India / Edge City visa flow — search visas, upload a passport & photo, file the application, and take payment — as tools any MCP-compatible AI agent can call on a user's behalf.

MCP https://visa-mcp.naytive.com/mcp

Overview

The server speaks the Model Context Protocol over Streamable HTTP. Point any MCP client at the endpoint above; the client discovers the tools and drives them. Every call acts on behalf of a signed-in Naytive user, so the visa is filed against a real account with a real document vault.

Transport

Streamable HTTP (JSON-RPC 2.0). One endpoint: POST /mcp.

Auth

OAuth 2.1 with PKCE. The user signs in with Naytive (Privy) in the browser — the client handles it.

Scope

Currently India visas for the Edge City / builder events. New destinations are data-only additions.

Staging. This deployment runs against Naytive's staging environment and Stripe test mode. Use it to build and validate; a production endpoint is issued separately.

Connect a client

The endpoint is a standard remote MCP server, so anything that speaks MCP works. On first use the client opens a browser window for the user to sign in, then remembers the session.

Claude Desktop / Claude Code

Add the server to your MCP config (Claude Code: run the CLI command; Desktop: edit claude_desktop_config.json).

claude_desktop_config.json
// mcpServers entry
{
  "mcpServers": {
    "naytive-visa": {
      "type": "http",
      "url": "https://visa-mcp.naytive.com/mcp"
    }
  }
}
Claude Code — terminal
claude mcp add --transport http naytive-visa https://visa-mcp.naytive.com/mcp

Cursor

Add to ~/.cursor/mcp.json (or the project's .cursor/mcp.json) with the same shape — "url" pointing at the endpoint.

MCP Inspector

The quickest way to click through the tools. Use v2+, which supports the OAuth sign-in:

terminal
npx @modelcontextprotocol/inspector@latest

In the UI: Add Server → Transport Streamable HTTP → URL the endpoint → toggle it on → complete the Naytive sign-in.

Custom client (TypeScript SDK)

client.ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const transport = new StreamableHTTPClientTransport(
  new URL("https://visa-mcp.naytive.com/mcp"),
  { authProvider }  // your OAuthClientProvider — SDK runs the PKCE flow
);
const client = new Client({ name: "my-agent", version: "1.0.0" }, { capabilities: {} });
await client.connect(transport);

const { tools } = await client.listTools();
const res = await client.callTool({ name: "list_india_events", arguments: {} });

Try it

Run the tools live against staging, right here. Catalog tools work with no sign-in; account tools need a Bearer access token — grab one from the MCP Inspector or your connected client and paste it below.

Ready
// the response will appear here

Tip: run list_india_events first to get a visaConfigId, then paste it into get_visa_details.

Authentication

The /mcp endpoint is protected. An unauthenticated request returns 401 with a WWW-Authenticate header pointing at the server's OAuth metadata — a compliant MCP client uses that to run the sign-in automatically. You don't implement any of this by hand; you just approve the browser prompt.

  1. The client discovers the authorization server at /.well-known/oauth-authorization-server and registers itself (Dynamic Client Registration).
  2. It opens the authorize URL; the server redirects to Naytive's hosted sign-in, where the user logs in with email or wallet (Privy).
  3. Naytive verifies the identity and the server issues a short-lived access token bound to that user. The client sends it as Authorization: Bearer … on every call.
One account per session. Whatever Naytive account signs in is the account the visa is filed under, using that user's stored documents. Tokens are short-lived and refresh automatically.
GET /.well-known/oauth-authorization-server
{
  "issuer": "https://visa-mcp.naytive.com/",
  "authorization_endpoint": ".../authorize",
  "token_endpoint": ".../token",
  "registration_endpoint": ".../register",
  "code_challenge_methods_supported": ["S256"],
  "grant_types_supported": ["authorization_code", "refresh_token"]
}

Tools

Nine tools, grouped by what they touch. Read tools return catalog or account data; write tools mutate the user's vault or applications. Every tool returns a JSON payload as text content.

You never hardcode identifiers. A visaConfigId comes from list_india_events; an applicationId / applicationNumber comes from submit_application or list_applications; document and photo ids come from the upload tools. Treat them as opaque handles.

Catalog

list_india_events

Read

The India builder events you can get a visa for (Edge City India, India Blockchain Week, Devcon Mumbai) and the available India visa options. No arguments.

get_visa_details

Read

Full detail for one visa: validity, max stay, entries, processing time, nationality-aware pricing, and the required documents.

FieldTypeNotes
visaConfigIdintegerrequiredFrom list_india_events.
nationalitystringoptionalISO code (US, GB, IN) for accurate pricing/docs.

get_required_documents

Read

Required documents for a visa, merged with what the signed-in user already has stored — so the agent only asks for what's missing.

FieldTypeNotes
visaConfigIdintegerrequiredThe handle returned by list_india_events.
nationalitystringoptionalISO code.

Applications

list_applications

Read

The signed-in user's filed visa applications with current status, travel dates, and amount. No arguments.

check_application_status

Read

Status and detail for one application by its number.

FieldTypeNotes
applicationNumberstringrequirede.g. NV…

Documents

upload_passport

Write

Upload a passport image or PDF. It's read, the identity fields extracted, and stored in the traveller's vault. Returns a documentId to pass to submit_application.

FieldTypeNotes
fileBase64stringrequiredRaw base64 (no data: prefix), ≤ 6 MB.
fileNamestringrequired
fileMimestringrequiredimage/png, image/jpeg, or application/pdf.

upload_photo

Write

Upload the traveller's passport-size photo. Returns a photoId for submit_application.

FieldTypeNotes
imageBase64stringrequiredBase64 JPEG/PNG.
countryCodestringoptionalDestination code, default IN.
formatstringoptionaljpeg / png.

Submit & pay

submit_application

Write

File the application. Every traveller must already have a passport documentId and a photoId, or the call is rejected. Creates a pending_payment application — payment is confirmed separately by the gateway.

FieldTypeNotes
visaConfigIdintegerrequiredThe handle returned by list_india_events.
nationalitystringoptionalISO code.
arrivalDate / departureDatestringoptionalYYYY-MM-DD.
eventName / visitTypestringoptionale.g. "Edge City India".
documentIdsinteger[]singlePassport doc id(s) for a solo applicant.
processedPhotoIdintegersinglePhoto id for a solo applicant.
travelersobject[]groupOne per traveller: { fullName?, relationship?, documentIds, processedPhotoId }.

create_payment_link

Write

Get a hosted payment link for a pending application. Card returns a Stripe checkout url; crypto returns a Helio paylink. Open it in a browser to pay — the gateway then flips the application to submitted.

FieldTypeNotes
applicationIdintegerrequiredFrom submit_application.
paymentModestringoptionalcard (default) or crypto.
returnUrlstringoptionalWhere to send the browser after payment.

Filing a visa, end to end

A typical agent conversation walks the user from "what can I get" to a filed, paid application. The server owns the rules; the agent just calls tools in order.

  1. Discover

    list_india_events → pick a visa and event. get_visa_details for pricing and the document checklist.

  2. Check the vault

    get_required_documents shows what the user already has stored versus what's still needed.

  3. Collect documents

    upload_passport and upload_photo for anything missing. Keep the returned documentId and photoId.

  4. File

    submit_application with the ids, dates, and event. Returns an applicationNumber in pending_payment.

  5. Pay

    create_payment_link → open the URL in a browser and complete payment. The gateway confirms it server-side.

  6. Track

    list_applications / check_application_status — the application now reads submitted and moves through review.

Errors & limits

Tool errors come back as text content with isError: true and a plain message. A few things worth knowing before you build against it.

SituationWhat you get
No / expired token401 + WWW-Authenticate — the client re-runs sign-in.
Missing passport or photo on submitError: "Passport and photo are required for every traveller before filing."
File over 6 MBError: file too large.
Application already paidcreate_payment_link → "already paid".
  • Payment is server-authoritative. An application only becomes submitted when the Stripe/Helio webhook confirms the charge — never from the client claiming success.
  • Documents belong to the signed-in user. A tool can only reference documents and applications owned by the authenticated account.
  • Staging Stripe is in test mode — use a test card at checkout.
Need production access, a rate-limit bump, or a new destination? Contact the Naytive team — production issues a separate endpoint and credentials.