@guestcraft/mcp
Questi contenuti non sono ancora disponibili nella tua lingua.
A Model Context Protocol server for a GuestCraft instance. It
gives an AI assistant GuestCraft’s public operations as MCP tools and resources, plus composed
front-desk tools, calls them with the caller’s own identity, and runs a destructive tool only
once the caller confirmed it. MIT licensed; it is a client of the public API only and imports no
other GuestCraft package than @guestcraft/sdk. Every instance also serves it at
/api/mcp, composed by @guestcraft/server with OAuth.
This is a private pnpm workspace package, version 0.0.0, not published to npm. Its entry
points are TypeScript sources that Node.js 24 runs directly. Setup for Claude Desktop, Claude
Code, Cursor, VS Code and the MCP Inspector is in the MCP guide.
Transports
Section titled “Transports”stdio (the default): one process per assistant, acting with one credential read from the environment.
GUESTCRAFT_BASE_URL=https://hotel.example.com \GUESTCRAFT_API_KEY=gc_live_… \node packages/mcp/src/main.ts| Variable | Meaning |
|---|---|
GUESTCRAFT_BASE_URL |
The instance root, such as https://hotel.example.com, without /api/v1. Required. |
GUESTCRAFT_API_KEY |
A workspace API key (gc_live_… or gc_test_…). |
GUESTCRAFT_ORGANIZATION_TOKEN |
An organization token (gc_org_…). Exactly one of the two credentials is required. |
GUESTCRAFT_WORKSPACE_ID |
Optional default workspace (ws_…), for an organization token that reaches several. |
Streamable HTTP: node packages/mcp/src/main.ts --http [--host 127.0.0.1] [--port 3100] [--allow-origin <origin>]... serves one stateless endpoint at /mcp (MCP protocol revision
2026-07-28, with the stateless fallback for 2025 clients). It holds no credential of its own and
refuses to start when one of the credential variables is set: each request brings its caller’s
credential as Authorization: Bearer gc_live_… (or gc_test_…, gc_org_…), and optionally its
default workspace as GuestCraft-Workspace-Id. A missing or malformed credential answers 401
with WWW-Authenticate: Bearer; the instance checks the credential itself on every call. It binds
to 127.0.0.1 unless --host says otherwise; put it behind TLS before exposing it. A request
whose Origin header is not one of the --allow-origin values (none by default) answers 403,
which protects a local server from DNS rebinding; an allowed origin gets CORS answers (its
preflight without a credential, WWW-Authenticate readable, never cookies).
--help prints the usage. Wrong settings exit with status 2 and say what to change.
Library
Section titled “Library”import { createGuestCraftMcpHandler, createGuestCraftMcpServers } from "@guestcraft/mcp";import { serveStdio } from "@modelcontextprotocol/server/stdio";
// stdio, or any transport of the MCP SDK: one caller's servers.serveStdio(createGuestCraftMcpServers({ baseUrl, apiKey, workspaceId }));
// Streamable HTTP in any runtime with web-standard Request and Response.const handler = createGuestCraftMcpHandler({ baseUrl, allowedOrigins: ["https://app.example.com"] });const response = await handler.fetch(request);Both take an optional fetch for every request to the instance, confirmations (below) and
callTimeoutMs, the bound of one tool call. The handler also takes authenticate, which decides
who each request is before anything else runs (the default reads an API key or organization token
from the bearer): it answers the caller’s connection, either a credential or a fetch that
carries an already verified identity in process, and a binding that identifies the caller for
confirmations, and for an OAuth access token the permissions its scopes name (scopes). A
refused request gets 401 with resource_metadata when resourceMetadataUrl is set, or 429.
A caller with scopes whose tool call or resource read needs a permission they lack is answered
403 with WWW-Authenticate: Bearer error="insufficient_scope", scope="…" before the call runs
(src/scopes.ts): the scope names the permissions of the operation, one of which
is enough, and for a composed tool those of the operations it always calls. The options also take
now, the clock of the readiness tools’ dates. toolCatalog(document) is the pure conversion of an OpenAPI document
(parseOpenApiDocument(json)) into the tools below.
Tools and resources
Section titled “Tools and resources”The server reads the instance’s own OpenAPI document, GET {baseUrl}/api/v1/openapi.json, once per
process (a failed read is retried on the next request), so its tools always match the installed
version.
- Which operations: one tool per operation whose
securitylistsapiKeyororganizationToken. Browser-only operations (sessions only, such as API key management), person-only ones (device sessions) and public ones (sign-in, discovery) have no tool. - Names: the
operationIdin snake case:reservationRooms.checkInbecomesreservation_rooms_check_in. The title is the operation’s summary; the description is the summary, the operation’s description (which names the required permission) and its method and path. - Arguments: one object with
path(path parameters),query(query parameters),body(the JSON request body),if_match(theIf-Matchversion),idempotency_key(Idempotency-Key) andworkspace_id(GuestCraft-Workspace-Id, overriding the default workspace), each present when the operation takes it and required when the operation requires it. Referenced component schemas are copied into the schema’s$defs, only those it reaches. Arguments are validated against the schema before the call; the API validates them again. - Permissions: each tool keeps the permissions its operation declares in
x-guestcraft-permissions, of which every call needs one; the scope check of OAuth tokens uses them. An instance before API revision 6.12.0 declares none, and nothing is checked before the call. - Answers: a JSON answer is the tool result’s
structuredContent(a list ornullis wrapped as{ "data": … }) and a JSON text block; a CSV report is its text. An API problem is a result withisError: trueand the problem body:status,code,detail, fielderrorsandrequest_id. It never carries the credential, a request header or the request body. - Resources: every GET tool without a required query parameter is also a
resource,
guestcraft://v1/<path>, a template when the path has parameters (guestcraft://v1/properties/{id}). Reads use the server’s credential and default workspace.
Write marking
Section titled “Write marking”Every operation of the API declares what it does to the instance’s state, as
x-guestcraft-effect in the OpenAPI document: read, create (something new, nothing else
changed), update (a change or replacement of what exists), transition (a check-in, a
cancellation, a revocation, opening a drawer, …) or delete. The tool annotations follow it
(annotationsOf in src/catalog.ts):
| Declared effect | readOnlyHint |
destructiveHint |
idempotentHint |
|---|---|---|---|
read (also a quote or a simulation sent as POST) |
true | — | true |
create |
false | false | true for PUT, else false |
update, transition, delete |
false | true | true for PUT and DELETE, else false |
| none: a mutation of an instance before API revision 6.2.0 | false | true | as above |
The description of every write tool starts with “Writes data”, and of a destructive one with “Writes data: a state transition or a deletion, which may not be reversible”. A mutation that declares no effect counts as destructive, and every destructive tool runs only once confirmed.
Composed tools
Section titled “Composed tools”availability_and_quotes, find_reservation, change_booking, check_in_readiness,
check_out_readiness and room_status (src/composed.ts) each call several
public operations with the caller’s transport. A composed tool is listed only when the instance’s
document has every operation it calls. A refusal of an operation it always calls fails the call;
one of a part (a quote, a unit’s status, a balance) is reported in its result. Their inputs are Zod schemas, published as JSON Schema.
change_booking is destructive; its preview is GuestCraft’s own dry run
(reservationRooms.update with validate_only=true), and a change the dry run refuses is refused
at once.
Confirmation
Section titled “Confirmation”A destructive tool’s first call changes nothing (src/gate.ts): it runs the tool’s
preview and issues a challenge, HMAC-SHA256-sealed with the server’s key over the caller’s
binding, the tool, a digest of its arguments in canonical JSON (so if_match and every body field
count) and a random nonce, valid for ten minutes (src/confirmation.ts).
- A client that offers form elicitation (its capabilities, per request on revision 2026-07-28, or
from the handshake of a 2025 connection) receives an
input_requiredresult asking one yes/no question, with the challenge asrequestState; the SDK’srequestState.verifyhook refuses a forged, expired or foreign state with-32602. On a 2025 stdio connection the SDK’s shim asks the client directly. Accept runs the tool; decline and cancel use the challenge without running. - Any other client, including a stateless 2025 HTTP request, receives the challenge as
confirmation_token, an optional argument of every destructive tool, which the model sends back with the same arguments.
The ConfirmationLedger port claims each nonce once and records the outcome, identifiers and
status only; a retry answers that outcome and never runs again. The key and the ledger must be
shared by every process that serves the same callers: @guestcraft/server derives the key from
the instance’s secret and keeps the ledger in PostgreSQL. The default, a random key and
memoryConfirmationLedger, suits one process, as stdio is.
Security model
Section titled “Security model”- Every call goes to the public API with the caller’s credential (or, in the built-in server, the
caller’s principal in process) and no cookie, so the API’s own checks decide: an API key acts in
its workspace, with its permissions and properties, narrowed to what its creator still holds;
an organization token in the workspaces it reaches; and, in the built-in server, an OAuth
application’s access token for the member who authorized it, within the grant they gave and
what they still hold. The MCP server holds no other credential and widens nothing. A tool the credential may not use answers
403 auth.forbiddenas an error result. - Over HTTP, each request is authenticated first and served by a fresh server with that caller only; the shared parts are the public OpenAPI document, the argument validator and the confirmation key and ledger. Nothing of a caller outlives its request.
- The environment is read only by the command (
src/main.ts); the library takes options.
pnpm --filter @guestcraft/mcp typecheckpnpm --filter @guestcraft/mcp testpnpm exec vitest run --project server-integration packages/server/test/integration/mcp.integration.test.tspnpm exec vitest run --project server-integration packages/server/test/integration/mcp-endpoint.integration.test.tsThe unit tests drive the server with the real MCP client in process (as over stdio, and over
HTTP through handler.fetch and a loopback socket), in both protocol eras, against a fake instance
behind an injected fetch: the confirmation by token and by elicitation, and the refusal of
forged, foreign, stale and replayed confirmations; the composed tools against the repository’s
document. A contract test checks that every operation of openapi/v1.json
maps to exactly one tool or is excluded because no integration credential may call it, and that
exactly the updates, transitions and deletions need a confirmation. The server’s integration
tests drive this package against the composed HTTP runtime on PostgreSQL, through stdio and
through the built-in /api/mcp endpoint.