@esignlaunchpad/mcp-server 1.0.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/LICENSE +21 -0
- package/README.md +86 -0
- package/package.json +56 -0
- package/src/index.js +308 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 eSign Launchpad
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# eSign Launchpad MCP Server
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server that enables AI agents to interact with the eSign Launchpad e-signature platform. Create, send, and manage signing packages directly from Claude, Cursor, or any MCP-compatible AI client.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install
|
|
9
|
+
npm install @esignlaunchpad/mcp-server
|
|
10
|
+
|
|
11
|
+
# Run (requires API key)
|
|
12
|
+
ESIGN_API_KEY=eslp_live_your_key_here npx @esignlaunchpad/mcp-server
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Claude Desktop Configuration
|
|
16
|
+
|
|
17
|
+
Add to your `claude_desktop_config.json`:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"esign-launchpad": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": ["@esignlaunchpad/mcp-server"],
|
|
25
|
+
"env": {
|
|
26
|
+
"ESIGN_API_KEY": "eslp_live_your_key_here"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## What Can AI Agents Do?
|
|
34
|
+
|
|
35
|
+
With this MCP server, AI agents can:
|
|
36
|
+
|
|
37
|
+
- **Create signing packages** with documents, signers, and annotation fields
|
|
38
|
+
- **Upload documents** (PDF, DOCX) via chunked upload
|
|
39
|
+
- **Add signers** with authentication requirements (email, SMS, KBA, ID verification)
|
|
40
|
+
- **Place signature fields** on documents with precise positioning
|
|
41
|
+
- **Send packages** for signing with custom email messages
|
|
42
|
+
- **Track status** of packages and individual signers
|
|
43
|
+
- **Download completed documents** and certificates of completion
|
|
44
|
+
- **Manage templates** for reusable signing workflows
|
|
45
|
+
- **Configure webhooks** for real-time event notifications
|
|
46
|
+
- **Manage signing groups** for team-based signing
|
|
47
|
+
|
|
48
|
+
## Environment Variables
|
|
49
|
+
|
|
50
|
+
| Variable | Required | Default | Description |
|
|
51
|
+
|----------|----------|---------|-------------|
|
|
52
|
+
| `ESIGN_API_KEY` | Yes | - | Your eSign Launchpad API key (`eslp_live_...`) |
|
|
53
|
+
| `ESIGN_API_URL` | No | `https://api.esignlaunchpad.com` | API base URL |
|
|
54
|
+
| `ESIGN_SPEC_URL` | No | `{API_URL}/openapi/tenant-api.json` | OpenAPI spec URL |
|
|
55
|
+
|
|
56
|
+
## API Key Setup
|
|
57
|
+
|
|
58
|
+
1. Log in to [eSign Launchpad](https://app.esignlaunchpad.com)
|
|
59
|
+
2. Go to **Settings > API**
|
|
60
|
+
3. Click **Generate API Key**
|
|
61
|
+
4. Select the permissions your agent needs
|
|
62
|
+
5. Copy the key (shown only once)
|
|
63
|
+
|
|
64
|
+
## Available Permissions
|
|
65
|
+
|
|
66
|
+
API keys are scoped. Select only what your agent needs:
|
|
67
|
+
|
|
68
|
+
| Scope | Operations |
|
|
69
|
+
|-------|-----------|
|
|
70
|
+
| `packages:read` | List, view, download packages |
|
|
71
|
+
| `packages:write` | Create, send, void, archive packages |
|
|
72
|
+
| `templates:read` | List and view templates |
|
|
73
|
+
| `templates:write` | Create and modify templates |
|
|
74
|
+
| `webhooks:manage` | Create and manage webhook subscriptions |
|
|
75
|
+
| `signing-groups:read` | View signing groups |
|
|
76
|
+
| `signing-groups:manage` | Create and manage signing groups |
|
|
77
|
+
|
|
78
|
+
## How It Works
|
|
79
|
+
|
|
80
|
+
This MCP server reads the eSign Launchpad OpenAPI specification at startup and dynamically generates MCP tools for each API endpoint. When the API is updated with new features, simply restart the server to pick up new tools automatically.
|
|
81
|
+
|
|
82
|
+
No code generation needed. No manual tool definitions. The OpenAPI spec is the single source of truth.
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@esignlaunchpad/mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP (Model Context Protocol) server for eSign Launchpad - enables AI agents to create, send, and manage e-sign packages.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"esign-mcp": "src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"model-context-protocol",
|
|
16
|
+
"esign",
|
|
17
|
+
"e-signature",
|
|
18
|
+
"electronic-signature",
|
|
19
|
+
"ai-agent",
|
|
20
|
+
"claude",
|
|
21
|
+
"cursor",
|
|
22
|
+
"signing",
|
|
23
|
+
"documents",
|
|
24
|
+
"api"
|
|
25
|
+
],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"author": {
|
|
28
|
+
"name": "eSign Launchpad",
|
|
29
|
+
"email": "preflight@notarylaunchpad.com",
|
|
30
|
+
"url": "https://www.esignlaunchpad.com"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://www.esignlaunchpad.com/Home/Developers",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/esignlaunchpad/mcp-server"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://www.esignlaunchpad.com/Home/Support",
|
|
39
|
+
"email": "preflight@notarylaunchpad.com"
|
|
40
|
+
},
|
|
41
|
+
"funding": {
|
|
42
|
+
"type": "commercial",
|
|
43
|
+
"url": "https://www.esignlaunchpad.com/Home/Pricing"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@modelcontextprotocol/sdk": "^1.12.1"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18.0.0"
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"src/",
|
|
53
|
+
"README.md",
|
|
54
|
+
"LICENSE"
|
|
55
|
+
]
|
|
56
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* eSign Launchpad MCP Server
|
|
5
|
+
*
|
|
6
|
+
* Exposes the eSign Launchpad REST API as MCP tools for AI agents.
|
|
7
|
+
* Reads the OpenAPI spec at startup and dynamically generates tools
|
|
8
|
+
* for each API operation. When the API changes, restart the server
|
|
9
|
+
* to pick up new endpoints automatically.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* npx @esignlaunchpad/mcp-server
|
|
13
|
+
*
|
|
14
|
+
* Environment variables:
|
|
15
|
+
* ESIGN_API_KEY - Your eSign Launchpad API key (eslp_live_...)
|
|
16
|
+
* ESIGN_API_URL - API base URL (default: https://api.esignlaunchpad.com)
|
|
17
|
+
* ESIGN_SPEC_URL - OpenAPI spec URL (default: {ESIGN_API_URL}/openapi/tenant-api.json)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
21
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
22
|
+
import { z } from "zod";
|
|
23
|
+
|
|
24
|
+
const API_KEY = process.env.ESIGN_API_KEY;
|
|
25
|
+
const API_URL = (process.env.ESIGN_API_URL || "https://api.esignlaunchpad.com").replace(/\/$/, "");
|
|
26
|
+
const SPEC_URL = process.env.ESIGN_SPEC_URL || `${API_URL}/openapi/tenant-api.json`;
|
|
27
|
+
|
|
28
|
+
if (!API_KEY) {
|
|
29
|
+
console.error("Error: ESIGN_API_KEY environment variable is required.");
|
|
30
|
+
console.error("Generate an API key at https://app.esignlaunchpad.com/Settings/Api");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ── Fetch and parse OpenAPI spec ──
|
|
35
|
+
|
|
36
|
+
async function fetchSpec() {
|
|
37
|
+
const res = await fetch(SPEC_URL);
|
|
38
|
+
if (!res.ok) throw new Error(`Failed to fetch OpenAPI spec from ${SPEC_URL}: ${res.status}`);
|
|
39
|
+
return res.json();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Convert OpenAPI schema to Zod schema ──
|
|
43
|
+
|
|
44
|
+
function openApiSchemaToZod(schema, required = false) {
|
|
45
|
+
if (!schema) return required ? z.unknown() : z.unknown().optional();
|
|
46
|
+
|
|
47
|
+
if (schema.enum) {
|
|
48
|
+
return required ? z.enum(schema.enum) : z.enum(schema.enum).optional();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
switch (schema.type) {
|
|
52
|
+
case "string":
|
|
53
|
+
return required ? z.string().describe(schema.description || "") : z.string().optional().describe(schema.description || "");
|
|
54
|
+
case "integer":
|
|
55
|
+
case "number":
|
|
56
|
+
return required ? z.number().describe(schema.description || "") : z.number().optional().describe(schema.description || "");
|
|
57
|
+
case "boolean":
|
|
58
|
+
return required ? z.boolean().describe(schema.description || "") : z.boolean().optional().describe(schema.description || "");
|
|
59
|
+
case "array":
|
|
60
|
+
const itemSchema = openApiSchemaToZod(schema.items, true);
|
|
61
|
+
return required ? z.array(itemSchema).describe(schema.description || "") : z.array(itemSchema).optional().describe(schema.description || "");
|
|
62
|
+
case "object":
|
|
63
|
+
if (schema.properties) {
|
|
64
|
+
const shape = {};
|
|
65
|
+
const reqFields = new Set(schema.required || []);
|
|
66
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
67
|
+
shape[key] = openApiSchemaToZod(prop, reqFields.has(key));
|
|
68
|
+
}
|
|
69
|
+
return required ? z.object(shape).describe(schema.description || "") : z.object(shape).optional().describe(schema.description || "");
|
|
70
|
+
}
|
|
71
|
+
return required ? z.record(z.unknown()) : z.record(z.unknown()).optional();
|
|
72
|
+
default:
|
|
73
|
+
return required ? z.unknown() : z.unknown().optional();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── Resolve $ref in OpenAPI spec ──
|
|
78
|
+
|
|
79
|
+
function resolveRef(spec, ref) {
|
|
80
|
+
if (!ref || !ref.startsWith("#/")) return ref;
|
|
81
|
+
const parts = ref.substring(2).split("/");
|
|
82
|
+
let current = spec;
|
|
83
|
+
for (const part of parts) {
|
|
84
|
+
current = current?.[part];
|
|
85
|
+
if (current === undefined) return null;
|
|
86
|
+
}
|
|
87
|
+
return current;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function resolveSchema(spec, schema) {
|
|
91
|
+
if (!schema) return null;
|
|
92
|
+
if (schema.$ref) return resolveRef(spec, schema.$ref);
|
|
93
|
+
return schema;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Build tool name from operationId or path ──
|
|
97
|
+
|
|
98
|
+
function buildToolName(method, path, operation) {
|
|
99
|
+
if (operation.operationId) {
|
|
100
|
+
// Convert PascalCase to snake_case
|
|
101
|
+
return operation.operationId
|
|
102
|
+
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
103
|
+
.replace(/([A-Z])([A-Z][a-z])/g, "$1_$2")
|
|
104
|
+
.toLowerCase()
|
|
105
|
+
.replace(/[^a-z0-9_]/g, "_")
|
|
106
|
+
.replace(/_+/g, "_")
|
|
107
|
+
.substring(0, 64);
|
|
108
|
+
}
|
|
109
|
+
// Fallback: method + path
|
|
110
|
+
return `${method}_${path.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_")}`.toLowerCase().substring(0, 64);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Build tool description ──
|
|
114
|
+
|
|
115
|
+
function buildDescription(operation, method, path) {
|
|
116
|
+
const parts = [];
|
|
117
|
+
if (operation.summary) parts.push(operation.summary);
|
|
118
|
+
if (operation.description && operation.description !== operation.summary) {
|
|
119
|
+
parts.push(operation.description);
|
|
120
|
+
}
|
|
121
|
+
parts.push(`[${method.toUpperCase()} ${path}]`);
|
|
122
|
+
return parts.join("\n\n");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── Extract parameters and request body into a single Zod schema ──
|
|
126
|
+
|
|
127
|
+
function buildInputSchema(spec, operation, pathParams) {
|
|
128
|
+
const shape = {};
|
|
129
|
+
|
|
130
|
+
// Path and query parameters
|
|
131
|
+
const allParams = [...(pathParams || []), ...(operation.parameters || [])];
|
|
132
|
+
for (const param of allParams) {
|
|
133
|
+
const resolved = param.$ref ? resolveRef(spec, param.$ref) : param;
|
|
134
|
+
if (!resolved) continue;
|
|
135
|
+
const paramSchema = resolveSchema(spec, resolved.schema) || { type: "string" };
|
|
136
|
+
shape[resolved.name] = openApiSchemaToZod(paramSchema, resolved.required);
|
|
137
|
+
if (resolved.description && shape[resolved.name]) {
|
|
138
|
+
shape[resolved.name] = shape[resolved.name].describe(resolved.description);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Request body (JSON)
|
|
143
|
+
const body = operation.requestBody;
|
|
144
|
+
if (body) {
|
|
145
|
+
const resolved = body.$ref ? resolveRef(spec, body.$ref) : body;
|
|
146
|
+
const content = resolved?.content?.["application/json"];
|
|
147
|
+
if (content?.schema) {
|
|
148
|
+
const bodySchema = resolveSchema(spec, content.schema);
|
|
149
|
+
if (bodySchema?.properties) {
|
|
150
|
+
const reqFields = new Set(bodySchema.required || []);
|
|
151
|
+
for (const [key, prop] of Object.entries(bodySchema.properties)) {
|
|
152
|
+
const resolvedProp = resolveSchema(spec, prop) || prop;
|
|
153
|
+
shape[key] = openApiSchemaToZod(resolvedProp, reqFields.has(key));
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
// Opaque body — accept as JSON string
|
|
157
|
+
shape["_body"] = z.string().optional().describe("JSON request body");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return Object.keys(shape).length > 0 ? z.object(shape) : z.object({});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── Make API call ──
|
|
166
|
+
|
|
167
|
+
async function callApi(method, pathTemplate, params, spec) {
|
|
168
|
+
// Substitute path parameters
|
|
169
|
+
let path = pathTemplate;
|
|
170
|
+
const queryParams = {};
|
|
171
|
+
const bodyParams = {};
|
|
172
|
+
|
|
173
|
+
// Identify which params are path, query, or body
|
|
174
|
+
const pathParamNames = new Set(
|
|
175
|
+
(pathTemplate.match(/\{([^}]+)\}/g) || []).map((m) => m.slice(1, -1))
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// Collect all declared parameters
|
|
179
|
+
const operation = getOperation(spec, method, pathTemplate);
|
|
180
|
+
const declaredParams = new Set();
|
|
181
|
+
for (const p of [...(spec.paths[pathTemplate]?.parameters || []), ...(operation?.parameters || [])]) {
|
|
182
|
+
const resolved = p.$ref ? resolveRef(spec, p.$ref) : p;
|
|
183
|
+
if (resolved) declaredParams.add(resolved.name);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
for (const [key, value] of Object.entries(params || {})) {
|
|
187
|
+
if (value === undefined || value === null) continue;
|
|
188
|
+
if (key === "_body") continue;
|
|
189
|
+
if (pathParamNames.has(key)) {
|
|
190
|
+
path = path.replace(`{${key}}`, encodeURIComponent(String(value)));
|
|
191
|
+
} else if (declaredParams.has(key)) {
|
|
192
|
+
// It's a declared query param
|
|
193
|
+
queryParams[key] = value;
|
|
194
|
+
} else {
|
|
195
|
+
// Everything else goes into body
|
|
196
|
+
bodyParams[key] = value;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Build URL with query string
|
|
201
|
+
const url = new URL(`${API_URL}${path}`);
|
|
202
|
+
for (const [k, v] of Object.entries(queryParams)) {
|
|
203
|
+
url.searchParams.set(k, String(v));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Build request
|
|
207
|
+
const fetchOptions = {
|
|
208
|
+
method: method.toUpperCase(),
|
|
209
|
+
headers: {
|
|
210
|
+
"X-API-KEY": API_KEY,
|
|
211
|
+
"Accept": "application/json",
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// Add body for non-GET methods
|
|
216
|
+
const hasBody = Object.keys(bodyParams).length > 0 || params?._body;
|
|
217
|
+
if (hasBody && method !== "get") {
|
|
218
|
+
fetchOptions.headers["Content-Type"] = "application/json";
|
|
219
|
+
fetchOptions.body = params?._body || JSON.stringify(bodyParams);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const res = await fetch(url.toString(), fetchOptions);
|
|
223
|
+
const contentType = res.headers.get("content-type") || "";
|
|
224
|
+
|
|
225
|
+
if (contentType.includes("application/json")) {
|
|
226
|
+
const json = await res.json();
|
|
227
|
+
return { status: res.status, data: json };
|
|
228
|
+
} else {
|
|
229
|
+
const text = await res.text();
|
|
230
|
+
return { status: res.status, data: text.substring(0, 5000) };
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function getOperation(spec, method, path) {
|
|
235
|
+
return spec.paths?.[path]?.[method];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ── Main ──
|
|
239
|
+
|
|
240
|
+
async function main() {
|
|
241
|
+
console.error(`eSign Launchpad MCP Server starting...`);
|
|
242
|
+
console.error(`API: ${API_URL}`);
|
|
243
|
+
console.error(`Spec: ${SPEC_URL}`);
|
|
244
|
+
|
|
245
|
+
const spec = await fetchSpec();
|
|
246
|
+
console.error(`Loaded OpenAPI spec: ${spec.info?.title} ${spec.info?.version}`);
|
|
247
|
+
|
|
248
|
+
const server = new McpServer({
|
|
249
|
+
name: "esign-launchpad",
|
|
250
|
+
version: "1.0.0",
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Register a tool for each API operation
|
|
254
|
+
let toolCount = 0;
|
|
255
|
+
for (const [path, pathItem] of Object.entries(spec.paths || {})) {
|
|
256
|
+
const pathParams = pathItem.parameters || [];
|
|
257
|
+
|
|
258
|
+
for (const method of ["get", "post", "put", "patch", "delete"]) {
|
|
259
|
+
const operation = pathItem[method];
|
|
260
|
+
if (!operation) continue;
|
|
261
|
+
|
|
262
|
+
// Skip operations not in the tenant-api group
|
|
263
|
+
const tags = operation.tags || [];
|
|
264
|
+
|
|
265
|
+
const toolName = buildToolName(method, path, operation);
|
|
266
|
+
const description = buildDescription(operation, method, path);
|
|
267
|
+
const inputSchema = buildInputSchema(spec, operation, pathParams);
|
|
268
|
+
|
|
269
|
+
server.tool(toolName, description, inputSchema.shape, async (params) => {
|
|
270
|
+
try {
|
|
271
|
+
const result = await callApi(method, path, params, spec);
|
|
272
|
+
return {
|
|
273
|
+
content: [
|
|
274
|
+
{
|
|
275
|
+
type: "text",
|
|
276
|
+
text: JSON.stringify(result, null, 2),
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
};
|
|
280
|
+
} catch (err) {
|
|
281
|
+
return {
|
|
282
|
+
content: [
|
|
283
|
+
{
|
|
284
|
+
type: "text",
|
|
285
|
+
text: JSON.stringify({ error: err.message }, null, 2),
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
isError: true,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
toolCount++;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
console.error(`Registered ${toolCount} tools from OpenAPI spec`);
|
|
298
|
+
|
|
299
|
+
// Connect via stdio transport
|
|
300
|
+
const transport = new StdioServerTransport();
|
|
301
|
+
await server.connect(transport);
|
|
302
|
+
console.error("MCP server connected via stdio");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
main().catch((err) => {
|
|
306
|
+
console.error("Fatal error:", err);
|
|
307
|
+
process.exit(1);
|
|
308
|
+
});
|