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
Two keys: a Naytive partner API key (X-API-Key, issued to you) identifies your integration; the traveller signs in with Privy (OAuth 2.1 + PKCE) in the browser.
Billing
You charge your own users through your own Stripe. Naytive's per-application fee is drawn from a prepaid wallet you top up.
Get an API key
Every call carries a Naytive partner API key in the X-API-Key header, so all usage is attributed to your integration and billed to your wallet. Keys are issued by Naytive â request one below and we'll approve it and send your key (shown once).
1 · Request
Send your organisation and a contact email. We review and approve.
2 · Receive your key
On approval you get an oc_live_⦠/ oc_test_⦠key. Store it as a secret.
3 · Top up & build
Add funds to your wallet, then point your MCP client at the endpoint with your key set.
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). Set your partner key as the X-API-Key header. Optionally add X-Stripe-Key (a restricted Stripe key) to collect end-user payments on your own Stripe â see Wallet & billing.
// mcpServers entry { "mcpServers": { "naytive-visa": { "type": "http", "url": "https://visa-mcp.naytive.com/mcp", "headers": { "X-API-Key": "oc_test_your_key_here", // optional â collect on your own Stripe: "X-Stripe-Key": "rk_test_your_restricted_key" } } } }
claude mcp add --transport http naytive-visa https://visa-mcp.naytive.com/mcp \ --header "X-API-Key: oc_test_your_key_here"
Cursor
Add to ~/.cursor/mcp.json (or the project's .cursor/mcp.json) with the same shape â "url" pointing at the endpoint and an X-API-Key header.
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 any tool live, right here. Every call needs your X-API-Key. Wallet & catalog tools run with just the key; traveller tools also need a Bearer access token.
// the response will appear here
Tip: run list_india_events first to get a visaConfigId, then paste it into get_visa_details.
Authentication
Two layers. Your partner API key (X-API-Key) identifies your integration on every request and is what your wallet is billed against. On top of that, user-scoped tools need the traveller's Privy sign-in (OAuth 2.1). A request with no key returns 401; a user tool with no sign-in returns 401 + WWW-Authenticate, which a compliant client uses to run the browser sign-in automatically.
- Set your Naytive-issued key as the
X-API-Keyheader (see Get an API key). It never expires unless revoked. - For a user-scoped tool, the client opens the authorize URL; the server redirects to Naytive's hosted sign-in, where the traveller 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 â¦alongside yourX-API-Key.
X-API-Key = your integration (billing + attribution); the Privy sign-in = the traveller the visa is filed under, using that user's stored documents. Access tokens are short-lived and refresh automatically.{
"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"]
}
Wallet & billing
Two ways to collect payment from your end-user â you choose per connection with the optional X-Stripe-Key header:
Your own Stripe
Set an X-Stripe-Key header (a restricted Stripe key). The end-user's checkout is created on your Stripe â they pay you. Naytive's fee is drawn from your prepaid wallet and the visa is filed immediately; submit_application returns a paymentUrl for your end-user.
Naytive collects (default)
No X-Stripe-Key â the end-user pays Naytive. submit_application creates a pending_payment; call create_payment_link for a Naytive checkout. No wallet is used.
Top up & rate
Own-Stripe mode debits fee à traveller count per filing. Check balance + rate with get_wallet_balance; add funds with create_wallet_topup_link. Low balance â the application is not created (402).
X-Stripe-Key travels as a request header (not a tool argument, so it never enters the model's context), is used just-in-time to create the checkout on your account, and is never logged or persisted. Use a restricted key. Wallet debits are atomic and idempotent â a retried submit is charged at most once and the balance can never go negative.Tools
Twelve tools, grouped by what they touch. Read tools return catalog, account, or wallet data; write tools mutate the user's vault, applications, or your wallet. Every tool returns a JSON payload as text content. All tools require your X-API-Key; user-scoped tools also need the traveller's sign-in.
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. Naytive's fee (rate à travellers) is debited from your wallet and the application is filed immediately (submitted). If the balance is too low nothing is created â top up and retry. You collect payment from your own end-user separately.
| 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
WriteAlternate flow. Have Naytive collect from the traveller directly (Stripe/Helio) instead of your wallet. Not needed for the wallet model above â a wallet-billed application is already 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. Pass your own app URL (e.g. your success page) — if omitted it defaults to naytive.com. |
Wallet & billing
get_wallet_balance
ReadYour prepaid wallet balance (USD cents), currency, and the per-application wholesale fee. No arguments.
list_wallet_transactions
ReadRecent ledger entries â top-ups, per-application debits, and refunds â each with the running balance.
| Field | Type | Notes | |
|---|---|---|---|
| limit | integer | optional | 1â100, default 20. |
create_wallet_topup_link
WriteA Stripe checkout url to add funds to your wallet. Open it in a browser to pay; the balance updates automatically once the charge settles.
| Field | Type | Notes | |
|---|---|---|---|
| amountUsd | number | required | Amount to add in USD (e.g. 100 = $100). Minimum $1. |
| returnUrl | string | optional | Where to send the browser after payment. Pass your own app URL (e.g. your success page) — if omitted it defaults to naytive.com. |
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.Fund your wallet
Once, up front:
get_wallet_balanceto check funds;create_wallet_topup_linkif you need more. Your end-user pays you separately, through your own Stripe.File
submit_applicationwith the ids, dates, and event. Naytive's fee is debited from your wallet and the application is filed â it returns anapplicationNumberalreadysubmitted.Track
list_applications/check_application_statusâ the application 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 |
|---|---|
| Missing / invalid API key | 401 â set your Naytive X-API-Key header. |
| No / expired user token | 401 + WWW-Authenticate â the client re-runs sign-in. |
| Wallet balance too low on submit | 402 insufficient_wallet_balance with the fee and current balance â no application is created. Top up and retry. |
| 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. |
- Wallet debits are atomic & idempotent. A submitted application is charged once; a retry is never double-charged and the balance can't go negative. Top-ups are credited by the Stripe webhook, not 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.