Fortrip

io.github.FortripEngineeringPublished by GitHub account FortripEngineeringv1.4.13Updated Nov 7, 2026

Search hotels worldwide, compare live room rates, book and pay securely, and cancel bookings.

VerifiedStreamable HTTPWeb executableLocation & LifestyleBusiness & Commerce

Overview

AI-generated overview

Fortrip lets an assistant search hotels worldwide, compare live room offers, book, pay and cancel stays through a Fortrip account.

What you can do
  • Search hotels by destination, name or landmark, with indicative prices
  • Read one hotel's details, policies, amenities and rooms
  • Fetch live bookable offers for up to 15 hotels and one stay
  • Book an offer, creating an unpaid order with a payment link
  • List and inspect the user's orders, pay an order, or cancel it under its terms
  • Get planning guidance for flexible dates, budgets, split stays or several destinations
When to use it
Useful when someone wants an assistant to find and compare hotels, check real bookable rates for specific dates and party sizes, and carry a booking through to payment or cancellation without leaving the chat.
Requirements
Either the hosted remote endpoint or the local npm package run as a bridge, which needs Node.js 22 or newer. A Fortrip account signed in with OAuth in the browser; no API key or token is configured. Network access to Fortrip's API.
Before you install
Booking creates real orders and paying spends money; the server asks the assistant to show the total, stay and terms and get a clear confirmation first. A payment link can be used by anyone who holds it, so it should go only to the user. Card details must never be shared; payment goes through Stripe. Cancellations may carry a penalty. Guest names and party details are sent to Fortrip and the hotel.

Installation

In SourceWeft

  1. Open Fortrip in the dashboard and add it to a workspace.
  2. Connect the required credentials. They are stored privately and never shown on public pages.
  3. Enable the server for the chats that should use its tools.

Web executable via Streamable HTTP. Remote servers run from the web runtime once configured in a workspace.

Use with an agent

Paste this prompt into Claude Code, Codex, Cursor or another agent — or into SourceWeft chat. The agent reads this server's install guide, shows you its publisher, endpoint and sign-in, and adds it to your client once you agree. Keys never go into a command.

Read https://sourceweft.com/mcp/io.github.FortripEngineering%2Ffortrip/install.md and connect the MCP server it describes to my client. Before changing any config, show me its publisher, endpoint and sign-in method and wait for my OK. Never put an API key in a command or in a file I would commit; ask me where it should come from.

Read the install guide the agent follows

Other MCP clients

Add this to your client's mcpServers config. The client completes OAuth sign-in on first connect.

{
  "mcpServers": {
    "fortrip": {
      "type": "http",
      "url": "https://mcp.fortrip.ai/"
    }
  }
}

README

Fortrip

Fortrip's public agent surface in one repository: the fortrip command-line tool for hotel search, live offers and orders, the contract that every agent surface shares, and the store listings that point agents at Fortrip. Hotels only for now.

Everything here talks to Fortrip API v1, https://api.fortrip.ai/v1, or to the hosted MCP server, https://mcp.fortrip.ai/, as a Fortrip user who has signed in with OAuth in the browser.

Using Fortrip

  • From a shell, or an agent with one: npx -y fortrip <command>. People start with npx -y fortrip login. Agents run npx -y fortrip guide and sign in through the loginUrl a command prints when it exits 3. The CLI's README has the commands, sign-in and exit codes.

  • In an MCP client: connect the hosted server https://mcp.fortrip.ai/ if the client supports remote servers with OAuth, or run the local bridge, npx -y fortrip mcp. The CLI's README has the config for Claude Desktop, Cursor, Codex and Claude Code. The Claude and OpenAI plugins use the hosted server.

  • In Claude Code, as a plugin (the hosted server plus Fortrip's skill):

    /plugin marketplace add FortripEngineering/fortrip/plugin install fortrip@fortrip
  • As a skill, for agents that can run shell commands but don't have Fortrip's MCP tools:

    bash
    npx skills add FortripEngineering/fortrip --skill fortrip-cli

Layout

PathWhat it isHow it's published
packages/contractThe contract of Fortrip API v1: limits, paths, request and response schemas, error codes, field rules, the specs of the MCP tools and CLI commands, and the agent guide, written once and rendered for the MCP server, the Claude skill and the CLInpm: @fortrip/agent-contract, from a v* tag on main, together with fortrip
packages/cliThe fortrip CLI: hotel search, live offers, orders and payment links from a shell, the local MCP bridge (fortrip mcp) and the agent guide (fortrip guide)npm: fortrip, from a v* tag on main, together with @fortrip/agent-contract
plugins/claudeClaude plugin (hosted MCP server + static skill, generated)Submitted in the developer portal; .claude-plugin/marketplace.json at the root lets Claude Code add this repo as a marketplace
plugins/openaiChatGPT / Codex pluginZip uploaded in the OpenAI portal
plugins/registryMCP Registry entryThe Publish Fortrip to MCP Registry workflow
skills/fortrip-cliSkill for agents with a shell (generated)npx skills add FortripEngineering/fortrip --skill fortrip-cli reads it from main
scriptsRepo-wide scripts: generate.ts renders the skills, validate.mjs checks the listings

Development

Node.js 22 or newer and pnpm 11.

bash
pnpm installpnpm test             # tests of every package, and of the scriptspnpm generate         # renders the skills from the contractpnpm generate --check # fails with a diff when a generated file is stalepnpm build && pnpm validate   # checks the store listings

pnpm typecheck and pnpm build run for every package too. CI runs typecheck, tests and the build on Node.js 22 and 24, and on 24 also pnpm generate --check, pnpm validate and claude plugin validate --strict on ., plugins/claude and skills.

Generated files

The skills (SKILL.md and references/planning.md in plugins/claude/skills/fortrip and skills/fortrip-cli) and the contract's src/guide-data.generated.ts are generated. Never edit them: change the guide's fragments in packages/contract/guide/*.md (or a skill's front matter in scripts/generate.ts), run pnpm generate and commit the result. The fragments say which surface states what, and the Claude skill, the MCP instructions and the CLI's guide all render from them, so they cannot drift apart.

Publishing

Claude plugin

plugins/claude is a plugin with two parts:

  • .mcp.json points at the hosted MCP server. On claude.ai and in Cowork it shows up on the plugin's Connectors tab, where users sign in with their Fortrip account (OAuth); Claude Code connects to it directly.
  • skills/fortrip/ is the generated skill that tells Claude how to use the tools: call order, occupancy per room, prices and currency, confirming before the payment link, and errors. references/planning.md covers flexible dates, budgets, split stays and several destinations.

The skill is self-contained: the directory doesn't allow a plugin to fetch instructions at runtime (Software Directory Policy §2F), and its validator blocks unpinned npx launchers, so the CLI skill stays outside plugins/claude. pnpm validate fails if a file under it mentions npx, fortrip@latest or get_planning_guide.

Submit the plugins/claude folder in the developer portal. Check it with:

bash
claude plugin validate --strict plugins/claudeclaude plugin validate --strict .

The second command checks .claude-plugin/marketplace.json at the repository root. It lists plugins/claude as a plugin, which lets npx skills add find the plugin's skill next to skills/fortrip-cli, and lets Claude Code add this repository as a marketplace.

Claude Code keeps users on the version in plugin.json, so bump it whenever anything in plugins/claude changes, regenerated skill text included: patch for wording, minor for new behaviour or components, major for changes that break how people use it. The directory takes the listing icon from the first submission only.

CLI skill

skills/fortrip-cli is the CLI's guide as a static skill: the agent reads the commands, call order, sign-in and what to show the user before a payment link from the skill itself. It needs Node.js 22 or newer and isn't part of the Claude directory plugin.

bash
npx skills add FortripEngineering/fortrip --skill fortrip-cli

OpenAI plugin

plugins/openai/plugin.json holds the listing copy and review test cases, and mcp.json the hosted server URL. mcp.json follows the Agent Plugins schema (type: streamable-http), while plugins/claude/.mcp.json follows Claude's (type: http); both point at the same server. Build the upload:

bash
cd plugins/openai && rm -f ../../fortrip-openai.zip && zip -rX ../../fortrip-openai.zip plugin.json mcp.json assets

Upload the zip in the OpenAI portal. The portal verifies the domain through https://mcp.fortrip.ai/.well-known/openai-apps-challenge, served by mcp.fortrip.ai.

MCP Registry

plugins/registry/server.json lists Fortrip as io.github.FortripEngineering/fortrip, with two ways to connect: the hosted server (remotes) and the npm package fortrip, run as fortrip mcp (packages; the mcpName in packages/cli/package.json must equal the entry's name). The entry is published by its own workflow, Publish Fortrip to MCP Registry (.github/workflows/publish-mcp-registry.yml), which runs only from main and waits for a human to approve the mcp-registry environment. To publish a change, bump version (a version can't go down), set packages[].version to the CLI version on npm, merge to main and run the workflow. After a CLI release, run it once npm has the new version.

To deprecate an old version, or make it active again, run Set an MCP Registry version's status (.github/workflows/mcp-registry-status.yml) with the version, the status and, when deprecating, a message (the publisher takes none for active). It has the same main-only, approval-gated setup. A personal mcp-publisher login github can't do this: it only gets io.github.<user>/*, so the org namespace comes through this repository's OIDC identity.

The Registry binds the server URL to one name, so a rename means deleting the old entry before publishing the new one.

Releasing

fortrip and @fortrip/agent-contract are released together, at the same version.

  1. In a PR, set version to the new X.Y.Z in both packages/cli/package.json and packages/contract/package.json (npm version X.Y.Z --no-git-tag-version in each package's directory), set packages[].version in plugins/registry/server.json to it too, and run pnpm generate and commit what it changes. Merge the PR into main once CI passes.

  2. Tag the merge commit on main and push the tag:

    bash
    git checkout main && git pullgit tag vX.Y.Z && git push origin vX.Y.Z
  3. A human approves the npm-publish environment. The workflow publish.yml then publishes both packages to npm through trusted publishing (npm's OIDC sign-in from GitHub Actions, so no npm token is stored in GitHub), which gives them provenance.

  4. Once npm has the new version, run the MCP Registry workflow (above).

The workflow refuses a tag that isn't vX.Y.Z or vX.Y.Z-rc.N, that differs from the version in either package.json, or that isn't on main. Its build job runs typecheck, tests and the build and packs both tarballs; its publish job, in the npm-publish environment, publishes those tarballs (@fortrip/agent-contract first) without installing or building anything. A release candidate, tagged vX.Y.Z-rc.N, is published under the next dist-tag, so npm install doesn't pick it up. Rerunning the publish job after a partial publish skips what is already on npm.

Docs

packages/docs is the Fumadocs site behind docs.fortrip.ai.

bash
pnpm --filter ./packages/docs dev   # local dev serverpnpm --filter ./packages/docs test  # its tests

Production docs follow the live server, not main. A docs deploy is a docs-* tag, and the order is:

  1. Tag vX.Y.Z (npm, see Releasing above).
  2. Ship the server release that pins that contract to production.
  3. Push the tag docs-X.Y.Z.
  4. A human approves the docs-production environment. The workflow deploy-docs.yml then builds and deploys to Vercel.

The workflow refuses to deploy unless all of these hold at the tagged commit:

  • the contract's version is a plain X.Y.Z, not an rc;
  • packages/contract/src is unchanged since the tag vX.Y.Z of that version;
  • the changelog has the version's entry, dated, and no entry says "Not released yet.".

The tag name itself is free-form. So once main has moved on from the live release (contract changes, or a new undated changelog entry), a docs deploy can't go out from main: cut a short branch from the live v* tag, make the docs-only fix there, tag docs-X.Y.Z-<n> on that branch and push the tag, and also merge the fix to main. A docs-only fix needs no npm release and no changelog entry.

Before tagging a stable release, replace the version's "Not released yet." with "Released YYYY-MM-DD." in packages/docs/content/docs/changelog.mdx, or the changelog gate fails.

One-time setup, by the owner:

  • A Vercel project with Root Directory packages/docs, framework Next.js, "Include files outside the root directory" left on (the build reads packages/contract and packages/cli/package.json) and preview deployments on. packages/docs/vercel.json turns off Git deployments of main: production would otherwise follow main and skip the approval. Other branches still get previews. On the first real run, confirm a push to main creates no production deployment. Importing the project deploys main once anyway; the first docs-* deploy replaces it.
  • The docs.fortrip.ai domain on that project, and Vercel Analytics enabled.
  • The GitHub environment docs-production, with the same required reviewers as npm-publish ("Prevent self-review" off, as there, so whoever pushes the tag can approve), deployment branches and tags limited to the tag pattern docs-*, and the secrets VERCEL_TOKEN, VERCEL_ORG_ID and VERCEL_PROJECT_ID.
  • On the first deploy, confirm vercel build succeeds without VERCEL_TOKEN: the workflow gives the token only to vercel pull and vercel deploy, since the build runs the dependencies' code.

License

Apache License 2.0. Copyright 2026 FortripAI, Inc. (see NOTICE). Releases before 2.0.0 were published under the MIT License. This license does not grant access to the hosted Fortrip service, hotel inventory, or third-party data.

Trademarks

"Fortrip" and the Fortrip logo are trademarks of FortripAI, Inc. The Apache License 2.0 does not grant permission to use the trade names, trademarks, service marks or product names of FortripAI, Inc., except as required for reasonable and customary use in describing the origin of the software and reproducing the content of the NOTICE file (section 6).

Source: README.md at commit b6aeab9

Tools

9

get_planning_guide

Planning guide

Returns Fortrip's planning guide for a request that is more than one hotel for fixed dates (flexible dates, a budget, split stays, several destinations, "find me the best"): what to ask the user, which results count as evidence, how to rank and how to present options.

Takes no parameters.

search_hotels

Search hotels

Find hotels by destination and/or text, with an indicative per-room-night price when a stay is given. destination is a place_name with its countryCode, a place or locality by placeId from an earlier answer, a point, or a country. A name several places bear answers destination_ambiguous with candidates: ask the user, then search the chosen placeId. text matches hotel names, brands and areas word for word. Indicative prices are estimates and not bookable: get_hotel_offers returns what the user pays. A search too broad to answer is refused with search_too_broad and its localities: pass one back as { kind: "locality", placeId }, never by name. The answer states the scope, the counts and notes; a next page is the cursor and, optionally, limit.

ParameterssortstaytextlimitcursorlocalefilterspricingdestinationdisplayCurrency

get_hotel

Hotel details

One hotel's catalogue facts: names, address, location, time zone, check-in and check-out times, the full description, every policy and the amenities, and its rooms with include: "rooms". "Not listed" is not "no". No prices or availability: get_hotel_offers has those.

ParameterslocalehotelIdrequiredinclude

get_hotel_offers

Live offers

Live, bookable offers for up to 15 hotels and one stay. Every room in stay.rooms must have the same party: for different parties, call once per party. Each offer has the room, the meals included, price.total in USD for the whole stay and every room (what is charged), fees payable at the property, the cancellation terms with times in UTC and the hotel's time zone, the confirmation mode, and an offerId for book_hotel that expires at expiresAt. Hotels with nothing to offer are listed under unavailable with the reason. Up to limitPerHotel offers per hotel, default 10, chosen for a spread of choices in this order while room remains: the cheapest offer, the cheapest that can be cancelled free, the cheapest with breakfast, the cheapest of each other room, then the rest by price; listed cheapest first.

ParametersstayrequiredlocalefiltershotelIdsrequiredlimitPerHoteldisplayCurrency

book_hotel

Create order

Order one offer from get_hotel_offers. The answer is an unpaid order with a payment link: status awaiting_payment, the total, the stay and its terms, and payment.url, a Fortrip page to pay on. Nothing is charged until the order is paid, on that page or with pay_order (or Alipay/WeChat Pay); anyone holding the link can pay. Send the contact and, for each room of the offer, at least one guest. If the price or terms changed since the offer, the answer is offer_changed with every change and a new offer: show the changes and ask the user again before ordering it. Sending the same details again returns the same order. An offer is ordered once: for another order of the stay (another party, or a corrected guest name), get offers again and order the new offer; when it replaces an unpaid order, tell the user to pay only the new link, as the earlier one expires unpaid. To cancel an order, use cancel_order; for changes, or a cancellation cancel_order can't make, the user emails [email protected] with the order's reference.

Parametersitemsrequiredcontactrequired

get_order

Get order

One of the user's orders by orderId: its status, the payment status and, while it awaits payment, payment.url; the stay and its terms; and the hotel's confirmation number once there is one. Check it after the user pays. An order awaiting payment whose payment.url is null had its link unreadable this time: read it again.

ParameterslocaleorderIdrequired

list_orders

List orders

The user's hotel orders, newest first, including those made on the Fortrip website. Pass nextCursor back as cursor for the next page.

Parameterslimitcursorlocale

cancel_order

Cancel order

Cancel one of the user's orders by orderId under its cancellation terms, when its cancel.available is true: the hotel booking is cancelled and cancel.refund, what was paid less cancel.penalty, is refunded. Before you call cancel_order, show the user the order's stay (hotel, dates), cancel.penalty and cancel.refund (amount and currency), then end your reply by asking them to confirm the cancellation, and wait for a clear yes. Pass maximumPenalty as the number in cancel.penalty.amount ("120.00" → 120). Never state a refund the order did not return. If the penalty is now higher than maximumPenalty, the answer is cancellation_changed and nothing changes: read the order again and ask the user again. Answers the order: status cancelling, then cancelled once the hotel cancels and the refund is issued; poll get_order. If the hotel refuses, the order goes back to confirmed with cancel.reason refused. Calling it again for the same order answers the order as it is.

ParametersorderIdrequiredmaximumPenaltyrequired

pay_order

Pay order

Pay an order without its link, by shared payment token or Alipay/WeChat Pay (method alipay_wechat_pay). Token: only if payment.handlers isn't empty and you can request one from the user's Link wallet (US and Canadian accounts); never ask the user for one. First show the user the USD total, the stay and every term (meals, cancellation, fees payable at the property), ask them to confirm and wait for a clear yes. Token: request it with config.business_profile and payment.credentialLimits; pass it with price.total. Wallet: pass method, price.total; show payment.wallets (WeChat code as an image if you can, both links); they pay once by either; a code lasts 120 minutes; then the WeChat link opens a new one, or call again. Pay one way only: payment.url or pay_order. On success payment.url stops working; on failure it stays open. Then poll get_order. payment_total_changed: confirm details.total, retry. payment_credential_invalid: new token. service_unavailable: retry. Else payment.url. A token pays one order only.

ParameterstotalrequiredmethodorderIdrequiredpaymentsharedPaymentToken

Version history

10
  1. v1.4.13LatestNov 6, 2026
  2. v1.4.12Nov 5, 2026
  3. v1.4.10Nov 4, 2026
  4. v1.4.8Nov 4, 2026
  5. v1.4.7Nov 4, 2026
  6. v1.4.4Nov 3, 2026
  7. v1.4.2Oct 29, 2026
  8. v1.4.0Oct 26, 2026
  9. v1.3.0Oct 25, 2026
  10. v1.0.3Oct 21, 2026

Score

100% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 daysRequired25 pts
  • Auth declaredRequired10 pts
  • Tools listed15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts