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.
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.
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).
// mcpServers entry { "mcpServers": { "naytive-visa": { "type": "http", "url": "https://visa-mcp.naytive.com/mcp" } } }
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:
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)
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.
// 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.
- The client discovers the authorization server at
/.well-known/oauth-authorization-serverand registers itself (Dynamic Client Registration). - It opens the authorize URL; the server redirects to Naytive's hosted sign-in, where the user logs in with email or wallet (Privy).
- 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.
{
"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.
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
ReadThe 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
ReadFull detail for one visa: validity, max stay, entries, processing time, nationality-aware pricing, and the required documents.
| Field | Type | Notes | |
|---|---|---|---|
| visaConfigId | integer | required | From list_india_events. |
| nationality | string | optional | ISO code (US, GB, IN) for accurate pricing/docs. |
get_required_documents
ReadRequired documents for a visa, merged with what the signed-in user already has stored — so the agent only asks for what's missing.
| Field | Type | Notes | |
|---|---|---|---|
| visaConfigId | integer | required | The handle returned by list_india_events. |
| nationality | string | optional | ISO code. |
Applications
list_applications
ReadThe signed-in user's filed visa applications with current status, travel dates, and amount. No arguments.
check_application_status
ReadStatus and detail for one application by its number.
| Field | Type | Notes | |
|---|---|---|---|
| applicationNumber | string | required | e.g. NV… |
Documents
upload_passport
WriteUpload 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.
| Field | Type | Notes | |
|---|---|---|---|
| fileBase64 | string | required | Raw base64 (no data: prefix), ≤ 6 MB. |
| fileName | string | required | |
| fileMime | string | required | image/png, image/jpeg, or application/pdf. |
upload_photo
WriteUpload the traveller's passport-size photo. Returns a photoId for submit_application.
| Field | Type | Notes | |
|---|---|---|---|
| imageBase64 | string | required | Base64 JPEG/PNG. |
| countryCode | string | optional | Destination code, default IN. |
| format | string | optional | jpeg / png. |
Submit & pay
submit_application
WriteFile 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.
| Field | Type | Notes | |
|---|---|---|---|
| visaConfigId | integer | required | The handle returned by list_india_events. |
| nationality | string | optional | ISO code. |
| arrivalDate / departureDate | string | optional | YYYY-MM-DD. |
| eventName / visitType | string | optional | e.g. "Edge City India". |
| documentIds | integer[] | single | Passport doc id(s) for a solo applicant. |
| processedPhotoId | integer | single | Photo id for a solo applicant. |
| travelers | object[] | group | One per traveller: { fullName?, relationship?, documentIds, processedPhotoId }. |
create_payment_link
WriteGet 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.
| Field | Type | Notes | |
|---|---|---|---|
| applicationId | integer | required | From submit_application. |
| paymentMode | string | optional | card (default) or crypto. |
| returnUrl | string | optional | Where 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.
Discover
list_india_events→ pick a visa and event.get_visa_detailsfor pricing and the document checklist.Check the vault
get_required_documentsshows what the user already has stored versus what's still needed.Collect documents
upload_passportandupload_photofor anything missing. Keep the returneddocumentIdandphotoId.File
submit_applicationwith the ids, dates, and event. Returns anapplicationNumberinpending_payment.Pay
create_payment_link→ open the URL in a browser and complete payment. The gateway confirms it server-side.Track
list_applications/check_application_status— the application now readssubmittedand 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.
| Situation | What you get |
|---|---|
| No / expired token | 401 + WWW-Authenticate — the client re-runs sign-in. |
| Missing passport or photo on submit | Error: "Passport and photo are required for every traveller before filing." |
| File over 6 MB | Error: file too large. |
| Application already paid | create_payment_link → "already paid". |
- Payment is server-authoritative. An application only becomes
submittedwhen 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.