@ratio-mcp-qa/shared
v1.0.8
Published
> The **single source of truth** for the Ratio MCP v2 ecosystem. Shared types, platform constants, and scope data consumed by both the `dev-server` and `docs-server` packages.
Readme
@ratio-mcp-qa/shared
The single source of truth for the Ratio MCP v2 ecosystem. Shared types, platform constants, and scope data consumed by both the
dev-serveranddocs-serverpackages.
TL;DR
This package is a pure, dependency-free TypeScript library (no runtime deps outside Node's path and os) that eliminates duplication between the two MCP servers in this monorepo. If dev-server and docs-server were microservices, this would be their shared SDK.
Three files. Three jobs.
| File | What it holds | Why it exists |
|---|---|---|
| constants.ts | Env-driven URLs, store paths, server identity | One variable change retargets the whole stack (QA ↔ prod) |
| types.ts | 14 TypeScript interfaces + 1 type alias | Compile-time contracts between writers and readers of the same data |
| scopes-data.ts | 147 real scopes across 78 resources + metadata + helpers | Offline anti-hallucination — validate scopes without a network call |
Quick Start
Installing
This package is consumed via pnpm workspace protocol from the monorepo root. You never install it separately.
// From anywhere in dev-server or docs-server
import {
RATIO_API_BASE_URL,
ALL_SCOPE_CODES,
type AppRequirements,
} from '@ratio-mcp-qa/shared';All exports flow through a single barrel (src/index.ts) so you never reach into individual files.
Building
pnpm build # compiles src/*.ts to dist/*.js + dist/*.d.ts
pnpm typecheck # tsc --noEmitPublished artifact is dist/ only (per the files field in package.json).
Why This Package Exists
Three concrete reasons, each one a problem this package solves.
1. One env variable retargets the whole stack
RATIO_API_BASE_URL is read once at module load in constants.ts, then flows to:
- The developer-dashboard HTTP client (
api/developer.ts) - The OAuth helpers (
api/oauth.ts) - The webhook management tools (
tools/webhooks.ts) - The generated backend's
.envfile written byscaffold_backend
Change RATIO_API_BASE_URL=https://...prod... in your .env and every one of those targets flips together. No drift between MCP and scaffolded code.
2. Shape contracts between writers and readers
Data shapes like AppRequirements and StoredToken are written by one module (a tool handler) and read by another (a persistent store, or the scaffolded backend). Defining the shape once in shared/ means:
- Add a field → both consumers see it at compile time.
- Rename a field → the TypeScript compiler points out every place it lands.
- No hand-rolled "keep these in sync" comments.
3. Anti-hallucination without a network call
The ALL_SCOPE_CODES Set lets any tool validate a scope code in O(1) — purely offline. Combined with SCOPES_BY_RESOURCE and RESOURCE_METADATA, four tools across two servers can reason about scopes without ever hitting the platform API. If this data lived only on the server, every tool would need HTTP; if it were duplicated per-server, drift would creep in.
Package Layout
packages/shared/
├── src/
│ ├── index.ts # Barrel re-export of the three files below
│ ├── constants.ts # 28 lines — runtime env + paths + identity
│ ├── types.ts # 183 lines — compile-time data contracts
│ └── scopes-data.ts # 489 lines — platform truth: 147 scopes × 78 resources
├── dist/ # Build output (ESM + .d.ts + sourcemaps)
├── package.json
├── tsconfig.json # extends ../../tsconfig.base.json
└── README.md # (this file)Design choices worth knowing:
- ESM-only (
"type": "module"inpackage.json). Imports use.jsextensions even in.tssource — required by Node16 module resolution. - Strict TypeScript inherited from
tsconfig.base.json(strict: true, full declaration maps for IDE round-tripping). - No Zod here. Types are compile-time only; runtime validation lives in the servers. Keeps this package lightweight and server-agnostic.
API Reference
constants.ts
Runtime values. Nine exports in six logical groups.
API URLs (env-driven with QA defaults)
RATIO_API_BASE_URL // default: https://api-gw-v4.dev.gokwik.in/qa/aes
RATIO_API_VERSION // 'v1'
RATIO_DEVELOPER_DASHBOARD_URL // default: https://dev-developers.dev.gokwik.inRead from process.env at module load time. Callers that want env overrides (e.g. dev-server/src/index.ts) must call dotenv.config() before importing from this package.
Persistent store paths
STORE_DIR // ~/.ratio-v2/
TOKENS_FILE // ~/.ratio-v2/tokens.json
REQUIREMENTS_FILE // ~/.ratio-v2/requirements.jsonComputed with join(homedir(), '.ratio-v2') — cross-platform (mac/linux/windows).
OAuth & developer API
OAUTH_BASE_PATH // /api/v1/oauth (interpolated with RATIO_API_VERSION)
DEVELOPER_API_BASE_PATH // /api/developer (no version — different subsystem)
DEFAULT_REDIRECT_URI // http://localhost:3000/auth/callbackMCP server identity
DEV_SERVER_NAME = 'ratio-dev-server' DEV_SERVER_VERSION = '1.0.0'
DOCS_SERVER_NAME = 'ratio-docs-server' DOCS_SERVER_VERSION = '1.0.0'Passed to new McpServer({ name, version }) — surfaces in the MCP handshake and in client UIs (Claude Desktop, Claude Code).
types.ts
Compile-time contracts. Erased at build; zero runtime cost.
Scope types (input shape from the platform API)
interface ApiScope {
id: string; // UUID from platform
code: string; // e.g. 'read_orders'
label: string; // 'Read Orders'
resource: string; // 'orders'
createdAt: string; // ISO timestamp
description?: string; // optional enrichment
use_cases?: string[];
commonly_paired_with?: string[];
}
interface ResourceMeta {
description: string; // rich text for keyword matching
codegen_ready: boolean; // true iff docs-server has a JSON schema
commonly_needed_with?: string[];
example_app_types?: string[];
}
interface ScopesApiResponse {
data: Record<string, ApiScope[]>; // raw shape of GET /api/v1/scopes
}The codegen_ready flag is load-bearing: generate_api_routes refuses to emit code for resources where it's false. Today only orders and products are true.
API schema types (docs-server consumption)
interface EndpointParam { name, type, required, description, default?, enum? }
interface EndpointDefinition {
method, path, controller_order, handler_name,
required_scope, summary, description?,
query_params?, path_params?, request_body?,
response_schema?, extra_headers?
}
interface ResourceSchema {
resource, base_url, auth?, endpoints, schemas, statuses?
}
interface WebhookEvent { topic, description, resource, required_scope?, example_payload? }
interface WebhooksSchema { version, verification?, retry_policy?, events }
interface OAuthSchema { version, flows }These match the JSON files in docs-server/src/schemas/ (orders, products, webhooks, oauth). The types give callers type safety without runtime validation overhead.
controller_order is deterministic — codegen emits methods in this stable order so diffs stay clean across regenerations.
Validation types (anti-hallucination)
type ValidationErrorType =
| 'UNKNOWN_ENDPOINT' | 'WRONG_ORDER' | 'SCOPE_MISMATCH'
| 'MISSING_ENDPOINT' | 'WRONG_METHOD' | 'WRONG_HANDLER_NAME';
interface ValidationError { type, message, suggestion }
interface ValidationResult { valid, errors, endpoints_validated? }The LLM consumes these structurally. A stable error union + suggestion field is more valuable than free-form error text — it lets the LLM map errors to fix strategies.
Persistent store shapes
interface AppRequirements {
app_id: string; app_name?, client_id?, project_path?;
scopes: string[]; data_needs: string[]; features: string[];
created_at: string; updated_at: string;
}
interface StoredToken {
access_token, refresh_token, scopes, merchant_id,
expires_at, created_at, client_id, client_secret, redirect_uri
}AppRequirements records live in ~/.ratio-v2/requirements.json. StoredToken records live in ~/.ratio-v2/tokens.json. client_secret is stored in plaintext because this is local developer tooling, not a production vault.
Other helpers
interface ScopeEndpointMapping { scope, resource, endpoints }
interface DeveloperCredentials { developer_token, developer_id, email }
interface ToolResult { [key: string]: unknown }scopes-data.ts
The data backbone of the entire MCP. Frozen snapshot from GET /api/v1/scopes, last refreshed 2026-04-08.
PRIMARY_RESOURCES — opinionated default ordering
export const PRIMARY_RESOURCES = [
'orders', 'products', 'customers', 'inventory', 'discounts',
'fulfillments', 'draft_orders', 'returns', 'shipping', 'gift_cards',
'analytics', 'channels', 'content', 'files', 'locations',
'themes', 'markets', 'reports', 'locales', 'publications', 'apps',
] as const;
export type PrimaryResource = (typeof PRIMARY_RESOURCES)[number];21 resources most apps need. as const gives a readonly tuple so PrimaryResource is a strict union of string literals.
RESOURCE_METADATA — the LLM-readable layer
export const RESOURCE_METADATA: Record<string, ResourceMeta> = {
orders: {
description: 'Merchant orders — line items, pricing, customer info, payment and fulfillment status, addresses.',
codegen_ready: true,
commonly_needed_with: ['products', 'customers', 'inventory'],
example_app_types: ['Order analytics', 'Fraud detection', 'Shipping & logistics', 'Accounting'],
},
// ... 20 more primary resources
};Only primary resources have metadata. The other 57 resources in SCOPES_BY_RESOURCE are matched by resource name alone.
The description + example_app_types strings are the fuel for keyword matching in gather_requirements. Rich text here = better matches for developer descriptions like "I want to build a fraud detection tool".
SCOPES_BY_RESOURCE — the scope corpus
export const SCOPES_BY_RESOURCE: Record<string, ApiScope[]> = {
orders: [
{ code: 'order-cancel', label: 'Delete Orders', ... },
{ code: 'order-cancelled', label: 'Order cancel', ... },
{ code: 'order-create', label: 'Order Create', ... },
{ code: 'order-update', label: 'Order Update', ... },
{ code: 'read_orders', label: 'Read Orders', ... },
{ code: 'write_orders', label: 'Write Orders', ... },
],
customers: [
{ code: 'manage_customers', ... },
{ code: 'read_customers', ... },
{ code: 'write_customers', ... },
],
// ... 76 more resources
};All 78 resources, 147 scopes. Naming conventions are inconsistent — most follow read_X / write_X, but watch out for:
ordersuses kebab-case legacy codes:order-create,order-update,order-cancel,order-cancelledcustomershas amanage_customerscodetheme_codeonly haswrite_theme_code(no read)- Some resources are read-only, some write-only, some both
Any code that infers scope relationships from names must handle these exceptions. getResourceForScope() already does.
ALL_SCOPE_CODES — the anti-hallucination backstop
export const ALL_SCOPE_CODES: Set<string> = new Set(
Object.values(SCOPES_BY_RESOURCE).flatMap((scopes) => scopes.map((s) => s.code))
);Built once at module load. O(1) membership check thereafter. If the LLM fabricates read_store_analytics, it's rejected instantly — no network call.
Helper functions
getResourceForScope(code: string): string | undefined
getAllResources(): string[] // all 78 resource namesgetResourceForScope handles all naming variants — kebab-case and manage_ exceptions included. Prefer it over hand-rolling code.split('_')[1].
Who Uses What
The real-world consumer map. 17 files across the two servers import from this package.
Constants (8 files)
| Import | Consumer | Purpose |
|---|---|---|
| RATIO_API_BASE_URL | api/developer, api/oauth, tools/webhooks, tools/scaffold | All HTTP calls; injected into generated backend .env |
| OAUTH_BASE_PATH | api/oauth | Composed with base URL for OAuth endpoints |
| STORE_DIR, TOKENS_FILE | store/token-store | Persistent token cache paths |
| STORE_DIR, REQUIREMENTS_FILE | store/requirements-store | Persistent requirements store paths |
| DEV_SERVER_NAME/VERSION | dev-server/server, orchestrated-server | MCP handshake identity |
| DOCS_SERVER_NAME/VERSION | docs-server/server | MCP handshake identity |
Types (9 files)
| Import | Consumer | Purpose |
|---|---|---|
| AppRequirements | store/requirements-store, tools/requirements | Shape of persisted app state; writer + reader agree |
| StoredToken | store/token-store, tools/auth | Shape of cached tokens; writer + .env injector agree |
| ResourceSchema, WebhooksSchema, OAuthSchema | docs-server/utils/schema-loader | Typed JSON loading — no runtime cost |
| ValidationResult, ValidationError | docs-server/tools/validate-code | Stable error contract for the LLM |
| ScopeEndpointMapping | docs-server/tools/get-scope-map | Codegen handoff shape |
Scope data (4 files, all the "scope reasoning" tools)
| Import | Consumer | Purpose |
|---|---|---|
| ALL_SCOPE_CODES | tools/requirements | O(1) offline validation in define_app_requirements + validate_scopes |
| SCOPES_BY_RESOURCE | tools/requirements, tools/gather-requirements, docs-server/tools/get-scopes, lookup-docs | Scope lookups, label resolution, pairing suggestions |
| RESOURCE_METADATA | tools/requirements, tools/gather-requirements, docs-server/tools/get-scopes, lookup-docs | Keyword matching fuel, codegen-ready flag, browse enrichment |
| PRIMARY_RESOURCES | docs-server/tools/get-scopes, lookup-docs | Sort primary-first in scope browsing |
| getResourceForScope() | tools/requirements | Derive data_needs from scope list; group scopes by resource |
| getAllResources() | docs-server/tools/get-scopes | Full 78-resource enumeration |
Usage Examples
1. Validating scopes before presenting to the developer
From dev-server/src/tools/requirements.ts:
import { ALL_SCOPE_CODES } from '@ratio-mcp-qa/shared';
const validScopes: string[] = [];
const invalidScopes: string[] = [];
for (const scope of scopes) {
if (ALL_SCOPE_CODES.has(scope)) {
validScopes.push(scope);
} else {
invalidScopes.push(scope);
}
}This is how validate_scopes and define_app_requirements catch hallucinated scopes before they reach the developer.
2. Typed HTTP client targeting
From dev-server/src/api/developer.ts:
import { RATIO_API_BASE_URL } from '@ratio-mcp-qa/shared';
// Every tool that calls the platform hits this URL
const url = `${RATIO_API_BASE_URL}/api/developer${endpoint}`;The same RATIO_API_BASE_URL is written into the scaffolded backend's .env, so MCP and generated code agree on the target environment.
3. Keyword matching for developer descriptions
From dev-server/src/tools/gather-requirements.ts:
import { RESOURCE_METADATA, SCOPES_BY_RESOURCE } from '@ratio-mcp-qa/shared';
for (const [resource, meta] of Object.entries(RESOURCE_METADATA)) {
const metaText = [meta.description, ...(meta.example_app_types || [])]
.join(' ').toLowerCase();
// score resource by token overlap with metaText
}The richer the metadata strings, the better the matches.
4. Persistent store with shared shape
From dev-server/src/store/requirements-store.ts:
import { STORE_DIR, REQUIREMENTS_FILE } from '@ratio-mcp-qa/shared';
import type { AppRequirements } from '@ratio-mcp-qa/shared';
interface RequirementsStoreFile {
version: 1;
requirements: Record<string, AppRequirements>;
}The shape defined in shared/ is used both by the store (reader) and by tools/requirements.ts (writer). No hand-rolled sync.
Gotchas & Conventions
Env var timing
constants.ts reads process.env at module load time. If a consumer imports @ratio-mcp-qa/shared before calling dotenv.config(), the defaults are locked in. dev-server/src/index.ts calls dotenv.config() as its very first line for this reason.
ESM .js imports on .ts sources
export * from './types.js'; // yes, .js — even though the file is types.tsRequired by Node16 module resolution. TypeScript rewrites the paths at build. A common trap when adding new files to this package.
Scope data is frozen
scopes-data.ts is a snapshot, not a live fetch. To refresh:
- Hit
GET /api/v1/scopeson the platform - Regenerate
SCOPES_BY_RESOURCE(mirror the exact shape) - Update the
Last updatedcomment at the top of the file - Bump the package version
- Rebuild consumers (
pnpm build)
There's no automatic drift detection — stale data here means stale data everywhere.
Only 2 codegen-ready resources today
orders and products. Every other resource has codegen_ready: false. generate_api_routes checks this flag and silently skips non-ready resources. This is a hidden product constraint that lives in RESOURCE_METADATA.
Naming inconsistencies in scopes
Don't assume read_X / write_X. Always use getResourceForScope() instead of splitting on _.
ALL_SCOPE_CODES is a singleton
Built once at load. Safe to share across all consumers. Never mutate it — it's a Set, but treat it as frozen.
Add a field to a type → rebuild is required
Consumers import from dist/, not src/. After editing types.ts or constants.ts, run pnpm build in packages/shared before expecting consumers to see the change.
Build & Development
Building
pnpm build # tsc → dist/*.js + *.d.ts + sourcemaps
pnpm typecheck # tsc --noEmit (CI-friendly)Output
dist/ contains:
index.js,types.js,constants.js,scopes-data.js— ESM modules*.d.ts— type declarations*.d.ts.map+*.js.map— full source navigation from consumers
Published artifact
The files field in package.json ships only dist/. The exports map points ESM consumers at dist/index.js and types at dist/index.d.ts.
Inherited config
tsconfig.json extends ../../tsconfig.base.json:
target: ES2022,module: Node16,moduleResolution: Node16strict: true,declaration: true, full sourcemapsoutDir: dist,rootDir: src
File Quick Reference
| File | Lines | Exports | Runtime? |
|---|---|---|---|
| index.ts | 3 | Barrel re-export | No |
| constants.ts | 28 | 10 constants | Yes (reads process.env at load) |
| types.ts | 183 | 14 interfaces + 1 type alias | No (erased at compile) |
| scopes-data.ts | 489 | 5 exports + 2 functions + 1 type | Yes (builds Set at load) |
~703 lines of pure, dependency-free TypeScript underpinning the entire 40-tool MCP ecosystem.
Related Packages
@ratio-mcp-qa/dev-server— The main tool server (33 tools). Consumes constants, types, and all ofscopes-data.@ratio-mcp-qa/docs-server— Read-only reference server (7 tools). Consumes constants, schema types, and all ofscopes-data.
See the monorepo root CLAUDE.md for the full architecture overview.