Kas Fastmcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Kas Fastmcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A production-grade Model Context Protocol (MCP) server for Notion, providing comprehensive database operations with schema validation and intelligent property handling.
kas-fastmcp/
├── main.py # Entry point
├── config.py # NotionConfig, loads databases.yaml
├── notion_server/
│ ├── server.py # FastMCP("KasNotionMCP") instance
│ ├── deps.py # Shared NotionClient + SchemaManager singletons
│ ├── core/
│ │ ├── client.py # NotionClient (httpx wrapper, 429 retry, connection pool)
│ │ ├── schema.py # SchemaManager (10-min TTL, disk cache)
│ │ └── formatters.py # PropertyFormatter, BlockFormatter (markdown <-> blocks)
│ ├── tools/
│ │ ├── query.py # notion_query, notion_find_page_by_name, notion_search, notion_list_data_sources, notion_discover_databases
│ │ ├── pages.py # notion_get_page, notion_get_data_source, notion_create_item, notion_update_item
│ │ ├── content.py # notion_get_page_content, notion_append_content, notion_replace_content
│ │ └── schema_sync.py # notion_sync_schemas, notion_validate_config
│ └── utils/
│ └── validators.py # PropertyValidator
└── databases.yaml # Database configurationgit clone <your-repo-url>
cd kas-fastmcpuv synccp .env.example .env
# Edit .env and add your NOTION_TOKENcp databases.yaml.example databases.yaml
# Edit databases.yaml and add your database configurationsdatabases.yamldatabases.yamlDefine all your Notion databases:
zettelkasten:
data_source_id: "your-data-source-id-here"
database_id: "your-database-id-here"
title_property: "title"
description: "Personal knowledge management"
habits:
data_source_id: "your-data-source-id-here"
database_id: "your-database-id-here"
title_property: "title"
description: "Habit tracking".envNOTION_TOKEN=ntn_your_token_here
NOTION_API_VERSION=2025-09-03Development:
python main.pyWith FastMCP CLI:
fastmcp run main.pyAdd to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"kas-notion": {
"command": "python",
"args": ["/path/to/kas-fastmcp/main.py"]
}
}
}#### Query Tools (query.py)
notion_query - Query pages from a databasenotion_find_page_by_name - Find page by exact titlenotion_search - Workspace-wide searchnotion_list_data_sources - List data sources for a databasenotion_discover_databases - Discover all accessible databases#### Page Tools (pages.py)
notion_get_page - Get page with properties (fully paginated when include_content=True)notion_get_data_source - Get database schemanotion_create_item - Create new pagenotion_update_item - Update page properties#### Content Tools (content.py)
notion_get_page_content - Get page content as markdownnotion_append_content - Append blocks to pagenotion_replace_content - Clear all blocks on a page and write new markdown content#### Schema Tools (schema_sync.py)
notion_sync_schemas - Sync schemas from Notion and optionally update confignotion_validate_config - Validate current database configurationnotion://databases - Lists all configured databases as JSONnotion://databases/{source_name}/schema - Returns the cached schema for a specific databaseQuick token verification:
python connection_testing/token_verification.pyFull connection test:
python connection_testing/test_connection.pyThis will:
Test the property validator directly:
python test_validation.pydatabases.yaml:my_database:
data_source_id: "your-id"
database_id: "your-id"
title_property: "Name"
description: "My new database"query.py, pages.py, or content.py)@mcp.tool decoratorExample:
from notion_server.server import mcp
from notion_server.deps import _client # shared singleton — do not instantiate a new one
@mcp.tool
async def my_new_tool(param: str) -> dict:
"""
Tool description.
Args:
param: Parameter description
Returns:
Result description
"""
result = await _client.get(f"endpoint/{param}")
return result`core/` - Pure business logic, no MCP dependencies
`tools/` - MCP tool definitions
`utils/` - Shared utilities
Properties are validated against Notion's schema before API calls:
# Automatic validation
notion_create_item(
source_name="zettelkasten",
properties={
"title": {"title": [{"text": {"content": "My Note"}, "type": "text"}]},
"nonexistent": {"rich_text": [...]} # ❌ Validation error!
}
)✅ Property exists in schema ✅ Property type matches ✅ Select options are valid ✅ Read-only properties rejected ✅ Required properties present (strict mode)
Use the validator directly in your code:
from notion_server.core import SchemaManager, NotionClient
from notion_server.utils import PropertyValidator
client = NotionClient()
schema_manager = SchemaManager(client)
schema = await schema_manager.get_schema("zettelkasten")
validator = PropertyValidator(schema)
is_valid, errors = validator.validate_properties(properties)"NOTION_TOKEN not found"
.env file from .env.example"Database not found (404)"
databases.yaml"No databases configured"
databases.yaml from databases.yaml.example"Property validation failed"
Enable detailed logging by checking server output:
python main.py 2>&1 | tee server.logclient = NotionClient(token=None, api_version=None)
await client.get(endpoint)
await client.post(endpoint, payload)
await client.patch(endpoint, payload)schema_manager = SchemaManager(client)
schema = await schema_manager.get_schema(source_name)
data_source_id = await schema_manager.get_data_source_id(source_name)
config = schema_manager.get_source_config(source_name)formatter = PropertyFormatter()
title = formatter.extract_title(properties)
formatted = formatter.format_for_display(properties)formatter = BlockFormatter()
markdown = formatter.to_markdown(blocks)
blocks = formatter.from_markdown(markdown)MIT
See CHANGELOG.md for version history.
For issues, questions, or contributions, please open an issue on GitHub.
Built with ❤️ for AI-powered productivity
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.