paso Works the Same in Python

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:

Get started.