stata-code — VSCode extension
Run Stata code from VSCode through the agent-native stata-code MCP server.
Status: v0.12 (July 2026). Full UI surface — orange Stata title-bar
buttons, editor context menu, status bar with session/cancel actions, activity-bar
sidebar with sessions / last result / data (variables) / run history / logs / graphs / outputs panels,
syntax highlighting, outline/section navigation, code-lens cells and sections,
inline error squigglies, graph webview Save/Open actions, run-bundle export,
and Stata working-directory helpers. Source build with
npm install && npm run compile.
What it does
A thin VSCode transport in front of the same stata-code-mcp server
that Claude Code / Cursor use. The extension owns no Stata logic;
everything goes through MCP stata_run, get_log, get_graph,
get_matrix, list_sessions, cancel_session, reset_session.
Because it is a human-facing surface rather than an agent one, the extension
requests include_results: "full" — matrices stay inline instead of becoming
the matrix:// stubs the server returns by default, so the sidebar's matrix
view has values to render. The Jupyter kernel does the same, for the same
reason.
The Outputs panel draws on both of the server's artifact sources: the files
a run wrote in its working directory (result.outputs, reported on every run
since 0.11, shown with their size) and the archived copies inside a run bundle
(log.files.output_paths, present only when persist_log_files was requested,
labelled bundle). Working-directory files are listed first — that is the file
you actually want to open.
UI surface
Editor
| Affordance |
What it does |
| Orange toolbar buttons in editor title bar |
Run selection/current line, view data preview, stop, new tab, switch tab, restart, show output, and working-directory actions |
| Run button in editor title bar |
Run all / active file |
| Right-click menu → Stata: Run Selection / Run Active File / View Data Preview |
Same, from the editor |
| Right-click menu → Stata: Working Directory... |
Show or change Stata's current directory |
Cmd/Ctrl+Enter or Cmd/Ctrl+D |
Run selection (or current line if no selection) |
Cmd/Ctrl+Shift+Enter |
Run the current hierarchical section |
Inline ▶ Run Cell code-lens above any * %% line |
Run that cell |
Inline ▶ Run Section code-lens above **# headings |
Run from that heading to the next equal/higher heading |
Outline view entries for **# headings and program define blocks |
Navigate long .do / .ado files |
| Completion |
Built-in commands, configured community commands, functions, variables from the last result, and variables inferred from the open document |
| F2 rename on recognized variables |
Rename variables across the current document while skipping comments and Stata commands |
| Red squiggle on the failing line |
After a failed run; hover to see the typed-error message and suggestions |
| Stata: View Data Preview |
Opens the data in the active session's memory in the data viewer (first stataCode.dataPreviewObs rows), with labels, formats and notes intact |
Opening any *.dta file |
Shows it in the data viewer — no Stata needed (see below) |
Data viewer for .dta files
Double-click a .dta file in the Explorer and it opens in a grid,
the way Stata's Data Browser would show it. The file is read directly, from
StataCorp's published format specification, so this works without Stata or
Python installed and without the MCP server running.
Nothing in the file is lost on the way to the screen:
- Variable labels sit under each column name, and in the variables panel.
- Value labels replace the codes they stand for (
1 → Married), in a
distinct colour; untick Value labels to see the underlying numbers.
- Display formats are honoured:
%td / %tc / %tm / %tq dates
(including custom ones such as %tdCCYY-NN-DD), %9.2f, %12.0gc, and so on
print as Stata prints them.
- Missing values stay distinct —
., .a … .z — and a value label on
an extended missing value is applied.
- Notes, the dataset label, the sort order and the save timestamp appear
in the panel on the right; click a variable there to jump to its column and
see its type, format, notes and full value-label mapping.
- strL variables and non-ASCII text (UTF-8 in format 118+; auto-detected
UTF-8 / GB18030 / Windows-1252 in older files) are decoded.
Observations are fixed-width records, so only the rows on screen are read:
a multi-gigabyte file opens as fast as a small one. Go to row jumps
anywhere; arrow keys, Page Up/Down and Home/End move the selection;
Cmd/Ctrl+C copies the selected cell. If Stata re-saves the file while it is
open, the viewer reloads.
Filter, sort, summarize. The bar under the title takes a Stata if
expression and keeps the rows that satisfy it:
age > 60 & !missing(income)
region == "South":regionlbl
inlist(city, "Boston", "北京") | joined >= td(01jan2020)
It follows Stata's rules, not JavaScript's — a missing value is larger than
every number (so age > 60 keeps missing ages, exactly as count if does),
arithmetic on a missing value is missing, and comparing a string with a number
is a type mismatch. The evaluator is tested against Stata 18's own
count if on 80 expressions. Supported: the operators
! ~ ^ - * / + == != ~= < <= > >= & |, _n / _N, "text":labelname, and
missing mi inlist inrange strpos regexm strmatch lower upper trim strlen ustrlen substr abs floor ceil int round mod min max sqrt exp ln log10 mdy td.
A variable can be named by any abbreviation only it starts with (gro for
growth_flag); an ambiguous one is refused, as in Stata. On a dataset that
was tsset or xtset, the time-series operators work too: L.sales,
L2.sales, F.sales, D.sales, S12.sales, and runs such as L2D.sales.
The lag is found by panel and time, whatever order the rows are saved in.
Macros and operator lists (L(1/3).x) are not supported.
Hover a column header and click its arrow to sort (ascending, descending,
off); Shift-click adds a secondary key. Sorting is by the underlying values
with missing values last, as Stata's sort does, and ties keep their dataset
order. The row-number gutter keeps showing each row's observation number in
the file. Drag a header's right edge to resize the column; double-click it to
fit.
Selecting a variable shows a summary of it over the rows in view — count,
missing, distinct, mean, standard deviation, min, quartiles, max (equal to
summarize, detail), and for categorical variables the most frequent values;
click one to filter to it.
Copy and export. Drag, Shift-click or Shift-arrow to select a range;
Cmd/Ctrl+C copies it as tab-separated text and Cmd/Ctrl+Shift+C includes
the variable names. The Export menu writes:
- the current view as CSV — filtered and sorted as shown, numbers at full
precision rather than display precision, dates as dates, and value labels
as text when Value labels is ticked;
- the codebook — one row per variable with its type, format, value label,
variable label and notes, plus a second file with every value-label mapping.
This is the half of a dataset a CSV cannot carry.
Variable labels can be edited in place. Select a variable, click Edit
beside its label in the details panel (or double-click the variable in the
list), type, and press Enter; Esc cancels. The label is written straight into
the file. Only that label's fixed-width field is overwritten, so the data,
value labels, notes and formats keep their bytes, and no Stata is involved.
Labels follow Stata's limit of 80 characters; files in formats older than 118
take ASCII only, since those formats do not say which encoding they use.
So can value labels and the dataset label. In a numeric variable's details
the Value label list attaches any of the file's value labels, none, or a new
one. Edit above the table of codes turns it into a list with one
code label pair per line (a code is an integer or .a to .z); Save to
file or Ctrl/Cmd+Enter replaces the whole set, which changes it for every
variable that uses it. Drop removes a value label after asking and detaches
it from the variables that used it. The dataset label has its own Edit in
the dataset details (shown when no variable is selected).
Many labels at once go through the codebook. Export codebook writes one
row per variable; edit the label and value_label columns in a spreadsheet
(and the codes in the _value_labels.csv beside it), then choose Import
labels from a codebook CSV… in the same menu. The viewer shows how many
labels would change and applies them as one edit. Only the name column is
required: a column that is absent is left alone, and so is a variable or a
value label the files do not mention.
Every edit shows what it did in the status bar with an Undo button, and
the viewer keeps the last 50 edits to take back. Attaching a value label
overwrites one fixed-width field in place. Changing a value label's contents
or the dataset label changes the length of the file, so a new copy is written
beside it and swapped in with one rename; a crash leaves either the old file
or the new one. In both cases the observations, notes and formats are copied,
never re-encoded. The data cells are not editable. Agents get the same
operations in bulk through the set_variable_labels and set_value_labels
MCP tools.
Load in Stata (also on the Explorer context menu of any .dta) runs
use "<file>", clear in the current session, asking first if that session has
unsaved changes.
Filtering, sorting and summaries read the whole column. They are bounded by
memory, not by a row count: stataCode.dtaViewerMemoryMb (default 2048)
is what they may hold at once. A filter keeps 4 bytes per matching row and a
sort about 20 bytes per row, so the default sorts roughly 80 million rows.
Past the limit a filter or sort stops and says how much it needed; a summary
still gives the count, mean, standard deviation, minimum and maximum, and
leaves out the percentiles. Browsing has no limit. A descending sort keeps
missing values at the end, as gsort does.
Supported formats: 113, 114, 115 (Stata 8–12), 117 (Stata 13), 118 and 119
(Stata 14+), 120 and 121 (Stata 18 alias variables). Formats 102 to 111
(Stata 1 to 7) open read-only: they can be browsed, filtered, sorted and
summarized, and their labels cannot be edited in place. To open a .dta as
raw bytes instead, use Reopen Editor With….
View Data Preview uses the same viewer for data that is only in memory: it
copies the first rows through a scratch frame into a temporary .dta (your
frame, its sort order, c(filename) and c(changed) are untouched) and opens
that. On Stata 15 or older, and with the console backend, it falls back to a
text listing.
Custom keyboard shortcuts. All shortcuts can be changed with VS Code's
Preferences: Open Keyboard Shortcuts command. Search for
Stata: Run Selection / Current Line, click the pencil icon, and enter the
preferred key combination. The command ID for editing keybindings.json
directly is stataCode.runSelection. The built-in Cmd/Ctrl+D binding only
applies while a Stata editor has focus; a user-defined shortcut takes
precedence if another extension or VS Code command conflicts with it.
Cell convention. A line whose trimmed text is * %% (optionally
followed by a title, e.g. * %% setup) marks the start of a cell. The
marker line is a Stata comment, so plain Stata still runs the file
unchanged. The cell ends at the next marker or EOF. This is the Stata
analog of Python's # %% cells.
Section convention. Lines beginning with **#, **##, ..., **######
are hierarchical section headings. Running a section includes the heading
comment line and stops before the next heading at the same or higher level.
The Outline view shows the same hierarchy, with program define blocks nested
under the section that contains them.
Status bar
A left-aligned $(database) Stata: <session> chip is always visible
once the extension activates. Click it for a QuickPick with compact
actions:
- New Stata tab… / Switch tab… (live sessions + locally-known
"not started" tabs, plus New tab…)
- View data preview (first
stataCode.dataPreviewObs rows, in the data viewer)
- Export latest run / Open latest log / Show latest graphs
- Working directory… / Show output channel
- Cancel
<sid> / Reset <sid> / Close tab <sid>
While a run is in flight, the icon swaps to $(sync~spin) and the
notification toast has a Cancel button (cooperative cancellation via
the MCP cancel_session tool).
A new entry on the activity bar opens a sidebar with seven sections:
- Sessions — live
list_sessions view with the current session
highlighted; header buttons for New / Refresh and per-item actions for
Switch / Cancel / Reset / Close. Sessions you've used in this workspace
but that aren't currently live still appear as "not started".
- Last Result — collapsible groups for
r() / e() returns,
matrices, warnings, dataset summary (with variable list), a clickable
log leaf (opens the run's full log via get_log(ref)), a clickable
graphs leaf, and on failure a typed error block with the failing line
and suggestions. Matrix entries open on demand via get_matrix(ref) as
TSV text.
- Data — a persistent variables browser for the dataset currently in
memory: a summary row (
N obs × K vars, frame, and a "modified" flag)
followed by one row per variable, each with a type-aware icon
(numeric vs string), its variable label as the description, and a
tooltip with the full name · type · label. A display format that is not
the storage type's default (%td, %9.2f) and an attached value label
([origin]) are shown too. Click a variable to copy
its name; use the title-bar buttons to open the full data preview or
refresh. The view tracks the last run, so it updates as the data
changes — the agent-native equivalent of Stata's Variables window.
- Run History — recent runs (capped at 64). Each item can rerun the
exact submitted code, copy the code, open the JSON result, or export a
reproducible run bundle.
- Logs — recent runs (capped at 64) tagged with success/failure and
line count; click to open the full log in an editor pane, or save it
from the item context menu.
- Graphs — every captured graph in reverse-chronological order
(capped at 64); click any item to open it in the graph webview, or
save it directly from the item context menu.
- Outputs — table and export artifacts written during your runs
(
esttab/outreg2 tables, export delimited/export excel files,
saved .dta, figures exported to disk), newest-first with a format-aware
icon and file size. Click to open the file, or reveal it in
Finder/Explorer from the item context menu. Since 0.12 this is populated
on every run from result.outputs — the files written in the working
directory — and no longer needs run-bundle logging. When
persist_log_files=true is also on (as the method-workflow MCP prompts
set it), the bundle's archived copies appear too, tagged bundle and
listed after the working-directory originals.
Run bundles
Any run in Run History can be exported to a folder:
stata-run-20260507T213000Z-abc123/
code.do
log.txt
result.json
manifest.json
graphs/
01-scatter.png
This is intentionally simple: it captures exactly what was submitted and
what came back, without turning the extension into a notebook system.
Working directory
Stata: Working Directory... offers four focused actions:
- show current Stata
pwd
cd to the workspace folder
cd to the current file's folder
- choose a folder with VS Code's native picker
These utility commands run in the active Stata session but do not add
noise to Run History / Logs / Graphs.
Graph webview
Successful runs with one or more graphs auto-open a side webview that
fetches each graph's bytes via get_graph(ref) lazily. v0.2 adds:
- Per-graph Save as… button → native save dialog → writes PNG / SVG / PDF
- Per-graph Open externally button → writes to OS temp + opens with the
default app
- Per-graph collapsible result block for hiding old figures while comparing
- Top-level Refresh button → re-fetches the current panel
The webview uses a strict CSP with a per-render nonce. Graph bytes
never leave the local pystata / MCP boundary.
Setup
cd vscode
npm install
npm run compile
For local development, open this vscode/ folder in VSCode and F5 to
launch an Extension Development Host. The first command invocation
spawns stata-code-mcp lazily (one process per workspace). Make sure
stata-code-mcp is on PATH or override. The extension also expands
common macOS Python script directories (~/.local/bin, Homebrew, and
~/Library/Python/*/bin) before spawning, and falls back to
workspace .venv / venv interpreters, stataCode.pythonPath, the
Python extension interpreter, and system python3 -m stata_code.mcp
when the default console script cannot be found.
// settings.json
{
"stataCode.serverCommand": "/abs/path/to/python",
"stataCode.serverArgs": ["-m", "stata_code.mcp"]
}
To diagnose PATH or virtualenv mismatches without launching Stata, run the
read-only package doctor in the Python environment the extension should use:
stata-code doctor --no-stata-probe
It reports whether stata-code-mcp is on PATH, whether the MCP extra is
installed, where pystata would be discovered, which project/user MCP config
files mention stata-code, and which absolute-path / python -m stata_code.mcp fallback is safest for GUI clients.
Configuration
| Key |
Default |
Purpose |
stataCode.serverCommand |
stata-code-mcp |
Command to spawn the MCP server |
stataCode.serverArgs |
[] |
Extra args passed to the server process |
stataCode.pythonPath |
"" |
Fallback Python interpreter for -m stata_code.mcp |
stataCode.sessionId |
"main" |
Session id passed to stata_run (also driven by the status bar's Switch session…). Must match [A-Za-z0-9_-]+. |
stataCode.dataPreviewObs |
1000 |
Rows View data preview copies from memory into the data viewer (1–100000). Shorter datasets are shown in full. The text fallback is capped at 200 rows. |
stataCode.dtaLegacyEncoding |
"auto" |
Text encoding for .dta formats older than 118, which predate UTF-8. auto tries UTF-8, then GB18030, then Windows-1252; set e.g. gbk, big5, shift_jis or windows-1252 to force one. |
stataCode.includeFullLog |
false |
Inline full log instead of fetching via get_log(ref) |
stataCode.persistLogFiles |
true |
Save file-backed runs as immutable log bundles next to the source file |
stataCode.persistGeneratedFiles |
true |
Copy newly-created tables/exports and captured graphs into the run bundle |
stataCode.useDoFileDirectory |
true |
cd Stata to the source .do file's directory before file-backed runs |
TypeScript types
src/types/runResult.ts is the hand-rolled subset the extension
consumes. The full machine-readable schema lives at
../schema/run_result.schema.json (generated from the Pydantic model
by python scripts/export_schema.py in the repo root). To regenerate
the full TypeScript types side-by-side as src/types/runResult.generated.ts:
npm run gen-types
Diff the two and pull in any newly-added fields by hand.
Architecture
VSCode editor / sidebar / status bar / webview
│
▼ (high-level callTool)
StataMcpClient (src/mcpClient.ts)
│
▼ (stdio JSON-RPC, MCP)
stata-code-mcp (Python subprocess)
│
▼ (in-process)
pystata → Stata 17+
UI modules:
| File |
Responsibility |
src/extension.ts |
activation, command registration, run pipeline |
src/mcpClient.ts |
MCP transport (stdio child process) |
src/statusBar.ts |
status bar item + running-state spinner |
src/treeProviders.ts |
sessions / last-result / data / run-history / logs / graphs / outputs sidebar trees |
src/dataBrowser.ts |
variables-window data nodes for the Data view |
src/outputs.ts |
table/export artifact nodes for the Outputs view |
src/dtaReader.ts |
Stata-free .dta parser (formats 102–111 read-only, 113–115, 117–121), random-access row reads |
src/webviewHarness.ts |
runs the viewer's page in headless Chrome against the real session (npm run test:webview) |
src/dtaFormat.ts |
Stata display formats (%td, %9.2f, %12.0gc, …) |
src/dtaFilter.ts |
Stata if expression evaluator for the row filter |
src/dtaQuery.ts |
filter + sort row order, per-variable summary statistics |
src/dtaExport.ts |
CSV, codebook and clipboard exports |
src/dtaImport.ts |
Label edits read back from an edited codebook CSV |
src/dtaWriter.ts |
in-place variable-label edits |
src/dtaViewModel.ts |
shapes metadata and formatted row blocks for the viewer |
src/dtaEditor.ts |
custom editor + in-memory snapshot panel |
media/dtaViewer.{js,css} |
the viewer's virtual grid and variables panel |
src/cellLens.ts |
* %% code-lens provider |
src/diagnostics.ts |
inline error squigglies |
src/graphPanel.ts |
graph webview (Save / Open / Refresh) |
Status / planned polish
.do notebook editor (kept deliberately minimal in favor of code-lens cells)
- Full paged/filterable dataset table view (the current button is a first-100-row preview)
- Rich matrix formatting beyond TSV
- Extension-host tests (planned)
License
MIT, same as the parent project.