Skip to content

VS Code extension

The StayFn extension (tools/vscode-stayfn, publisher CostinRizan, plan Phase 7b) brings the function catalog, scaffolding, validation, local runs and invocation inspection into the editor. It is a thin client: it talks only to the local dev host (stayfn dev, HTTP) and to the StayFn MCP server (stayfn mcp stdio, the same server Claude Code uses). It never calls OHIP itself and has no language server; C# language features come from C# Dev Kit / OmniSharp plus the StayFn.Analyzers package (diagnostics and code fixes).

Until v1 the extension ships as a pre-release .vsix. See Publishing for making it downloadable.

  1. Get the .vsix. Choose one:

    • download stayfn-vscode-<version>.vsix from a GitHub release of the repository (every v* tag attaches one);

    • download the stayfn-vsix artifact of the vscode-extension CI job;

    • build it yourself. The extension bundles components from ui/shared, so install that first:

      Terminal window
      (cd ui/shared && npm ci)
      cd tools/vscode-stayfn && npm ci && npm run package # writes stayfn-<version>.vsix
  2. Install it with Extensions → … → Install from VSIX…, or code --install-extension stayfn-<version>.vsix.

  3. Requirements: VS Code 1.95 or later, the .NET 8 SDK, Docker (for the local PostgreSQL), and C# Dev Kit for C# editing.

  4. The stayfn CLI (outside the repository). It is a .NET tool on GitHub Packages and carries the host and the OHIP specs, so nobody needs the private repository. With a GitHub personal access token (classic) that has read:packages:

    Terminal window
    dotnet nuget add source https://nuget.pkg.github.com/rizanc/index.json --name stayfn-github \
    --username <your GitHub user name> --password <token> --store-password-in-clear-text
    dotnet tool install -g stayfn --add-source https://nuget.pkg.github.com/rizanc/index.json
    stayfn new project MyHotelFunctions # then open the folder

    The extension finds the CLI through the workspace .mcp.json (the project’s names stayfn), the setting stayfn.mcpPath, the repository build, or the installed tool on the PATH and, when VS Code was started without it, in ~/.dotnet/tools.

The extension activates in a workspace that contains a .csproj or a .mcp.json.

The Get started with StayFn walkthrough (Help → Welcome → Walkthroughs) has the same steps:

  1. Install the .NET 8 SDK and the stayfn tool, create a project with stayfn new project MyHotelFunctions and open it (or open this repository). A project from stayfn new project has a functions/ project on the StayFn SDK packages, a tests/ project, a .mcp.json pointing at stayfn mcp stdio --root . and stayfn.functionsProject already set. The walkthrough steps Install the .NET 8 SDK and Install the stayfn CLI complete once dotnet and the CLI are found (stayfn.dotnetFound, stayfn.cliFound).
  2. StayFn: Start Dev Host runs stayfn dev --root <workspace> --url <stayfn.devHostUrl> --env <stayfn.environment> in a terminal: PostgreSQL via docker compose when it is not reachable, the host in the Development profile (migrations, the development tenant, /api/dev/*), a rebuild and restart whenever the functions project changes, and an owner dev token in .stayfn/dev-token (git-ignored, 12 h). The status bar turns to ● StayFn <environment> / <hotel>.
  3. StayFn: New Function asks for the name, area, description, OHIP operation (searched with catalog.search) and the input and output members, then calls MCP function.scaffold, opens the class and offers Validate.
  4. Click ▶ Invoke above the [OhipFunction] attribute: the input editor is pre-filled from the input schema; Run sends it to the dev host with the selected hotel, an Idempotency-Key and an X-Correlation-Id, and shows the output and the invocation id.
  5. Show Invocation (or a click in the Invocations view) opens the invocation webview with the OHIP calls timeline.

Functions that call OHIP need the dev tenant’s environment: OHIP_<ENVIRONMENT>_* variables in .env (see .env.example). Without them stayfn dev says so and the dev tenant is not seeded.

The StayFn activity-bar container has five views:

View Shows Actions
Catalog OHIP modules → operations from the dev host catalog; hover shows the flat C# call (ctx.Ohip.Api.<Module>.<Op>Async) with its signature and any deprecation, the Kiota chain, and the request/response types (MCP ohip.operation) Search Catalog (magnifier), Clear Catalog Search, Open Catalog (full window), Insert Typed Call Snippet, Show OHIP Operation
Functions Functions, handlers and scheduled functions from the registry merged with a scan of [OhipFunction]/[EventHandler] in the workspace, grouped by area; unregistered files are marked; STAYFN diagnostics as badges Open Function Source, Invoke, Validate, Last 10 Invocations
Invocations The live feed (latest page + server-sent events), filterable by function, hotel and status Show Invocation, Replay Dead Letter, Filter Invocations
Events Inbox/outbox counts, recent events, checkpoint lag per hotel Send Test Event
Variables Tenant, environment and hotel scoped variables; secrets show as ••• Edit Variable (secrets: the reference only)

StayFn: Open Catalog, or the preview icon on the Catalog view, opens every OHIP operation in an editor tab. The tab has these parts:

  • Search box that filters as you type. Every word must match the module, operation, method or path, for example rsv post searches.
  • Table of all operations (about 2,800) with Module, Operation, Method and Path. Click a column header to sort, and again to reverse.
  • Detail pane for the selected operation, from MCP ohip.operation:
    • its typed call (ctx.Ohip.Api.<Module>.<Op>Async(…)) and signature;
    • any deprecation with its replacement;
    • whether it is hotel-scoped.
  • Insert call puts that call into the last C# file you worked in, at the cursor where you left it. It reopens that file in its column first, since the catalog tab may have taken its place. The snippet is the same one Insert Typed Call Snippet inserts.
  • Full schema opens Show OHIP Operation.
  • Refresh reloads the catalog from the dev host.

The tab is an ordinary editor tab, so it can be maximised, split, or moved to its own window with View: Move Editor into New Window.

Show Invocation opens one reused panel (React, the same @stayfn/shared components as the dashboard’s drill-down): the facts (correlation id, hotel, attempt, duration, sizes, idempotency key, parent), the error, the dead letter with its payload and Replay when the invocation failed or is dead, the input and output (or problem) of invocations started from this window, and the OHIP calls timeline (offset, method, operation, path template, status, duration, retries, rate limiting). The host stores payload sizes, not payloads, so input/output appear only for invocations this window started; they stay in memory (last 50) and are never written to disk. Every payload is masked on display: values under personal-data keys (names, e-mail, phone, address, birth date, document and card numbers) become •••, and e-mail addresses, card numbers and phone numbers inside other strings are replaced as PiiMasker does on the server.

All commands are in the palette under StayFn:.

Command Id What it does
New Function stayfn.newFunction Wizard → MCP function.scaffold (function); opens the class, offers Validate
New Event Handler stayfn.newEventHandler Wizard (event name) → function.scaffold (handler)
New Scheduled Function stayfn.newScheduledFunction Wizard (6-field cron) → function.scaffold (scheduled)
Validate Current File stayfn.validateCurrentFile MCP function.validate → Problems, with help links
Invoke Function… stayfn.invokeFunction Pick → JSON input from the schema → hotel → run on the dev host → output and invocation id
Start Dev Host stayfn.startDevHost stayfn dev in a terminal
Stop Dev Host stayfn.stopDevHost Closes that terminal
Reload Dev Host stayfn.reloadDevHost POST /api/dev/reload (restart with the latest build)
Open Dashboard stayfn.openDashboard The dev host’s /ui in the Simple Browser
Regenerate OHIP SDKs stayfn.regenerateSdks tools/kiota/generate.sh in a terminal
Run Equivalence Report stayfn.runEquivalenceReport stayfn equivalence run (Phase 7 legacy harness)
Export Catalog Docs stayfn.exportCatalogDocs docs/functions/<area>.md and a README with schemas
Refresh Catalog stayfn.refreshCatalog Reloads the Catalog view
Search Catalog… stayfn.searchCatalog Filters the Catalog view across every module: every word must match the module, operation, method or path. Matching modules open, and the view header shows the search and its match count. Empty clears it
Clear Catalog Search stayfn.clearCatalogSearch Removes the Catalog search (shown while one is active)
Open Catalog stayfn.openCatalog Opens the full-window catalog in an editor tab (see below), starting from the Catalog view’s search
Refresh Functions stayfn.refreshFunctions Reloads the Functions view
Refresh Invocations stayfn.refreshInvocations Reloads the Invocations view
Refresh Events stayfn.refreshEvents Reloads the Events view
Refresh Variables stayfn.refreshVariables Reloads the Variables view
Insert Typed Call Snippet stayfn.insertCallSnippet Inserts the operation’s flat call ctx.Ohip.Api.<Module>.<Op>Async(…) at the cursor, with the Kiota chain as a comment (a server without the flat layer gives its call chain; a module without a client gives RawAsync)
Show OHIP Operation stayfn.showOperation The operation’s method, path, flat call, Kiota chain and types
Open Function Source stayfn.openFunction Opens the file of a function
Show Invocation stayfn.showInvocation The invocation webview
Replay Dead Letter stayfn.replayDeadLetter Replays the dead letter of a failed invocation
Filter Invocations… stayfn.filterInvocations Function / hotel / status filter of the feed
Last 10 Invocations stayfn.lastInvocations The last 10 invocations of a function
Send Test Event… stayfn.sendTestEvent POST /api/dev/events/test into the dev tenant’s inbox
Edit Variable… stayfn.editVariable Edits a variable (secrets blind; a 409 explains the version conflict)
Select Environment / Hotel… stayfn.selectHotel The hotel used for invocations

stayfn.newFunction / stayfn.newScheduledFunction also take an argument object, and stayfn.invokeFunction a second one, for automation (tasks, keybindings, the end-to-end suite): { name, area, description, operation, schedule, inputs, outputs } (inputs and outputs as name:type, … text) and (fullName, { input, hotel }) skip every prompt and return the scaffold result or the invocation outcome. activate() returns a read-only API (dev host state, a health probe, the Functions and Invocations views’ rows, the invocation webview’s last message) for other extensions and the tests.

  • CodeLens above every [OhipFunction] / [EventHandler]: ▶ Invoke (invokable functions only; handlers and scheduled functions are not on /api/functions/*), Validate and Last 10 invocations.
  • Snippets (C#): stayfn-function, stayfn-handler, stayfn-scheduled.
  • JSON schemas: stayfn.settings.json and function input fixtures under tests/fixtures/**/*.json.
  • Task and problem matcher: the stayfn task type ({ "type": "stayfn", "command": "validate", "file": "…" }, or Tasks: Run Task → stayfn: validate for the active file) prints path(line,col): error STAYFN001: … lines that the $stayfn-validate problem matcher turns into Problems.
  • Status bar: ● running / ○ stopped / starting with the environment and hotel (click: start the host, or select the hotel), and the OHIP rate-limit budget of the selected environment’s app key ($(pulse) available/capacity, warning colour below 20 % or while OHIP has blocked the key after a 429).

StayFn.Analyzers ships its code fixes (light bulb, Ctrl+.), described in analyzers.md: Replace HttpClient usage with ctx.Ohip (STAYFN001) and Make input type a record (STAYFN005). Add IFunctionContext parameter is no longer offered: since plain signatures a function without a context is valid. They need the analyzer package (or, in this repository, the analyzer project reference) and C# Dev Kit. The Functions view and CodeLens find [OhipFunction] and its long and qualified spellings. The same package generates the typed function client (ctx.Functions.Rsv().…Async), so IntelliSense lists every function of the workspace and of referenced function libraries.

Setting Default Meaning
stayfn.devHostUrl http://localhost:5080 Base URL of the dev host
stayfn.environment sandbox OHIP environment of the dev tenant (OHIP_<ENVIRONMENT>_* variables)
stayfn.hotel empty Hotel code for invocations (empty: the tenant’s default hotel)
stayfn.mcpPath empty The stayfn CLI for MCP and stayfn dev; empty = .mcp.json, then stayfn on PATH, then the repository build
stayfn.functionsProject empty The project stayfn dev watches (empty = auto-detect)
stayfn.telemetry false Opt-in usage telemetry; no sink ships (see below)

Telemetry is off by default (stayfn.telemetry = false). This version ships no telemetry sink: there is no telemetry package and no reporting code, so turning the setting on only records the choice. A future sink would send command names and error kinds only, never payloads, hotel codes, tenant ids, tokens or file contents. Independently of the setting, the extension only talks to the configured dev host and the local MCP server process; the dev token is read from .stayfn/dev-token and sent to the dev host only.

  • Views say the dev host is unreachable: start it (StayFn: Start Dev Host) and check stayfn.devHostUrl; the StayFn output channel logs every health probe transition.
  • 401 from the dev host: the token in .stayfn/dev-token expired (12 h) or belongs to another run; restart stayfn dev.
  • No MCP server found: set stayfn.mcpPath to StayFn.Cli.dll (or the stayfn tool), add the stayfn server to .mcp.json, or build the repository (dotnet build). The MCP server’s stderr goes to the StayFn output channel.
  • A new function does not appear: stayfn dev rebuilds on save and restarts the host; a failed build keeps the previous host running and prints the errors in the dev host terminal.
  • Functions that call OHIP fail with a configuration error: the dev tenant needs OHIP_<ENVIRONMENT>_* in .env.
  • Code fixes do not show: C# Dev Kit must be installed and the project must reference StayFn.Analyzers as an analyzer.

There are three ways to make the extension downloadable. They can be combined.

Push a version tag. .github/workflows/release.yml builds the extension with the release version stamped into its manifest and attaches stayfn-vscode-<version>.vsix to the GitHub release, next to the SBOMs:

Terminal window
git tag v0.8.0 # must equal VersionPrefix in Directory.Build.props (tools/ci/release-version.sh checks it)
git push origin v0.8.0

The repository is private, so only people with access to it can download the file. They install it with Install from VSIX…. Updates are manual: download the next release and install it over the old one. The same file is also produced on every push as the stayfn-vsix artifact of the vscode-extension CI job, which is kept for 30 days.

2. Visual Studio Marketplace (public, one-click install and auto-update)

Section titled “2. Visual Studio Marketplace (public, one-click install and auto-update)”

A Marketplace listing is public: anyone can find and install it from the Extensions view. There is no private listing.

  1. Create a publisher at <https://marketplace.visualstudio.com/manage/createpublisher> with a Microsoft account. Its ID must equal publisher in tools/vscode-stayfn/package.json (today CostinRizan), or change the manifest to match.

  2. Create a personal access token in an Azure DevOps organization (<https://dev.azure.com>, free): User settings → Personal access tokens → New token. Set Organization: All accessible organizations and Scopes: Marketplace → Manage.

  3. Prepare the listing:

    • Licence. LICENSE.md lets anyone install and use the extension free of charge, and forbids redistribution and modification. npm run package writes THIRD_PARTY_NOTICES.md (the bundled open-source packages and their licences) into the .vsix.
    • Images. vsce rewrites the README’s relative image links to the repository URL. For a private repository those links do not load on the Marketplace. The README therefore ships without the screenshot while the repository is private. To add it back, make the repository public, or host the screenshots elsewhere and package with --baseImagesUrl <public https URL>.
    • Content. The README is the listing’s Details tab, CHANGELOG.md its Changelog tab, and media/icon.png its icon. "preview": true shows a Preview badge.
  4. Publish:

    Terminal window
    (cd ui/shared && npm ci)
    cd tools/vscode-stayfn && npm ci
    npx vsce login CostinRizan # paste the token once; or set VSCE_PAT for a single command
    npx vsce publish --pre-release --no-dependencies # or: npx vsce publish --packagePath stayfn-<version>.vsix

    --pre-release publishes to the pre-release channel, which users opt into with Switch to Pre-Release Version. Drop it for v1. Every publish needs a higher version than the last one. The Marketplace recommends even minor versions for releases and odd ones for pre-releases.

3. Open VSX (VSCodium, Cursor, Gitpod, Theia and other VS Code builds)

Section titled “3. Open VSX (VSCodium, Cursor, Gitpod, Theia and other VS Code builds)”

These editors cannot use the Microsoft Marketplace. Open VSX is their registry:

  1. Sign in at <https://open-vsx.org> with GitHub, link an Eclipse account and accept the publisher agreement.

  2. Create an access token under your profile.

  3. Create the namespace once, then publish the same .vsix:

    Terminal window
    npx ovsx create-namespace CostinRizan -p <token>
    npx ovsx publish tools/vscode-stayfn/stayfn-<version>.vsix -p <token>

Automating Marketplace and Open VSX publishing

Section titled “Automating Marketplace and Open VSX publishing”

The release workflow can publish on every tag once the secrets exist: VSCE_PAT (the Azure DevOps token) and OVSX_PAT (the Open VSX token) as repository secrets, plus a step after the .vsix step that runs npx vsce publish --packagePath dist/*.vsix and npx ovsx publish dist/*.vsix. This step is not in the workflow yet: add it after the licence and image decisions above.

npm ci, then npm run lint, npm run format, npm test -- --run (vitest, a vscode mock), npm run build (esbuild: the extension and the webview), npm run test:integration (VS Code through @vscode/test-electron on the fixture workspace), and npm run package (.vsix). The full scenario (create → validate → invoke → see the invocation with its OHIP calls against a Testcontainers-backed dev host) is dotnet test tests/StayFn.VsCode.E2E after npm run build && npm run build:integration; CI runs it under xvfb. node scripts/screenshot-webview.mjs (with CHROME set) re-renders the README screenshot from the built webview.