mcp-stata 1.0.1__py3-none-any.whl → 1.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mcp-stata might be problematic. Click here for more details.

mcp_stata/discovery.py CHANGED
@@ -2,6 +2,7 @@ import os
2
2
  import platform
3
3
  import glob
4
4
  import logging
5
+ import shutil
5
6
 
6
7
  from typing import Tuple, Optional, List
7
8
 
@@ -41,7 +42,7 @@ def find_stata_path() -> Tuple[str, str]:
41
42
  raise FileNotFoundError(
42
43
  f"STATA_PATH points to '{path}', but that file does not exist. "
43
44
  "Update STATA_PATH to your Stata binary (e.g., "
44
- "/Applications/StataNow/StataMP.app/Contents/MacOS/stata-mp)."
45
+ "/Applications/StataNow/StataMP.app/Contents/MacOS/stata-mp or /usr/local/stata18/stata-mp)."
45
46
  )
46
47
  if not os.access(path, os.X_OK):
47
48
  raise PermissionError(
@@ -96,14 +97,51 @@ def find_stata_path() -> Tuple[str, str]:
96
97
  candidates.append((full_path, edition))
97
98
 
98
99
  elif system == "Linux":
99
- for binary, edition in [
100
- ("/usr/local/stata/stata-mp", "mp"),
101
- ("/usr/local/stata/stata-se", "se"),
102
- ("/usr/local/stata/stata", "be"),
103
- ("/usr/bin/stata", "be"),
104
- ]:
105
- if os.path.exists(binary):
106
- candidates.append((binary, edition))
100
+ linux_binaries = [
101
+ ("stata-mp", "mp"),
102
+ ("stata-se", "se"),
103
+ ("stata-ic", "be"),
104
+ ("stata", "be"),
105
+ ("xstata-mp", "mp"),
106
+ ("xstata-se", "se"),
107
+ ("xstata-ic", "be"),
108
+ ("xstata", "be"),
109
+ ]
110
+
111
+ # 2a. Try binaries available on PATH first
112
+ for binary, edition in linux_binaries:
113
+ found = shutil.which(binary)
114
+ if found:
115
+ candidates.append((found, edition))
116
+
117
+ # 2b. Search common install prefixes used by Stata's Linux installer
118
+ linux_roots = [
119
+ "/usr/local",
120
+ "/opt",
121
+ os.path.expanduser("~/stata"),
122
+ os.path.expanduser("~/Stata"),
123
+ ]
124
+
125
+ for root in linux_roots:
126
+ patterns: List[str] = []
127
+ if root.endswith(("stata", "Stata")):
128
+ patterns.append(root)
129
+ else:
130
+ patterns.extend(
131
+ [
132
+ os.path.join(root, "stata*"),
133
+ os.path.join(root, "Stata*"),
134
+ ]
135
+ )
136
+
137
+ for pattern in patterns:
138
+ for base_dir in glob.glob(pattern):
139
+ if not os.path.isdir(base_dir):
140
+ continue
141
+ for binary, edition in linux_binaries:
142
+ full_path = os.path.join(base_dir, binary)
143
+ if os.path.exists(full_path):
144
+ candidates.append((full_path, edition))
107
145
 
108
146
  candidates = _dedupe_preserve(candidates)
109
147
 
@@ -120,5 +158,5 @@ def find_stata_path() -> Tuple[str, str]:
120
158
  raise FileNotFoundError(
121
159
  "Could not automatically locate Stata. "
122
160
  "Set STATA_PATH to your Stata executable (e.g., "
123
- "/Applications/StataNow/StataMP.app/Contents/MacOS/stata-mp or C:\\Program Files\\Stata18\\StataMP-64.exe)."
161
+ "/Applications/StataNow/StataMP.app/Contents/MacOS/stata-mp, /usr/local/stata18/stata-mp, or C:\\Program Files\\Stata18\\StataMP-64.exe)."
124
162
  )
mcp_stata/server.py CHANGED
@@ -1,5 +1,4 @@
1
1
  from mcp.server.fastmcp import FastMCP
2
- from mcp.server.fastmcp import Image
3
2
  import mcp.types as types
4
3
  from .stata_client import StataClient
5
4
  from .models import (
@@ -76,28 +75,28 @@ def list_graphs() -> str:
76
75
  """
77
76
  Lists the names of all graphs currently stored in Stata's memory.
78
77
 
79
- Use this to see which graphs are available for export via `export_graph`.
78
+ Use this to see which graphs are available for export via `export_graph`. The
79
+ response marks the active graph so the agent knows which one will export by
80
+ default.
80
81
  """
81
82
  graphs = client.list_graphs_structured()
82
83
  return graphs.model_dump_json(indent=2)
83
84
 
84
85
  @mcp.tool()
85
- def export_graph(graph_name: str = None) -> Image:
86
+ def export_graph(graph_name: str = None, format: str = "pdf") -> str:
86
87
  """
87
- Exports a stored Stata graph to an image format (PNG) and returns it.
88
-
88
+ Exports a stored Stata graph to a file and returns its path.
89
+
89
90
  Args:
90
91
  graph_name: The name of the graph to export (as seen in `list_graphs`).
91
92
  If None, exports the currently active graph.
93
+ format: Output format, defaults to "pdf". Supported: "pdf", "png". Use
94
+ "png" to view the plot directly so the agent can visually check
95
+ titles, labels, legends, colors, and other user requirements.
92
96
  """
93
97
  try:
94
- path = client.export_graph(graph_name)
95
- with open(path, "rb") as f:
96
- data = f.read()
97
- return Image(data=data, format="png")
98
+ return client.export_graph(graph_name, format=format)
98
99
  except Exception as e:
99
- # Return error as text if image fails?
100
- # FastMCP expects Image or error.
101
100
  raise RuntimeError(f"Failed to export graph: {e}")
102
101
 
103
102
  @mcp.tool()
@@ -174,12 +173,17 @@ def get_graph_list() -> str:
174
173
  """Returns list of active graphs."""
175
174
  return client.list_graphs_structured().model_dump_json(indent=2)
176
175
 
177
- @mcp.resource("stata://variables/list")
176
+ @mcp.tool()
178
177
  def get_variable_list() -> str:
179
178
  """Returns JSON list of all variables."""
180
179
  variables = client.list_variables_structured()
181
180
  return variables.model_dump_json(indent=2)
182
181
 
182
+ @mcp.resource("stata://variables/list")
183
+ def get_variable_list_resource() -> str:
184
+ """Resource wrapper for the variable list."""
185
+ return get_variable_list()
186
+
183
187
  @mcp.resource("stata://results/stored")
184
188
  def get_stored_results_resource() -> str:
185
189
  """Returns stored r() and e() results."""
@@ -190,7 +194,9 @@ def get_stored_results_resource() -> str:
190
194
  def export_graphs_all() -> str:
191
195
  """
192
196
  Exports all graphs in memory to base64-encoded PNGs.
193
- Returns a JSON envelope listing graph names and images.
197
+ Returns a JSON envelope listing graph names and images so the agent can open
198
+ them directly and verify visuals (titles/labels/colors/legends) against the
199
+ user's requested spec without an extra fetch.
194
200
  """
195
201
  exports = client.export_graphs_all()
196
202
  return exports.model_dump_json(indent=2)
mcp_stata/stata_client.py CHANGED
@@ -321,6 +321,9 @@ class StataClient:
321
321
 
322
322
  def list_graphs(self) -> List[str]:
323
323
  """Returns list of graphs in memory."""
324
+ if not self._initialized:
325
+ self.init()
326
+
324
327
  # 'graph dir' returns list in r(list)
325
328
  # We need to ensure we run it quietly so we don't spam.
326
329
  self.stata.run("quietly graph dir, memory")
@@ -341,11 +344,17 @@ class StataClient:
341
344
  graphs = [GraphInfo(name=n, active=(n == active_name)) for n in names]
342
345
  return GraphListResponse(graphs=graphs)
343
346
 
344
- def export_graph(self, graph_name: str = None, filename: str = None) -> str:
345
- """Exports graph to a temp file and returns path."""
347
+ def export_graph(self, graph_name: str = None, filename: str = None, format: str = "pdf") -> str:
348
+ """Exports graph to a temp file (pdf or png) and returns the path."""
346
349
  import tempfile
350
+
351
+ fmt = (format or "pdf").strip().lower()
352
+ if fmt not in {"pdf", "png"}:
353
+ raise ValueError(f"Unsupported graph export format: {format}. Allowed: pdf, png.")
354
+
347
355
  if not filename:
348
- with tempfile.NamedTemporaryFile(prefix="mcp_stata_", suffix=".png", delete=False) as tmp:
356
+ suffix = f".{fmt}"
357
+ with tempfile.NamedTemporaryFile(prefix="mcp_stata_", suffix=suffix, delete=False) as tmp:
349
358
  filename = tmp.name
350
359
  else:
351
360
  # Ensure fresh start
@@ -357,9 +366,9 @@ class StataClient:
357
366
 
358
367
  cmd = "graph export"
359
368
  if graph_name:
360
- cmd += f' "{filename}", name("{graph_name}") replace'
369
+ cmd += f' "{filename}", name("{graph_name}") replace as({fmt})'
361
370
  else:
362
- cmd += f' "{filename}", replace'
371
+ cmd += f' "{filename}", replace as({fmt})'
363
372
 
364
373
  output = self.run_command(cmd)
365
374
 
@@ -386,6 +395,9 @@ class StataClient:
386
395
 
387
396
  def get_help(self, topic: str, plain_text: bool = False) -> str:
388
397
  """Returns help text as Markdown (default) or plain text."""
398
+ if not self._initialized:
399
+ self.init()
400
+
389
401
  # Try to locate the .sthlp help file
390
402
  # We use 'capture' to avoid crashing if not found
391
403
  self.stata.run(f"capture findfile {topic}.sthlp")
@@ -477,7 +489,7 @@ class StataClient:
477
489
  exports: List[GraphExport] = []
478
490
  for name in self.list_graphs():
479
491
  try:
480
- path = self.export_graph(name)
492
+ path = self.export_graph(name, format="png")
481
493
  with open(path, "rb") as f:
482
494
  b64 = base64.b64encode(f.read()).decode("ascii")
483
495
  exports.append(GraphExport(name=name, image_base64=b64))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-stata
3
- Version: 1.0.1
3
+ Version: 1.2.2
4
4
  Summary: A lightweight Model Context Protocol (MCP) server for Stata. Execute commands, inspect data, retrieve stored results (`r()`/`e()`), and view graphs in your chat interface. Built for economists who want to integrate LLM assistance into their Stata workflow.
5
5
  Project-URL: Homepage, https://github.com/tmonk/mcp-stata
6
6
  Project-URL: Repository, https://github.com/tmonk/mcp-stata
@@ -33,9 +33,12 @@ Description-Content-Type: text/markdown
33
33
 
34
34
  # Stata MCP Server
35
35
 
36
+ [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=mcp-stata&config=eyJjb21tYW5kIjoidXZ4IC0tZnJvbSBtY3Atc3RhdGEgbWNwLXN0YXRhIn0%3D)
37
+
36
38
  A [Model Context Protocol](https://github.com/modelcontextprotocol) (MCP) server that connects AI assistants to a local Stata installation.
37
39
 
38
40
  Built by <a href="https://tdmonk.com">Thomas Monk</a>, London School of Economics.
41
+ <!-- mcp-name: io.github.tmonk/mcp-stata -->
39
42
 
40
43
  This server enables LLMs to:
41
44
  - **Execute Stata code**: run any Stata command (e.g. `sysuse auto`, `regress price mpg`).
@@ -59,8 +62,6 @@ uvx --from mcp-stata mcp-stata
59
62
 
60
63
  `uvx` is an alias for `uv tool run` and runs the tool in an isolated, cached environment.
61
64
 
62
- > Tip: You can pin a version (example): `uvx --from mcp-stata==0.1.0 mcp-stata`
63
-
64
65
  ## Configuration
65
66
 
66
67
  This server attempts to automatically discover your Stata installation (supporting standard paths and StataNow).
@@ -75,6 +76,16 @@ export STATA_PATH="/Applications/StataNow/StataMP.app/Contents/MacOS/stata-mp"
75
76
  set STATA_PATH="C:\Program Files\Stata18\StataMP-64.exe"
76
77
  ```
77
78
 
79
+ If you prefer, add `STATA_PATH` to your MCP config's `env` for any IDE shown below. It's optional and only needed when discovery cannot find Stata.
80
+
81
+ Optional `env` example (add inside your MCP server entry):
82
+
83
+ ```json
84
+ "env": {
85
+ "STATA_PATH": "/Applications/StataNow/StataMP.app/Contents/MacOS/stata-mp"
86
+ }
87
+ ```
88
+
78
89
  ## IDE Setup (MCP)
79
90
 
80
91
  This MCP server uses the **stdio** transport (the IDE launches the process and communicates over stdin/stdout).
@@ -94,12 +105,9 @@ Config file locations include:
94
105
  ```json
95
106
  {
96
107
  "mcpServers": {
97
- "stata": {
108
+ "mcp-stata": {
98
109
  "command": "uvx",
99
- "args": ["--from", "mcp-stata", "mcp-stata"],
100
- "env": {
101
- "STATA_PATH": "/Applications/StataNow/StataMP.app/Contents/MacOS/stata-mp"
102
- }
110
+ "args": ["--from", "mcp-stata", "mcp-stata"]
103
111
  }
104
112
  }
105
113
  }
@@ -121,12 +129,9 @@ Cursor supports MCP config at:
121
129
  ```json
122
130
  {
123
131
  "mcpServers": {
124
- "stata": {
132
+ "mcp-stata": {
125
133
  "command": "uvx",
126
- "args": ["--from", "mcp-stata", "mcp-stata"],
127
- "env": {
128
- "STATA_PATH": "/Applications/StataNow/StataMP.app/Contents/MacOS/stata-mp"
129
- }
134
+ "args": ["--from", "mcp-stata", "mcp-stata"]
130
135
  }
131
136
  }
132
137
  }
@@ -144,12 +149,9 @@ A common location is `~/.codeium/windsurf/mcp_config.json`.
144
149
  ```json
145
150
  {
146
151
  "mcpServers": {
147
- "stata": {
152
+ "mcp-stata": {
148
153
  "command": "uvx",
149
- "args": ["--from", "mcp-stata", "mcp-stata"],
150
- "env": {
151
- "STATA_PATH": "/Applications/StataNow/StataMP.app/Contents/MacOS/stata-mp"
152
- }
154
+ "args": ["--from", "mcp-stata", "mcp-stata"]
153
155
  }
154
156
  }
155
157
  }
@@ -166,12 +168,9 @@ In Antigravity, MCP servers are managed from the MCP store/menu; you can open **
166
168
  ```json
167
169
  {
168
170
  "mcpServers": {
169
- "stata": {
171
+ "mcp-stata": {
170
172
  "command": "uvx",
171
- "args": ["--from", "mcp-stata", "mcp-stata"],
172
- "env": {
173
- "STATA_PATH": "/Applications/StataNow/StataMP.app/Contents/MacOS/stata-mp"
174
- }
173
+ "args": ["--from", "mcp-stata", "mcp-stata"]
175
174
  }
176
175
  }
177
176
  }
@@ -190,13 +189,10 @@ Create `.vscode/mcp.json`:
190
189
  ```json
191
190
  {
192
191
  "servers": {
193
- "stata": {
192
+ "mcp-stata": {
194
193
  "type": "stdio",
195
194
  "command": "uvx",
196
195
  "args": ["--from", "mcp-stata", "mcp-stata"],
197
- "env": {
198
- "STATA_PATH": "/Applications/StataNow/StataMP.app/Contents/MacOS/stata-mp"
199
- }
200
196
  }
201
197
  }
202
198
  }
@@ -206,6 +202,10 @@ VS Code documents `.vscode/mcp.json` and the `servers` schema, including `type`
206
202
 
207
203
  ---
208
204
 
205
+ ## Skills
206
+
207
+ - Skill file (for Claude/Codex): [skill/SKILL.md](skill/SKILL.md)
208
+
209
209
  ## Tools Available
210
210
 
211
211
  * `run_command(code, raw=false)`: Execute Stata syntax. Returns a structured JSON envelope by default; set `raw=true` for plain stdout/stderr. `trace=true` temporarily enables `set trace on`.
@@ -214,7 +214,7 @@ VS Code documents `.vscode/mcp.json` and the `servers` schema, including `type`
214
214
  * `describe()`: View dataset structure.
215
215
  * `codebook(variable, raw=false)`: Variable-level metadata (JSON envelope by default).
216
216
  * `run_do_file(path, trace=false, raw=false)`: Execute a .do file with rich error capture (JSON by default).
217
- * `export_graph(name)`: Export a graph as an image.
217
+ * `export_graph(name, format="pdf")`: Export a graph to a file path (default PDF; use `format="png"` for PNG).
218
218
  * `export_graphs_all()`: Export all in-memory graphs as base64-encoded PNGs (JSON response).
219
219
  * `list_graphs()`: See available graphs in memory (JSON list with an `active` flag).
220
220
  * `get_stored_results()`: Get `r()` and `e()` scalars/macros.
@@ -237,4 +237,4 @@ See the LICENSE file for the full text.
237
237
 
238
238
  ## Logging
239
239
 
240
- Set `STATA_MCP_LOGLEVEL` (e.g., `DEBUG`, `INFO`) to control server logging. Logs include discovery details (edition/path) and command-init traces for easier troubleshooting.
240
+ Set `STATA_MCP_LOGLEVEL` (e.g., `DEBUG`, `INFO`) to control server logging. Logs include discovery details (edition/path) and command-init traces for easier troubleshooting.
@@ -0,0 +1,11 @@
1
+ mcp_stata/__init__.py,sha256=kJKKRn7lGuVCuS2-GaN5VoVcvnxtNlfuswW_VOlYqwg,98
2
+ mcp_stata/discovery.py,sha256=KVNgLzmsZfkQ41k0UDZjNfwfz8yRwJs7iZR9NMu0qCE,5791
3
+ mcp_stata/models.py,sha256=mVRvZYZMeXf1o5MLcD0bSwd0xO16ldEWaY6qXsJehRc,1078
4
+ mcp_stata/server.py,sha256=KxxuFeVfYbIH5aRmCgYFA_wQwVw-Q1JMD5vC3e7vzAU,7804
5
+ mcp_stata/stata_client.py,sha256=GQi5t3L9jX6xSocaOqIWo0Lp3ORaqRMDYQHfITjNXYM,20474
6
+ mcp_stata/smcl/smcl2html.py,sha256=wi91mOMeV9MCmHtNr0toihNbaiDCNZ_NP6a6xEAzWLM,2624
7
+ mcp_stata-1.2.2.dist-info/METADATA,sha256=DH7y6cqEDJr-blD62LE0p4rudhmzaBkiSuHwtO2uagA,7840
8
+ mcp_stata-1.2.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
9
+ mcp_stata-1.2.2.dist-info/entry_points.txt,sha256=TcOgrtiTL4LGFEDb1pCrQWA-fUZvIujDOvQ-bWFh5Z8,52
10
+ mcp_stata-1.2.2.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
11
+ mcp_stata-1.2.2.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- mcp_stata/__init__.py,sha256=kJKKRn7lGuVCuS2-GaN5VoVcvnxtNlfuswW_VOlYqwg,98
2
- mcp_stata/discovery.py,sha256=_tkZJyvYQujR4sEbbnKCWbs2l-gIfO6TZcdI6Xbdk94,4482
3
- mcp_stata/models.py,sha256=mVRvZYZMeXf1o5MLcD0bSwd0xO16ldEWaY6qXsJehRc,1078
4
- mcp_stata/server.py,sha256=uj6n8Uyo5L6_AUnYlKo3PS7bFwMYkQpg7Y0VefRmZuc,7392
5
- mcp_stata/stata_client.py,sha256=4UmMa74HDKxDC9Y0viFyZrEXsUntBLWqOA3nU4jNvnM,20054
6
- mcp_stata/smcl/smcl2html.py,sha256=wi91mOMeV9MCmHtNr0toihNbaiDCNZ_NP6a6xEAzWLM,2624
7
- mcp_stata-1.0.1.dist-info/METADATA,sha256=tEWUYreccqVPe8YW2vfYi3lzr3jfLs3S7AilYFJFqfs,7736
8
- mcp_stata-1.0.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
9
- mcp_stata-1.0.1.dist-info/entry_points.txt,sha256=TcOgrtiTL4LGFEDb1pCrQWA-fUZvIujDOvQ-bWFh5Z8,52
10
- mcp_stata-1.0.1.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
11
- mcp_stata-1.0.1.dist-info/RECORD,,