Jinko Travel MCP Server
by jinkoso
README.md
# Jinko Travel — Vellum Assistant plugin
Exposes Jinko's remote MCP server to a Vellum assistant: flight and hotel
search, trip assembly, checkout, agentic payment, and servicing (refund,
exchange, cancel). The plugin ships no travel logic of its own — every tool,
schema, and instruction comes from the Jinko server.
## Architecture
```
Vellum assistant
│ stdio (MCP)
▼
skills/jinko-travel/scripts/mcp-proxy.ts ← this plugin
│ Streamable HTTP + Authorization: Bearer jnk_t_...
▼
https://mcp.builders.gojinko.com/mcp ← Jinko remote MCP server
```
The remote server is Streamable HTTP and `mcp.json` can declare that
transport directly, but a plugin cannot put a per-user credential in it:
`headers` in `mcp.json` are literal package data, the spec defines no vault
placeholder, and the host never lends a plugin its own `mcp:<id>:*`
credentials. So the plugin declares a `stdio` server instead. The child
resolves the key at start, opens one authenticated session to Jinko, and
relays `tools/list` and `tools/call`. The remote's `instructions` and tools
capability are copied into the proxy's own initialize response, so the
assistant reads the same booking guidance a direct connection would give it.
The proxy script lives under `skills/jinko-travel/scripts/` because that is
where it has to be. The host does not inject `VELLUM_PLUGIN_NAME` into MCP
children; its credential scoping recovers the plugin name from an entry
script path shaped `plugins/<name>/skills/<skill>/{scripts,tools}/<file>`.
Moving the script breaks the vault lookup.
## Install
```bash
assistant plugins install https://github.com/jinkoso/jinko-vellum-plugin
```
Until the plugin is listed in the Vellum marketplace (`plugins/marketplace.json`),
it installs as an unreviewed plugin: the install itself is the only review.
## Authentication
Jinko issues one tenant API key (`jnk_t_...`) per Vellum organization. The
key is sent as `Authorization: Bearer <key>`; `X-API-Key` is not accepted.
The key is not shipped with the plugin. At start, the MCP child reads it
from the assistant's credential vault under `jinko-vellum-plugin/api_key`,
or from `JINKO_API_KEY` in the environment when a host injects it there.
The credential's service name must equal the plugin's install-directory
name, because the host scopes a plugin's vault reads to its own name.
There are two ways the key gets into the vault.
**Platform-provisioned (Vellum Cloud).** The Vellum control plane writes
the organization's key into every managed assistant's vault as a
platform-managed credential, the same way it provisions
`vellum:assistant_api_key`: pushed through the daemon's `POST /v1/secrets`,
hidden from `assistant credentials list` and `reveal`, and not deletable
by the user. The plugin then needs no setup step. This requires Vellum to
extend its platform-managed credential set with `jinko-vellum-plugin:api_key`
and to keep the owning plugin's `resolveCredential` able to read it.
**Self-stored (self-hosted assistants, or before provisioning exists).**
Request a tenant API key for your organization from Jinko and store it:
```bash
bun skills/jinko-travel/scripts/setup.ts
```
which runs:
```bash
assistant credentials prompt \
--service jinko-vellum-plugin \
--field api_key \
--label "Jinko API key" \
--description "Tenant API key (jnk_t_...) issued by Jinko for this Vellum organization" \
--placeholder "jnk_t_..." \
--allowed-domains mcp.builders.gojinko.com,jinko-e90ee33b.alpic.live
```
A self-stored credential is readable back with `assistant credentials
reveal` by whoever controls that assistant, so a shared organization key
should only be distributed this way to people allowed to hold it.
The MCP child reads the key once, at start. After storing a key, disable and
re-enable the plugin (or restart the assistant) so the server reconnects.
## Configuration
`mcp.json` sets `JINKO_MCP_URL`; the other two are for local work or a host
that supplies the key itself.
| Variable | Required | Default (from `mcp.json`) | Effect |
| ---------------------- | -------- | --------------------------------------- | ----------------------------------------------------------------------------- |
| `JINKO_MCP_URL` | yes | `https://mcp.builders.gojinko.com/mcp` | Remote endpoint. Dev builder: `https://jinko-e90ee33b.alpic.live/mcp`. |
| `JINKO_TOOL_ALLOWLIST` | no | unset | Comma-separated tool names. When set, only these are advertised and callable. |
| `JINKO_API_KEY` | no | unset | Overrides the vault lookup. Used for local development. |
## Tools
The proxy advertises whatever the remote advertises. The production builder
exposes 14:
`find_destination`, `flight_calendar`, `price_monitoring`, `flight_search`,
`hotel_search`, `hotel_details`, `trip`, `get_trip`, `checkout`,
`submit_agent_payment`, `get_booking`, `flight_refund`, `flight_exchange`,
`hotel_cancel`.
The dev builder exposes 20 — the 14 above plus `find_ground`,
`ground_cancel`, `ground_exchange`, `car_search`, `car_cancel`,
`car_exchange`.
## Skill content
The guidance in `skills/jinko-travel/` — which tool to call first, the
booking order, the payment hand-off, the money and traveler-data rules — is
not written in this repository. It is rendered from Jinko's canonical Agent
Skills package, [jinkoso/jinko-skills](https://github.com/jinkoso/jinko-skills)
(internal), so a Vellum assistant reads the same skill every other host does.
The vendored commit is recorded in `vendor/jinko-skills/UPSTREAM.json`; today
it is `8015d30b69288afa488f296a37aa408ab5eae85c` of `skills/jinko-travel`.
```
vendor/jinko-skills/ pristine upstream, never edited here
SKILL.md upstream frontmatter + body
references/*.md flights · hotels · booking · places
UPSTREAM.json repo, commit, path, sync date
vellum/frontmatter.json the metadata.vellum block to merge
vellum/overlay.md the Vellum-only sections
scripts/render-skill.ts vendor + overlay → skills/jinko-travel/
scripts/sync-upstream.ts refresh vendor/, then render
skills/jinko-travel/ rendered; do not hand-edit
```
The renderer keeps upstream's `name`, `description`, `compatibility` and
`metadata.author` / `version` / `surface` verbatim, merges
`metadata.vellum` from `vellum/frontmatter.json`, inserts the overlay just
before upstream's `## Read next` heading so the references list stays last,
and copies `references/` across unchanged. It is deterministic and
idempotent; `bun scripts/render-skill.ts --check` prints a unified diff and
exits 1 when `skills/jinko-travel/` has drifted, and CI runs it.
Upstream's rule is **moved, not rewritten**: upstream text is never edited
in this repository. Anything Vellum-specific — the `mcp__…__jinko__` tool
prefix, the setup script, paying through a Link spend request — belongs in
`vellum/overlay.md`.
To pick up a new upstream revision:
```bash
bun scripts/sync-upstream.ts /path/to/jinko-skills
```
With no argument it shallow-clones the upstream instead. Either way it needs
a maintainer who can read an internal repository, so CI never runs it — CI
only checks that what is committed matches the render.
Two things the rendered skill says that do not hold on this surface:
- It names `find_dates` and `lowest_fare` in the tool map and the routing
list. The Builder MCP this plugin proxies advertises neither, on production
or dev. The text is left as upstream wrote it; `flight_calendar` covers the
same intent, and correcting the tool map is an upstream change.
- `references/places.md` documents `GET /api/v1/flights/places/resolve`. A
Vellum assistant cannot call it: the proxy is the only authenticated path to
Jinko and it speaks MCP, and the assistant itself holds no Jinko key. The
page already says there is no `resolve_places` MCP tool and gives the
MCP-side fallback, which is the part that applies here.
## Local development
```bash
bun install
bun run typecheck
bun test
bun scripts/render-skill.ts --check
```
The tests start a fake Jinko server in-process (the MCP SDK's own Streamable
HTTP server transport on a random port), so they make no network calls and
need no key.
To drive the proxy by hand against the dev builder:
```bash
JINKO_API_KEY=jnk_t_... JINKO_MCP_URL=https://jinko-e90ee33b.alpic.live/mcp \
npx @modelcontextprotocol/inspector@2.7.0 \
bun skills/jinko-travel/scripts/mcp-proxy.ts
```
Live tool calls cost Jinko credits. `tools/list` does not.
## Limitations
- **20 tools per server.** The assistant keeps the first 20 tools a server
reports and drops the rest without a message. The dev builder is at exactly
20 today; anything added beyond that is invisible. Use
`JINKO_TOOL_ALLOWLIST` to choose which 20 survive. The proxy writes a
warning to stderr when it forwards more than 20.
- **The key is read once, at start.** Storing or rotating a key does not
reach a running server; the plugin has to be reconnected.
- **One key per assistant, not per end user.** The tenant key identifies the
organization. Per-traveler identity lives inside the trip, not in the
credential.
- **`cwd` in `mcp.json` is ignored by the host**, so the plugin passes an
absolute `${PLUGIN_ROOT}` path in `args` instead.
- **Vault reads from an MCP child fail on vellum-assistant `175c45b6fa76`
(2026-09-22).** `resolveCredential()` calls `resolveCredentialRefLive()`
before anything has connected the child to the credential store, so the
first lookup in a plugin-spawned stdio process reports "Credential store
is unreachable" even when the key is stored. Verified in a local
self-hosted assistant: the same process resolves the key once the store
connection is primed, and `JINKO_API_KEY` in the environment works. Until
Vellum orders the backend connection before the live lookup in
`assistant/src/plugin-api/resolve-credential.ts`, the vault path
described above does not start the server; the env path does.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues