On this page
paso has two SDK implementations: Node.js (TypeScript) and Python. They produce identical behavior for the same input.
This is not an afterthought. It’s a design constraint we enforce with shared test fixtures, identical CLI output formats, and cross-SDK parity checks in CI.
Same CLI
Install either one:
# Node.js
npm install -g usepaso
# Python
pip install usepaso
The commands are identical:
usepaso init --name "Notion"
usepaso validate
usepaso inspect
usepaso test list_databases --dry-run
usepaso serve
usepaso doctor
Same output
Both SDKs produce the same output for the same operation. This is not approximate. It’s exact.
Validate:
valid (Notion, 4 capabilities, 0 regrets)
Serve:
usepaso serving "Notion" (4 capabilities). Agents welcome.
Test —dry-run:
--- DRY RUN (no request will be made) ---
POST https://api.notion.com/v1/databases/{database_id}/query
Authorization: Bearer ...
Notion-Version: 2022-06-28
Doctor:
usepaso doctor
ok usepaso.yaml found
ok YAML parses correctly
ok Validation passes (4 capabilities)
ok USEPASO_AUTH_TOKEN set
ok Base URL reachable (https://api.notion.com/v1, 134ms)
If you switch from Node.js to Python (or the other way), nothing changes for your users or your CI.
Same declaration
The usepaso.yaml file is the same. No SDK-specific fields. No conditional logic.
version: "1.0"
service:
name: Notion
description: Workspace and knowledge management for teams
base_url: https://api.notion.com/v1
auth:
type: bearer
capabilities:
- name: list_databases
description: List all databases shared with the integration
method: POST
path: /search
permission: read
inputs:
filter:
type: string
default: database
description: Object type to search for
- name: query_database
description: Query a database with optional filters
method: POST
path: /databases/{database_id}/query
permission: read
inputs:
database_id:
type: string
required: true
description: The database ID
in: path
- name: create_page
description: Create a new page in a database
method: POST
path: /pages
permission: write
consent_required: true
inputs:
parent:
type: string
required: true
description: Parent database ID
properties:
type: string
required: true
description: Page properties as JSON
- name: update_page
description: Update properties on an existing page
method: PATCH
path: /pages/{page_id}
permission: write
consent_required: true
inputs:
page_id:
type: string
required: true
description: The page ID
in: path
properties:
type: string
required: true
description: Updated properties as JSON
Write it once. Run it in either SDK.
Same programmatic API
Both SDKs export the same functions:
Node.js:
import { parseFile, validate, generateMcpServer } from 'usepaso';
const decl = parseFile('./usepaso.yaml');
const errors = validate(decl);
const server = generateMcpServer(decl);
Python:
from usepaso import parse_file, validate, generate_mcp_server
decl = parse_file('./usepaso.yaml')
errors = validate(decl)
server = generate_mcp_server(decl)
The function names follow each language’s conventions (camelCase in TypeScript, snake_case in Python), but the behavior is identical.
How we enforce parity
The SDK repository has a test-fixtures/ directory with shared YAML fixtures and expected output. Both SDKs must pass every fixture. CI runs both test suites on every commit.
When we add a feature to one SDK, we add it to both. When we fix a bug in one, we fix it in both. The shared fixtures are the contract.
Pick the one your team uses
If your stack is Node.js, use npm install usepaso. If it’s Python, use pip install usepaso. The result is the same MCP server, the same CLI experience, the same declaration format.
Related:
- The Complete Guide to MCP Servers. the full reference
- How to Create an MCP Server for the full setup comparison
- Five Ways to Test Before You Ship to verify your declaration
- How to Make Your API Work with Claude for Claude Desktop setup