@transita/mcp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Transita / Okapi Works
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # @transita/mcp-server
2
+
3
+ > Immigration eligibility intelligence for Claude Desktop, Cursor, Cline, Continue, Zed, and any MCP-compatible AI assistant.
4
+
5
+ [Transita](https://transita.app) figures out which country you should move to — and runs the whole relocation. This MCP server exposes the same matcher, country comparisons, visa checklists, and nationality guides that power transita.app, so you can ask any AI assistant "where should I move?" and get a real, sourced answer.
6
+
7
+ The server is a stateless shim. It calls the public Transita JSON API, so visa data updates the moment transita.app redeploys. No data lives in this package.
8
+
9
+ ## Install
10
+
11
+ ### Claude Desktop
12
+
13
+ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
14
+
15
+ ```json
16
+ {
17
+ "mcpServers": {
18
+ "transita": {
19
+ "command": "npx",
20
+ "args": ["-y", "@transita/mcp-server"]
21
+ }
22
+ }
23
+ }
24
+ ```
25
+
26
+ Restart Claude Desktop. The Transita tools should appear in the input box.
27
+
28
+ ### Cursor
29
+
30
+ Add to `~/.cursor/mcp.json`:
31
+
32
+ ```json
33
+ {
34
+ "mcpServers": {
35
+ "transita": {
36
+ "command": "npx",
37
+ "args": ["-y", "@transita/mcp-server"]
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ ### Cline (VS Code extension)
44
+
45
+ Open the Cline MCP settings panel and add:
46
+
47
+ ```json
48
+ {
49
+ "transita": {
50
+ "command": "npx",
51
+ "args": ["-y", "@transita/mcp-server"]
52
+ }
53
+ }
54
+ ```
55
+
56
+ ### Continue, Zed, any other MCP client
57
+
58
+ Use the same pattern — `command: npx`, `args: ["-y", "@transita/mcp-server"]`.
59
+
60
+ ## What it exposes
61
+
62
+ ### Tools (5)
63
+
64
+ | Tool | What it does |
65
+ |---|---|
66
+ | `transita_match_visas` | Score a profile (citizenship, education, work years, income, goal, timeline) against every visa Transita supports. Returns top matches ranked by eligibility. |
67
+ | `transita_visa_details` | Full details for a single visa: eligibility, processing time, fees, validity, PR path, and a free preview of the document checklist. |
68
+ | `transita_compare_visas` | Side-by-side comparison of 2-3 visa pathways with deltas (fastest processing, lowest cost, longest validity, PR path). |
69
+ | `transita_country_overview` | Country-level summary: number of visa pathways, EU membership, score dimensions, drawbacks, top cities with rent ranges, PR/citizenship timelines. |
70
+ | `transita_search_by_nationality` | Curated top destinations and recommended visas for citizens of a given country. Best first stop for "where should I move?" |
71
+
72
+ ### Resources (4 base + dynamic templates)
73
+
74
+ - `transita://visas` — full active visa catalogue
75
+ - `transita://countries` — destination countries + visa counts
76
+ - `transita://nationalities` — index of curated nationality landing pages
77
+ - `transita://nationality/<slug>` — single nationality page (e.g. `transita://nationality/indian`)
78
+
79
+ ### Prompts (3)
80
+
81
+ - `find-my-visa` — guided eligibility quiz then `transita_match_visas`
82
+ - `compare-options` — drives `transita_compare_visas` with a chosen set of ids
83
+ - `where-should-i-move` — open exploration starting from nationality
84
+
85
+ ## Try it
86
+
87
+ Once installed, ask your AI assistant questions like:
88
+
89
+ - *"I'm an Indian software engineer with a master's and 5 years of experience. Where should I move?"*
90
+ - *"Compare the US O-1A, Germany's EU Blue Card, and Portugal's D8 — which is fastest, cheapest, and which has the best path to permanent residency?"*
91
+ - *"What does Portugal's D8 digital nomad visa actually require?"*
92
+ - *"How do destination scores compare for Germany versus the Netherlands?"*
93
+
94
+ The model will pick the right Transita tool, call it, and synthesise the answer. Visa data is verified by the Transita team monthly; every match links back to the official government source.
95
+
96
+ ## Configuration
97
+
98
+ | Env var | Default | Purpose |
99
+ |---|---|---|
100
+ | `TRANSITA_API_URL` | `https://transita.app` | Override the API base URL (useful for local dev against `http://localhost:3000`). |
101
+ | `TRANSITA_MCP_USER_AGENT` | `transita-mcp-server/0.1.0` | Custom UA string for analytics. |
102
+
103
+ ## Free vs paid
104
+
105
+ Everything this MCP server exposes is **free**. The full document checklist + 30-day action plan is part of Transita's $9 paid plan and lives at `https://transita.app/visa/<id>`. The MCP server's tools include a checklist preview (lead time, ancillary cost, first section) and link to the full plan.
106
+
107
+ ## Development
108
+
109
+ ```bash
110
+ git clone https://github.com/snenenenenenene/mova.git
111
+ cd mova/packages/mcp-server
112
+ bun install
113
+ bun run build
114
+
115
+ # Point at a local Transita dev server
116
+ TRANSITA_API_URL=http://localhost:3000 node dist/cli.js
117
+ ```
118
+
119
+ To smoke-test from the command line:
120
+
121
+ ```bash
122
+ {
123
+ echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"0.0"}}}'
124
+ echo '{"jsonrpc":"2.0","method":"notifications/initialized"}'
125
+ echo '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
126
+ } | node dist/cli.js
127
+ ```
128
+
129
+ ## Publishing to npm + the MCP Registry
130
+
131
+ Two-step flow. The MCP Registry stores metadata; npm hosts the artifact.
132
+
133
+ ```bash
134
+ # 1. Publish to npm (must be on a fresh version; bump in package.json first)
135
+ cd packages/mcp-server
136
+ bun run build
137
+ npm publish --access public
138
+
139
+ # 2. Publish metadata to the MCP Registry
140
+ # Install mcp-publisher once: brew install mcp-publisher
141
+ # server.json is committed alongside this README.
142
+ mcp-publisher login github
143
+ mcp-publisher publish
144
+ ```
145
+
146
+ After publishing, the server is listed at <https://registry.modelcontextprotocol.io>
147
+ and discoverable inside MCP-compatible clients.
148
+
149
+ ## License
150
+
151
+ MIT — see LICENSE.
152
+
153
+ ## Links
154
+
155
+ - Website: <https://transita.app>
156
+ - MCP landing page: <https://transita.app/mcp>
157
+ - Source: <https://github.com/snenenenenenene/mova/tree/main/packages/mcp-server>
158
+ - MCP protocol: <https://modelcontextprotocol.io>
package/dist/api.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Thin HTTP client over the public Transita JSON API.
3
+ *
4
+ * The MCP server is a stateless shim — it never owns visa data. It
5
+ * forwards every tool call to transita.app, which means visa updates
6
+ * propagate the moment the website redeploys. The API base URL can
7
+ * be overridden via the TRANSITA_API_URL env var to point at a local
8
+ * dev server during testing.
9
+ */
10
+ export declare const API_BASE_URL: string;
11
+ interface FetchJsonOptions {
12
+ method?: "GET" | "POST";
13
+ body?: unknown;
14
+ signal?: AbortSignal;
15
+ }
16
+ export declare function fetchJson<T>(path: string, opts?: FetchJsonOptions): Promise<T>;
17
+ export {};
package/dist/api.js ADDED
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Thin HTTP client over the public Transita JSON API.
3
+ *
4
+ * The MCP server is a stateless shim — it never owns visa data. It
5
+ * forwards every tool call to transita.app, which means visa updates
6
+ * propagate the moment the website redeploys. The API base URL can
7
+ * be overridden via the TRANSITA_API_URL env var to point at a local
8
+ * dev server during testing.
9
+ */
10
+ export const API_BASE_URL = process.env.TRANSITA_API_URL?.replace(/\/$/, "") ?? "https://transita.app";
11
+ const USER_AGENT = process.env.TRANSITA_MCP_USER_AGENT ?? "transita-mcp-server/0.1.0";
12
+ export async function fetchJson(path, opts = {}) {
13
+ const url = `${API_BASE_URL}${path}`;
14
+ const init = {
15
+ method: opts.method ?? "GET",
16
+ headers: {
17
+ "Accept": "application/json",
18
+ "User-Agent": USER_AGENT,
19
+ "X-Source": "MCP",
20
+ ...(opts.body !== undefined ? { "Content-Type": "application/json" } : {}),
21
+ },
22
+ signal: opts.signal,
23
+ };
24
+ if (opts.body !== undefined) {
25
+ init.body = JSON.stringify(opts.body);
26
+ }
27
+ const res = await fetch(url, init);
28
+ const text = await res.text();
29
+ if (!res.ok) {
30
+ let detail = text;
31
+ try {
32
+ const parsed = JSON.parse(text);
33
+ if (parsed?.error)
34
+ detail = parsed.error;
35
+ }
36
+ catch {
37
+ // leave detail as raw text
38
+ }
39
+ throw new Error(`Transita API ${res.status} ${res.statusText} on ${path}: ${detail}`);
40
+ }
41
+ try {
42
+ return JSON.parse(text);
43
+ }
44
+ catch {
45
+ throw new Error(`Transita API returned non-JSON body on ${path}`);
46
+ }
47
+ }
48
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,CAAC,MAAM,YAAY,GACvB,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,sBAAsB,CAAC;AAE7E,MAAM,UAAU,GACd,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,2BAA2B,CAAC;AAQrE,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,IAAY,EACZ,OAAyB,EAAE;IAE3B,MAAM,GAAG,GAAG,GAAG,YAAY,GAAG,IAAI,EAAE,CAAC;IACrC,MAAM,IAAI,GAAgB;QACxB,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK;QAC5B,OAAO,EAAE;YACP,QAAQ,EAAE,kBAAkB;YAC5B,YAAY,EAAE,UAAU;YACxB,UAAU,EAAE,KAAK;YACjB,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC3E;QACD,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC;IACF,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAE9B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,IAAI,MAAM,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAuB,CAAC;YACtD,IAAI,MAAM,EAAE,KAAK;gBAAE,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,2BAA2B;QAC7B,CAAC;QACD,MAAM,IAAI,KAAK,CACb,gBAAgB,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,OAAO,IAAI,KAAK,MAAM,EAAE,CACrE,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAM,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,0CAA0C,IAAI,EAAE,CAAC,CAAC;IACpE,CAAC;AACH,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Transita MCP server — stdio entry point.
4
+ *
5
+ * Used by Claude Desktop, Cursor, Cline, Continue, Zed, and any
6
+ * MCP-compatible client that spawns servers as subprocesses. Reads
7
+ * MCP messages from stdin and writes responses to stdout — all
8
+ * logging goes to stderr to stay out of the protocol stream.
9
+ */
10
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Transita MCP server — stdio entry point.
4
+ *
5
+ * Used by Claude Desktop, Cursor, Cline, Continue, Zed, and any
6
+ * MCP-compatible client that spawns servers as subprocesses. Reads
7
+ * MCP messages from stdin and writes responses to stdout — all
8
+ * logging goes to stderr to stay out of the protocol stream.
9
+ */
10
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
+ import { createTransitaMcpServer, SERVER_NAME, SERVER_VERSION } from "./server.js";
12
+ import { API_BASE_URL } from "./api.js";
13
+ async function main() {
14
+ const server = createTransitaMcpServer();
15
+ const transport = new StdioServerTransport();
16
+ await server.connect(transport);
17
+ process.stderr.write(`[${SERVER_NAME}@${SERVER_VERSION}] connected over stdio (api=${API_BASE_URL})\n`);
18
+ }
19
+ main().catch((err) => {
20
+ process.stderr.write(`[transita-mcp] fatal: ${err.message}\n`);
21
+ process.exit(1);
22
+ });
23
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;GAOG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,uBAAuB,EAAE,CAAC;IACzC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,IAAI,WAAW,IAAI,cAAc,+BAA+B,YAAY,KAAK,CAClF,CAAC;AACJ,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAA0B,GAAa,CAAC,OAAO,IAAI,CAAC,CAAC;IAC1E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerPrompts(server: McpServer): void;
@@ -0,0 +1,73 @@
1
+ import { z } from "zod/v3";
2
+ export function registerPrompts(server) {
3
+ server.registerPrompt("find-my-visa", {
4
+ title: "Find my visa",
5
+ description: "Walk a user through Transita's eligibility quiz, then call transita_match_visas with their answers.",
6
+ argsSchema: {
7
+ citizenship: z
8
+ .string()
9
+ .optional()
10
+ .describe("Pre-fill the user's nationality if known."),
11
+ },
12
+ }, ({ citizenship }) => ({
13
+ messages: [
14
+ {
15
+ role: "user",
16
+ content: {
17
+ type: "text",
18
+ text: `I want to find a visa pathway using Transita. ` +
19
+ (citizenship
20
+ ? `My nationality is ${citizenship}. `
21
+ : `Please ask me for my nationality first. `) +
22
+ `After collecting my nationality, ask me — one at a time — for my education level, total years of work experience, primary work field, annual income (USD), relocation goal, target countries (or leave open), and timeline. Then call the transita_match_visas tool with my answers and summarise the top 3 matches with concrete next steps.`,
23
+ },
24
+ },
25
+ ],
26
+ }));
27
+ server.registerPrompt("compare-options", {
28
+ title: "Compare visa options",
29
+ description: "Compare two or three Transita visa ids side-by-side with deltas and recommendations.",
30
+ argsSchema: {
31
+ visa_ids: z
32
+ .string()
33
+ .describe("Comma-separated Transita visa ids (e.g. 'us-o1a,de-blue-card,pt-d7')."),
34
+ },
35
+ }, ({ visa_ids }) => ({
36
+ messages: [
37
+ {
38
+ role: "user",
39
+ content: {
40
+ type: "text",
41
+ text: `Compare these visas using transita_compare_visas: ${visa_ids}. ` +
42
+ `After receiving the data, summarise which option fits which kind of applicant best, ` +
43
+ `and call transita_visa_details on each id to surface the document-checklist preview if useful.`,
44
+ },
45
+ },
46
+ ],
47
+ }));
48
+ server.registerPrompt("where-should-i-move", {
49
+ title: "Where should I move?",
50
+ description: "Open-ended exploration. Surfaces nationality-specific top destinations, then drills into matches.",
51
+ argsSchema: {
52
+ nationality: z.string().describe("User's nationality, e.g. 'Indian'."),
53
+ profession: z
54
+ .string()
55
+ .optional()
56
+ .describe("Optional — narrows the recommended visas."),
57
+ },
58
+ }, ({ nationality, profession }) => ({
59
+ messages: [
60
+ {
61
+ role: "user",
62
+ content: {
63
+ type: "text",
64
+ text: `I'm a ${profession ? `${profession} ` : ""}${nationality} citizen looking to move abroad. ` +
65
+ `First, call transita_search_by_nationality with my nationality to surface the curated top destinations. ` +
66
+ `Then, ask me a few quick clarifying questions (income range, timeline, family or solo) and call transita_match_visas with my answers. ` +
67
+ `End with a concrete recommendation: which 2-3 pathways are worth investigating and what to do this week.`,
68
+ },
69
+ },
70
+ ],
71
+ }));
72
+ }
73
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/prompts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAG3B,MAAM,UAAU,eAAe,CAAC,MAAiB;IAC/C,MAAM,CAAC,cAAc,CACnB,cAAc,EACd;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,qGAAqG;QACvG,UAAU,EAAE;YACV,WAAW,EAAE,CAAC;iBACX,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,2CAA2C,CAAC;SACzD;KACF,EACD,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;QACpB,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE;oBACP,IAAI,EAAE,MAAM;oBACZ,IAAI,EACF,gDAAgD;wBAChD,CAAC,WAAW;4BACV,CAAC,CAAC,qBAAqB,WAAW,IAAI;4BACtC,CAAC,CAAC,0CAA0C,CAAC;wBAC/C,+UAA+U;iBAClV;aACF;SACF;KACF,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,cAAc,CACnB,iBAAiB,EACjB;QACE,KAAK,EAAE,sBAAsB;QAC7B,WAAW,EACT,sFAAsF;QACxF,UAAU,EAAE;YACV,QAAQ,EAAE,CAAC;iBACR,MAAM,EAAE;iBACR,QAAQ,CACP,uEAAuE,CACxE;SACJ;KACF,EACD,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;QACjB,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE;oBACP,IAAI,EAAE,MAAM;oBACZ,IAAI,EACF,qDAAqD,QAAQ,IAAI;wBACjE,sFAAsF;wBACtF,gGAAgG;iBACnG;aACF;SACF;KACF,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,cAAc,CACnB,qBAAqB,EACrB;QACE,KAAK,EAAE,sBAAsB;QAC7B,WAAW,EACT,mGAAmG;QACrG,UAAU,EAAE;YACV,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,oCAAoC,CAAC;YACtE,UAAU,EAAE,CAAC;iBACV,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,2CAA2C,CAAC;SACzD;KACF,EACD,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC;QAChC,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE;oBACP,IAAI,EAAE,MAAM;oBACZ,IAAI,EACF,SAAS,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,WAAW,mCAAmC;wBAC5F,0GAA0G;wBAC1G,wIAAwI;wBACxI,0GAA0G;iBAC7G;aACF;SACF;KACF,CAAC,CACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { type McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerResources(server: McpServer): void;
@@ -0,0 +1,82 @@
1
+ import { ResourceTemplate, } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { fetchJson } from "../api.js";
3
+ export function registerResources(server) {
4
+ server.registerResource("transita-visas", "transita://visas", {
5
+ title: "Transita visa catalogue",
6
+ description: "Full active list of every visa pathway Transita scores. Refreshed monthly.",
7
+ mimeType: "application/json",
8
+ }, async (uri) => {
9
+ const data = await fetchJson("/api/visas");
10
+ return {
11
+ contents: [
12
+ {
13
+ uri: uri.href,
14
+ mimeType: "application/json",
15
+ text: JSON.stringify(data, null, 2),
16
+ },
17
+ ],
18
+ };
19
+ });
20
+ server.registerResource("transita-countries", "transita://countries", {
21
+ title: "Transita destination countries",
22
+ description: "Active destination countries plus per-country visa counts.",
23
+ mimeType: "application/json",
24
+ }, async (uri) => {
25
+ const data = await fetchJson("/api/countries");
26
+ return {
27
+ contents: [
28
+ {
29
+ uri: uri.href,
30
+ mimeType: "application/json",
31
+ text: JSON.stringify(data, null, 2),
32
+ },
33
+ ],
34
+ };
35
+ });
36
+ server.registerResource("transita-nationalities", "transita://nationalities", {
37
+ title: "Transita nationality guides",
38
+ description: "Index of nationality-specific landing pages with curated top-destination guidance.",
39
+ mimeType: "application/json",
40
+ }, async (uri) => {
41
+ const data = await fetchJson("/api/nationalities");
42
+ return {
43
+ contents: [
44
+ {
45
+ uri: uri.href,
46
+ mimeType: "application/json",
47
+ text: JSON.stringify(data, null, 2),
48
+ },
49
+ ],
50
+ };
51
+ });
52
+ server.registerResource("transita-nationality", new ResourceTemplate("transita://nationality/{slug}", {
53
+ list: async () => {
54
+ const data = await fetchJson("/api/nationalities");
55
+ return {
56
+ resources: data.nationalities.map((n) => ({
57
+ uri: `transita://nationality/${n.slug}`,
58
+ name: `${n.flag} ${n.nationality}`,
59
+ description: `Top destinations and recommended visas for ${n.nationality} citizens.`,
60
+ mimeType: "application/json",
61
+ })),
62
+ };
63
+ },
64
+ }), {
65
+ title: "Transita nationality guide",
66
+ description: "Single nationality landing-page payload — top destinations, recommended visas, key stats, FAQ.",
67
+ mimeType: "application/json",
68
+ }, async (uri, { slug }) => {
69
+ const slugStr = Array.isArray(slug) ? slug[0] : String(slug);
70
+ const data = await fetchJson(`/api/nationality/${encodeURIComponent(slugStr)}`);
71
+ return {
72
+ contents: [
73
+ {
74
+ uri: uri.href,
75
+ mimeType: "application/json",
76
+ text: JSON.stringify(data, null, 2),
77
+ },
78
+ ],
79
+ };
80
+ });
81
+ }
82
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resources/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Transita MCP server factory.
3
+ *
4
+ * Builds an McpServer with all Transita tools, resources, and prompts
5
+ * wired to the public Transita JSON API. The same factory is used by
6
+ * both the stdio CLI entry point (cli.ts) and the hosted SSE endpoint
7
+ * exposed at transita.app/api/mcp/sse.
8
+ */
9
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
+ export declare const SERVER_NAME = "transita";
11
+ export declare const SERVER_VERSION = "0.1.0";
12
+ export declare function createTransitaMcpServer(): McpServer;
package/dist/server.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Transita MCP server factory.
3
+ *
4
+ * Builds an McpServer with all Transita tools, resources, and prompts
5
+ * wired to the public Transita JSON API. The same factory is used by
6
+ * both the stdio CLI entry point (cli.ts) and the hosted SSE endpoint
7
+ * exposed at transita.app/api/mcp/sse.
8
+ */
9
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
+ import { registerMatchVisas } from "./tools/match-visas.js";
11
+ import { registerVisaDetails } from "./tools/visa-details.js";
12
+ import { registerCompareVisas } from "./tools/compare-visas.js";
13
+ import { registerCountryOverview } from "./tools/country-overview.js";
14
+ import { registerSearchByNationality } from "./tools/search-by-nationality.js";
15
+ import { registerResources } from "./resources/index.js";
16
+ import { registerPrompts } from "./prompts/index.js";
17
+ export const SERVER_NAME = "transita";
18
+ export const SERVER_VERSION = "0.1.0";
19
+ export function createTransitaMcpServer() {
20
+ const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION }, {
21
+ capabilities: { tools: {}, resources: {}, prompts: {}, logging: {} },
22
+ instructions: "Transita scores immigration eligibility across 8 countries and 45+ visa pathways. Start by calling transita_match_visas with the user's profile, or transita_search_by_nationality if they only know their citizenship. Use transita_visa_details and transita_compare_visas to drill in. The full document checklist + 30-day plan is paid ($9) and lives at https://transita.app/visa/<id>.",
23
+ });
24
+ registerMatchVisas(server);
25
+ registerVisaDetails(server);
26
+ registerCompareVisas(server);
27
+ registerCountryOverview(server);
28
+ registerSearchByNationality(server);
29
+ registerResources(server);
30
+ registerPrompts(server);
31
+ return server;
32
+ }
33
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CAAC;AACtC,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC;AAEtC,MAAM,UAAU,uBAAuB;IACrC,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,EAAE,EAC9C;QACE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QACpE,YAAY,EACV,+XAA+X;KAClY,CACF,CAAC;IAEF,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAChC,2BAA2B,CAAC,MAAM,CAAC,CAAC;IACpC,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,eAAe,CAAC,MAAM,CAAC,CAAC;IAExB,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerCompareVisas(server: McpServer): void;
@@ -0,0 +1,44 @@
1
+ import { z } from "zod/v3";
2
+ import { fetchJson } from "../api.js";
3
+ export function registerCompareVisas(server) {
4
+ server.registerTool("transita_compare_visas", {
5
+ title: "Compare visas",
6
+ description: "Compare 2-3 visa pathways side-by-side. Returns processing time, cost, validity, PR-pathway, and a delta summary highlighting the fastest, cheapest, and most permanent option.",
7
+ inputSchema: {
8
+ visa_ids: z
9
+ .array(z.string())
10
+ .min(2)
11
+ .max(3)
12
+ .describe("2-3 Transita visa ids. Discover ids via transita_match_visas or the transita://visas resource."),
13
+ },
14
+ }, async ({ visa_ids }) => {
15
+ const ids = visa_ids.map((s) => s.trim()).filter(Boolean);
16
+ const data = await fetchJson(`/api/compare?ids=${ids.map(encodeURIComponent).join(",")}`);
17
+ const idToVisa = new Map(data.visas.map((v) => [v.id, v]));
18
+ const named = (id) => {
19
+ const v = idToVisa.get(id);
20
+ return v ? `${v.countryFlag} ${v.countryName} ${v.code}` : id;
21
+ };
22
+ const lines = [
23
+ `Comparing ${data.visas.length} visa pathways:`,
24
+ ...data.visas.map((v) => `\n${v.countryFlag} ${v.countryName} — ${v.name} (${v.code})\n` +
25
+ ` Processing: ${v.processingTimeMonths.min}-${v.processingTimeMonths.max} months\n` +
26
+ ` Cost: $${v.costUSD} USD\n` +
27
+ ` Validity: ${v.validityYears} year(s)\n` +
28
+ ` Path to PR: ${v.pathToPR ? `yes${v.prTimelineYears != null ? ` (~${v.prTimelineYears}y)` : ""}` : "no"}`),
29
+ "",
30
+ "Deltas:",
31
+ ` Fastest processing: ${named(data.deltas.fastestProcessing)}`,
32
+ ` Lowest cost: ${named(data.deltas.lowestCost)}`,
33
+ ` Longest validity: ${named(data.deltas.longestValidity)}`,
34
+ data.deltas.pathToPR.length
35
+ ? ` Path to PR: ${data.deltas.pathToPR.map(named).join(", ")}`
36
+ : ` Path to PR: none of the compared options`,
37
+ ];
38
+ return {
39
+ content: [{ type: "text", text: lines.join("\n") }],
40
+ structuredContent: data,
41
+ };
42
+ });
43
+ }
44
+ //# sourceMappingURL=compare-visas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare-visas.js","sourceRoot":"","sources":["../../src/tools/compare-visas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAE3B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAatC,MAAM,UAAU,oBAAoB,CAAC,MAAiB;IACpD,MAAM,CAAC,YAAY,CACjB,wBAAwB,EACxB;QACE,KAAK,EAAE,eAAe;QACtB,WAAW,EACT,iLAAiL;QACnL,WAAW,EAAE;YACX,QAAQ,EAAE,CAAC;iBACR,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;iBACjB,GAAG,CAAC,CAAC,CAAC;iBACN,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,CACP,gGAAgG,CACjG;SACJ;KACF,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;QACrB,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,MAAM,SAAS,CAC1B,oBAAoB,GAAG,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAC5D,CAAC;QAEF,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE;YAC3B,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC3B,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,CAAC,CAAC;QAEF,MAAM,KAAK,GAAG;YACZ,aAAa,IAAI,CAAC,KAAK,CAAC,MAAM,iBAAiB;YAC/C,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CACf,CAAC,CAAC,EAAE,EAAE,CACJ,KAAK,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,WAAW,MAAM,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,KAAK;gBAC/D,iBAAiB,CAAC,CAAC,oBAAoB,CAAC,GAAG,IAAI,CAAC,CAAC,oBAAoB,CAAC,GAAG,WAAW;gBACpF,YAAY,CAAC,CAAC,OAAO,QAAQ;gBAC7B,eAAe,CAAC,CAAC,aAAa,YAAY;gBAC1C,iBAAiB,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAC9G;YACD,EAAE;YACF,SAAS;YACT,yBAAyB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,EAAE;YAC/D,kBAAkB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE;YACjD,uBAAuB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,EAAE;YAC3D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM;gBACzB,CAAC,CAAC,iBAAiB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBAC/D,CAAC,CAAC,4CAA4C;SACjD,CAAC;QAEF,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,iBAAiB,EAAE,IAA0C;SAC9D,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerCountryOverview(server: McpServer): void;
@@ -0,0 +1,40 @@
1
+ import { z } from "zod/v3";
2
+ import { fetchJson } from "../api.js";
3
+ export function registerCountryOverview(server) {
4
+ server.registerTool("transita_country_overview", {
5
+ title: "Country overview",
6
+ description: "Get summary data for a destination country: number of visa pathways, EU member status, score dimensions, drawbacks, top cities with rent ranges, and PR/citizenship timelines.",
7
+ inputSchema: {
8
+ country_code: z
9
+ .string()
10
+ .describe("Lowercase country code, e.g. 'us', 'de', 'pt'. List available codes via the transita://countries resource."),
11
+ },
12
+ }, async ({ country_code }) => {
13
+ const data = await fetchJson(`/api/country/${encodeURIComponent(country_code.toLowerCase())}`);
14
+ const c = data.country;
15
+ const lines = [
16
+ `${c.flag} ${c.name} (${c.code})${c.isEUMember ? " — EU member" : ""}`,
17
+ c.guideIntro,
18
+ "",
19
+ `Visa pathways supported: ${data.visaCount}`,
20
+ `PR after: ~${c.prTimelineYears} years · Citizenship after: ~${c.citizenshipTimelineYears} years`,
21
+ "",
22
+ "Score dimensions:",
23
+ ...c.scoreDimensions.map((s) => ` ${s.label}: ${s.score}/100`),
24
+ "",
25
+ "Cost of living (sample cities):",
26
+ ...c.cities.map((city) => ` ${city.city}: ${city.monthlyUSD}${city.note ? ` (${city.note})` : ""}`),
27
+ "",
28
+ "Drawbacks:",
29
+ ...c.drawbacks.map((d) => ` • ${d}`),
30
+ "",
31
+ "Visas available:",
32
+ ...data.visas.map((v) => ` - ${v.code}: ${v.name} (id: ${v.id})`),
33
+ ];
34
+ return {
35
+ content: [{ type: "text", text: lines.join("\n") }],
36
+ structuredContent: data,
37
+ };
38
+ });
39
+ }
40
+ //# sourceMappingURL=country-overview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"country-overview.js","sourceRoot":"","sources":["../../src/tools/country-overview.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAE3B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAStC,MAAM,UAAU,uBAAuB,CAAC,MAAiB;IACvD,MAAM,CAAC,YAAY,CACjB,2BAA2B,EAC3B;QACE,KAAK,EAAE,kBAAkB;QACzB,WAAW,EACT,gLAAgL;QAClL,WAAW,EAAE;YACX,YAAY,EAAE,CAAC;iBACZ,MAAM,EAAE;iBACR,QAAQ,CACP,4GAA4G,CAC7G;SACJ;KACF,EACD,KAAK,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE;QACzB,MAAM,IAAI,GAAG,MAAM,SAAS,CAC1B,gBAAgB,kBAAkB,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC,EAAE,CACjE,CAAC;QAEF,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC;QACvB,MAAM,KAAK,GAAG;YACZ,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE;YACtE,CAAC,CAAC,UAAU;YACZ,EAAE;YACF,4BAA4B,IAAI,CAAC,SAAS,EAAE;YAC5C,cAAc,CAAC,CAAC,eAAe,gCAAgC,CAAC,CAAC,wBAAwB,QAAQ;YACjG,EAAE;YACF,mBAAmB;YACnB,GAAG,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,MAAM,CAAC;YAC/D,EAAE;YACF,iCAAiC;YACjC,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;YACpG,EAAE;YACF,YAAY;YACZ,GAAG,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;YACrC,EAAE;YACF,kBAAkB;YAClB,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC;SACnE,CAAC;QAEF,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,iBAAiB,EAAE,IAA0C;SAC9D,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerMatchVisas(server: McpServer): void;
@@ -0,0 +1,83 @@
1
+ import { z } from "zod/v3";
2
+ import { fetchJson } from "../api.js";
3
+ const inputSchema = {
4
+ citizenship: z.string(),
5
+ current_country: z.string().optional(),
6
+ education: z
7
+ .enum(["high_school", "associate", "bachelor", "master", "phd", "professional"])
8
+ .optional(),
9
+ work_years: z.number().int().min(0).max(60).optional(),
10
+ work_field: z.string().optional(),
11
+ income_usd: z.number().int().min(0).optional(),
12
+ goal: z
13
+ .enum([
14
+ "work_tech",
15
+ "start_company",
16
+ "study_work",
17
+ "relocate_family",
18
+ "digital_nomad",
19
+ ])
20
+ .optional(),
21
+ target_countries: z.array(z.string()).optional(),
22
+ timeline: z.enum(["asap", "6months", "1year", "2plus"]).optional(),
23
+ age: z.number().int().min(15).max(80).optional(),
24
+ };
25
+ const incomeRange = (income) => {
26
+ if (income == null)
27
+ return "";
28
+ if (income < 30_000)
29
+ return "under_30k";
30
+ if (income < 60_000)
31
+ return "30k_60k";
32
+ if (income < 100_000)
33
+ return "60k_100k";
34
+ if (income < 150_000)
35
+ return "100k_150k";
36
+ return "over_150k";
37
+ };
38
+ export function registerMatchVisas(server) {
39
+ server.registerTool("transita_match_visas", {
40
+ title: "Match visas",
41
+ description: "Score a user's profile against every visa pathway Transita supports. Returns the top matches ranked by eligibility, with timeline, cost, and links to official sources. EU citizens automatically surface freedom-of-movement options first.",
42
+ inputSchema,
43
+ }, async (args) => {
44
+ const quizAnswers = {
45
+ citizenship: args.citizenship,
46
+ currentCountry: args.current_country ?? "",
47
+ education: args.education ?? "",
48
+ workYears: args.work_years != null ? String(args.work_years) : "",
49
+ workField: args.work_field ?? "",
50
+ income: incomeRange(args.income_usd),
51
+ goal: args.goal ?? "",
52
+ targetCountries: args.target_countries ?? [],
53
+ timeline: args.timeline ?? "",
54
+ };
55
+ const data = await fetchJson("/api/match/fast", {
56
+ method: "POST",
57
+ body: quizAnswers,
58
+ });
59
+ const slim = data.results.slice(0, 6).map((r) => ({
60
+ id: r.id,
61
+ visa: `${r.countryFlag} ${r.countryName} — ${r.visaName} (${r.visaCode})`,
62
+ score: r.matchScore,
63
+ timeline: r.timelineMonths,
64
+ cost: r.costRange,
65
+ pathToPR: r.pathToPR ?? false,
66
+ officialUrl: r.officialUrl,
67
+ officialSource: r.officialSource,
68
+ description: r.description,
69
+ details_url: `https://transita.app/visa/${r.id}`,
70
+ }));
71
+ const summary = slim.length
72
+ ? `Top ${slim.length} visa matches for a ${args.citizenship} citizen:\n` +
73
+ slim
74
+ .map((m, i) => `${i + 1}. ${m.visa} — score ${m.score}/100, ~${m.timeline}, ${m.cost}${m.pathToPR ? " · path to PR" : ""}\n ${m.description}\n Official: ${m.officialUrl ?? "n/a"}\n Full details: ${m.details_url}`)
75
+ .join("\n\n")
76
+ : "No visa matches above the minimum score threshold for this profile.";
77
+ return {
78
+ content: [{ type: "text", text: summary }],
79
+ structuredContent: { matches: slim, count: slim.length },
80
+ };
81
+ });
82
+ }
83
+ //# sourceMappingURL=match-visas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match-visas.js","sourceRoot":"","sources":["../../src/tools/match-visas.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerSearchByNationality(server: McpServer): void;
@@ -0,0 +1,78 @@
1
+ import { z } from "zod/v3";
2
+ import { fetchJson } from "../api.js";
3
+ const SLUG_OVERRIDES = {
4
+ america: "american",
5
+ americans: "american",
6
+ india: "indian",
7
+ indians: "indian",
8
+ brazil: "brazilian",
9
+ brazilians: "brazilian",
10
+ uk: "british",
11
+ britain: "british",
12
+ britons: "british",
13
+ germany: "german",
14
+ germans: "german",
15
+ france: "french",
16
+ spain: "spanish",
17
+ };
18
+ function nationalityToSlug(input) {
19
+ const normalized = input.trim().toLowerCase();
20
+ if (SLUG_OVERRIDES[normalized])
21
+ return SLUG_OVERRIDES[normalized];
22
+ return normalized;
23
+ }
24
+ export function registerSearchByNationality(server) {
25
+ server.registerTool("transita_search_by_nationality", {
26
+ title: "Search visas by nationality",
27
+ description: "Show top destinations and recommended visa pathways for citizens of a given country. Ideal first stop for the question 'Where should I move?'. Returns 4-6 destinations with the best-fit visa for each, key stats, and FAQ. Falls back gracefully if a slug isn't yet curated.",
28
+ inputSchema: {
29
+ nationality: z
30
+ .string()
31
+ .describe("Nationality adjective (e.g. 'Indian') or country (e.g. 'India'). Case-insensitive."),
32
+ },
33
+ }, async ({ nationality }) => {
34
+ const slug = nationalityToSlug(nationality);
35
+ try {
36
+ const data = await fetchJson(`/api/nationality/${encodeURIComponent(slug)}`);
37
+ const n = data.nationality;
38
+ const lines = [
39
+ `${n.flag} ${n.headline}`,
40
+ "",
41
+ n.intro,
42
+ "",
43
+ "Top destinations:",
44
+ ...n.topDestinations.map((d, i) => ` ${i + 1}. ${d.flag} ${d.country} — ${d.visaName}\n ${d.why}`),
45
+ "",
46
+ ...(n.keyStats.length
47
+ ? ["Key stats:", ...n.keyStats.map((s) => ` ${s.value} — ${s.label}`), ""]
48
+ : []),
49
+ ...(n.faq.length
50
+ ? ["FAQ:", ...n.faq.slice(0, 3).map((f) => ` Q: ${f.q}\n A: ${f.a}`)]
51
+ : []),
52
+ "",
53
+ `More: https://transita.app/from/${n.slug}`,
54
+ ];
55
+ return {
56
+ content: [{ type: "text", text: lines.join("\n") }],
57
+ structuredContent: data,
58
+ };
59
+ }
60
+ catch (err) {
61
+ const index = await fetchJson(`/api/nationalities`);
62
+ const available = index.nationalities
63
+ .map((n) => `${n.flag} ${n.nationality} (${n.slug})`)
64
+ .join(", ");
65
+ const message = `No curated guidance for "${nationality}" yet. ` +
66
+ `Try transita_match_visas with citizenship="${nationality}" instead, or pick a curated nationality:\n\n${available}`;
67
+ return {
68
+ content: [{ type: "text", text: message }],
69
+ isError: false,
70
+ structuredContent: {
71
+ error: err.message,
72
+ availableSlugs: index.nationalities.map((n) => n.slug),
73
+ },
74
+ };
75
+ }
76
+ });
77
+ }
78
+ //# sourceMappingURL=search-by-nationality.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-by-nationality.js","sourceRoot":"","sources":["../../src/tools/search-by-nationality.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerVisaDetails(server: McpServer): void;
@@ -0,0 +1,47 @@
1
+ import { z } from "zod/v3";
2
+ import { fetchJson } from "../api.js";
3
+ export function registerVisaDetails(server) {
4
+ server.registerTool("transita_visa_details", {
5
+ title: "Visa details",
6
+ description: "Fetch full details for a single visa pathway: eligibility category, processing time, fees, validity, PR timeline, and a free preview of the document checklist (lead time, ancillary cost, first section). The full multi-section checklist is part of Transita's $9 paid action plan.",
7
+ inputSchema: {
8
+ visa_id: z
9
+ .string()
10
+ .describe("Transita visa id, e.g. 'us-o1a', 'es-digital-nomad'. Use transita_match_visas or the transita://visas resource to discover ids."),
11
+ },
12
+ }, async ({ visa_id }) => {
13
+ const data = await fetchJson(`/api/visa/${encodeURIComponent(visa_id)}`);
14
+ const v = data.visa;
15
+ const c = data.checklistPreview;
16
+ const lines = [
17
+ `${v.countryFlag} ${v.countryName} — ${v.name} (${v.code})`,
18
+ v.description,
19
+ "",
20
+ `Category: ${v.category}`,
21
+ `Processing time: ${v.processingTimeMonths.min}-${v.processingTimeMonths.max} months`,
22
+ `Government fee: $${v.costUSD} USD`,
23
+ `Validity: ${v.validityYears} year(s)`,
24
+ `Path to PR: ${v.pathToPR ? "yes" : "no"}${v.prTimelineYears != null ? ` (~${v.prTimelineYears} years)` : ""}`,
25
+ v.requiresEmployerSponsor ? `Requires employer sponsor: yes` : null,
26
+ `Official source: ${v.officialUrl}`,
27
+ "",
28
+ "Document checklist preview (free):",
29
+ ` Start preparing: ${c.leadTime}`,
30
+ ` Ancillary cost: ${c.ancillaryCost}`,
31
+ c.firstSection
32
+ ? ` First section — ${c.firstSection.label}:\n` +
33
+ c.firstSection.docs
34
+ .map((d) => ` • ${d.name} — ${d.where}${d.time ? ` (${d.time})` : ""}${d.cost ? `, ${d.cost}` : ""}`)
35
+ .join("\n")
36
+ : null,
37
+ c.totalSections > 1
38
+ ? ` ${c.totalSections - 1} more section(s) available in the full plan: ${c.paidPlanUrl}`
39
+ : null,
40
+ ].filter(Boolean);
41
+ return {
42
+ content: [{ type: "text", text: lines.join("\n") }],
43
+ structuredContent: data,
44
+ };
45
+ });
46
+ }
47
+ //# sourceMappingURL=visa-details.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visa-details.js","sourceRoot":"","sources":["../../src/tools/visa-details.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAE3B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAQtC,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,MAAM,CAAC,YAAY,CACjB,uBAAuB,EACvB;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,wRAAwR;QAC1R,WAAW,EAAE;YACX,OAAO,EAAE,CAAC;iBACP,MAAM,EAAE;iBACR,QAAQ,CACP,iIAAiI,CAClI;SACJ;KACF,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QACpB,MAAM,IAAI,GAAG,MAAM,SAAS,CAC1B,aAAa,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAC3C,CAAC;QAEF,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;QACpB,MAAM,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC;QAChC,MAAM,KAAK,GAAG;YACZ,GAAG,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,WAAW,MAAM,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,GAAG;YAC3D,CAAC,CAAC,WAAW;YACb,EAAE;YACF,aAAa,CAAC,CAAC,QAAQ,EAAE;YACzB,oBAAoB,CAAC,CAAC,oBAAoB,CAAC,GAAG,IAAI,CAAC,CAAC,oBAAoB,CAAC,GAAG,SAAS;YACrF,oBAAoB,CAAC,CAAC,OAAO,MAAM;YACnC,aAAa,CAAC,CAAC,aAAa,UAAU;YACtC,eAAe,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,eAAe,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE;YAC9G,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,IAAI;YACnE,oBAAoB,CAAC,CAAC,WAAW,EAAE;YACnC,EAAE;YACF,oCAAoC;YACpC,sBAAsB,CAAC,CAAC,QAAQ,EAAE;YAClC,qBAAqB,CAAC,CAAC,aAAa,EAAE;YACtC,CAAC,CAAC,YAAY;gBACZ,CAAC,CAAC,qBAAqB,CAAC,CAAC,YAAY,CAAC,KAAK,KAAK;oBAC9C,CAAC,CAAC,YAAY,CAAC,IAAI;yBAChB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;yBACvG,IAAI,CAAC,IAAI,CAAC;gBACf,CAAC,CAAC,IAAI;YACR,CAAC,CAAC,aAAa,GAAG,CAAC;gBACjB,CAAC,CAAC,KAAK,CAAC,CAAC,aAAa,GAAG,CAAC,gDAAgD,CAAC,CAAC,WAAW,EAAE;gBACzF,CAAC,CAAC,IAAI;SACT,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAElB,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,iBAAiB,EAAE,IAA0C;SAC9D,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Mirror of the Transita web app types we consume from the public
3
+ * JSON API. Kept narrow on purpose — only the fields the MCP server
4
+ * actually surfaces. Source of truth lives in apps/web/src/lib.
5
+ */
6
+ export interface VisaEntry {
7
+ id: string;
8
+ country: string;
9
+ countryName: string;
10
+ countryFlag: string;
11
+ name: string;
12
+ code: string;
13
+ category: string;
14
+ description: string;
15
+ processingTimeMonths: {
16
+ min: number;
17
+ max: number;
18
+ };
19
+ costUSD: number;
20
+ validityYears: number;
21
+ pathToPR: boolean;
22
+ prTimelineYears?: number;
23
+ pointsBased: boolean;
24
+ officialUrl: string;
25
+ targetGoals: string[];
26
+ requiresEmployerSponsor?: boolean;
27
+ }
28
+ export interface ChecklistDoc {
29
+ name: string;
30
+ where: string;
31
+ url?: string;
32
+ time?: string;
33
+ cost?: string;
34
+ selfHeld?: boolean;
35
+ }
36
+ export interface ChecklistSection {
37
+ label: string;
38
+ docs: ChecklistDoc[];
39
+ }
40
+ export interface ChecklistPreview {
41
+ leadTime: string;
42
+ ancillaryCost: string;
43
+ totalSections: number;
44
+ firstSection: ChecklistSection | null;
45
+ paidPlanUrl: string;
46
+ }
47
+ export interface VisaPath {
48
+ id: string;
49
+ country: string;
50
+ countryName: string;
51
+ countryFlag: string;
52
+ visaName: string;
53
+ visaCode: string;
54
+ matchScore: number;
55
+ timelineMonths: string;
56
+ costRange: string;
57
+ description: string;
58
+ highlights?: string[];
59
+ blockers?: string[];
60
+ pathToPR?: boolean;
61
+ prTimelineYears?: number;
62
+ officialUrl?: string;
63
+ officialSource?: string;
64
+ dataUpdatedAt?: string;
65
+ }
66
+ export interface CityCostOfLiving {
67
+ city: string;
68
+ monthlyUSD: string;
69
+ note?: string;
70
+ }
71
+ export interface ScoreDimension {
72
+ label: string;
73
+ score: number;
74
+ }
75
+ export interface CountryConfig {
76
+ code: string;
77
+ iso2: string;
78
+ name: string;
79
+ flag: string;
80
+ slug: string;
81
+ isEUMember: boolean;
82
+ isActive: boolean;
83
+ cardDescription: string;
84
+ guideIntro: string;
85
+ guideHighlights: string[];
86
+ scoreDimensions: ScoreDimension[];
87
+ cities: CityCostOfLiving[];
88
+ drawbacks: string[];
89
+ prTimelineYears: number;
90
+ citizenshipTimelineYears: number;
91
+ visaCount?: number;
92
+ }
93
+ export interface NationalityPageData {
94
+ slug: string;
95
+ nationality: string;
96
+ country: string;
97
+ flag: string;
98
+ headline: string;
99
+ intro: string;
100
+ topDestinations: {
101
+ country: string;
102
+ flag: string;
103
+ visaName: string;
104
+ why: string;
105
+ }[];
106
+ keyStats: {
107
+ value: string;
108
+ label: string;
109
+ }[];
110
+ faq: {
111
+ q: string;
112
+ a: string;
113
+ }[];
114
+ metaDescription: string;
115
+ }
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Mirror of the Transita web app types we consume from the public
3
+ * JSON API. Kept narrow on purpose — only the fields the MCP server
4
+ * actually surfaces. Source of truth lives in apps/web/src/lib.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@transita/mcp-server",
3
+ "version": "0.1.0",
4
+ "mcpName": "io.github.snenenenenenene/transita-mcp-server",
5
+ "description": "Model Context Protocol server for Transita — visa eligibility matcher, country comparisons, and immigration intelligence inside Claude Desktop, Cursor, Cline, and any MCP-compatible AI assistant.",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "bin": {
9
+ "transita-mcp": "./dist/cli.js"
10
+ },
11
+ "main": "./dist/server.js",
12
+ "types": "./dist/server.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "import": "./dist/server.js",
16
+ "types": "./dist/server.d.ts"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.json",
25
+ "dev": "tsc -p tsconfig.json --watch",
26
+ "prepublishOnly": "bun run build",
27
+ "lint": "echo 'no-op'",
28
+ "type-check": "tsc -p tsconfig.json --noEmit"
29
+ },
30
+ "keywords": [
31
+ "mcp",
32
+ "model-context-protocol",
33
+ "transita",
34
+ "immigration",
35
+ "visa",
36
+ "claude",
37
+ "ai-tools"
38
+ ],
39
+ "homepage": "https://transita.app/mcp",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/snenenenenenene/mova.git",
43
+ "directory": "packages/mcp-server"
44
+ },
45
+ "engines": {
46
+ "node": ">=20"
47
+ },
48
+ "dependencies": {
49
+ "@modelcontextprotocol/sdk": "^1.29.0",
50
+ "zod": "^4.3.0"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^20",
54
+ "typescript": "^5"
55
+ }
56
+ }