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).
Install
Section titled “Install”Until v1 the extension ships as a pre-release .vsix. See Publishing for making it downloadable.
-
Get the
.vsix. Choose one:-
download
stayfn-vscode-<version>.vsixfrom a GitHub release of the repository (everyv*tag attaches one); -
download the
stayfn-vsixartifact of thevscode-extensionCI 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
-
-
Install it with Extensions → … → Install from VSIX…, or
code --install-extension stayfn-<version>.vsix. -
Requirements: VS Code 1.95 or later, the .NET 8 SDK, Docker (for the local PostgreSQL), and C# Dev Kit for C# editing.
-
The
stayfnCLI (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 hasread: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-textdotnet tool install -g stayfn --add-source https://nuget.pkg.github.com/rizanc/index.jsonstayfn new project MyHotelFunctions # then open the folderThe extension finds the CLI through the workspace
.mcp.json(the project’s namesstayfn), the settingstayfn.mcpPath, the repository build, or the installed tool on thePATHand, when VS Code was started without it, in~/.dotnet/tools.
The extension activates in a workspace that contains a .csproj or a .mcp.json.
Getting started
Section titled “Getting started”The Get started with StayFn walkthrough (Help → Welcome → Walkthroughs) has the same steps:
- Install the .NET 8 SDK and the
stayfntool, create a project withstayfn new project MyHotelFunctionsand open it (or open this repository). A project fromstayfn new projecthas afunctions/project on the StayFn SDK packages, atests/project, a.mcp.jsonpointing atstayfn mcp stdio --root .andstayfn.functionsProjectalready set. The walkthrough steps Install the .NET 8 SDK and Install the stayfn CLI complete oncedotnetand the CLI are found (stayfn.dotnetFound,stayfn.cliFound). - StayFn: Start Dev Host runs
stayfn dev --root <workspace> --url <stayfn.devHostUrl> --env <stayfn.environment>in a terminal: PostgreSQL viadocker composewhen 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>. - StayFn: New Function asks for the name, area, description, OHIP operation (searched with
catalog.search) and the input and output members, then calls MCPfunction.scaffold, opens the class and offers Validate. - 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, anIdempotency-Keyand anX-Correlation-Id, and shows the output and the invocation id. - 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) |
Full-window catalog
Section titled “Full-window catalog”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.
- its typed call (
- 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.
Invocation webview
Section titled “Invocation webview”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.
Commands
Section titled “Commands”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.
Editor features
Section titled “Editor features”- 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.jsonand function input fixtures undertests/fixtures/**/*.json. - Task and problem matcher: the
stayfntask type ({ "type": "stayfn", "command": "validate", "file": "…" }, or Tasks: Run Task → stayfn: validate for the active file) printspath(line,col): error STAYFN001: …lines that the$stayfn-validateproblem 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).
Code fixes
Section titled “Code fixes”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.
Settings
Section titled “Settings”| 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 and privacy
Section titled “Telemetry and privacy”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.
Troubleshooting
Section titled “Troubleshooting”- 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-tokenexpired (12 h) or belongs to another run; restartstayfn dev. - No MCP server found: set
stayfn.mcpPathtoStayFn.Cli.dll(or thestayfntool), add thestayfnserver 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 devrebuilds 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.Analyzersas an analyzer.
Publishing
Section titled “Publishing”There are three ways to make the extension downloadable. They can be combined.
1. GitHub release (private, works today)
Section titled “1. GitHub release (private, works today)”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:
git tag v0.8.0 # must equal VersionPrefix in Directory.Build.props (tools/ci/release-version.sh checks it)git push origin v0.8.0The 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.
-
Create a publisher at <https://marketplace.visualstudio.com/manage/createpublisher> with a Microsoft account. Its ID must equal
publisherintools/vscode-stayfn/package.json(todayCostinRizan), or change the manifest to match. -
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.
-
Prepare the listing:
- Licence.
LICENSE.mdlets anyone install and use the extension free of charge, and forbids redistribution and modification.npm run packagewritesTHIRD_PARTY_NOTICES.md(the bundled open-source packages and their licences) into the.vsix. - Images.
vscerewrites 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.mdits Changelog tab, andmedia/icon.pngits icon."preview": trueshows a Preview badge.
- Licence.
-
Publish:
Terminal window (cd ui/shared && npm ci)cd tools/vscode-stayfn && npm cinpx vsce login CostinRizan # paste the token once; or set VSCE_PAT for a single commandnpx vsce publish --pre-release --no-dependencies # or: npx vsce publish --packagePath stayfn-<version>.vsix--pre-releasepublishes 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:
-
Sign in at <https://open-vsx.org> with GitHub, link an Eclipse account and accept the publisher agreement.
-
Create an access token under your profile.
-
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.
Development
Section titled “Development”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.