by sassoftware
Provides a self‑hosted Model Context Protocol server that connects to SAS Viya, exposing over 40 analytics lifecycle tools via HTTP, stdio, or container deployment.
Offers a Model Context Protocol server for executing SAS code, training AutoML projects, scoring models, and performing a wide range of data‑governance, discovery, operations, reporting, batch jobs, and model‑management tasks directly against a SAS Viya environment.
uv package manager, and have access to a SAS Viya compute service.uv sync to create a virtual environment and install dependencies..env.sample to .env and set VIYA_ENDPOINT (and optional flags such as COLLECTION_MODE).uv run app (available at http://localhost:8134/mcp).sas-viya auth loginCode or uv run sas-mcp-login, then launch the server on demand via uv run app-stdio.ghcr.io/sassoftware/sas-mcp-server:latest and run it, exposing port 8134.ALLOW_RAW_BEARER=true) for CI/CD pipelines, scheduled jobs, or external tools like Claude Code CLI.Q: Do I need a full SAS Viya license? A: Yes, the server acts as a gateway to a Viya environment; a valid Viya compute service and license are required.
Q: Can I run the server without installing Python? A: Use the Docker/Podman deployment, which contains all runtime dependencies.
Q: How is authentication handled?
A: By default the server uses OAuth2 PKCE flow in the browser. Tokens are cached locally (~/.sas/credentials.json). For scripted scenarios, set ALLOW_RAW_BEARER=true and pass a Viya bearer token directly.
Q: Is any telemetry sent externally? A: No. Collection mode writes logs only to a local file and is disabled by default.
Q: Which SAS versions are supported? A: The server works with Viya 2022.11 and later; earlier versions may lack the required APIs.
Q: How do I enable the prompt templates?
A: They are built‑in tools; simply call the template name (e.g., debug_sas_log) from any MCP‑compatible client.
A Model Context Protocol (MCP) server for executing SAS code, training AutoML projects, scoring models and so much more for SAS Viya environments.
Here you can find getting articles on how to use and integrate the SAS MCP Server in different tools and what to build with it:
Required
Optional
git clone <repository-url>
cd sas-mcp-server
uv sync
NOTE: This will by default create a virtual environment called .venv in the project's root directory.
If for some reason the virtual environment is not created, please run uv venv and then re-run uv sync.
cp .env.sample .env
Edit .env and set
VIYA_ENDPOINT=https://your-viya-server.com
Option A: HTTP mode (pre-run the server, connect from MCP client)
uv run app
The server will be available at http://localhost:8134/mcp by default. Authentication is handled via OAuth2 PKCE flow in the browser.
Option B: Stdio mode (MCP client starts the server on demand)
Authenticate once. Two equivalent options:
# Option B1 — if you have the SAS Viya CLI installed:
sas-viya auth loginCode
# Option B2 — built-in helper, no external CLI needed (Viya 2022.11+):
uv run sas-mcp-login
Both flows write an access token to a local cache (~/.sas/credentials.json and ~/.sas-mcp-server/credentials.json respectively); the stdio server reads whichever it finds. When the token expires, re-run the same command.
Then configure your MCP client to launch the server directly (see below).
Option C: Docker / Podman (containerized deployment)
Pull the pre-built image from GitHub Container Registry:
docker pull ghcr.io/sassoftware/sas-mcp-server:latest
docker run -e VIYA_ENDPOINT=https://your-viya-server.com -p 8134:8134 ghcr.io/sassoftware/sas-mcp-server:latest
Or build locally from source:
docker build -t sas-mcp-server .
docker run -e VIYA_ENDPOINT=https://your-viya-server.com -p 8134:8134 sas-mcp-server
Available image tags:
latest — most recent tagged release<major>.<minor>.<patch> (e.g. 1.0.0) — specific release<major>.<minor> (e.g. 1.0) — latest patch of a minor releaseedge — tip of main (unreleased, for testing)sha-<short> — pinned to a specific commitProgrammatic clients with a pre-existing Viya token
If your caller already holds a Viya access token (e.g. an automation script that obtained one via the SAS Viya CLI), start the HTTP-mode server with ALLOW_RAW_BEARER=true and pass the token directly:
curl -H "Authorization: Bearer $VIYA_TOKEN" http://localhost:8134/mcp ...
The server validates the token against Viya's JWKS and uses it upstream as-is, bypassing the MCP JWT swap. The default OAuth2 PKCE flow keeps working alongside — both client types share the same /mcp endpoint.
| HTTP | Stdio | Docker | |
|---|---|---|---|
| How it runs | Long-running server you start separately | MCP client spawns it on demand | Containerized HTTP server |
| Authentication | OAuth2 PKCE flow (browser popup) | Cached token via sas-viya CLI or sas-mcp-login |
OAuth2 PKCE flow (browser popup) |
| Best for | Multi-user or shared setups; production-like environments | Single-user local development; quick experimentation | Team deployments; CI/CD; environments without Python installed |
| Requires | Python + uv | Python + uv (+ optional sas-viya CLI) |
Docker or Podman only |
| Credentials stored? | No — user authenticates interactively | No — only an access token (not a password) is cached | No — user authenticates interactively |
| MCP client config | Point client to http://localhost:8134/mcp |
Client runs uv run app-stdio |
Point client to http://host:8134/mcp |
Quick guidance:
sas-viya auth loginCode or uv run sas-mcp-login, then your MCP client manages the server lifecycle.AssetType:Report, ranges). Each hit carries a resource_uri you can hand to the matching tool (e.g. get_report, get_castable_data).catalog_search queries.resource_uri, bridging a search hit to the profiling and download tools without handling an instance id by hand.informationPrivacy, nlpTerms, nlpTags, and mostImportantFields.profile_ready once results have landed on the asset — so a download isn't fired too early.instance_id or resource_uri.file_path (the server reads it off disk) or url (the server fetches it and converts it to the multipart upload the endpoint requires). Ingests the formats the casManagement uploadTable API accepts — csv, tsv (csv + tab delimiter), xls, xlsx (single sheet), sas7bdat, sashdat — auto-detected from the extension or set with data_format. parquet is not accepted by that endpoint and is rejected up front with guidance (load via a path-based caslib + promote_table_to_memory, or convert to csv/sas7bdat).package (zip), pdf, png, svg, csv, tsv, xlsx, or summary. Text formats come back inline, png as image content, and binary formats (package/pdf/xlsx) as an embedded file with the right MIME type.Example configurations are provided in the examples/ folder. Below are quick-start snippets for common clients.
.vscode/mcp.json)HTTP mode (requires uv run app running separately):
{
"servers": {
"sas-execution-mcp": {
"url": "http://localhost:8134/mcp",
"type": "http"
}
}
}
Stdio mode (starts the server on demand):
{
"servers": {
"sas-execution-mcp": {
"command": "uv",
"args": ["run", "app-stdio"],
"cwd": "${workspaceFolder}"
}
}
}
.gemini/settings.json)Gemini CLI only supports stdio mode. Add to your ~/.gemini/settings.json or project-level .gemini/settings.json:
{
"mcpServers": {
"sas-viya-mcp": {
"command": "uv",
"args": ["run", "app-stdio"],
"cwd": "/path/to/sas-mcp-server",
"timeout": 60000
}
}
}
Note: The
timeoutfield (in milliseconds) is important — SAS Viya API calls can take longer than the Gemini CLI default of 10 seconds. A value of60000(60s) is recommended. Setcwdto the absolute path of yoursas-mcp-servercheckout.
Execute SAS code through the MCP tool:
data work.students;
input Name $ Age Grade $;
datalines;
Alice 20 A
Bob 22 B
;
run;
proc print data=work.students;
run;
For more details, configuration options, and deployment options, please refer to the examples folder and follow the instructions listed there.
An opt-in, off-by-default mode that records how the server is actually used — which tools, for what goals, with what inputs, and where they fall short. It serves two audiences:
It is implemented as a FastMCP middleware wrapper (telemetry.py + usage_logger.py) and requires no changes to any tool.
🔒 Nothing is ever sent anywhere automatically. Collection mode only appends to a local log file on the machine running the server. It is disabled unless you explicitly enable it, and even when enabled the data stays on your disk — sharing it with anyone (including the maintainers) is a deliberate, manual step you take by sending the file yourself. There is no phone-home, no network transmission, and no third party involved.
When enabled it does two things:
goal parameter into every tool's schema, asking the model to state in one sentence why it chose that tool for the current
request. The goal is stripped from the arguments before the real tool runs, so tools never see it.Set the toggle in .env (all options are documented in .env.sample):
COLLECTION_MODE=true
# optional overrides (defaults shown):
# COLLECTION_LOG_PATH=~/.sas-mcp-server/tool-usage.log
# COLLECTION_LOG_RESULTS=false # false = record result shape only, not contents
By default (COLLECTION_LOG_RESULTS=false) tool results are recorded only as a content-free shape summary (e.g. {"_type":"array","_items":500}) — arguments, goal, status, and error text are still captured. Set it to true to capture (capped + redacted) result contents for richer analysis.
⚠️ Privacy: when enabled, the log captures your tool inputs (e.g. the SAS code and queries you submit) and — if
COLLECTION_LOG_RESULTS=true— real result data that may include table rows, SAS listings, and PII. Redaction is heuristic (credential-shaped keys + Bearer/JWT only) and does not detect PII in data values. Review the log before sharing it. The file is locked to your user (chmod 0600 on POSIX; icacls on Windows, best-effort).
Collection mode is designed to be cheap enough to leave on. Measured on this repo (45 registered tools, FastMCP 3.4.2):
goal field grows the tools/list schema the model sees by roughly +2,400 input tokens (~29%) per turn. Because the tool list is stable within a session it is served from the prompt cache after the first turn (steady-state ≈ +240 tokens/turn), plus ~15–30 output tokens per call for the model to write the goal sentence. This is the only client-visible cost and it applies only while collection mode is enabled.COLLECTION_LOG_RESULTS=true). The JSONL
write is offloaded to a worker thread so it never blocks the event loop. Against real Viya calls (typically hundreds of milliseconds to seconds) this is
negligible — the live integration suite passed identically with collection mode off and on, the overhead lost in normal network variance.COLLECTION_MAX_LOG_BYTES (default 10 MiB, ≈16k calls) and keeps COLLECTION_LOG_BACKUPS (default 3) rotated files, so on-disk growth is bounded.The project includes two layers of tests: unit tests (fast, no credentials required) and integration tests (run against a real SAS Viya instance).
Unit tests verify tool schemas, request payloads, and internal logic without making any network calls:
./run_tests.sh
Or directly via pytest:
uv run python -m pytest -m "not integration" -v
Integration tests call every tool against a live Viya environment. They require credentials, which can be provided via CLI arguments or .env:
Using .env (set VIYA_ENDPOINT, VIYA_USERNAME, VIYA_PASSWORD):
./run_tests.sh --integration
Using CLI arguments:
./run_tests.sh --integration \
--endpoint https://your-viya-server.com \
--username youruser \
--password yourpassword
Integration tests only (skip unit tests):
./run_tests.sh --integration-only
Binary upload formats. The upload_data Excel integration test generates its .xlsx
fixture with openpyxl. Install the optional group so it runs instead of importorskip-ing:
uv sync --group test-formats. (csv, tsv, and file_path/data_format coverage needs no
extra deps.) Generating a sas7bdat/sashdat fixture requires SAS itself, so those two
formats are covered by unit-level payload tests only, not live.
Every one of the 45 tools and 8 prompt templates has an integration test, enforced by the
test_every_tool_has_integration_coverage / test_every_prompt_has_integration_coverage
guards — adding a new tool or prompt without integration coverage fails the suite. The
resource-dependent tests discover real targets on the instance: score_data scores the most
recently modified MAS module (discovering a real step and its inputs), and run_ml_project
re-runs the most recently modified completed ML project. They skip only if the instance
has no such resource at all.
In CI: the .github/workflows/integration.yml workflow runs this suite on demand
(manual dispatch, or by adding the run-integration label to a PR) using repository
secrets, and publishes the results back to the PR as a status check, a sticky comment, and
a downloadable JUnit artifact. Result files are written to reports/ (git-ignored) and are
never committed.
Locally (attach results to a PR yourself): run with --report to write the JUnit XML
and a Markdown summary into reports/ (git-ignored), then post them to a PR with the GitHub
CLI — no commit, no CI required:
./run_tests.sh --integration-only --report
gh pr comment <PR> --body-file reports/integration-summary.md # summary table as a comment
gh gist create reports/integration.xml # full XML as a linkable gist
GitHub has no API/CLI to attach a binary file to a PR (drag-and-drop upload is browser-only), so the summary is posted as a comment and the raw XML is shared via a gist link or pasted in a collapsed
<details>block. To produce the canonical Actions artifact from your machine instead, trigger the workflow remotely:gh workflow run integration.yml.
| File | Description |
|---|---|
tests/test_tool_payloads.py |
Payload assertions for all 45 tools (URL paths, JSON body, query params, headers) plus error-path coverage |
tests/test_integration.py |
End-to-end workflow tests against a real Viya instance |
tests/test_tools.py |
Unit tests for the generic Viya REST helpers in viya_client (get_json, post_json, make_client, …) |
tests/test_viya_utils.py |
Unit tests for Viya compute session and job orchestration |
tests/test_mcp_server.py |
Unit tests for the HTTP auth middleware, health route, and token getter |
tests/test_config.py |
Unit tests for configuration loading |
tests/test_config_oauth.py |
Unit tests for PermissiveOAuthProxy raw-bearer handling |
tests/test_auth_login.py |
Unit tests for the sas-mcp-login OAuth/PKCE helper |
tests/test_stdio_server.py |
Unit tests for stdio token resolution and the device-code flow |
tests/test_env.py |
Unit tests for the env_bool helper |
tests/test_prompts.py |
Unit tests for prompt template rendering |
Maintainers are accepting patches and contributions to this project. Please read CONTRIBUTING.md for details about submitting contributions to this project.
Except for the the contents of the /static folder, this project is licensed under the Apache 2.0 License.
Elements in the /static folder are owned by SAS and are not released under an open source license.
SAS and all other SAS Institute Inc. product or service names are registered trademarks or trademarks of SAS Institute Inc. in the USA and other countries. ® indicates USA registration.
Separate commercial licenses for SAS software (e.g., SAS Viya) are not included and are required to use these capabilities with SAS software.
As with any container image, direct and indirect dependencies are governed by their own licenses. Users of the published container image are responsible for ensuring that their use complies with all applicable licenses.
All third-party trademarks referenced belong to their respective owners and are only used here for identification and reference purposes, and not to imply any affiliation or endorsement by the trademark owners.
This project requires the following dependencies.
| Dependency | License |
|---|---|
| Python | Python Software License |
| FastMCP | Apache License 2.0 |
| uvicorn | BSD 3-Clause License |
| starlette | BSD 3-Clause License |
| httpx | MIT License |
Please log in to share your review and rating for this MCP.
Explore related MCPs that share similar capabilities and solve comparable challenges
by mindsdb
Enables humans, AI agents, and applications to retrieve highly accurate answers across large‑scale data sources, unifying heterogeneous databases, warehouses, and SaaS platforms.
by mckinsey
Build high-quality data visualization apps quickly using a low-code toolkit that leverages Plotly, Dash, and Pydantic.
by antvis
Offers over 25 AntV chart types for automated chart generation and data analysis, callable via MCP tools, CLI, HTTP, SSE, or streamable transports.
by dbt-labs
Provides a Model Context Protocol server that exposes a rich set of dbt‑related tools—SQL execution, semantic‑layer queries, discovery APIs, dbt CLI commands, admin operations, code generation, lineage analysis, and product documentation retrieval—so AI agents can safely interact with dbt projects and platforms.
by reading-plus-ai
A versatile tool that enables interactive data exploration through prompts, CSV loading, and script execution.
by Canner
Provides a semantic engine that lets MCP clients and AI agents query enterprise data with contextual understanding, precise calculations, and built‑in governance.
by surendranb
Provides natural‑language access to Google Analytics 4 data via MCP, exposing over 200 dimensions and metrics for Claude, Cursor and other compatible clients.
by ergut
Provides secure, read‑only access to BigQuery datasets, allowing large language models to query and analyze data through a standardized interface.
by isaacwasserman
Provides an interface for LLMs to visualize data using Vega‑Lite syntax, supporting saving of data tables and rendering visualizations as either a full Vega‑Lite specification (text) or a base64‑encoded PNG image.