RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Configs/apache/superset/diff

Two files, one repository

apache/superset ships 3 formats across 3 indexed files. The question worth asking is whether the second one says anything the first does not.

CompareAGENTS.md ↔ CLAUDE.mdAGENTS.md ↔ Cursor rulesCLAUDE.md ↔ Cursor rules
A · AGENTS.md · 2095 wordsB · superset/mcp_service/CLAUDE.md · 2906 words
What each file covers, counted
DimensionSharedOnly in AOnly in BOverlap
Sections046600%
Commands01600%
Section tags76441%

What each file covers

Sections

0 shared · 46 only in A · 60 only in B
  • − LLM Context Guide for Apache Superset
  • − Run Pre-commit Before Pushing
  • − Stage your changes first
  • − Run pre-commit on staged files
  • − If there are auto-fixes, stage them and commit
  • − ⚠️ CRITICAL: Ongoing Refactors (What NOT to Do)
  • − Frontend Modernization
  • − Testing Strategy Migration
  • − Backend Type Safety
  • − UUID Migration
  • − Security and Threat Model
  • − Key Directories
  • − Code Standards
  • − TypeScript Frontend
  • − Python Backend
  • − Apache License Headers
  • − Code Comments
  • − Documentation Requirements
  • − Developer Portal: Storybook-to-MDX Documentation
  • − Core Philosophy
  • − Story Requirements for Docs Generation
  • − Generator Location
  • − Architecture Patterns
  • − Security & Features
  • − Test Utilities
  • − Python Test Helpers
  • − TypeScript Test Helpers
  • − Test Database Patterns
  • − Running Tests
  • − Frontend
  • − E2E Tests (Playwright - NEW)
  • − E2E Tests (Cypress - DEPRECATED)
  • − Backend
  • − If pytest fails with database/setup issues, ask the user to run test environment setup
  • − Environment Validation
  • − Verify Superset is running
  • − SQLAlchemy Query Best Practices
  • − Pull Request Guidelines
  • − Pre-commit Validation
  • − Install hooks
  • − IMPORTANT: Stage your changes first!
  • − Quick validation (faster than --all-files)
  • − Common File Patterns
  • − API Structure
  • − Migration Files
  • − Platform-Specific Instructions
  • + MCP Service - LLM Agent Guide
  • + CRITICAL: Apache License Headers
  • + Licensed to the Apache Software Foundation (ASF) under one
  • + or more contributor license agreements. See the NOTICE file
  • + distributed with this work for additional information
  • + regarding copyright ownership. The ASF licenses this file
  • + to you under the Apache License, Version 2.0 (the
  • + "License"); you may not use this file except in compliance
  • + with the License. You may obtain a copy of the License at
  • + # http://www.apache.org/licenses/LICENSE-2.0
  • + # Unless required by applicable law or agreed to in writing,
  • + software distributed under the License is distributed on an
  • + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  • + KIND, either express or implied. See the License for the
  • + specific language governing permissions and limitations
  • + under the License.
  • + Architecture Overview
  • + Key Components
  • + Dependency Injection Architecture
  • + Critical Convention: Tool, Prompt, and Resource Registration
  • + How to Add a New Tool
  • + superset/mcp_service/chart/tool/my_new_tool.py
  • + superset/mcp_service/app.py (at the bottom, after initialize_core_mcp_dependencies())
  • + How to Add a New Prompt
  • + superset/mcp_service/chart/prompts/my_new_prompt.py
  • + How to Add a New Resource
  • + superset/mcp_service/chart/resources/my_new_resource.py
  • + Tool Development Patterns
  • + 1. Tool Decorator Parameters
  • + 2. Use Core Classes for Reusability
  • + 3. Authentication and RBAC
  • + Authentication + RBAC enabled (default)
  • + Public tool (no auth) - use sparingly, and add the tool name to
  • + ALLOWED_UNPROTECTED in app.py (e.g. generate_bug_report)
  • + 4. Use Pydantic Schemas
  • + 5. Follow the DAO Pattern
  • + GOOD: Use DAO
  • + BAD: Don't query directly
  • + 6. Python Type Hints (Python 3.10+ Style)
  • + GOOD - Modern Python 3.10+ syntax
  • + BAD - Old-style (DO NOT USE)
  • + 7. Event Logger Instrumentation
  • + 8. Context Logging
  • + 9. Error Handling
  • + 10. Dataset Validation for Chart Tools
  • + 11. Compile Check for Chart Creation
  • + 12. Flexible Input Parsing
  • + Middleware
  • + Configuration
  • + Authentication
  • + RBAC
  • + Embedded guest auth (opt-in; requires the EMBEDDED_SUPERSET feature flag).
  • + Reuses core GUEST_TOKEN_JWT_* config — no MCP-specific guest secret/audience.
  • + Default-deny: the ONLY tools a guest may call (everything else is denied).
  • + Principal-agnostic extension point: given the current user, return an allow-list
  • + (only these tools are callable) or None if unrestricted. Defaults to restricting
  • + embedded guests to MCP_GUEST_ALLOWED_TOOLS; override to add other restricted
  • + principals without touching the enforcement path.
  • + Response Caching (optional, uses in-memory store by default; Redis when MCP_STORE_CONFIG enabled)
  • + Multi-pod Storage (optional, requires Redis)

Commands

0 shared · 16 only in A · 0 only in B
  • − git add .
  • − git commit --amend
  • − npm run test
  • − npm run test -- filename.test.tsx
  • − npm run playwright:test
  • − npm run playwright:ui
  • − npm run playwright:headed
  • − npx playwright test tests/auth/login.spec.ts
  • − npm run playwright:debug tests/auth/login.spec.ts
  • − npm run cypress-run-chrome
  • − npm run cypress-debug
  • − pytest
  • − pytest tests/unit_tests/specific_test.py
  • − pytest tests/unit_tests/
  • − npm run dev
  • − npm run lint

Section tags

7 shared · 6 only in A · 4 only in B
  • − setup
  • − lint-format
  • − git-pr
  • − database
  • − api
  • − docs
  • + build
  • + performance
  • + deployment
  • + do-not
  •   test
  •   code-style
  •   architecture
  •   types
  •   testing-strategy
  •   security
  •   agent-behaviour

Line diff

+555 added−230 removed85 unchanged13.3% identical
apache/superset · AGENTS.md
@@ −1 @@
1# LLM Context Guide for Apache Superset
2 
3Apache Superset is a data visualization platform with Flask/Python backend and React/TypeScript frontend.
4 
5## Run Pre-commit Before Pushing
6 
7Always run pre-commit against the files changed by the current branch before
8pushing. This matches CI and keeps unrelated failures already present on
9`master` from blocking otherwise independent work.
10 
11```bash
12# Stage your changes first
13git add .
 
 
14 
15# Run pre-commit on staged files
16pre-commit run
17 
18# If there are auto-fixes, stage them and commit
19git add .
20git commit --amend # or new commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21```
22 
23Use `pre-commit run --all-files` when auditing or repairing the repository-wide
24baseline. If that check finds failures in files untouched by the current branch,
25fix them in a separate branch rather than adding unrelated changes to the
26current pull request.
27 
28Common pre-commit failures:
29- **Formatting** - black, oxfmt, eslint will auto-fix
30- **Type errors** - mypy failures need manual fixes
31- **Linting** - ruff, pylint issues need manual fixes
32 
33## ⚠️ CRITICAL: Ongoing Refactors (What NOT to Do)
34 
35**These migrations are actively happening - avoid deprecated patterns:**
36 
37### Frontend Modernization
38- **NO `any` types** - Use proper TypeScript types
39- **NO JavaScript files** - Convert to TypeScript (.ts/.tsx)
40- **Use @superset-ui/core** - Don't import Ant Design directly, prefer Ant Design component wrappers from @superset-ui/core/components
41- **Use antd theming tokens** - Prefer antd tokens over legacy theming tokens
42- **Avoid custom css and styles** - Follow antd best practices and avoid styling and custom CSS whenever possible
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43 
44### Testing Strategy Migration
45- **Prefer unit tests** over integration tests
46- **Prefer integration tests** over end-to-end tests
47- **Use Playwright for E2E tests** - Migrating from Cypress
48- **Cypress is deprecated** - Will be removed once migration is completed
49- **Use Jest + React Testing Library** for component testing
50- **Use `test()` instead of `describe()`** - Follow [avoid nesting when testing](https://kentcdodds.com/blog/avoid-nesting-when-youre-testing) principles
51 
52### Backend Type Safety
53- **Add type hints** - All new Python code needs proper typing
54- **MyPy compliance** - Run `pre-commit run mypy` to validate
55- **SQLAlchemy typing** - Use proper model annotations
56 
57### UUID Migration
58- **Prefer UUIDs over auto-incrementing IDs** - New models should use UUID primary keys
59- **External API exposure** - Use UUIDs in public APIs instead of internal integer IDs
60- **Existing models** - Add UUID fields alongside integer IDs for gradual migration
 
61 
62## Security and Threat Model
63 
64Before evaluating any code path for security issues, read [`SECURITY.md`](SECURITY.md). It is the canonical, authoritative source for Apache Superset's security model and is referenced by both human reporters and automated scanners.
65 
66In short, the test for whether a finding is in scope is one question:
67 
68> *Does it let a principal perform an action the role and capability matrix in `SECURITY.md` does not entitle them to?*
 
 
 
69 
70If yes, it is in scope. If no, it is not.
 
 
 
 
71 
72The three trust boundaries are:
73 
741. **The Admin role** is a fully trusted operational principal. Anything an Admin can do through documented configuration, API, or UI is an intended capability, not a vulnerability.
752. **The operator** owns deployment-time decisions (secrets, network exposure, feature-flag selection, connector and codec choices, notification destinations, third-party plugins). Misconfiguration at this layer is a deployment defect, not a Superset vulnerability.
763. **The codebase** is responsible for enforcing the role and capability matrix wherever it exposes functionality to a principal: API routes, command and DAO layers, UI handlers, background jobs, and any other entry point. A missing or incorrect enforcement check is in scope no matter where it lives.
 
 
 
 
 
 
 
 
 
 
 
 
 
77 
78The security model assumes that operator-controlled infrastructure, including the metadata database, cache backends, message brokers, secret stores, and deployment environment, remains within the operator's trust boundary. Vulnerabilities must demonstrate a security boundary violation by an attacker who does not already control those systems.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79 
80Route-level authorization in this codebase uses one of three Flask-AppBuilder decorators depending on the route type:
 
 
 
 
 
 
 
 
81 
82- `@protect()` for REST API routes (`ModelRestApi` / `BaseApi`)
83- `@has_access_api` for legacy view routes
84- `@has_access` for legacy HTML view routes
85 
86Object-level authorization via `security_manager.raise_for_access(...)` applies to data-bearing resources: dashboards, charts, datasets and datasources, queries, database and table access, and query contexts. Other resources (annotations, tags, CSS templates, reports, RLS rules, and similar) rely on the route-level decorator plus DAO `base_filters` for ownership scoping; the absence of `raise_for_access` on these resources is by design, not a finding. Code that omits the per-object gate on a route that returns or mutates a specific data-bearing object is in scope; code that follows the correct pattern for its resource class can still contain injection, SSRF, XSS, or other classes of finding unrelated to authorization, which are evaluated separately.
87 
88The full role and capability matrix, in-scope and out-of-scope class lists, and CVE aggregation rules are in [`SECURITY.md`](SECURITY.md). Defer to that document for any specifics.
 
 
 
89 
90**Requirements for findings filed by automated tooling**
 
 
 
91 
92Automated scanners (LLM-based code scanners, static analyzers, dependency tools) that file findings against this codebase must, in each finding, name:
 
 
 
 
 
 
93 
941. The specific role and capability matrix row in [`SECURITY.md`](SECURITY.md) the finding believes is violated.
952. The principal the finding assumes the attacker holds (Public, Gamma, sql_lab, Alpha, Admin, Embedded guest token, or a custom role with explicit capability grants).
96 
97Findings that cannot identify both should be filed as questions, not vulnerabilities. This requirement exists to ensure every reported issue is testable against the published security model and to keep speculative or pattern-match-only reports out of the triage queue.
98 
99## Key Directories
 
 
 
100 
 
 
 
 
 
101```
102superset/
103├── superset/ # Python backend (Flask, SQLAlchemy)
104│ ├── views/api/ # REST API endpoints
105│ ├── models/ # Database models
106│ └── connectors/ # Database connections
107├── superset-frontend/src/ # React TypeScript frontend
108│ ├── components/ # Reusable components
109│ ├── explore/ # Chart builder
110│ ├── dashboard/ # Dashboard interface
111│ └── SqlLab/ # SQL editor
112├── superset-frontend/packages/
113│ └── superset-ui-core/ # UI component library (USE THIS)
114├── tests/ # Python/integration tests
115├── docs/ # Documentation (UPDATE FOR CHANGES)
116└── UPDATING.md # Breaking changes log
 
 
 
 
 
117```
118 
119## Code Standards
120 
121### TypeScript Frontend
122- **Avoid `any` types** - Use proper TypeScript, reuse existing types
123- **Functional components** with hooks
124- **@superset-ui/core** for UI components (not direct antd)
125- **Jest** for testing (NO Enzyme)
126- **Redux** for global state where it exists, hooks for local
127 
128### Python Backend
129- **Type hints required** for all new code
130- **MyPy compliant** - run `pre-commit run mypy`
131- **SQLAlchemy models** with proper typing
132- **pytest** for testing
 
 
 
133 
134### Apache License Headers
135- **New files require ASF license headers** - When creating new code files, include the standard Apache Software Foundation license header
136- **LLM instruction files are excluded** - Files like AGENTS.md, CLAUDE.md, etc. are in `.rat-excludes` to avoid header token overhead
137 
138### Code Comments
139- **Avoid time-specific language** - Don't use words like "now", "currently", "today" in code comments as they become outdated
140- **Write timeless comments** - Comments should remain accurate regardless of when they're read
141 
142## Documentation Requirements
 
143 
144- **docs/**: Update for any user-facing changes
145- **UPDATING.md**: Add breaking changes here
146- **Docstrings**: Required for new functions/classes
 
 
 
 
147 
148## Developer Portal: Storybook-to-MDX Documentation
 
 
 
 
 
149 
150The Developer Portal auto-generates MDX documentation from Storybook stories. **Stories are the single source of truth.**
 
151 
152### Core Philosophy
153- **Fix issues in the STORY, not the generator** - When something doesn't render correctly, update the story file first
154- **Generator should be lightweight** - It extracts and passes through data; avoid special cases
155- **Stories define everything** - Props, controls, galleries, examples all come from story metadata
 
 
 
156 
157### Story Requirements for Docs Generation
158- Use `export default { title: '...' }` (inline), not `const meta = ...; export default meta;`
159- Name interactive stories `Interactive${ComponentName}` (e.g., `InteractiveButton`)
160- Define `args` for default prop values
161- Define `argTypes` at the story level (not meta level) with control types and descriptions
162- Use `parameters.docs.gallery` for size×style variant grids
163- Use `parameters.docs.sampleChildren` for components that need children
164- Use `parameters.docs.liveExample` for custom live code blocks
165- Use `parameters.docs.staticProps` for complex object props that can't be parsed inline
166 
167### Generator Location
168- Script: `docs/scripts/generate-superset-components.mjs`
169- Wrapper: `docs/src/components/StorybookWrapper.jsx`
170- Output: `docs/developer_portal/components/`
171 
172## Architecture Patterns
173 
174### Security & Features
175- **Security model**: see the top-level [Security and Threat Model](#security-and-threat-model) section and [`SECURITY.md`](SECURITY.md)
176- **RBAC**: Role-based access via Flask-AppBuilder
177- **Feature flags**: Control feature rollouts
178- **Row-level security**: SQL-based data access control
179 
180## Test Utilities
 
181 
182### Python Test Helpers
183- **`SupersetTestCase`** - Base class in `tests/integration_tests/base_tests.py`
184- **`@with_config`** - Config mocking decorator
185- **`@with_feature_flags`** - Feature flag testing
186- **`login_as()`, `login_as_admin()`** - Authentication helpers
187- **`create_dashboard()`, `create_slice()`** - Data setup utilities
188 
189### TypeScript Test Helpers
190- **`superset-frontend/spec/helpers/testing-library.tsx`** - Custom render() with providers
191- **`createWrapper()`** - Redux/Router/Theme wrapper
192- **`selectOption()`** - Select component helper
193- **React Testing Library** - NO Enzyme (removed)
194 
195### Test Database Patterns
196- **Mock patterns**: Use `MagicMock()` for config objects, avoid `AsyncMock` for synchronous code
197- **API tests**: Update expected columns when adding new model fields
 
198 
199### Running Tests
200```bash
201# Frontend
202npm run test # All tests
203npm run test -- filename.test.tsx # Single file
204 
205# E2E Tests (Playwright - NEW)
206npm run playwright:test # All Playwright tests
207npm run playwright:ui # Interactive UI mode
208npm run playwright:headed # See browser during tests
209npx playwright test tests/auth/login.spec.ts # Single file
210npm run playwright:debug tests/auth/login.spec.ts # Debug specific file
211 
212# E2E Tests (Cypress - DEPRECATED)
213cd superset-frontend/cypress-base
214npm run cypress-run-chrome # All Cypress tests (headless)
215npm run cypress-debug # Interactive Cypress UI
216 
217# Backend
218pytest # All tests
219pytest tests/unit_tests/specific_test.py # Single file
220pytest tests/unit_tests/ # Directory
221 
222# If pytest fails with database/setup issues, ask the user to run test environment setup
 
223```
224 
225## Environment Validation
226 
227**Quick Setup Check (run this first):**
228 
229```bash
230# Verify Superset is running
231curl -f http://localhost:8088/health || echo "❌ Setup required - see https://superset.apache.org/docs/contributing/development#working-with-llms"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232```
233 
234**If health checks fail:**
235"It appears you aren't set up properly. Please refer to the [Working with LLMs](https://superset.apache.org/docs/contributing/development#working-with-llms) section in the development docs for setup instructions."
236 
237**Key Project Files:**
238- `superset-frontend/package.json` - Frontend build scripts (`npm run dev` on port 9000, `npm run test`, `npm run lint`)
239- `pyproject.toml` - Python tooling (ruff, mypy configs)
240- `requirements/` folder - Python dependencies (base.txt, development.txt)
241 
242## SQLAlchemy Query Best Practices
243- **Use negation operator**: `~Model.field` instead of `== False` to avoid ruff E712 errors
244- **Example**: `~Model.is_active` instead of `Model.is_active == False`
245 
246## Pull Request Guidelines
 
 
 
 
 
247 
248**When creating pull requests:**
249 
2501. **Read the current PR template**: Always check `.github/PULL_REQUEST_TEMPLATE.md` for the latest format
2512. **Use the template sections**: Include all sections from the template (SUMMARY, BEFORE/AFTER, TESTING INSTRUCTIONS, ADDITIONAL INFORMATION)
2523. **Follow PR title conventions**: Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
253 - Format: `type(scope): description`
254 - Example: `fix(dashboard): load charts correctly`
255 - Types: `fix`, `feat`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`
256 
257**Important**: Always reference the actual template file at `.github/PULL_REQUEST_TEMPLATE.md` instead of using cached content, as the template may be updated over time.
 
 
 
 
 
 
 
258 
259## Pre-commit Validation
260 
261**Use pre-commit hooks for quality validation:**
262 
263```bash
264# Install hooks
265pre-commit install
266 
267# IMPORTANT: Stage your changes first!
268git add . # Pre-commit only checks staged files
 
 
 
 
 
269 
270# Quick validation (faster than --all-files)
271pre-commit run # Staged files only
272pre-commit run mypy # Python type checking
273pre-commit run format # Code formatting
274pre-commit run eslint # Frontend linting
 
 
 
 
 
 
 
 
275```
276 
277**Important pre-commit usage notes:**
278- **Stage files first**: Run `git add .` before `pre-commit run` to check only changed files (much faster)
279- **Virtual environment**: Activate your Python virtual environment before running pre-commit
280 ```bash
281 # Common virtual environment locations (yours may differ):
282 source .venv/bin/activate # if using .venv
283 source venv/bin/activate # if using venv
284 source ~/venvs/superset/bin/activate # if using a central location
285 ```
286 If you get a "command not found" error, ask the user which virtual environment to activate
287- **Auto-fixes**: Some hooks auto-fix issues (e.g., trailing whitespace). Re-run after fixes are applied
288 
289## Common File Patterns
290 
291### API Structure
292- **`/api.py`** - REST endpoints with decorators and OpenAPI docstrings
293- **`/schemas.py`** - Marshmallow validation schemas for OpenAPI spec
294- **`/commands/`** - Business logic classes with @transaction() decorators
295- **`/models/`** - SQLAlchemy database models
296- **OpenAPI docs**: Auto-generated at `/swagger/v1` from docstrings and schemas
297 
298### Migration Files
299- **Location**: `superset/migrations/versions/`
300- **Naming**: `YYYY-MM-DD_HH-MM_hash_description.py`
301- **Utilities**: Use helpers from `superset.migrations.shared.utils` for database compatibility
302- **Pattern**: Import utilities instead of raw SQLAlchemy operations
 
 
 
303 
304## Platform-Specific Instructions
305 
306- **[CLAUDE.md](CLAUDE.md)** - For Claude/Anthropic tools
307- **[.github/copilot-instructions.md](.github/copilot-instructions.md)** - For GitHub Copilot
308- **[GEMINI.md](GEMINI.md)** - For Google Gemini tools
309- **[GPT.md](GPT.md)** - For OpenAI/ChatGPT tools
310- **[.cursor/rules/dev-standard.mdc](.cursor/rules/dev-standard.mdc)** - For Cursor editor
311 
312---
 
 
 
313 
314**LLM Note**: This codebase is actively modernizing toward full TypeScript and type safety. Always run `pre-commit run` to validate changes. Follow the ongoing refactors section to avoid deprecated patterns.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315 
apache/superset · superset/mcp_service/CLAUDE.md
@@ +1 @@
1# MCP Service - LLM Agent Guide
2 
3This guide helps LLM agents understand the Superset MCP (Model Context Protocol) service architecture and development conventions.
4 
5## CRITICAL: Apache License Headers
6 
7**EVERY Python file in the MCP service MUST have the Apache Software Foundation license header.**
 
 
8 
9This includes:
10- All `.py` files (tool files, schemas, __init__.py files, etc.)
11- **NEVER remove existing license headers during refactoring or edits**
12- **ALWAYS add license headers when creating new files**
13- **ALWAYS verify license headers are present after editing files**
14 
15If you see a file without a license header, ADD IT IMMEDIATELY. If you accidentally remove one during editing, ADD IT BACK.
 
16 
17Use this exact template at the top of EVERY Python file:
18 
19```python
20# Licensed to the Apache Software Foundation (ASF) under one
21# or more contributor license agreements. See the NOTICE file
22# distributed with this work for additional information
23# regarding copyright ownership. The ASF licenses this file
24# to you under the Apache License, Version 2.0 (the
25# "License"); you may not use this file except in compliance
26# with the License. You may obtain a copy of the License at
27#
28# http://www.apache.org/licenses/LICENSE-2.0
29#
30# Unless required by applicable law or agreed to in writing,
31# software distributed under the License is distributed on an
32# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
33# KIND, either express or implied. See the License for the
34# specific language governing permissions and limitations
35# under the License.
36```
37 
38**Note**: LLM instruction files like `CLAUDE.md`, `AGENTS.md`, etc. are excluded from this requirement (listed in `.rat-excludes`) to avoid token overhead, but ALL other Python files require it.
 
 
 
39 
40## Architecture Overview
 
 
 
41 
42The MCP service provides programmatic access to Superset via the Model Context Protocol, allowing AI assistants to interact with dashboards, charts, datasets, databases, SQL Lab, and instance metadata.
43 
44### Key Components
45 
46```
47superset/mcp_service/
48├── app.py # FastMCP app factory and tool registration
49├── auth.py # Authentication, authorization, and RBAC
50├── mcp_config.py # Default configuration
51├── mcp_core.py # Reusable core classes for tools
52├── flask_singleton.py # Flask app singleton for MCP context
53├── middleware.py # FastMCP middleware (logging, errors, size guards)
54├── server.py # Server startup (streamable-http, multi-pod)
55├── jwt_verifier.py # JWT token validation
56├── chart/ # Chart tools, schemas, prompts, resources
57│ ├── schemas.py
58│ ├── chart_utils.py
59│ ├── preview_utils.py
60│ ├── validation.py
61│ ├── tool/
62│ ├── prompts/
63│ └── resources/
64├── dashboard/ # Dashboard tools and schemas
65│ ├── schemas.py
66│ └── tool/
67├── dataset/ # Dataset tools and schemas
68│ ├── schemas.py
69│ └── tool/
70├── explore/ # Explore link generation
71│ ├── schemas.py
72│ └── tool/
73├── sql_lab/ # SQL Lab tools (execute, save, open)
74│ ├── schemas.py
75│ └── tool/
76├── system/ # System tools (health, instance info, schema)
77│ ├── schemas.py
78│ ├── tool/
79│ ├── prompts/
80│ └── resources/
81├── common/ # Shared error schemas
82├── commands/ # MCP-specific command classes
83└── utils/ # Utilities (URL, schema parsing, error builders)
84```
85 
86### Dependency Injection Architecture
 
 
 
 
 
 
87 
88The `@tool` and `@prompt` decorators are defined as stubs in the `superset-core` package (`superset_core.mcp.decorators`). At startup, `app.py` calls `initialize_core_mcp_dependencies()` which replaces these stubs with concrete implementations that register tools/prompts with the FastMCP instance. This avoids circular imports between `superset_core` and `superset`.
 
 
 
89 
90**Startup flow**:
911. `app.py` creates the FastMCP `mcp` instance
922. `initialize_core_mcp_dependencies()` injects the real decorator implementations
933. Tool/prompt/resource imports at the bottom of `app.py` trigger registration
944. `server.py` adds middleware and starts the transport
95 
96## Critical Convention: Tool, Prompt, and Resource Registration
97 
98**IMPORTANT**: When creating new MCP tools, prompts, or resources, you MUST add their imports to `app.py` for auto-registration. Do NOT add them to `server.py` - that approach doesn't work properly.
99 
100### How to Add a New Tool
101 
1021. **Create the tool file** in the appropriate directory (e.g., `chart/tool/my_new_tool.py`)
1032. **Decorate with `@tool`** using the decorator from `superset_core.mcp.decorators`
1043. **Export from the module's `__init__.py`** (e.g., `chart/tool/__init__.py`)
1054. **Add import to `app.py`** at the bottom of the file where other tools are imported
106 
107**Example (read-only tool)**:
108```python
109# superset/mcp_service/chart/tool/my_new_tool.py
110from fastmcp import Context
111from superset_core.mcp.decorators import tool, ToolAnnotations
112 
113from superset.extensions import event_logger
114 
115@tool(
116 tags=["core"],
117 class_permission_name="Chart",
118 annotations=ToolAnnotations(
119 title="My new tool",
120 readOnlyHint=True,
121 destructiveHint=False,
122 ),
123)
124async def my_new_tool(request: MyRequest, ctx: Context) -> MyResponse:
125 """Tool description for LLMs."""
126 await ctx.info("Doing something: param=%s" % (request.param,))
127 with event_logger.log_context(action="mcp.my_new_tool"):
128 result = do_something()
129 return MyResponse(data=result)
130```
131 
132**Example (mutating tool)**:
133```python
134@tool(
135 tags=["mutate"],
136 class_permission_name="Chart",
137 method_permission_name="write",
138 annotations=ToolAnnotations(
139 title="Create something",
140 readOnlyHint=False,
141 destructiveHint=False,
142 ),
143)
144async def create_something(request: CreateRequest, ctx: Context) -> CreateResponse:
145 """Creates a new resource."""
146 ...
147```
148 
149**Then add to app.py**:
150```python
151# superset/mcp_service/app.py (at the bottom, after initialize_core_mcp_dependencies())
152from superset.mcp_service.chart.tool import ( # noqa: F401, E402
153 get_chart_info,
154 list_charts,
155 my_new_tool, # ADD YOUR TOOL HERE
156)
157```
158 
159**Why this matters**: Tools register automatically on import via the `@tool` decorator. The import MUST be in `app.py` at the bottom (after `initialize_core_mcp_dependencies()` is called). DO NOT add imports to `server.py`.
 
 
160 
161### How to Add a New Prompt
162 
1631. **Create the prompt file** in the appropriate directory (e.g., `chart/prompts/my_new_prompt.py`)
1642. **Decorate with `@prompt`** from `superset_core.mcp.decorators`
1653. **Add import to module's `__init__.py`** (e.g., `chart/prompts/__init__.py`)
1664. **Ensure module is imported in `app.py`**
167 
168**Example**:
169```python
170# superset/mcp_service/chart/prompts/my_new_prompt.py
171from superset_core.mcp.decorators import prompt
172 
173@prompt("my_new_prompt")
174async def my_new_prompt_handler(
175 chart_type: str = "auto", business_goal: str = "exploration"
176) -> str:
177 """Interactive prompt for doing something."""
178 return "Prompt instructions here..."
179```
180 
181### How to Add a New Resource
 
182 
183Resources use direct FastMCP decorators and **must include `@mcp_auth_hook`** for authentication:
184 
185```python
186# superset/mcp_service/chart/resources/my_new_resource.py
187from superset.mcp_service.app import mcp
188from superset.mcp_service.auth import mcp_auth_hook # REQUIRED for resources
189 
190@mcp.resource("superset://chart/my_resource")
191@mcp_auth_hook # Always add this decorator to resources
192def get_my_resource() -> str:
193 """Resource description for LLMs."""
194 return "Resource data here..."
195```
196 
197## Tool Development Patterns
198 
199### 1. Tool Decorator Parameters
200 
201The `@tool` decorator from `superset_core.mcp.decorators` accepts:
202 
203- **`tags`**: List of tags (e.g., `["core"]`, `["mutate"]`). Default: `[]`
204- **`class_permission_name`**: FAB permission class (e.g., `"Chart"`, `"Dashboard"`). Default: `None`
205- **`method_permission_name`**: Permission action (e.g., `"read"`, `"write"`). Default: Auto — `"write"` if `"mutate"` in tags, else `"read"`
206- **`protect`**: Enable authentication wrapping. Default: `True`
207- **`annotations`**: MCP `ToolAnnotations` object. Default: `None`
208 
209**ToolAnnotations** (from `superset_core.mcp.decorators`):
210```python
211annotations=ToolAnnotations(
212 title="Human-readable title",
213 readOnlyHint=True, # Whether tool only reads data
214 destructiveHint=False, # Whether tool has destructive side effects
215)
216```
217 
218### 2. Use Core Classes for Reusability
219 
220The `mcp_core.py` module provides reusable patterns:
 
 
 
 
 
221 
222- **`ModelListCore`**: For listing resources with filtering, search, and pagination
223 - Used by: `list_charts`, `list_dashboards`, `list_datasets`, `list_databases`
224- **`ModelGetInfoCore`**: For getting resource details by ID, UUID, or slug
225 - Used by: `get_chart_info`, `get_dashboard_info`, `get_dataset_info`, `get_database_info`
226- **`ModelGetSchemaCore`**: For schema discovery (columns, filters, sortable columns)
227 - Used by: `get_schema`
228- **`InstanceInfoCore`**: For instance statistics and metadata
229 - Used by: `get_instance_info`
230 
231### 3. Authentication and RBAC
 
 
232 
233Authentication is handled automatically by the `@tool` decorator (via `mcp_auth_hook` internally). RBAC permission checking uses `class_permission_name` and `method_permission_name`.
 
 
234 
235```python
236from superset_core.mcp.decorators import tool, ToolAnnotations
237 
238# Authentication + RBAC enabled (default)
239@tool(
240 class_permission_name="Chart", # Checks user has Chart access
241)
242async def my_tool(request: MyRequest, ctx: Context) -> MyResponse:
243 # g.user is set automatically before this runs
244 ...
245 
246# Public tool (no auth) - use sparingly, and add the tool name to
247# ALLOWED_UNPROTECTED in app.py (e.g. generate_bug_report)
248@tool(protect=False)
249async def public_status(ctx: Context) -> dict:
250 return {"status": "healthy"}
251```
252 
253Note: `health_check` is a protected, authenticated tool (`@tool(tags=["core"], ...)`,
254no `protect=False`) — it is not an example of a public tool.
255 
256**Authentication priority order** (in `auth.py`):
2571. JWT context (per-request ContextVar from FastMCP). Also resolves a verified
258 embedded **guest token** to a `GuestUser` when `MCP_EMBEDDED_GUEST_AUTH_ENABLED`
259 + `EMBEDDED_SUPERSET` are on (a guest is never downgraded to a lower priority).
2602. API Key authentication (via FAB SecurityManager)
2613. `MCP_DEV_USERNAME` config (development only)
2624. `g.user` fallback (set by external middleware)
263 
264Guest tokens are verified by `GuestTokenVerifier` (in the `CompositeTokenVerifier`,
265before the JWT verifier) using the shared core `GUEST_TOKEN_JWT_*` config, then
266built into a `GuestUser` in `_resolve_user_from_jwt_context`. See `SECURITY.md`.
 
 
 
 
 
 
267 
268**`@mcp_auth_hook`** is only used directly on **resources** — tools get auth wrapping from `@tool(protect=True)`.
 
 
 
269 
270### 4. Use Pydantic Schemas
271 
272**All tool inputs and outputs must be Pydantic models**. Place schemas in `{module}/schemas.py`.
 
 
 
 
273 
274```python
275from pydantic import BaseModel, ConfigDict, Field
276 
277class MyToolRequest(BaseModel):
278 model_config = ConfigDict(populate_by_name=True)
 
 
 
 
279 
280 param: str = Field(..., description="Parameter description for LLMs")
281 optional_param: str | None = Field(None, description="Optional parameter")
 
 
 
282 
283class MyToolResponse(BaseModel):
284 result: str = Field(..., description="Result description")
285 error: str | None = Field(None, description="Error message if failed")
286```
287 
288### 5. Follow the DAO Pattern
 
 
 
 
289 
290**Use Superset's DAO (Data Access Object) layer** instead of direct database queries:
 
 
 
 
 
291 
292```python
293from superset.daos.dashboard import DashboardDAO
 
 
294 
295# GOOD: Use DAO
296dashboard = DashboardDAO.find_by_id(dashboard_id)
 
 
297 
298# BAD: Don't query directly
299dashboard = db.session.query(Dashboard).filter_by(id=dashboard_id).first()
300```
301 
302### 6. Python Type Hints (Python 3.10+ Style)
303 
304**CRITICAL**: Always use modern Python 3.10+ union syntax for type hints.
305 
306```python
307# GOOD - Modern Python 3.10+ syntax
308from typing import Any
309 
310from pydantic import BaseModel, Field
311 
312class MySchema(BaseModel):
313 name: str | None = Field(None, description="Optional name")
314 tags: list[str] = Field(default_factory=list)
315 metadata: dict[str, Any] = Field(default_factory=dict)
316 
317def my_function(
318 id: int,
319 filters: list[str] | None = None,
320) -> MySchema | None:
321 pass
322 
323# BAD - Old-style (DO NOT USE)
324from typing import Optional, List, Dict
325name: Optional[str] # Wrong! Use str | None
326tags: List[str] # Wrong! Use list[str]
327```
328 
329### 7. Event Logger Instrumentation
 
330 
331**All tool operations should use `event_logger`** for observability:
 
 
 
332 
333```python
334from superset.extensions import event_logger
 
335 
336@tool(...)
337async def my_tool(request: MyRequest, ctx: Context) -> MyResponse:
338 with event_logger.log_context(action="mcp.my_tool.step_name"):
339 result = do_something()
340 return MyResponse(data=result)
341```
342 
343### 8. Context Logging
344 
345Use the FastMCP `Context` object for structured logging within tools:
 
 
 
 
 
346 
347```python
348async def my_tool(request: MyRequest, ctx: Context) -> MyResponse:
349 await ctx.info("Starting: param=%s" % (request.param,))
350 await ctx.debug("Details: keys=%s" % (sorted(request.model_dump().keys()),))
351 await ctx.warning("Something unexpected: %s" % (warning_msg,))
352 await ctx.error("Failed: %s" % (str(exc),))
353 await ctx.report_progress(1, 5, "Step 1 of 5")
354```
355 
356### 9. Error Handling
357 
358**Pattern**: Catch specific exceptions for known failure modes, use broad `Exception` only as the outermost safety net that re-raises:
359 
360```python
361from superset.commands.dataset.exceptions import DatasetInvalidError, DatasetCreateFailedError
 
362 
363@tool(...)
364async def my_tool(request: MyRequest, ctx: Context) -> MyResponse:
365 try:
366 # Specific exception handling for known failure modes
367 with event_logger.log_context(action="mcp.my_tool"):
368 result = SomeCommand(properties).run()
369 return MyResponse(data=result)
370 
371 except DatasetInvalidError as exc:
372 # Return structured error response (don't raise)
373 await ctx.error("Validation failed: %s" % (exc.normalized_messages(),))
374 return MyResponse(error=str(exc.normalized_messages()))
375 
376 except DatasetCreateFailedError as exc:
377 await ctx.error("Creation failed: %s" % (str(exc),))
378 return MyResponse(error=f"Failed: {exc}")
379 
380 except Exception as exc:
381 # Outermost safety net: log and re-raise (middleware handles it)
382 await ctx.error("Unexpected: %s: %s" % (type(exc).__name__, str(exc)))
383 raise
384```
385 
386### 10. Dataset Validation for Chart Tools
 
 
 
 
 
 
 
 
 
 
387 
388All chart-related tools must validate that the chart's dataset is accessible:
389 
390```python
391from superset.mcp_service.chart.chart_utils import validate_chart_dataset
 
 
 
 
392 
393validation_result = validate_chart_dataset(chart, check_access=True)
394if not validation_result.is_valid:
395 await ctx.warning("Dataset not accessible: %s" % (validation_result.error,))
396 return ChartError(
397 error=validation_result.error or "Chart's dataset is not accessible",
398 error_type="DatasetNotAccessible",
399 )
400```
401 
402Used by: `get_chart_info`, `get_chart_preview`, `get_chart_data`, `generate_chart`
403 
404### 11. Compile Check for Chart Creation
 
 
 
 
405 
406When creating, saving, or previewing charts, run schema validation (Tier 1)
407and optionally a compile check (Tier 2) before persisting or caching.
408``validate_and_compile`` glues both together; tools with tight SLAs
409(``generate_explore_link``, ``update_chart_preview``) opt out of Tier 2.
410 
411```python
412from superset.mcp_service.chart.compile import validate_and_compile
413 
414result = validate_and_compile(
415 config, form_data, dataset, run_compile_check=True
416)
417if not result.success:
418 # ``result.error_obj`` is a ``ChartGenerationError`` with fuzzy-match
419 # suggestions ("did you mean sum_boys?") so the LLM can self-correct.
420 ...
421```
422 
423The lower-level ``_compile_chart(form_data, dataset_id)`` is still exported
424for callers that have already done their own schema validation.
425 
426### 12. Flexible Input Parsing
427 
428`ModelListCore` handles JSON string vs. native object parsing automatically via utilities in `superset.mcp_service.utils.schema_utils`:
429 
430- `parse_json_or_passthrough(value, param_name)` - JSON string or dict
431- `parse_json_or_list(value, param_name)` - JSON array, list, or comma-separated string
432- `parse_json_or_model(value, model_class, param_name)` - JSON string or dict to Pydantic model
433- `parse_json_or_model_list(value, model_class, param_name)` - JSON array to list of Pydantic models
434 
435These are used internally by `ModelListCore` for `filters` and `select_columns`. Individual tools using core classes do NOT need to add parsing logic.
436 
437## Middleware
438 
439The MCP service uses FastMCP middleware (registered in `server.py`):
440 
441- **`LoggingMiddleware`**: Logs tool calls with duration, entity IDs, sanitizes sensitive data
442- **`GlobalErrorHandlerMiddleware`**: Catches unhandled exceptions, converts to ToolError
443- **`StructuredContentStripperMiddleware`**: Strips structuredContent from responses (Claude.ai compatibility)
444- **`ResponseSizeGuardMiddleware`**: Prevents oversized responses from crashing clients
445- **`ResponseCachingMiddleware`**: Optional response caching (in-memory by default, Redis when store enabled)
446 
447Middleware is applied in `server.py` and should NOT be modified in individual tools.
448 
449## Configuration
450 
451Default configuration is in `mcp_config.py`. Override in `superset_config.py`:
452 
453```python
454# Authentication
455MCP_DEV_USERNAME = None # Fallback username for dev mode
456MCP_AUTH_ENABLED = False # Enable JWT/API key auth
457MCP_AUTH_FACTORY = None # Custom auth factory function
458MCP_JWT_PUBLIC_KEY = None
459MCP_JWT_SECRET = None
460MCP_JWKS_URI = None
461MCP_USER_RESOLVER = None # Custom function to extract username from JWT
462 
463# RBAC
464MCP_RBAC_ENABLED = True # Enable permission checking (default: True)
465 
466# Embedded guest auth (opt-in; requires the EMBEDDED_SUPERSET feature flag).
467# Reuses core GUEST_TOKEN_JWT_* config — no MCP-specific guest secret/audience.
468MCP_EMBEDDED_GUEST_AUTH_ENABLED = False
469# Default-deny: the ONLY tools a guest may call (everything else is denied).
470MCP_GUEST_ALLOWED_TOOLS = {
471 "get_dashboard_info", "get_dashboard_layout", "list_dashboards",
472 "list_charts", "get_chart_info", "get_chart_data", "get_chart_preview",
473}
474# Principal-agnostic extension point: given the current user, return an allow-list
475# (only these tools are callable) or None if unrestricted. Defaults to restricting
476# embedded guests to MCP_GUEST_ALLOWED_TOOLS; override to add other restricted
477# principals without touching the enforcement path.
478MCP_RESTRICTED_TOOL_POLICY = None # Callable[[user], frozenset[str] | None]
479 
480 
481# Response Caching (optional, uses in-memory store by default; Redis when MCP_STORE_CONFIG enabled)
482MCP_CACHE_CONFIG = {
483 "enabled": False,
484 "list_tools_ttl": 300,
485 "call_tool_ttl": 3600,
486 "excluded_tools": ["execute_sql", "generate_dashboard"], # add tools to exclude
487}
488 
489# Multi-pod Storage (optional, requires Redis)
490MCP_STORE_CONFIG = {
491 "enabled": False,
492 "CACHE_REDIS_URL": None,
493 "event_store_ttl": 3600,
494}
495```
496 
497## Testing Conventions
498 
499### Test Organization
500 
501Tests mirror the MCP service module structure:
502```
503tests/unit_tests/mcp_service/
504├── conftest.py # Global fixtures (disable_mcp_rbac)
505├── chart/
506│ ├── test_chart_utils.py
507│ ├── test_chart_schemas.py
508│ └── tool/
509│ ├── test_list_charts.py
510│ ├── test_generate_chart.py
511│ └── ...
512├── dashboard/tool/
513├── dataset/tool/
514├── sql_lab/tool/
515├── system/tool/
516├── test_auth_*.py # Auth/RBAC tests
517└── test_middleware*.py # Middleware tests
518```
519 
520### Async Tool Tests (primary pattern)
521 
522```python
523from unittest.mock import MagicMock, patch
524import pytest
525from fastmcp import Client
526 
527from superset.mcp_service.app import mcp
528from superset.utils import json
529 
530@pytest.fixture
531def mcp_server():
532 return mcp
533 
534@pytest.mark.asyncio
535async def test_my_tool_success(mcp_server):
536 mock_obj = MagicMock()
537 mock_obj.id = 1
538 mock_obj.name = "test"
539 
540 with patch("superset.daos.chart.ChartDAO.find_by_id", return_value=mock_obj):
541 async with Client(mcp_server) as client:
542 result = await client.call_tool(
543 "my_tool", {"request": {"id": 1}}
544 )
545 data = json.loads(result.content[0].text)
546 
547 assert data["id"] == 1
548```
549 
550### Key Testing Patterns
551 
552- **RBAC is disabled globally** via `conftest.py` autouse fixture (`MCP_RBAC_ENABLED = False`)
553- **RBAC tests** are separate in `test_auth_rbac.py` with their own `enable_mcp_rbac` fixture
554- **Auth is mocked** via `mock_auth` fixture that patches `get_user_from_request`
555- **Mock objects** must have all attributes set explicitly (no auto-generation)
556- **Patch at the DAO level**: `patch("superset.daos.chart.ChartDAO.find_by_id", ...)`
557- **Schema validation tests** are synchronous (no Client needed)
558 
559## Common Pitfalls to Avoid
560 
561### 1. Forgetting Tool Import in app.py
562**Problem**: Tool exists but isn't available to MCP clients.
563**Solution**: Add tool import to `app.py` at the bottom (after `initialize_core_mcp_dependencies()`).
564 
565### 2. Adding Tool Imports to server.py
566**Problem**: Tools won't register properly.
567**Solution**: Tool imports MUST be in `app.py`, not `server.py`.
568 
569### 3. Wrong Decorator Import Path
570**Problem**: Using stale import path.
571**Solution**: Use `from superset_core.mcp.decorators import tool, ToolAnnotations` (NOT `superset_core.api.mcp`).
572 
573### 4. Missing ToolAnnotations
574**Problem**: Tool lacks MCP directory compliance metadata.
575**Solution**: Always include `annotations=ToolAnnotations(title=..., readOnlyHint=..., destructiveHint=...)`.
576 
577### 5. Using `Optional` Instead of Union Syntax
578**Problem**: Old-style `Optional[T]` is not Python 3.10+ style.
579**Solution**: Use `T | None` and `list[str]` instead of `Optional[T]` and `List[str]`.
580 
581### 6. Direct Database Queries
582**Problem**: Bypasses Superset's security and caching layers.
583**Solution**: Use DAO classes (ChartDAO, DashboardDAO, DatasetDAO, DatabaseDAO).
584 
585### 7. Not Using Core Classes
586**Problem**: Duplicating list/get_info logic across tools.
587**Solution**: Use `ModelListCore`, `ModelGetInfoCore`, `ModelGetSchemaCore`.
588 
589### 8. Missing Apache License Headers
590**Problem**: CI fails on license check.
591**Solution**: Add ASF license header to all new `.py` files (see template at top of this doc).
592 
593### 9. Circular Imports
594**Problem**: Importing from `app.py` in tool files causes circular dependencies.
595**Solution**: Use `from superset_core.mcp.decorators import tool` for tools/prompts. Only import `from superset.mcp_service.app import mcp` in resource files.
596 
597### 10. Missing event_logger Instrumentation
598**Problem**: Tool operations are invisible to observability.
599**Solution**: Wrap key operations with `event_logger.log_context(action="mcp.tool_name.step")`.
600 
601## Quick Checklist for New Tools
602 
603- [ ] Created tool file in `{module}/tool/{tool_name}.py`
604- [ ] Added ASF license header
605- [ ] Used `@tool(tags=[...], class_permission_name="...", annotations=ToolAnnotations(...))` decorator
606- [ ] Import: `from superset_core.mcp.decorators import tool, ToolAnnotations`
607- [ ] Created Pydantic request/response schemas in `{module}/schemas.py`
608- [ ] Used DAO classes instead of direct queries
609- [ ] Added `event_logger.log_context()` instrumentation
610- [ ] Used `await ctx.info/error/debug()` for context logging
611- [ ] Exported from `{module}/tool/__init__.py`
612- [ ] Added tool import to `app.py` at the bottom
613- [ ] Created async unit tests in `tests/unit_tests/mcp_service/{module}/tool/`
614- [ ] Updated `DEFAULT_INSTRUCTIONS` in `app.py` if adding new capability
615 
616## Quick Checklist for New Prompts
617 
618- [ ] Created prompt file in `{module}/prompts/{prompt_name}.py`
619- [ ] Added ASF license header
620- [ ] Used `@prompt("prompt_name")` from `superset_core.mcp.decorators`
621- [ ] Made function async: `async def prompt_handler(...) -> str`
622- [ ] Added import to `{module}/prompts/__init__.py`
623- [ ] Verified module import exists in `app.py`
624 
625## Quick Checklist for New Resources
626 
627- [ ] Created resource file in `{module}/resources/{resource_name}.py`
628- [ ] Added ASF license header
629- [ ] Used `@mcp.resource("superset://{path}")` decorator
630- [ ] Added `@mcp_auth_hook` decorator
631- [ ] Added import to `{module}/resources/__init__.py`
632- [ ] Verified module import exists in `app.py`
633 
634## Getting Help
635 
636- Check existing tool implementations for patterns (chart/tool/, dashboard/tool/)
637- Review core classes in `mcp_core.py` for reusable functionality
638- See `CLAUDE.md` in project root for general Superset development guidelines
639- Consult Superset documentation: https://superset.apache.org/docs/
640 
@@ −1 +1 @@
1−# LLM Context Guide for Apache Superset
1+# MCP Service - LLM Agent Guide
22  
3−Apache Superset is a data visualization platform with Flask/Python backend and React/TypeScript frontend.
3+This guide helps LLM agents understand the Superset MCP (Model Context Protocol) service architecture and development conventions.
44  
5−## Run Pre-commit Before Pushing
5+## CRITICAL: Apache License Headers
66  
7−Always run pre-commit against the files changed by the current branch before
8−pushing. This matches CI and keeps unrelated failures already present on
9−`master` from blocking otherwise independent work.
7+**EVERY Python file in the MCP service MUST have the Apache Software Foundation license header.**
108  
11−```bash
12−# Stage your changes first
13−git add .
9+This includes:
10+- All `.py` files (tool files, schemas, __init__.py files, etc.)
11+- **NEVER remove existing license headers during refactoring or edits**
12+- **ALWAYS add license headers when creating new files**
13+- **ALWAYS verify license headers are present after editing files**
1414  
15−# Run pre-commit on staged files
16−pre-commit run
15+If you see a file without a license header, ADD IT IMMEDIATELY. If you accidentally remove one during editing, ADD IT BACK.
1716  
18−# If there are auto-fixes, stage them and commit
19−git add .
20−git commit --amend # or new commit
17+Use this exact template at the top of EVERY Python file:
18+ 
19+```python
20+# Licensed to the Apache Software Foundation (ASF) under one
21+# or more contributor license agreements. See the NOTICE file
22+# distributed with this work for additional information
23+# regarding copyright ownership. The ASF licenses this file
24+# to you under the Apache License, Version 2.0 (the
25+# "License"); you may not use this file except in compliance
26+# with the License. You may obtain a copy of the License at
27+#
28+# http://www.apache.org/licenses/LICENSE-2.0
29+#
30+# Unless required by applicable law or agreed to in writing,
31+# software distributed under the License is distributed on an
32+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
33+# KIND, either express or implied. See the License for the
34+# specific language governing permissions and limitations
35+# under the License.
2136 ```
2237  
23−Use `pre-commit run --all-files` when auditing or repairing the repository-wide
24−baseline. If that check finds failures in files untouched by the current branch,
25−fix them in a separate branch rather than adding unrelated changes to the
26−current pull request.
38+**Note**: LLM instruction files like `CLAUDE.md`, `AGENTS.md`, etc. are excluded from this requirement (listed in `.rat-excludes`) to avoid token overhead, but ALL other Python files require it.
2739  
28−Common pre-commit failures:
29−- **Formatting** - black, oxfmt, eslint will auto-fix
30−- **Type errors** - mypy failures need manual fixes
31−- **Linting** - ruff, pylint issues need manual fixes
40+## Architecture Overview
3241  
33−## ⚠️ CRITICAL: Ongoing Refactors (What NOT to Do)
42+The MCP service provides programmatic access to Superset via the Model Context Protocol, allowing AI assistants to interact with dashboards, charts, datasets, databases, SQL Lab, and instance metadata.
3443  
35−**These migrations are actively happening - avoid deprecated patterns:**
44+### Key Components
3645  
37−### Frontend Modernization
38−- **NO `any` types** - Use proper TypeScript types
39−- **NO JavaScript files** - Convert to TypeScript (.ts/.tsx)
40−- **Use @superset-ui/core** - Don't import Ant Design directly, prefer Ant Design component wrappers from @superset-ui/core/components
41−- **Use antd theming tokens** - Prefer antd tokens over legacy theming tokens
42−- **Avoid custom css and styles** - Follow antd best practices and avoid styling and custom CSS whenever possible
46+```
47+superset/mcp_service/
48+├── app.py # FastMCP app factory and tool registration
49+├── auth.py # Authentication, authorization, and RBAC
50+├── mcp_config.py # Default configuration
51+├── mcp_core.py # Reusable core classes for tools
52+├── flask_singleton.py # Flask app singleton for MCP context
53+├── middleware.py # FastMCP middleware (logging, errors, size guards)
54+├── server.py # Server startup (streamable-http, multi-pod)
55+├── jwt_verifier.py # JWT token validation
56+├── chart/ # Chart tools, schemas, prompts, resources
57+│ ├── schemas.py
58+│ ├── chart_utils.py
59+│ ├── preview_utils.py
60+│ ├── validation.py
61+│ ├── tool/
62+│ ├── prompts/
63+│ └── resources/
64+├── dashboard/ # Dashboard tools and schemas
65+│ ├── schemas.py
66+│ └── tool/
67+├── dataset/ # Dataset tools and schemas
68+│ ├── schemas.py
69+│ └── tool/
70+├── explore/ # Explore link generation
71+│ ├── schemas.py
72+│ └── tool/
73+├── sql_lab/ # SQL Lab tools (execute, save, open)
74+│ ├── schemas.py
75+│ └── tool/
76+├── system/ # System tools (health, instance info, schema)
77+│ ├── schemas.py
78+│ ├── tool/
79+│ ├── prompts/
80+│ └── resources/
81+├── common/ # Shared error schemas
82+├── commands/ # MCP-specific command classes
83+└── utils/ # Utilities (URL, schema parsing, error builders)
84+```
4385  
44−### Testing Strategy Migration
45−- **Prefer unit tests** over integration tests
46−- **Prefer integration tests** over end-to-end tests
47−- **Use Playwright for E2E tests** - Migrating from Cypress
48−- **Cypress is deprecated** - Will be removed once migration is completed
49−- **Use Jest + React Testing Library** for component testing
50−- **Use `test()` instead of `describe()`** - Follow [avoid nesting when testing](https://kentcdodds.com/blog/avoid-nesting-when-youre-testing) principles
86+### Dependency Injection Architecture
5187  
52−### Backend Type Safety
53−- **Add type hints** - All new Python code needs proper typing
54−- **MyPy compliance** - Run `pre-commit run mypy` to validate
55−- **SQLAlchemy typing** - Use proper model annotations
88+The `@tool` and `@prompt` decorators are defined as stubs in the `superset-core` package (`superset_core.mcp.decorators`). At startup, `app.py` calls `initialize_core_mcp_dependencies()` which replaces these stubs with concrete implementations that register tools/prompts with the FastMCP instance. This avoids circular imports between `superset_core` and `superset`.
5689  
57−### UUID Migration
58−- **Prefer UUIDs over auto-incrementing IDs** - New models should use UUID primary keys
59−- **External API exposure** - Use UUIDs in public APIs instead of internal integer IDs
60−- **Existing models** - Add UUID fields alongside integer IDs for gradual migration
90+**Startup flow**:
91+1. `app.py` creates the FastMCP `mcp` instance
92+2. `initialize_core_mcp_dependencies()` injects the real decorator implementations
93+3. Tool/prompt/resource imports at the bottom of `app.py` trigger registration
94+4. `server.py` adds middleware and starts the transport
6195  
62−## Security and Threat Model
96+## Critical Convention: Tool, Prompt, and Resource Registration
6397  
64−Before evaluating any code path for security issues, read [`SECURITY.md`](SECURITY.md). It is the canonical, authoritative source for Apache Superset's security model and is referenced by both human reporters and automated scanners.
98+**IMPORTANT**: When creating new MCP tools, prompts, or resources, you MUST add their imports to `app.py` for auto-registration. Do NOT add them to `server.py` - that approach doesn't work properly.
6599  
66−In short, the test for whether a finding is in scope is one question:
100+### How to Add a New Tool
67101  
68−> *Does it let a principal perform an action the role and capability matrix in `SECURITY.md` does not entitle them to?*
102+1. **Create the tool file** in the appropriate directory (e.g., `chart/tool/my_new_tool.py`)
103+2. **Decorate with `@tool`** using the decorator from `superset_core.mcp.decorators`
104+3. **Export from the module's `__init__.py`** (e.g., `chart/tool/__init__.py`)
105+4. **Add import to `app.py`** at the bottom of the file where other tools are imported
69106  
70−If yes, it is in scope. If no, it is not.
107+**Example (read-only tool)**:
108+```python
109+# superset/mcp_service/chart/tool/my_new_tool.py
110+from fastmcp import Context
111+from superset_core.mcp.decorators import tool, ToolAnnotations
71112  
72−The three trust boundaries are:
113+from superset.extensions import event_logger
73114  
74−1. **The Admin role** is a fully trusted operational principal. Anything an Admin can do through documented configuration, API, or UI is an intended capability, not a vulnerability.
75−2. **The operator** owns deployment-time decisions (secrets, network exposure, feature-flag selection, connector and codec choices, notification destinations, third-party plugins). Misconfiguration at this layer is a deployment defect, not a Superset vulnerability.
76−3. **The codebase** is responsible for enforcing the role and capability matrix wherever it exposes functionality to a principal: API routes, command and DAO layers, UI handlers, background jobs, and any other entry point. A missing or incorrect enforcement check is in scope no matter where it lives.
115+@tool(
116+ tags=["core"],
117+ class_permission_name="Chart",
118+ annotations=ToolAnnotations(
119+ title="My new tool",
120+ readOnlyHint=True,
121+ destructiveHint=False,
122+ ),
123+)
124+async def my_new_tool(request: MyRequest, ctx: Context) -> MyResponse:
125+ """Tool description for LLMs."""
126+ await ctx.info("Doing something: param=%s" % (request.param,))
127+ with event_logger.log_context(action="mcp.my_new_tool"):
128+ result = do_something()
129+ return MyResponse(data=result)
130+```
77131  
78−The security model assumes that operator-controlled infrastructure, including the metadata database, cache backends, message brokers, secret stores, and deployment environment, remains within the operator's trust boundary. Vulnerabilities must demonstrate a security boundary violation by an attacker who does not already control those systems.
132+**Example (mutating tool)**:
133+```python
134+@tool(
135+ tags=["mutate"],
136+ class_permission_name="Chart",
137+ method_permission_name="write",
138+ annotations=ToolAnnotations(
139+ title="Create something",
140+ readOnlyHint=False,
141+ destructiveHint=False,
142+ ),
143+)
144+async def create_something(request: CreateRequest, ctx: Context) -> CreateResponse:
145+ """Creates a new resource."""
146+ ...
147+```
79148  
80−Route-level authorization in this codebase uses one of three Flask-AppBuilder decorators depending on the route type:
149+**Then add to app.py**:
150+```python
151+# superset/mcp_service/app.py (at the bottom, after initialize_core_mcp_dependencies())
152+from superset.mcp_service.chart.tool import ( # noqa: F401, E402
153+ get_chart_info,
154+ list_charts,
155+ my_new_tool, # ADD YOUR TOOL HERE
156+)
157+```
81158  
82−- `@protect()` for REST API routes (`ModelRestApi` / `BaseApi`)
83−- `@has_access_api` for legacy view routes
84−- `@has_access` for legacy HTML view routes
159+**Why this matters**: Tools register automatically on import via the `@tool` decorator. The import MUST be in `app.py` at the bottom (after `initialize_core_mcp_dependencies()` is called). DO NOT add imports to `server.py`.
85160  
86−Object-level authorization via `security_manager.raise_for_access(...)` applies to data-bearing resources: dashboards, charts, datasets and datasources, queries, database and table access, and query contexts. Other resources (annotations, tags, CSS templates, reports, RLS rules, and similar) rely on the route-level decorator plus DAO `base_filters` for ownership scoping; the absence of `raise_for_access` on these resources is by design, not a finding. Code that omits the per-object gate on a route that returns or mutates a specific data-bearing object is in scope; code that follows the correct pattern for its resource class can still contain injection, SSRF, XSS, or other classes of finding unrelated to authorization, which are evaluated separately.
161+### How to Add a New Prompt
87162  
88−The full role and capability matrix, in-scope and out-of-scope class lists, and CVE aggregation rules are in [`SECURITY.md`](SECURITY.md). Defer to that document for any specifics.
163+1. **Create the prompt file** in the appropriate directory (e.g., `chart/prompts/my_new_prompt.py`)
164+2. **Decorate with `@prompt`** from `superset_core.mcp.decorators`
165+3. **Add import to module's `__init__.py`** (e.g., `chart/prompts/__init__.py`)
166+4. **Ensure module is imported in `app.py`**
89167  
90−**Requirements for findings filed by automated tooling**
168+**Example**:
169+```python
170+# superset/mcp_service/chart/prompts/my_new_prompt.py
171+from superset_core.mcp.decorators import prompt
91172  
92−Automated scanners (LLM-based code scanners, static analyzers, dependency tools) that file findings against this codebase must, in each finding, name:
173+@prompt("my_new_prompt")
174+async def my_new_prompt_handler(
175+ chart_type: str = "auto", business_goal: str = "exploration"
176+) -> str:
177+ """Interactive prompt for doing something."""
178+ return "Prompt instructions here..."
179+```
93180  
94−1. The specific role and capability matrix row in [`SECURITY.md`](SECURITY.md) the finding believes is violated.
95−2. The principal the finding assumes the attacker holds (Public, Gamma, sql_lab, Alpha, Admin, Embedded guest token, or a custom role with explicit capability grants).
181+### How to Add a New Resource
96182  
97−Findings that cannot identify both should be filed as questions, not vulnerabilities. This requirement exists to ensure every reported issue is testable against the published security model and to keep speculative or pattern-match-only reports out of the triage queue.
183+Resources use direct FastMCP decorators and **must include `@mcp_auth_hook`** for authentication:
98184  
99−## Key Directories
185+```python
186+# superset/mcp_service/chart/resources/my_new_resource.py
187+from superset.mcp_service.app import mcp
188+from superset.mcp_service.auth import mcp_auth_hook # REQUIRED for resources
100189  
190+@mcp.resource("superset://chart/my_resource")
191+@mcp_auth_hook # Always add this decorator to resources
192+def get_my_resource() -> str:
193+ """Resource description for LLMs."""
194+ return "Resource data here..."
101195 ```
102−superset/
103−├── superset/ # Python backend (Flask, SQLAlchemy)
104−│ ├── views/api/ # REST API endpoints
105−│ ├── models/ # Database models
106−│ └── connectors/ # Database connections
107−├── superset-frontend/src/ # React TypeScript frontend
108−│ ├── components/ # Reusable components
109−│ ├── explore/ # Chart builder
110−│ ├── dashboard/ # Dashboard interface
111−│ └── SqlLab/ # SQL editor
112−├── superset-frontend/packages/
113−│ └── superset-ui-core/ # UI component library (USE THIS)
114−├── tests/ # Python/integration tests
115−├── docs/ # Documentation (UPDATE FOR CHANGES)
116−└── UPDATING.md # Breaking changes log
196+ 
197+## Tool Development Patterns
198+ 
199+### 1. Tool Decorator Parameters
200+ 
201+The `@tool` decorator from `superset_core.mcp.decorators` accepts:
202+ 
203+- **`tags`**: List of tags (e.g., `["core"]`, `["mutate"]`). Default: `[]`
204+- **`class_permission_name`**: FAB permission class (e.g., `"Chart"`, `"Dashboard"`). Default: `None`
205+- **`method_permission_name`**: Permission action (e.g., `"read"`, `"write"`). Default: Auto — `"write"` if `"mutate"` in tags, else `"read"`
206+- **`protect`**: Enable authentication wrapping. Default: `True`
207+- **`annotations`**: MCP `ToolAnnotations` object. Default: `None`
208+ 
209+**ToolAnnotations** (from `superset_core.mcp.decorators`):
210+```python
211+annotations=ToolAnnotations(
212+ title="Human-readable title",
213+ readOnlyHint=True, # Whether tool only reads data
214+ destructiveHint=False, # Whether tool has destructive side effects
215+)
117216 ```
118217  
119−## Code Standards
218+### 2. Use Core Classes for Reusability
120219  
121−### TypeScript Frontend
122−- **Avoid `any` types** - Use proper TypeScript, reuse existing types
123−- **Functional components** with hooks
124−- **@superset-ui/core** for UI components (not direct antd)
125−- **Jest** for testing (NO Enzyme)
126−- **Redux** for global state where it exists, hooks for local
220+The `mcp_core.py` module provides reusable patterns:
127221  
128−### Python Backend
129−- **Type hints required** for all new code
130−- **MyPy compliant** - run `pre-commit run mypy`
131−- **SQLAlchemy models** with proper typing
132−- **pytest** for testing
222+- **`ModelListCore`**: For listing resources with filtering, search, and pagination
223+ - Used by: `list_charts`, `list_dashboards`, `list_datasets`, `list_databases`
224+- **`ModelGetInfoCore`**: For getting resource details by ID, UUID, or slug
225+ - Used by: `get_chart_info`, `get_dashboard_info`, `get_dataset_info`, `get_database_info`
226+- **`ModelGetSchemaCore`**: For schema discovery (columns, filters, sortable columns)
227+ - Used by: `get_schema`
228+- **`InstanceInfoCore`**: For instance statistics and metadata
229+ - Used by: `get_instance_info`
133230  
134−### Apache License Headers
135−- **New files require ASF license headers** - When creating new code files, include the standard Apache Software Foundation license header
136−- **LLM instruction files are excluded** - Files like AGENTS.md, CLAUDE.md, etc. are in `.rat-excludes` to avoid header token overhead
231+### 3. Authentication and RBAC
137232  
138−### Code Comments
139−- **Avoid time-specific language** - Don't use words like "now", "currently", "today" in code comments as they become outdated
140−- **Write timeless comments** - Comments should remain accurate regardless of when they're read
233+Authentication is handled automatically by the `@tool` decorator (via `mcp_auth_hook` internally). RBAC permission checking uses `class_permission_name` and `method_permission_name`.
141234  
142−## Documentation Requirements
235+```python
236+from superset_core.mcp.decorators import tool, ToolAnnotations
143237  
144−- **docs/**: Update for any user-facing changes
145−- **UPDATING.md**: Add breaking changes here
146−- **Docstrings**: Required for new functions/classes
238+# Authentication + RBAC enabled (default)
239+@tool(
240+ class_permission_name="Chart", # Checks user has Chart access
241+)
242+async def my_tool(request: MyRequest, ctx: Context) -> MyResponse:
243+ # g.user is set automatically before this runs
244+ ...
147245  
148−## Developer Portal: Storybook-to-MDX Documentation
246+# Public tool (no auth) - use sparingly, and add the tool name to
247+# ALLOWED_UNPROTECTED in app.py (e.g. generate_bug_report)
248+@tool(protect=False)
249+async def public_status(ctx: Context) -> dict:
250+ return {"status": "healthy"}
251+```
149252  
150−The Developer Portal auto-generates MDX documentation from Storybook stories. **Stories are the single source of truth.**
253+Note: `health_check` is a protected, authenticated tool (`@tool(tags=["core"], ...)`,
254+no `protect=False`) — it is not an example of a public tool.
151255  
152−### Core Philosophy
153−- **Fix issues in the STORY, not the generator** - When something doesn't render correctly, update the story file first
154−- **Generator should be lightweight** - It extracts and passes through data; avoid special cases
155−- **Stories define everything** - Props, controls, galleries, examples all come from story metadata
256+**Authentication priority order** (in `auth.py`):
257+1. JWT context (per-request ContextVar from FastMCP). Also resolves a verified
258+ embedded **guest token** to a `GuestUser` when `MCP_EMBEDDED_GUEST_AUTH_ENABLED`
259+ + `EMBEDDED_SUPERSET` are on (a guest is never downgraded to a lower priority).
260+2. API Key authentication (via FAB SecurityManager)
261+3. `MCP_DEV_USERNAME` config (development only)
262+4. `g.user` fallback (set by external middleware)
156263  
157−### Story Requirements for Docs Generation
158−- Use `export default { title: '...' }` (inline), not `const meta = ...; export default meta;`
159−- Name interactive stories `Interactive${ComponentName}` (e.g., `InteractiveButton`)
160−- Define `args` for default prop values
161−- Define `argTypes` at the story level (not meta level) with control types and descriptions
162−- Use `parameters.docs.gallery` for size×style variant grids
163−- Use `parameters.docs.sampleChildren` for components that need children
164−- Use `parameters.docs.liveExample` for custom live code blocks
165−- Use `parameters.docs.staticProps` for complex object props that can't be parsed inline
264+Guest tokens are verified by `GuestTokenVerifier` (in the `CompositeTokenVerifier`,
265+before the JWT verifier) using the shared core `GUEST_TOKEN_JWT_*` config, then
266+built into a `GuestUser` in `_resolve_user_from_jwt_context`. See `SECURITY.md`.
166267  
167−### Generator Location
168−- Script: `docs/scripts/generate-superset-components.mjs`
169−- Wrapper: `docs/src/components/StorybookWrapper.jsx`
170−- Output: `docs/developer_portal/components/`
268+**`@mcp_auth_hook`** is only used directly on **resources** — tools get auth wrapping from `@tool(protect=True)`.
171269  
172−## Architecture Patterns
270+### 4. Use Pydantic Schemas
173271  
174−### Security & Features
175−- **Security model**: see the top-level [Security and Threat Model](#security-and-threat-model) section and [`SECURITY.md`](SECURITY.md)
176−- **RBAC**: Role-based access via Flask-AppBuilder
177−- **Feature flags**: Control feature rollouts
178−- **Row-level security**: SQL-based data access control
272+**All tool inputs and outputs must be Pydantic models**. Place schemas in `{module}/schemas.py`.
179273  
180−## Test Utilities
274+```python
275+from pydantic import BaseModel, ConfigDict, Field
181276  
182−### Python Test Helpers
183−- **`SupersetTestCase`** - Base class in `tests/integration_tests/base_tests.py`
184−- **`@with_config`** - Config mocking decorator
185−- **`@with_feature_flags`** - Feature flag testing
186−- **`login_as()`, `login_as_admin()`** - Authentication helpers
187−- **`create_dashboard()`, `create_slice()`** - Data setup utilities
277+class MyToolRequest(BaseModel):
278+ model_config = ConfigDict(populate_by_name=True)
188279  
189−### TypeScript Test Helpers
190−- **`superset-frontend/spec/helpers/testing-library.tsx`** - Custom render() with providers
191−- **`createWrapper()`** - Redux/Router/Theme wrapper
192−- **`selectOption()`** - Select component helper
193−- **React Testing Library** - NO Enzyme (removed)
280+ param: str = Field(..., description="Parameter description for LLMs")
281+ optional_param: str | None = Field(None, description="Optional parameter")
194282  
195−### Test Database Patterns
196−- **Mock patterns**: Use `MagicMock()` for config objects, avoid `AsyncMock` for synchronous code
197−- **API tests**: Update expected columns when adding new model fields
283+class MyToolResponse(BaseModel):
284+ result: str = Field(..., description="Result description")
285+ error: str | None = Field(None, description="Error message if failed")
286+```
198287  
199−### Running Tests
200−```bash
201−# Frontend
202−npm run test # All tests
203−npm run test -- filename.test.tsx # Single file
288+### 5. Follow the DAO Pattern
204289  
205−# E2E Tests (Playwright - NEW)
206−npm run playwright:test # All Playwright tests
207−npm run playwright:ui # Interactive UI mode
208−npm run playwright:headed # See browser during tests
209−npx playwright test tests/auth/login.spec.ts # Single file
210−npm run playwright:debug tests/auth/login.spec.ts # Debug specific file
290+**Use Superset's DAO (Data Access Object) layer** instead of direct database queries:
211291  
212−# E2E Tests (Cypress - DEPRECATED)
213−cd superset-frontend/cypress-base
214−npm run cypress-run-chrome # All Cypress tests (headless)
215−npm run cypress-debug # Interactive Cypress UI
292+```python
293+from superset.daos.dashboard import DashboardDAO
216294  
217−# Backend
218−pytest # All tests
219−pytest tests/unit_tests/specific_test.py # Single file
220−pytest tests/unit_tests/ # Directory
295+# GOOD: Use DAO
296+dashboard = DashboardDAO.find_by_id(dashboard_id)
221297  
222−# If pytest fails with database/setup issues, ask the user to run test environment setup
298+# BAD: Don't query directly
299+dashboard = db.session.query(Dashboard).filter_by(id=dashboard_id).first()
223300 ```
224301  
225−## Environment Validation
302+### 6. Python Type Hints (Python 3.10+ Style)
226303  
227−**Quick Setup Check (run this first):**
304+**CRITICAL**: Always use modern Python 3.10+ union syntax for type hints.
228305  
229−```bash
230−# Verify Superset is running
231−curl -f http://localhost:8088/health || echo "❌ Setup required - see https://superset.apache.org/docs/contributing/development#working-with-llms"
306+```python
307+# GOOD - Modern Python 3.10+ syntax
308+from typing import Any
309+ 
310+from pydantic import BaseModel, Field
311+ 
312+class MySchema(BaseModel):
313+ name: str | None = Field(None, description="Optional name")
314+ tags: list[str] = Field(default_factory=list)
315+ metadata: dict[str, Any] = Field(default_factory=dict)
316+ 
317+def my_function(
318+ id: int,
319+ filters: list[str] | None = None,
320+) -> MySchema | None:
321+ pass
322+ 
323+# BAD - Old-style (DO NOT USE)
324+from typing import Optional, List, Dict
325+name: Optional[str] # Wrong! Use str | None
326+tags: List[str] # Wrong! Use list[str]
232327 ```
233328  
234−**If health checks fail:**
235−"It appears you aren't set up properly. Please refer to the [Working with LLMs](https://superset.apache.org/docs/contributing/development#working-with-llms) section in the development docs for setup instructions."
329+### 7. Event Logger Instrumentation
236330  
237−**Key Project Files:**
238−- `superset-frontend/package.json` - Frontend build scripts (`npm run dev` on port 9000, `npm run test`, `npm run lint`)
239−- `pyproject.toml` - Python tooling (ruff, mypy configs)
240−- `requirements/` folder - Python dependencies (base.txt, development.txt)
331+**All tool operations should use `event_logger`** for observability:
241332  
242−## SQLAlchemy Query Best Practices
243−- **Use negation operator**: `~Model.field` instead of `== False` to avoid ruff E712 errors
244−- **Example**: `~Model.is_active` instead of `Model.is_active == False`
333+```python
334+from superset.extensions import event_logger
245335  
246−## Pull Request Guidelines
336+@tool(...)
337+async def my_tool(request: MyRequest, ctx: Context) -> MyResponse:
338+ with event_logger.log_context(action="mcp.my_tool.step_name"):
339+ result = do_something()
340+ return MyResponse(data=result)
341+```
247342  
248−**When creating pull requests:**
343+### 8. Context Logging
249344  
250−1. **Read the current PR template**: Always check `.github/PULL_REQUEST_TEMPLATE.md` for the latest format
251−2. **Use the template sections**: Include all sections from the template (SUMMARY, BEFORE/AFTER, TESTING INSTRUCTIONS, ADDITIONAL INFORMATION)
252−3. **Follow PR title conventions**: Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
253− - Format: `type(scope): description`
254− - Example: `fix(dashboard): load charts correctly`
255− - Types: `fix`, `feat`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`
345+Use the FastMCP `Context` object for structured logging within tools:
256346  
257−**Important**: Always reference the actual template file at `.github/PULL_REQUEST_TEMPLATE.md` instead of using cached content, as the template may be updated over time.
347+```python
348+async def my_tool(request: MyRequest, ctx: Context) -> MyResponse:
349+ await ctx.info("Starting: param=%s" % (request.param,))
350+ await ctx.debug("Details: keys=%s" % (sorted(request.model_dump().keys()),))
351+ await ctx.warning("Something unexpected: %s" % (warning_msg,))
352+ await ctx.error("Failed: %s" % (str(exc),))
353+ await ctx.report_progress(1, 5, "Step 1 of 5")
354+```
258355  
259−## Pre-commit Validation
356+### 9. Error Handling
260357  
261−**Use pre-commit hooks for quality validation:**
358+**Pattern**: Catch specific exceptions for known failure modes, use broad `Exception` only as the outermost safety net that re-raises:
262359  
263−```bash
264−# Install hooks
265−pre-commit install
360+```python
361+from superset.commands.dataset.exceptions import DatasetInvalidError, DatasetCreateFailedError
266362  
267−# IMPORTANT: Stage your changes first!
268−git add . # Pre-commit only checks staged files
363+@tool(...)
364+async def my_tool(request: MyRequest, ctx: Context) -> MyResponse:
365+ try:
366+ # Specific exception handling for known failure modes
367+ with event_logger.log_context(action="mcp.my_tool"):
368+ result = SomeCommand(properties).run()
369+ return MyResponse(data=result)
269370  
270−# Quick validation (faster than --all-files)
271−pre-commit run # Staged files only
272−pre-commit run mypy # Python type checking
273−pre-commit run format # Code formatting
274−pre-commit run eslint # Frontend linting
371+ except DatasetInvalidError as exc:
372+ # Return structured error response (don't raise)
373+ await ctx.error("Validation failed: %s" % (exc.normalized_messages(),))
374+ return MyResponse(error=str(exc.normalized_messages()))
375+ 
376+ except DatasetCreateFailedError as exc:
377+ await ctx.error("Creation failed: %s" % (str(exc),))
378+ return MyResponse(error=f"Failed: {exc}")
379+ 
380+ except Exception as exc:
381+ # Outermost safety net: log and re-raise (middleware handles it)
382+ await ctx.error("Unexpected: %s: %s" % (type(exc).__name__, str(exc)))
383+ raise
275384 ```
276385  
277−**Important pre-commit usage notes:**
278−- **Stage files first**: Run `git add .` before `pre-commit run` to check only changed files (much faster)
279−- **Virtual environment**: Activate your Python virtual environment before running pre-commit
280− ```bash
281− # Common virtual environment locations (yours may differ):
282− source .venv/bin/activate # if using .venv
283− source venv/bin/activate # if using venv
284− source ~/venvs/superset/bin/activate # if using a central location
285− ```
286− If you get a "command not found" error, ask the user which virtual environment to activate
287−- **Auto-fixes**: Some hooks auto-fix issues (e.g., trailing whitespace). Re-run after fixes are applied
386+### 10. Dataset Validation for Chart Tools
288387  
289−## Common File Patterns
388+All chart-related tools must validate that the chart's dataset is accessible:
290389  
291−### API Structure
292−- **`/api.py`** - REST endpoints with decorators and OpenAPI docstrings
293−- **`/schemas.py`** - Marshmallow validation schemas for OpenAPI spec
294−- **`/commands/`** - Business logic classes with @transaction() decorators
295−- **`/models/`** - SQLAlchemy database models
296−- **OpenAPI docs**: Auto-generated at `/swagger/v1` from docstrings and schemas
390+```python
391+from superset.mcp_service.chart.chart_utils import validate_chart_dataset
297392  
298−### Migration Files
299−- **Location**: `superset/migrations/versions/`
300−- **Naming**: `YYYY-MM-DD_HH-MM_hash_description.py`
301−- **Utilities**: Use helpers from `superset.migrations.shared.utils` for database compatibility
302−- **Pattern**: Import utilities instead of raw SQLAlchemy operations
393+validation_result = validate_chart_dataset(chart, check_access=True)
394+if not validation_result.is_valid:
395+ await ctx.warning("Dataset not accessible: %s" % (validation_result.error,))
396+ return ChartError(
397+ error=validation_result.error or "Chart's dataset is not accessible",
398+ error_type="DatasetNotAccessible",
399+ )
400+```
303401  
304−## Platform-Specific Instructions
402+Used by: `get_chart_info`, `get_chart_preview`, `get_chart_data`, `generate_chart`
305403  
306−- **[CLAUDE.md](CLAUDE.md)** - For Claude/Anthropic tools
307−- **[.github/copilot-instructions.md](.github/copilot-instructions.md)** - For GitHub Copilot
308−- **[GEMINI.md](GEMINI.md)** - For Google Gemini tools
309−- **[GPT.md](GPT.md)** - For OpenAI/ChatGPT tools
310−- **[.cursor/rules/dev-standard.mdc](.cursor/rules/dev-standard.mdc)** - For Cursor editor
404+### 11. Compile Check for Chart Creation
311405  
312−---
406+When creating, saving, or previewing charts, run schema validation (Tier 1)
407+and optionally a compile check (Tier 2) before persisting or caching.
408+``validate_and_compile`` glues both together; tools with tight SLAs
409+(``generate_explore_link``, ``update_chart_preview``) opt out of Tier 2.
313410  
314−**LLM Note**: This codebase is actively modernizing toward full TypeScript and type safety. Always run `pre-commit run` to validate changes. Follow the ongoing refactors section to avoid deprecated patterns.
411+```python
412+from superset.mcp_service.chart.compile import validate_and_compile
413+ 
414+result = validate_and_compile(
415+ config, form_data, dataset, run_compile_check=True
416+)
417+if not result.success:
418+ # ``result.error_obj`` is a ``ChartGenerationError`` with fuzzy-match
419+ # suggestions ("did you mean sum_boys?") so the LLM can self-correct.
420+ ...
421+```
422+ 
423+The lower-level ``_compile_chart(form_data, dataset_id)`` is still exported
424+for callers that have already done their own schema validation.
425+ 
426+### 12. Flexible Input Parsing
427+ 
428+`ModelListCore` handles JSON string vs. native object parsing automatically via utilities in `superset.mcp_service.utils.schema_utils`:
429+ 
430+- `parse_json_or_passthrough(value, param_name)` - JSON string or dict
431+- `parse_json_or_list(value, param_name)` - JSON array, list, or comma-separated string
432+- `parse_json_or_model(value, model_class, param_name)` - JSON string or dict to Pydantic model
433+- `parse_json_or_model_list(value, model_class, param_name)` - JSON array to list of Pydantic models
434+ 
435+These are used internally by `ModelListCore` for `filters` and `select_columns`. Individual tools using core classes do NOT need to add parsing logic.
436+ 
437+## Middleware
438+ 
439+The MCP service uses FastMCP middleware (registered in `server.py`):
440+ 
441+- **`LoggingMiddleware`**: Logs tool calls with duration, entity IDs, sanitizes sensitive data
442+- **`GlobalErrorHandlerMiddleware`**: Catches unhandled exceptions, converts to ToolError
443+- **`StructuredContentStripperMiddleware`**: Strips structuredContent from responses (Claude.ai compatibility)
444+- **`ResponseSizeGuardMiddleware`**: Prevents oversized responses from crashing clients
445+- **`ResponseCachingMiddleware`**: Optional response caching (in-memory by default, Redis when store enabled)
446+ 
447+Middleware is applied in `server.py` and should NOT be modified in individual tools.
448+ 
449+## Configuration
450+ 
451+Default configuration is in `mcp_config.py`. Override in `superset_config.py`:
452+ 
453+```python
454+# Authentication
455+MCP_DEV_USERNAME = None # Fallback username for dev mode
456+MCP_AUTH_ENABLED = False # Enable JWT/API key auth
457+MCP_AUTH_FACTORY = None # Custom auth factory function
458+MCP_JWT_PUBLIC_KEY = None
459+MCP_JWT_SECRET = None
460+MCP_JWKS_URI = None
461+MCP_USER_RESOLVER = None # Custom function to extract username from JWT
462+ 
463+# RBAC
464+MCP_RBAC_ENABLED = True # Enable permission checking (default: True)
465+ 
466+# Embedded guest auth (opt-in; requires the EMBEDDED_SUPERSET feature flag).
467+# Reuses core GUEST_TOKEN_JWT_* config — no MCP-specific guest secret/audience.
468+MCP_EMBEDDED_GUEST_AUTH_ENABLED = False
469+# Default-deny: the ONLY tools a guest may call (everything else is denied).
470+MCP_GUEST_ALLOWED_TOOLS = {
471+ "get_dashboard_info", "get_dashboard_layout", "list_dashboards",
472+ "list_charts", "get_chart_info", "get_chart_data", "get_chart_preview",
473+}
474+# Principal-agnostic extension point: given the current user, return an allow-list
475+# (only these tools are callable) or None if unrestricted. Defaults to restricting
476+# embedded guests to MCP_GUEST_ALLOWED_TOOLS; override to add other restricted
477+# principals without touching the enforcement path.
478+MCP_RESTRICTED_TOOL_POLICY = None # Callable[[user], frozenset[str] | None]
479+ 
480+ 
481+# Response Caching (optional, uses in-memory store by default; Redis when MCP_STORE_CONFIG enabled)
482+MCP_CACHE_CONFIG = {
483+ "enabled": False,
484+ "list_tools_ttl": 300,
485+ "call_tool_ttl": 3600,
486+ "excluded_tools": ["execute_sql", "generate_dashboard"], # add tools to exclude
487+}
488+ 
489+# Multi-pod Storage (optional, requires Redis)
490+MCP_STORE_CONFIG = {
491+ "enabled": False,
492+ "CACHE_REDIS_URL": None,
493+ "event_store_ttl": 3600,
494+}
495+```
496+ 
497+## Testing Conventions
498+ 
499+### Test Organization
500+ 
501+Tests mirror the MCP service module structure:
502+```
503+tests/unit_tests/mcp_service/
504+├── conftest.py # Global fixtures (disable_mcp_rbac)
505+├── chart/
506+│ ├── test_chart_utils.py
507+│ ├── test_chart_schemas.py
508+│ └── tool/
509+│ ├── test_list_charts.py
510+│ ├── test_generate_chart.py
511+│ └── ...
512+├── dashboard/tool/
513+├── dataset/tool/
514+├── sql_lab/tool/
515+├── system/tool/
516+├── test_auth_*.py # Auth/RBAC tests
517+└── test_middleware*.py # Middleware tests
518+```
519+ 
520+### Async Tool Tests (primary pattern)
521+ 
522+```python
523+from unittest.mock import MagicMock, patch
524+import pytest
525+from fastmcp import Client
526+ 
527+from superset.mcp_service.app import mcp
528+from superset.utils import json
529+ 
530+@pytest.fixture
531+def mcp_server():
532+ return mcp
533+ 
534+@pytest.mark.asyncio
535+async def test_my_tool_success(mcp_server):
536+ mock_obj = MagicMock()
537+ mock_obj.id = 1
538+ mock_obj.name = "test"
539+ 
540+ with patch("superset.daos.chart.ChartDAO.find_by_id", return_value=mock_obj):
541+ async with Client(mcp_server) as client:
542+ result = await client.call_tool(
543+ "my_tool", {"request": {"id": 1}}
544+ )
545+ data = json.loads(result.content[0].text)
546+ 
547+ assert data["id"] == 1
548+```
549+ 
550+### Key Testing Patterns
551+ 
552+- **RBAC is disabled globally** via `conftest.py` autouse fixture (`MCP_RBAC_ENABLED = False`)
553+- **RBAC tests** are separate in `test_auth_rbac.py` with their own `enable_mcp_rbac` fixture
554+- **Auth is mocked** via `mock_auth` fixture that patches `get_user_from_request`
555+- **Mock objects** must have all attributes set explicitly (no auto-generation)
556+- **Patch at the DAO level**: `patch("superset.daos.chart.ChartDAO.find_by_id", ...)`
557+- **Schema validation tests** are synchronous (no Client needed)
558+ 
559+## Common Pitfalls to Avoid
560+ 
561+### 1. Forgetting Tool Import in app.py
562+**Problem**: Tool exists but isn't available to MCP clients.
563+**Solution**: Add tool import to `app.py` at the bottom (after `initialize_core_mcp_dependencies()`).
564+ 
565+### 2. Adding Tool Imports to server.py
566+**Problem**: Tools won't register properly.
567+**Solution**: Tool imports MUST be in `app.py`, not `server.py`.
568+ 
569+### 3. Wrong Decorator Import Path
570+**Problem**: Using stale import path.
571+**Solution**: Use `from superset_core.mcp.decorators import tool, ToolAnnotations` (NOT `superset_core.api.mcp`).
572+ 
573+### 4. Missing ToolAnnotations
574+**Problem**: Tool lacks MCP directory compliance metadata.
575+**Solution**: Always include `annotations=ToolAnnotations(title=..., readOnlyHint=..., destructiveHint=...)`.
576+ 
577+### 5. Using `Optional` Instead of Union Syntax
578+**Problem**: Old-style `Optional[T]` is not Python 3.10+ style.
579+**Solution**: Use `T | None` and `list[str]` instead of `Optional[T]` and `List[str]`.
580+ 
581+### 6. Direct Database Queries
582+**Problem**: Bypasses Superset's security and caching layers.
583+**Solution**: Use DAO classes (ChartDAO, DashboardDAO, DatasetDAO, DatabaseDAO).
584+ 
585+### 7. Not Using Core Classes
586+**Problem**: Duplicating list/get_info logic across tools.
587+**Solution**: Use `ModelListCore`, `ModelGetInfoCore`, `ModelGetSchemaCore`.
588+ 
589+### 8. Missing Apache License Headers
590+**Problem**: CI fails on license check.
591+**Solution**: Add ASF license header to all new `.py` files (see template at top of this doc).
592+ 
593+### 9. Circular Imports
594+**Problem**: Importing from `app.py` in tool files causes circular dependencies.
595+**Solution**: Use `from superset_core.mcp.decorators import tool` for tools/prompts. Only import `from superset.mcp_service.app import mcp` in resource files.
596+ 
597+### 10. Missing event_logger Instrumentation
598+**Problem**: Tool operations are invisible to observability.
599+**Solution**: Wrap key operations with `event_logger.log_context(action="mcp.tool_name.step")`.
600+ 
601+## Quick Checklist for New Tools
602+ 
603+- [ ] Created tool file in `{module}/tool/{tool_name}.py`
604+- [ ] Added ASF license header
605+- [ ] Used `@tool(tags=[...], class_permission_name="...", annotations=ToolAnnotations(...))` decorator
606+- [ ] Import: `from superset_core.mcp.decorators import tool, ToolAnnotations`
607+- [ ] Created Pydantic request/response schemas in `{module}/schemas.py`
608+- [ ] Used DAO classes instead of direct queries
609+- [ ] Added `event_logger.log_context()` instrumentation
610+- [ ] Used `await ctx.info/error/debug()` for context logging
611+- [ ] Exported from `{module}/tool/__init__.py`
612+- [ ] Added tool import to `app.py` at the bottom
613+- [ ] Created async unit tests in `tests/unit_tests/mcp_service/{module}/tool/`
614+- [ ] Updated `DEFAULT_INSTRUCTIONS` in `app.py` if adding new capability
615+ 
616+## Quick Checklist for New Prompts
617+ 
618+- [ ] Created prompt file in `{module}/prompts/{prompt_name}.py`
619+- [ ] Added ASF license header
620+- [ ] Used `@prompt("prompt_name")` from `superset_core.mcp.decorators`
621+- [ ] Made function async: `async def prompt_handler(...) -> str`
622+- [ ] Added import to `{module}/prompts/__init__.py`
623+- [ ] Verified module import exists in `app.py`
624+ 
625+## Quick Checklist for New Resources
626+ 
627+- [ ] Created resource file in `{module}/resources/{resource_name}.py`
628+- [ ] Added ASF license header
629+- [ ] Used `@mcp.resource("superset://{path}")` decorator
630+- [ ] Added `@mcp_auth_hook` decorator
631+- [ ] Added import to `{module}/resources/__init__.py`
632+- [ ] Verified module import exists in `app.py`
633+ 
634+## Getting Help
635+ 
636+- Check existing tool implementations for patterns (chart/tool/, dashboard/tool/)
637+- Review core classes in `mcp_core.py` for reusable functionality
638+- See `CLAUDE.md` in project root for general Superset development guidelines
639+- Consult Superset documentation: https://superset.apache.org/docs/
315640  

Also from Kynth Studios

Built for the same person as RuleStack

ToolDrift

What the AI coding tools changed last night

tooldrift.kynth.studio

StillShipping

Which agent tools have stopped shipping

stillshipping.kynth.studio

BlockDex

Search inside every shadcn registry

blockdex.kynth.studio

The studio list

One product, taken apart, once a month

Kynth Studios pulls one shipped product open every month — what it does, what it cost to build, what the pipeline behind it looks like, and what the numbers did. One email a month, nothing in between.

Double opt-in — we send one confirmation link and nothing else until you click it.

RuleStack

Built by

Kynth Studios

the studio behind ToolDrift, StillShipping and BlockDex

part of Toolproof, the measurement layer for AI agent tooling

Directory

Configs
Stacks
Compare formats
AGENTS.md vs CLAUDE.md
Cursor rules alternatives
Diff two configs
Best AGENTS.md examples
Best Cursor rules examples
What goes in a CLAUDE.md

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

© 2026 RuleStack. A Kynth Studios product. Changelog

RuleStack

The studio list

One product, taken apart, once a month

Kynth Studios pulls one shipped product open every month — what it does, what it cost to build, what the pipeline behind it looks like, and what the numbers did. One email a month, nothing in between.

Double opt-in — we send one confirmation link and nothing else until you click it.

RuleStack

Built by

Kynth Studios

the studio behind ToolDrift, StillShipping and BlockDex

part of Toolproof, the measurement layer for AI agent tooling

Directory

Configs
Stacks
Compare formats
AGENTS.md vs CLAUDE.md
Cursor rules alternatives
Diff two configs
Best AGENTS.md examples
Best Cursor rules examples
What goes in a CLAUDE.md

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

© 2026 RuleStack. A Kynth Studios product. Changelog

RuleStack

The studio list

One product, taken apart, once a month

Kynth Studios pulls one shipped product open every month — what it does, what it cost to build, what the pipeline behind it looks like, and what the numbers did. One email a month, nothing in between.

Double opt-in — we send one confirmation link and nothing else until you click it.

RuleStack

Built by

Kynth Studios

the studio behind ToolDrift, StillShipping and BlockDex

part of Toolproof, the measurement layer for AI agent tooling

Directory

Configs
Stacks
Compare formats
AGENTS.md vs CLAUDE.md
Cursor rules alternatives
Diff two configs
Best AGENTS.md examples
Best Cursor rules examples
What goes in a CLAUDE.md

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

© 2026 RuleStack. A Kynth Studios product. Changelog

RuleStack