@easyonward/mcp-server
Advanced tools
| import { type Server } from "node:http"; | ||
| /** | ||
| * Build the hosted HTTP server exposing the EasyOnward MCP tools over the | ||
| * Streamable HTTP transport in **stateless** mode (read-only, no auth, no | ||
| * sessions): each POST /mcp gets a fresh server + transport, so there's no | ||
| * session store and no long-lived SSE stream to hold open. GET /health is a | ||
| * plain liveness probe. Returns the http.Server without listening, so tests can | ||
| * bind an ephemeral port. | ||
| */ | ||
| export declare function buildHttpServer(): Server; |
| import { createServer as createHttpServer, } from "node:http"; | ||
| import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; | ||
| import { createServer } from "./server.js"; | ||
| const MAX_BODY_BYTES = 256 * 1024; | ||
| function setCors(res) { | ||
| res.setHeader("Access-Control-Allow-Origin", "*"); | ||
| res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); | ||
| res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, mcp-session-id, mcp-protocol-version, last-event-id"); | ||
| res.setHeader("Access-Control-Expose-Headers", "mcp-session-id"); | ||
| } | ||
| async function readJsonBody(req) { | ||
| const chunks = []; | ||
| let size = 0; | ||
| for await (const chunk of req) { | ||
| size += chunk.length; | ||
| if (size > MAX_BODY_BYTES) | ||
| throw new Error("Request body too large"); | ||
| chunks.push(chunk); | ||
| } | ||
| const raw = Buffer.concat(chunks).toString("utf8"); | ||
| return raw.length ? JSON.parse(raw) : undefined; | ||
| } | ||
| function jsonRpcError(res, status, code, message) { | ||
| if (res.headersSent) | ||
| return; | ||
| res.writeHead(status, { "Content-Type": "application/json" }); | ||
| res.end(JSON.stringify({ jsonrpc: "2.0", error: { code, message }, id: null })); | ||
| } | ||
| /** | ||
| * Build the hosted HTTP server exposing the EasyOnward MCP tools over the | ||
| * Streamable HTTP transport in **stateless** mode (read-only, no auth, no | ||
| * sessions): each POST /mcp gets a fresh server + transport, so there's no | ||
| * session store and no long-lived SSE stream to hold open. GET /health is a | ||
| * plain liveness probe. Returns the http.Server without listening, so tests can | ||
| * bind an ephemeral port. | ||
| */ | ||
| export function buildHttpServer() { | ||
| return createHttpServer(async (req, res) => { | ||
| setCors(res); | ||
| if (req.method === "OPTIONS") { | ||
| res.writeHead(204).end(); | ||
| return; | ||
| } | ||
| const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`); | ||
| if (req.method === "GET" && url.pathname === "/health") { | ||
| res.writeHead(200, { "Content-Type": "application/json" }); | ||
| res.end(JSON.stringify({ | ||
| status: "ok", | ||
| service: "easyonward-mcp", | ||
| transport: "streamable-http", | ||
| })); | ||
| return; | ||
| } | ||
| if (req.method === "POST" && url.pathname === "/mcp") { | ||
| const server = createServer(); | ||
| const transport = new StreamableHTTPServerTransport({ | ||
| sessionIdGenerator: undefined, // stateless | ||
| enableJsonResponse: true, // plain JSON responses (no SSE stream) | ||
| }); | ||
| res.on("close", () => { | ||
| void transport.close(); | ||
| void server.close(); | ||
| }); | ||
| try { | ||
| const body = await readJsonBody(req); | ||
| await server.connect(transport); | ||
| await transport.handleRequest(req, res, body); | ||
| } | ||
| catch (err) { | ||
| process.stderr.write(`MCP request error: ${err instanceof Error ? err.stack : String(err)}\n`); | ||
| jsonRpcError(res, 500, -32603, "Internal server error"); | ||
| } | ||
| return; | ||
| } | ||
| // Stateless mode has no long-lived GET stream or session DELETE. | ||
| jsonRpcError(res, 405, -32000, "Method not allowed. Use POST /mcp for the MCP endpoint, or GET /health."); | ||
| }); | ||
| } | ||
| //# sourceMappingURL=http-server.js.map |
| {"version":3,"file":"http-server.js","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,IAAI,gBAAgB,GAIjC,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACnG,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,cAAc,GAAG,GAAG,GAAG,IAAI,CAAC;AAElC,SAAS,OAAO,CAAC,GAAmB;IAClC,GAAG,CAAC,SAAS,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;IAClD,GAAG,CAAC,SAAS,CAAC,8BAA8B,EAAE,oBAAoB,CAAC,CAAC;IACpE,GAAG,CAAC,SAAS,CACX,8BAA8B,EAC9B,2EAA2E,CAC5E,CAAC;IACF,GAAG,CAAC,SAAS,CAAC,+BAA+B,EAAE,gBAAgB,CAAC,CAAC;AACnE,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,GAAoB;IAC9C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;QAC9B,IAAI,IAAK,KAAgB,CAAC,MAAM,CAAC;QACjC,IAAI,IAAI,GAAG,cAAc;YAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QACrE,MAAM,CAAC,IAAI,CAAC,KAAe,CAAC,CAAC;IAC/B,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACnD,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAClD,CAAC;AAED,SAAS,YAAY,CAAC,GAAmB,EAAE,MAAc,EAAE,IAAY,EAAE,OAAe;IACtF,IAAI,GAAG,CAAC,WAAW;QAAE,OAAO;IAC5B,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAC9D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAClF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,gBAAgB,CAAC,KAAK,EAAE,GAAoB,EAAE,GAAmB,EAAE,EAAE;QAC1E,OAAO,CAAC,GAAG,CAAC,CAAC;QAEb,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC7B,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;YACzB,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,UAAU,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC,CAAC;QAEjF,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YACvD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CACL,IAAI,CAAC,SAAS,CAAC;gBACb,MAAM,EAAE,IAAI;gBACZ,OAAO,EAAE,gBAAgB;gBACzB,SAAS,EAAE,iBAAiB;aAC7B,CAAC,CACH,CAAC;YACF,OAAO;QACT,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;YACrD,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;YAC9B,MAAM,SAAS,GAAG,IAAI,6BAA6B,CAAC;gBAClD,kBAAkB,EAAE,SAAS,EAAE,YAAY;gBAC3C,kBAAkB,EAAE,IAAI,EAAE,uCAAuC;aAClE,CAAC,CAAC;YACH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;gBACnB,KAAK,SAAS,CAAC,KAAK,EAAE,CAAC;gBACvB,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC;YACtB,CAAC,CAAC,CAAC;YACH,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,GAAG,CAAC,CAAC;gBACrC,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;gBAChC,MAAM,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;YAChD,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,sBAAsB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CACzE,CAAC;gBACF,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,uBAAuB,CAAC,CAAC;YAC1D,CAAC;YACD,OAAO;QACT,CAAC;QAED,iEAAiE;QACjE,YAAY,CACV,GAAG,EACH,GAAG,EACH,CAAC,KAAK,EACN,yEAAyE,CAC1E,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"} |
| export {}; |
+13
| import { buildHttpServer } from "./http-server.js"; | ||
| /** | ||
| * Hosted HTTP entry point — runs the EasyOnward MCP server over Streamable HTTP | ||
| * so web-based AI clients can use it with no local install. Intended to run as a | ||
| * container behind a reverse proxy at https://mcp.easyonward.com/mcp. The npm | ||
| * package keeps stdio (index.ts) for desktop clients. | ||
| */ | ||
| const PORT = Number(process.env.PORT ?? 8200); | ||
| const server = buildHttpServer(); | ||
| server.listen(PORT, () => { | ||
| process.stderr.write(`EasyOnward MCP server (Streamable HTTP) listening on :${PORT}\n`); | ||
| }); | ||
| //# sourceMappingURL=http.js.map |
| {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;AAE9C,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;AACjC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;IACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,yDAAyD,IAAI,IAAI,CAClE,CAAC;AACJ,CAAC,CAAC,CAAC"} |
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| /** Kept in sync with package.json `version`. */ | ||
| export declare const SERVER_VERSION = "0.2.0"; | ||
| /** | ||
| * Build a fully-configured EasyOnward MCP server with the read-only tools. | ||
| * Shared by the stdio entry point (index.ts) and the HTTP entry point | ||
| * (http.ts) so both transports expose an identical tool surface. | ||
| */ | ||
| export declare function createServer(): McpServer; |
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| import { z } from "zod"; | ||
| import { checkVisa } from "./visa.js"; | ||
| import { resolveCountry } from "./countries.js"; | ||
| /** Kept in sync with package.json `version`. */ | ||
| export const SERVER_VERSION = "0.2.0"; | ||
| /** | ||
| * Build a fully-configured EasyOnward MCP server with the read-only tools. | ||
| * Shared by the stdio entry point (index.ts) and the HTTP entry point | ||
| * (http.ts) so both transports expose an identical tool surface. | ||
| */ | ||
| export function createServer() { | ||
| const server = new McpServer({ | ||
| name: "easyonward", | ||
| version: SERVER_VERSION, | ||
| }); | ||
| server.registerTool("check_visa", { | ||
| title: "Check visa & entry requirements", | ||
| description: "Check whether a traveler holding a given passport needs a visa, and " + | ||
| "what entry & transit requirements apply, for a destination country — " + | ||
| "with official government sources. Accepts ISO 3166-1 alpha-2 codes " + | ||
| '(e.g. "US", "KE") or common country names (e.g. "United States", ' + | ||
| '"Kenya"). Returns a plain-language verdict, the specific requirements ' + | ||
| "by severity, deduped official source links, and a deep link to the " + | ||
| "full EasyOnward analysis. Read-only and PII-free; the analysis is " + | ||
| "generic for any citizen of the passport country.", | ||
| inputSchema: { | ||
| passport_country: z | ||
| .string() | ||
| .describe('The traveler\'s passport-issuing country. ISO 3166-1 alpha-2 code (e.g. "US") or country name (e.g. "United States").'), | ||
| destination_country: z | ||
| .string() | ||
| .describe('The destination country. ISO 3166-1 alpha-2 code (e.g. "KE") or country name (e.g. "Kenya").'), | ||
| }, | ||
| }, async ({ passport_country, destination_country }) => { | ||
| const result = await checkVisa(passport_country, destination_country); | ||
| if (!result.ok) { | ||
| return { | ||
| content: [{ type: "text", text: result.text }], | ||
| structuredContent: { ok: false, error: result.error }, | ||
| isError: true, | ||
| }; | ||
| } | ||
| const { text, ...structured } = result; | ||
| return { | ||
| content: [{ type: "text", text }], | ||
| structuredContent: structured, | ||
| }; | ||
| }); | ||
| server.registerTool("country_code", { | ||
| title: "Resolve a country name to its ISO-2 code", | ||
| description: "Resolve a country name (or an existing ISO 3166-1 alpha-2 code) to its " + | ||
| 'uppercase ISO 3166-1 alpha-2 code — e.g. "Kenya" → "KE". Useful before ' + | ||
| "calling check_visa. Returns an error if the name can't be resolved.", | ||
| inputSchema: { | ||
| name: z | ||
| .string() | ||
| .describe('A country name or ISO-2 code, e.g. "Kenya" or "KE".'), | ||
| }, | ||
| }, async ({ name }) => { | ||
| const code = resolveCountry(name); | ||
| if (!code) { | ||
| const msg = `Could not resolve "${name}" to a country code. ` + | ||
| `Try a common country name (e.g. "Kenya") or an ISO 3166-1 ` + | ||
| `alpha-2 code (e.g. "KE").`; | ||
| return { | ||
| content: [{ type: "text", text: msg }], | ||
| structuredContent: { ok: false, error: msg }, | ||
| isError: true, | ||
| }; | ||
| } | ||
| return { | ||
| content: [{ type: "text", text: code }], | ||
| structuredContent: { ok: true, name, code }, | ||
| }; | ||
| }); | ||
| return server; | ||
| } | ||
| //# sourceMappingURL=server.js.map |
| {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"} |
+8
-68
| #!/usr/bin/env node | ||
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; | ||
| import { z } from "zod"; | ||
| import { checkVisa } from "./visa.js"; | ||
| import { resolveCountry } from "./countries.js"; | ||
| const server = new McpServer({ | ||
| name: "easyonward", | ||
| version: "0.1.0", | ||
| }); | ||
| server.registerTool("check_visa", { | ||
| title: "Check visa & entry requirements", | ||
| description: "Check whether a traveler holding a given passport needs a visa, and " + | ||
| "what entry & transit requirements apply, for a destination country — " + | ||
| "with official government sources. Accepts ISO 3166-1 alpha-2 codes " + | ||
| '(e.g. "US", "KE") or common country names (e.g. "United States", ' + | ||
| '"Kenya"). Returns a plain-language verdict, the specific requirements ' + | ||
| "by severity, deduped official source links, and a deep link to the " + | ||
| "full EasyOnward analysis. Read-only and PII-free; the analysis is " + | ||
| "generic for any citizen of the passport country.", | ||
| inputSchema: { | ||
| passport_country: z | ||
| .string() | ||
| .describe('The traveler\'s passport-issuing country. ISO 3166-1 alpha-2 code (e.g. "US") or country name (e.g. "United States").'), | ||
| destination_country: z | ||
| .string() | ||
| .describe('The destination country. ISO 3166-1 alpha-2 code (e.g. "KE") or country name (e.g. "Kenya").'), | ||
| }, | ||
| }, async ({ passport_country, destination_country }) => { | ||
| const result = await checkVisa(passport_country, destination_country); | ||
| if (!result.ok) { | ||
| return { | ||
| content: [{ type: "text", text: result.text }], | ||
| structuredContent: { ok: false, error: result.error }, | ||
| isError: true, | ||
| }; | ||
| } | ||
| const { text, ...structured } = result; | ||
| return { | ||
| content: [{ type: "text", text }], | ||
| structuredContent: structured, | ||
| }; | ||
| }); | ||
| server.registerTool("country_code", { | ||
| title: "Resolve a country name to its ISO-2 code", | ||
| description: "Resolve a country name (or an existing ISO 3166-1 alpha-2 code) to its " + | ||
| 'uppercase ISO 3166-1 alpha-2 code — e.g. "Kenya" → "KE". Useful before ' + | ||
| "calling check_visa. Returns an error if the name can't be resolved.", | ||
| inputSchema: { | ||
| name: z | ||
| .string() | ||
| .describe('A country name or ISO-2 code, e.g. "Kenya" or "KE".'), | ||
| }, | ||
| }, async ({ name }) => { | ||
| const code = resolveCountry(name); | ||
| if (!code) { | ||
| const msg = `Could not resolve "${name}" to a country code. ` + | ||
| `Try a common country name (e.g. "Kenya") or an ISO 3166-1 ` + | ||
| `alpha-2 code (e.g. "KE").`; | ||
| return { | ||
| content: [{ type: "text", text: msg }], | ||
| structuredContent: { ok: false, error: msg }, | ||
| isError: true, | ||
| }; | ||
| } | ||
| return { | ||
| content: [{ type: "text", text: code }], | ||
| structuredContent: { ok: true, name, code }, | ||
| }; | ||
| }); | ||
| import { createServer } from "./server.js"; | ||
| /** | ||
| * stdio entry point — the default for the npm package (`npx -y | ||
| * @easyonward/mcp-server`). Desktop/IDE clients (Claude Desktop, Cursor, | ||
| * Windsurf, Cline) spawn this and speak MCP over stdio. For the hosted HTTP | ||
| * transport, see http.ts. | ||
| */ | ||
| async function main() { | ||
| const server = createServer(); | ||
| const transport = new StdioServerTransport(); | ||
@@ -73,0 +13,0 @@ await server.connect(transport); |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;GAKG;AACH,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,qEAAqE;IACrE,yCAAyC;IACzC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC;AACnE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,+CACE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAC/C,IAAI,CACL,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"} |
+3
-2
| { | ||
| "name": "@easyonward/mcp-server", | ||
| "mcpName": "com.easyonward/mcp-server", | ||
| "version": "0.1.2", | ||
| "version": "0.2.0", | ||
| "description": "Read-only Model Context Protocol server for EasyOnward \u2014 check passport visa, entry, and transit requirements with official government sources.", | ||
@@ -36,3 +36,4 @@ "license": "MIT", | ||
| "start": "node dist/index.js", | ||
| "test": "node --test --import tsx test/*.test.ts" | ||
| "test": "node --test --import tsx test/*.test.ts", | ||
| "start:http": "node dist/http.js" | ||
| }, | ||
@@ -39,0 +40,0 @@ "keywords": [ |
+51
-4
@@ -70,12 +70,38 @@ # EasyOnward MCP Server | ||
| Runs on Node 18+ via `npx` (no install needed): | ||
| Two ways to connect, same tools: | ||
| - **Hosted (remote)** — no install, nothing to run. Point any HTTP-capable MCP | ||
| client at `https://mcp.easyonward.com/mcp` (Streamable HTTP). Best for most | ||
| users. | ||
| - **Local (stdio)** — runs on Node 18+ via `npx` (no install needed): | ||
| ```bash | ||
| npx -y @easyonward/mcp-server | ||
| ``` | ||
| The server speaks MCP over **stdio**. Best for offline/air-gapped setups or | ||
| clients without remote transport. | ||
| ### Hosted (remote) — Claude Code / any HTTP client | ||
| ```bash | ||
| npx -y @easyonward/mcp-server | ||
| claude mcp add --transport http easyonward https://mcp.easyonward.com/mcp | ||
| ``` | ||
| The server speaks MCP over **stdio**. | ||
| Or in a client config that supports remote servers: | ||
| ### Claude Desktop | ||
| ```json | ||
| { | ||
| "mcpServers": { | ||
| "easyonward": { | ||
| "type": "streamable-http", | ||
| "url": "https://mcp.easyonward.com/mcp" | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
| The hosted server is read-only, stateless, and PII-free — same two tools as | ||
| the local build. | ||
| ### Claude Desktop (local stdio) | ||
| Add to `claude_desktop_config.json` | ||
@@ -122,2 +148,9 @@ (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`, | ||
| ### Windsurf, Cline & other stdio clients | ||
| Any client that supports local stdio MCP servers uses the **same block** — | ||
| `command: npx`, `args: ["-y", "@easyonward/mcp-server"]`. In Windsurf it's | ||
| *Settings → Cascade → MCP Servers*; in Cline it's the MCP settings JSON. No | ||
| client-specific setup. | ||
| ## Configuration | ||
@@ -143,2 +176,16 @@ | ||
| ## Security & privacy | ||
| - **Read-only.** The server only performs GET lookups against the public EasyOnward | ||
| API — it never writes, books, or mutates anything. | ||
| - **No secrets, no auth.** There's no API key or token to configure, so there's | ||
| nothing to leak. | ||
| - **No PII.** It sends only a passport country + a destination country (ISO-2 codes | ||
| or country names). It never asks for or transmits passport numbers, names, dates | ||
| of birth, or any personal data. | ||
| - **Public data only.** Everything returned is public visa/entry/transit reference | ||
| information, with links to official government sources. | ||
| - **Fail-safe.** An unreachable API or an uncatalogued country pair returns a clear | ||
| message, not a crash. Requests are subject to the public API's rate limits. | ||
| ## Develop | ||
@@ -145,0 +192,0 @@ |
Network access
Supply chain riskThis module accesses the network.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
55910
26.01%21
75%796
19.16%203
30.13%3
50%4
100%