usepaso 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validate = validate;
4
+ const VALID_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
5
+ const VALID_PERMISSIONS = ['read', 'write', 'admin'];
6
+ const VALID_INPUT_TYPES = ['string', 'integer', 'number', 'boolean', 'enum', 'array', 'object'];
7
+ const VALID_OUTPUT_TYPES = ['string', 'integer', 'number', 'boolean', 'object', 'array'];
8
+ const VALID_AUTH_TYPES = ['api_key', 'bearer', 'oauth2', 'none'];
9
+ const VALID_IN_VALUES = ['query', 'path', 'body', 'header'];
10
+ const SNAKE_CASE_RE = /^[a-z][a-z0-9_]*$/;
11
+ /**
12
+ * Validate a parsed PasoDeclaration against the spec.
13
+ * Returns an array of errors. Empty array = valid.
14
+ */
15
+ function validate(decl) {
16
+ const errors = [];
17
+ // Version
18
+ if (!decl.version) {
19
+ errors.push({ path: 'version', message: 'version is required' });
20
+ }
21
+ else if (decl.version !== '1.0') {
22
+ errors.push({ path: 'version', message: 'version must be "1.0"' });
23
+ }
24
+ // Service
25
+ if (!decl.service) {
26
+ errors.push({ path: 'service', message: 'service is required' });
27
+ }
28
+ else {
29
+ if (!decl.service.name) {
30
+ errors.push({ path: 'service.name', message: 'service.name is required' });
31
+ }
32
+ if (!decl.service.description) {
33
+ errors.push({ path: 'service.description', message: 'service.description is required' });
34
+ }
35
+ if (!decl.service.base_url) {
36
+ errors.push({ path: 'service.base_url', message: 'service.base_url is required' });
37
+ }
38
+ else {
39
+ try {
40
+ new URL(decl.service.base_url);
41
+ }
42
+ catch {
43
+ errors.push({ path: 'service.base_url', message: 'service.base_url must be a valid URL' });
44
+ }
45
+ }
46
+ if (decl.service.auth) {
47
+ if (!VALID_AUTH_TYPES.includes(decl.service.auth.type)) {
48
+ errors.push({ path: 'service.auth.type', message: `auth.type must be one of: ${VALID_AUTH_TYPES.join(', ')}` });
49
+ }
50
+ }
51
+ }
52
+ // Capabilities
53
+ if (!decl.capabilities) {
54
+ errors.push({ path: 'capabilities', message: 'capabilities is required' });
55
+ }
56
+ else if (!Array.isArray(decl.capabilities)) {
57
+ errors.push({ path: 'capabilities', message: 'capabilities must be an array' });
58
+ }
59
+ else {
60
+ const names = new Set();
61
+ decl.capabilities.forEach((cap, i) => {
62
+ const prefix = `capabilities[${i}]`;
63
+ errors.push(...validateCapability(cap, prefix, names));
64
+ });
65
+ }
66
+ // Permissions
67
+ if (decl.permissions) {
68
+ const capNames = new Set((decl.capabilities || []).map(c => c.name));
69
+ const allReferenced = new Set();
70
+ for (const tier of ['read', 'write', 'admin', 'forbidden']) {
71
+ const list = decl.permissions[tier];
72
+ if (list) {
73
+ for (const name of list) {
74
+ // forbidden can reference capabilities not declared (to explicitly block API endpoints)
75
+ if (tier !== 'forbidden' && !capNames.has(name)) {
76
+ errors.push({ path: `permissions.${tier}`, message: `references unknown capability "${name}"` });
77
+ }
78
+ if (tier !== 'forbidden' && allReferenced.has(name)) {
79
+ // Check if it's in forbidden
80
+ }
81
+ allReferenced.add(name);
82
+ }
83
+ }
84
+ }
85
+ // Check forbidden doesn't overlap with tiers
86
+ if (decl.permissions.forbidden) {
87
+ const tiered = new Set([
88
+ ...(decl.permissions.read || []),
89
+ ...(decl.permissions.write || []),
90
+ ...(decl.permissions.admin || []),
91
+ ]);
92
+ for (const name of decl.permissions.forbidden) {
93
+ if (tiered.has(name)) {
94
+ errors.push({ path: 'permissions.forbidden', message: `"${name}" cannot be both in a permission tier and forbidden` });
95
+ }
96
+ }
97
+ }
98
+ }
99
+ return errors;
100
+ }
101
+ function validateCapability(cap, prefix, names) {
102
+ const errors = [];
103
+ if (!cap.name) {
104
+ errors.push({ path: `${prefix}.name`, message: 'name is required' });
105
+ }
106
+ else {
107
+ if (!SNAKE_CASE_RE.test(cap.name)) {
108
+ errors.push({ path: `${prefix}.name`, message: `"${cap.name}" must be snake_case` });
109
+ }
110
+ if (names.has(cap.name)) {
111
+ errors.push({ path: `${prefix}.name`, message: `duplicate capability name "${cap.name}"` });
112
+ }
113
+ names.add(cap.name);
114
+ }
115
+ if (!cap.description) {
116
+ errors.push({ path: `${prefix}.description`, message: 'description is required' });
117
+ }
118
+ if (!cap.method) {
119
+ errors.push({ path: `${prefix}.method`, message: 'method is required' });
120
+ }
121
+ else if (!VALID_METHODS.includes(cap.method)) {
122
+ errors.push({ path: `${prefix}.method`, message: `method must be one of: ${VALID_METHODS.join(', ')}` });
123
+ }
124
+ if (!cap.path) {
125
+ errors.push({ path: `${prefix}.path`, message: 'path is required' });
126
+ }
127
+ else if (!cap.path.startsWith('/')) {
128
+ errors.push({ path: `${prefix}.path`, message: 'path must start with /' });
129
+ }
130
+ if (!cap.permission) {
131
+ errors.push({ path: `${prefix}.permission`, message: 'permission is required' });
132
+ }
133
+ else if (!VALID_PERMISSIONS.includes(cap.permission)) {
134
+ errors.push({ path: `${prefix}.permission`, message: `permission must be one of: ${VALID_PERMISSIONS.join(', ')}` });
135
+ }
136
+ // Validate inputs
137
+ if (cap.inputs) {
138
+ for (const [inputName, input] of Object.entries(cap.inputs)) {
139
+ const inputPrefix = `${prefix}.inputs.${inputName}`;
140
+ if (!input.type) {
141
+ errors.push({ path: inputPrefix, message: 'type is required' });
142
+ }
143
+ else if (!VALID_INPUT_TYPES.includes(input.type)) {
144
+ errors.push({ path: inputPrefix, message: `type must be one of: ${VALID_INPUT_TYPES.join(', ')}` });
145
+ }
146
+ if (input.type === 'enum' && (!input.values || input.values.length === 0)) {
147
+ errors.push({ path: inputPrefix, message: 'enum type must have values defined' });
148
+ }
149
+ if (!input.description) {
150
+ errors.push({ path: inputPrefix, message: 'description is required' });
151
+ }
152
+ if (input.in && !VALID_IN_VALUES.includes(input.in)) {
153
+ errors.push({ path: `${inputPrefix}.in`, message: `in must be one of: ${VALID_IN_VALUES.join(', ')}` });
154
+ }
155
+ }
156
+ }
157
+ // Validate path params exist in inputs
158
+ if (cap.path && cap.inputs) {
159
+ const pathParams = cap.path.match(/\{([^}]+)\}/g);
160
+ if (pathParams) {
161
+ for (const param of pathParams) {
162
+ const paramName = param.slice(1, -1);
163
+ if (!cap.inputs[paramName]) {
164
+ errors.push({ path: `${prefix}.path`, message: `path parameter "{${paramName}}" not found in inputs` });
165
+ }
166
+ else if (cap.inputs[paramName].in && cap.inputs[paramName].in !== 'path') {
167
+ errors.push({ path: `${prefix}.inputs.${paramName}`, message: `path parameter must have in: path` });
168
+ }
169
+ }
170
+ }
171
+ }
172
+ // Validate output
173
+ if (cap.output) {
174
+ for (const [fieldName, output] of Object.entries(cap.output)) {
175
+ if (!output.type) {
176
+ errors.push({ path: `${prefix}.output.${fieldName}`, message: 'type is required' });
177
+ }
178
+ else if (!VALID_OUTPUT_TYPES.includes(output.type)) {
179
+ errors.push({ path: `${prefix}.output.${fieldName}`, message: `type must be one of: ${VALID_OUTPUT_TYPES.join(', ')}` });
180
+ }
181
+ }
182
+ }
183
+ return errors;
184
+ }
185
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "usepaso",
3
+ "version": "0.1.0",
4
+ "description": "Make your API agent-ready in minutes. One declaration, every protocol.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "usepaso": "dist/cli.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "test": "vitest run",
13
+ "test:watch": "vitest",
14
+ "dev": "tsc --watch"
15
+ },
16
+ "keywords": ["ai", "agents", "mcp", "a2a", "api", "sdk", "agent-readiness"],
17
+ "license": "Apache-2.0",
18
+ "dependencies": {
19
+ "@modelcontextprotocol/sdk": "^1.12.1",
20
+ "yaml": "^2.7.1",
21
+ "commander": "^13.1.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^22.15.3",
25
+ "typescript": "^5.8.3",
26
+ "vitest": "^3.1.2"
27
+ },
28
+ "engines": {
29
+ "node": ">=18"
30
+ }
31
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { resolve, join } from 'path';
5
+ import { existsSync, writeFileSync } from 'fs';
6
+ import { parseFile } from './parser';
7
+ import { validate } from './validator';
8
+ import { serveMcp } from './generators/mcp';
9
+
10
+ const program = new Command();
11
+
12
+ program
13
+ .name('usepaso')
14
+ .description('Make your API agent-ready in minutes')
15
+ .version('0.1.0');
16
+
17
+ program
18
+ .command('init')
19
+ .description('Create a paso.yaml template in the current directory')
20
+ .option('-n, --name <name>', 'Service name')
21
+ .action((opts) => {
22
+ const outPath = resolve('paso.yaml');
23
+ if (existsSync(outPath)) {
24
+ console.error('paso.yaml already exists in this directory.');
25
+ process.exit(1);
26
+ }
27
+
28
+ const name = opts.name || 'MyService';
29
+ const template = `version: "1.0"
30
+
31
+ service:
32
+ name: ${name}
33
+ description: TODO — describe what your service does
34
+ base_url: https://api.example.com
35
+ auth:
36
+ type: bearer
37
+
38
+ capabilities:
39
+ - name: example_action
40
+ description: TODO — describe what this action does
41
+ method: GET
42
+ path: /example
43
+ permission: read
44
+ inputs:
45
+ id:
46
+ type: string
47
+ required: true
48
+ description: TODO — describe this parameter
49
+ in: query
50
+ output:
51
+ result:
52
+ type: string
53
+ description: TODO — describe the output
54
+
55
+ permissions:
56
+ read:
57
+ - example_action
58
+ `;
59
+
60
+ writeFileSync(outPath, template, 'utf-8');
61
+ console.log(`Created paso.yaml for "${name}"`);
62
+ console.log('Edit the file to declare your API capabilities, then run: usepaso serve');
63
+ });
64
+
65
+ program
66
+ .command('validate')
67
+ .description('Validate a paso.yaml file')
68
+ .option('-f, --file <path>', 'Path to paso.yaml', 'paso.yaml')
69
+ .action((opts) => {
70
+ const filePath = resolve(opts.file);
71
+ if (!existsSync(filePath)) {
72
+ console.error(`File not found: ${filePath}`);
73
+ process.exit(1);
74
+ }
75
+
76
+ try {
77
+ const decl = parseFile(filePath);
78
+ const errors = validate(decl);
79
+
80
+ if (errors.length === 0) {
81
+ console.log(`${filePath} is valid.`);
82
+ console.log(`Service: ${decl.service.name}`);
83
+ console.log(`Capabilities: ${decl.capabilities.length}`);
84
+ } else {
85
+ console.error(`Found ${errors.length} error(s):`);
86
+ for (const err of errors) {
87
+ console.error(` ${err.path}: ${err.message}`);
88
+ }
89
+ process.exit(1);
90
+ }
91
+ } catch (err) {
92
+ console.error(`Failed to parse: ${err instanceof Error ? err.message : err}`);
93
+ process.exit(1);
94
+ }
95
+ });
96
+
97
+ program
98
+ .command('serve')
99
+ .description('Start an MCP server from a paso.yaml declaration')
100
+ .option('-f, --file <path>', 'Path to paso.yaml', 'paso.yaml')
101
+ .action(async (opts) => {
102
+ const filePath = resolve(opts.file);
103
+ if (!existsSync(filePath)) {
104
+ console.error(`File not found: ${filePath}`);
105
+ process.exit(1);
106
+ }
107
+
108
+ try {
109
+ const decl = parseFile(filePath);
110
+ const errors = validate(decl);
111
+
112
+ if (errors.length > 0) {
113
+ console.error(`Validation failed with ${errors.length} error(s):`);
114
+ for (const err of errors) {
115
+ console.error(` ${err.path}: ${err.message}`);
116
+ }
117
+ process.exit(1);
118
+ }
119
+
120
+ console.error(`Paso MCP server starting for "${decl.service.name}"...`);
121
+ console.error(`Capabilities: ${decl.capabilities.length}`);
122
+ console.error('Transport: stdio');
123
+ console.error('Waiting for MCP client connection...');
124
+
125
+ await serveMcp(decl);
126
+ } catch (err) {
127
+ console.error(`Failed to start: ${err instanceof Error ? err.message : err}`);
128
+ process.exit(1);
129
+ }
130
+ });
131
+
132
+ program.parse();
@@ -0,0 +1,229 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { z } from 'zod';
4
+ import { PasoDeclaration, PasoCapability, PasoInput } from '../types';
5
+
6
+ /**
7
+ * Generate and return an McpServer from a Paso declaration.
8
+ * Each capability becomes an MCP tool.
9
+ */
10
+ export function generateMcpServer(decl: PasoDeclaration): McpServer {
11
+ const server = new McpServer({
12
+ name: decl.service.name,
13
+ version: decl.service.version || '1.0.0',
14
+ });
15
+
16
+ const forbidden = new Set(decl.permissions?.forbidden || []);
17
+
18
+ for (const cap of decl.capabilities) {
19
+ if (forbidden.has(cap.name)) continue;
20
+
21
+ const inputSchema = buildZodSchema(cap);
22
+ const description = buildToolDescription(cap, decl);
23
+
24
+ if (inputSchema) {
25
+ server.tool(cap.name, description, inputSchema, async (args) => {
26
+ return await executeCapability(cap, args, decl);
27
+ });
28
+ } else {
29
+ server.tool(cap.name, description, async () => {
30
+ return await executeCapability(cap, {}, decl);
31
+ });
32
+ }
33
+ }
34
+
35
+ return server;
36
+ }
37
+
38
+ /**
39
+ * Start the MCP server on stdio transport.
40
+ */
41
+ export async function serveMcp(decl: PasoDeclaration): Promise<void> {
42
+ const server = generateMcpServer(decl);
43
+ const transport = new StdioServerTransport();
44
+ await server.connect(transport);
45
+ }
46
+
47
+ /**
48
+ * Build a Zod schema from a capability's inputs.
49
+ * Returns undefined if the capability has no inputs.
50
+ */
51
+ function buildZodSchema(cap: PasoCapability): Record<string, z.ZodTypeAny> | undefined {
52
+ if (!cap.inputs || Object.keys(cap.inputs).length === 0) return undefined;
53
+
54
+ const shape: Record<string, z.ZodTypeAny> = {};
55
+
56
+ for (const [name, input] of Object.entries(cap.inputs)) {
57
+ let field = inputToZod(input);
58
+
59
+ if (!input.required) {
60
+ field = field.optional();
61
+ }
62
+
63
+ if (input.description) {
64
+ field = field.describe(input.description);
65
+ }
66
+
67
+ shape[name] = field;
68
+ }
69
+
70
+ return shape;
71
+ }
72
+
73
+ /**
74
+ * Convert a PasoInput to a Zod type.
75
+ */
76
+ function inputToZod(input: PasoInput): z.ZodTypeAny {
77
+ switch (input.type) {
78
+ case 'string':
79
+ return z.string();
80
+ case 'integer':
81
+ return z.number().int();
82
+ case 'number':
83
+ return z.number();
84
+ case 'boolean':
85
+ return z.boolean();
86
+ case 'enum':
87
+ if (input.values && input.values.length > 0) {
88
+ const vals = input.values.map(v => String(v));
89
+ return z.enum(vals as [string, ...string[]]);
90
+ }
91
+ return z.string();
92
+ case 'array':
93
+ return z.array(z.unknown());
94
+ case 'object':
95
+ return z.record(z.string(), z.unknown());
96
+ default:
97
+ return z.unknown();
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Build a rich tool description from the capability and service info.
103
+ */
104
+ function buildToolDescription(cap: PasoCapability, decl: PasoDeclaration): string {
105
+ let desc = cap.description;
106
+
107
+ if (cap.consent_required) {
108
+ desc += '\n\n⚠️ REQUIRES USER CONSENT: You must confirm this action with the user before executing.';
109
+ }
110
+
111
+ if (cap.constraints && cap.constraints.length > 0) {
112
+ desc += '\n\nConstraints:';
113
+ for (const c of cap.constraints) {
114
+ if (c.description) desc += `\n- ${c.description}`;
115
+ if (c.max_per_hour) desc += `\n- Rate limit: ${c.max_per_hour}/hour`;
116
+ if (c.max_value) desc += `\n- Max value: ${c.max_value}`;
117
+ if (c.max_per_request) desc += `\n- Max per request: ${c.max_per_request}`;
118
+ }
119
+ }
120
+
121
+ return desc;
122
+ }
123
+
124
+ /**
125
+ * Execute a capability by making the actual HTTP request to the service.
126
+ */
127
+ async function executeCapability(
128
+ cap: PasoCapability,
129
+ args: Record<string, unknown>,
130
+ decl: PasoDeclaration,
131
+ ): Promise<{ content: Array<{ type: 'text'; text: string }> }> {
132
+ // Build the URL
133
+ let path = cap.path;
134
+ const queryParams: Record<string, string> = {};
135
+ const bodyParams: Record<string, unknown> = {};
136
+
137
+ if (cap.inputs) {
138
+ for (const [name, input] of Object.entries(cap.inputs)) {
139
+ const value = args[name];
140
+ if (value === undefined) continue;
141
+
142
+ const location = input.in || (
143
+ ['POST', 'PUT', 'PATCH'].includes(cap.method) ? 'body' : 'query'
144
+ );
145
+
146
+ switch (location) {
147
+ case 'path':
148
+ path = path.replace(`{${name}}`, encodeURIComponent(String(value)));
149
+ break;
150
+ case 'query':
151
+ queryParams[name] = String(value);
152
+ break;
153
+ case 'header':
154
+ // Headers handled separately
155
+ break;
156
+ case 'body':
157
+ default:
158
+ bodyParams[name] = value;
159
+ break;
160
+ }
161
+ }
162
+ }
163
+
164
+ const url = new URL(path, decl.service.base_url);
165
+ for (const [k, v] of Object.entries(queryParams)) {
166
+ url.searchParams.set(k, v);
167
+ }
168
+
169
+ // Build headers
170
+ const headers: Record<string, string> = {
171
+ 'Content-Type': 'application/json',
172
+ 'Accept': 'application/json',
173
+ };
174
+
175
+ if (decl.service.auth) {
176
+ // Auth token comes from environment variable: PASO_AUTH_TOKEN
177
+ const token = process.env.PASO_AUTH_TOKEN;
178
+ if (token) {
179
+ const authHeader = decl.service.auth.header || 'Authorization';
180
+ const prefix = decl.service.auth.prefix ?? (decl.service.auth.type === 'bearer' ? 'Bearer' : '');
181
+ headers[authHeader] = prefix ? `${prefix} ${token}` : token;
182
+ }
183
+ }
184
+
185
+ // Add any header-type inputs
186
+ if (cap.inputs) {
187
+ for (const [name, input] of Object.entries(cap.inputs)) {
188
+ if (input.in === 'header' && args[name] !== undefined) {
189
+ headers[name] = String(args[name]);
190
+ }
191
+ }
192
+ }
193
+
194
+ try {
195
+ const fetchOptions: RequestInit = {
196
+ method: cap.method,
197
+ headers,
198
+ };
199
+
200
+ if (['POST', 'PUT', 'PATCH'].includes(cap.method) && Object.keys(bodyParams).length > 0) {
201
+ fetchOptions.body = JSON.stringify(bodyParams);
202
+ }
203
+
204
+ const response = await fetch(url.toString(), fetchOptions);
205
+ const text = await response.text();
206
+
207
+ let result: string;
208
+ try {
209
+ const json = JSON.parse(text);
210
+ result = JSON.stringify(json, null, 2);
211
+ } catch {
212
+ result = text;
213
+ }
214
+
215
+ if (!response.ok) {
216
+ return {
217
+ content: [{ type: 'text', text: `Error ${response.status}: ${result}` }],
218
+ };
219
+ }
220
+
221
+ return {
222
+ content: [{ type: 'text', text: result }],
223
+ };
224
+ } catch (error) {
225
+ return {
226
+ content: [{ type: 'text', text: `Request failed: ${error instanceof Error ? error.message : String(error)}` }],
227
+ };
228
+ }
229
+ }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ export { parseFile, parseString } from './parser';
2
+ export { validate } from './validator';
3
+ export { generateMcpServer } from './generators/mcp';
4
+ export type {
5
+ PasoDeclaration,
6
+ PasoService,
7
+ PasoAuth,
8
+ PasoCapability,
9
+ PasoInput,
10
+ PasoOutput,
11
+ PasoConstraint,
12
+ PasoPermissions,
13
+ ValidationError,
14
+ } from './types';
package/src/parser.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { readFileSync } from 'fs';
2
+ import { parse as parseYaml } from 'yaml';
3
+ import { PasoDeclaration } from './types';
4
+
5
+ /**
6
+ * Parse a paso.yaml file from disk and return the raw declaration object.
7
+ * Does NOT validate — call validate() separately.
8
+ */
9
+ export function parseFile(filePath: string): PasoDeclaration {
10
+ const content = readFileSync(filePath, 'utf-8');
11
+ return parseString(content);
12
+ }
13
+
14
+ /**
15
+ * Parse a YAML string into a PasoDeclaration.
16
+ */
17
+ export function parseString(content: string): PasoDeclaration {
18
+ const parsed = parseYaml(content);
19
+ if (!parsed || typeof parsed !== 'object') {
20
+ throw new Error('Invalid YAML: expected an object');
21
+ }
22
+ return parsed as PasoDeclaration;
23
+ }