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.
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +123 -0
- package/dist/cli.js.map +1 -0
- package/dist/generators/mcp.d.ts +12 -0
- package/dist/generators/mcp.d.ts.map +1 -0
- package/dist/generators/mcp.js +204 -0
- package/dist/generators/mcp.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/parser.d.ts +11 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +25 -0
- package/dist/parser.js.map +1 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/validator.d.ts +7 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +185 -0
- package/dist/validator.js.map +1 -0
- package/package.json +31 -0
- package/src/cli.ts +132 -0
- package/src/generators/mcp.ts +229 -0
- package/src/index.ts +14 -0
- package/src/parser.ts +23 -0
- package/src/types.ts +67 -0
- package/src/validator.ts +188 -0
- package/tests/mcp.test.ts +119 -0
- package/tests/parser.test.ts +67 -0
- package/tests/validator.test.ts +133 -0
- package/tsconfig.json +19 -0
|
@@ -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
|
+
}
|