mocktail-mcp 3.1.4
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/README.md +91 -0
- package/index.js +196 -0
- package/package.json +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Mocktail MCP Server
|
|
2
|
+
|
|
3
|
+
An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server that lets AI assistants manage [Mocktail](https://github.com/Huseyinnurbaki/mocktail) mock API endpoints through natural language.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
| Tool | Description |
|
|
8
|
+
|------|-------------|
|
|
9
|
+
| `list_mocks` | List all configured mock endpoints |
|
|
10
|
+
| `create_mock` | Create a single mock endpoint |
|
|
11
|
+
| `update_mock` | Update an existing mock by ID |
|
|
12
|
+
| `delete_mock` | Delete a mock by ID |
|
|
13
|
+
| `import_mocks` | Bulk import multiple mock endpoints (skips duplicates) |
|
|
14
|
+
|
|
15
|
+
## Environment Variables
|
|
16
|
+
|
|
17
|
+
| Variable | Required | Description |
|
|
18
|
+
|----------|----------|-------------|
|
|
19
|
+
| `MOCKTAIL_URL` | Yes | Base URL of your Mocktail instance (e.g. `http://localhost:4000`) |
|
|
20
|
+
| `MOCKTAIL_API_KEY` | No | API key, sent as `X-API-Key` header |
|
|
21
|
+
|
|
22
|
+
> **Note:** If you're running Mocktail behind a reverse proxy or custom domain (via `MOCKTAIL_BASE_URL`), use that host as `MOCKTAIL_URL` (e.g. `MOCKTAIL_URL=https://api.mycompany.com`).
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
### npx (Recommended)
|
|
27
|
+
|
|
28
|
+
**Claude Code:**
|
|
29
|
+
```bash
|
|
30
|
+
claude mcp add mocktail \
|
|
31
|
+
-e MOCKTAIL_URL=http://localhost:4000 \
|
|
32
|
+
-e MOCKTAIL_API_KEY=your-api-key \
|
|
33
|
+
-- npx mocktail-mcp
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"mocktail": {
|
|
41
|
+
"command": "npx",
|
|
42
|
+
"args": ["mocktail-mcp"],
|
|
43
|
+
"env": {
|
|
44
|
+
"MOCKTAIL_URL": "http://localhost:4000",
|
|
45
|
+
"MOCKTAIL_API_KEY": "your-api-key"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### From Source (Development)
|
|
53
|
+
|
|
54
|
+
If you cloned the repo and want to run the MCP server locally:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
cd mcp-server && npm install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Claude Code:**
|
|
61
|
+
```bash
|
|
62
|
+
claude mcp add mocktail \
|
|
63
|
+
-e MOCKTAIL_URL=http://localhost:4000 \
|
|
64
|
+
-e MOCKTAIL_API_KEY=your-api-key \
|
|
65
|
+
-- node /absolute/path/to/mcp-server/index.js
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Claude Desktop:**
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"mcpServers": {
|
|
72
|
+
"mocktail": {
|
|
73
|
+
"command": "node",
|
|
74
|
+
"args": ["/absolute/path/to/mcp-server/index.js"],
|
|
75
|
+
"env": {
|
|
76
|
+
"MOCKTAIL_URL": "http://localhost:4000",
|
|
77
|
+
"MOCKTAIL_API_KEY": "your-api-key"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Usage Examples
|
|
85
|
+
|
|
86
|
+
Once connected, you can use natural language:
|
|
87
|
+
|
|
88
|
+
- "List all my mock endpoints"
|
|
89
|
+
- "Create a GET /api/users mock that returns a list of 3 users"
|
|
90
|
+
- "Delete mock endpoint #5"
|
|
91
|
+
- "Import mock endpoints for a blog API with posts, comments, and authors"
|
package/index.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
const MOCKTAIL_URL = process.env.MOCKTAIL_URL;
|
|
8
|
+
const MOCKTAIL_API_KEY = process.env.MOCKTAIL_API_KEY || "";
|
|
9
|
+
|
|
10
|
+
if (!MOCKTAIL_URL) {
|
|
11
|
+
console.error("MOCKTAIL_URL environment variable is required");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function mocktailRequest(path, options = {}) {
|
|
16
|
+
const url = `${MOCKTAIL_URL}${path}`;
|
|
17
|
+
const headers = { "Content-Type": "application/json" };
|
|
18
|
+
if (MOCKTAIL_API_KEY) {
|
|
19
|
+
headers["X-API-Key"] = MOCKTAIL_API_KEY;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const res = await fetch(url, { ...options, headers });
|
|
23
|
+
const body = await res.json();
|
|
24
|
+
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
throw new Error(body.message || `Request failed with status ${res.status}`);
|
|
27
|
+
}
|
|
28
|
+
return body;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const server = new McpServer({
|
|
32
|
+
name: "mocktail",
|
|
33
|
+
version: "3.1.4",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// list_mocks
|
|
37
|
+
server.tool(
|
|
38
|
+
"list_mocks",
|
|
39
|
+
"List all configured mock endpoints in Mocktail",
|
|
40
|
+
{},
|
|
41
|
+
async () => {
|
|
42
|
+
try {
|
|
43
|
+
const mocks = await mocktailRequest("/core/v1/apis");
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: "text", text: JSON.stringify(mocks, null, 2) + `\n\nMock endpoints are accessible at: ${MOCKTAIL_URL}/mocktail{Endpoint}` }],
|
|
46
|
+
};
|
|
47
|
+
} catch (err) {
|
|
48
|
+
return {
|
|
49
|
+
isError: true,
|
|
50
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// create_mock
|
|
57
|
+
server.tool(
|
|
58
|
+
"create_mock",
|
|
59
|
+
"Create a new mock endpoint. Method must be GET, POST, PUT, PATCH, or DELETE. Response should be a JSON value (object, array, string, etc.). The endpoint path is relative — e.g. '/api/users' becomes accessible at MOCKTAIL_URL/mocktail/api/users.",
|
|
60
|
+
{
|
|
61
|
+
method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).describe("HTTP method"),
|
|
62
|
+
endpoint: z.string().describe("URL path starting with /, e.g. /api/users. Accessible at MOCKTAIL_URL/mocktail/api/users"),
|
|
63
|
+
response: z.string().describe("JSON string for the response body"),
|
|
64
|
+
statusCode: z.number().optional().describe("HTTP status code (default 200)"),
|
|
65
|
+
delay: z.number().optional().describe("Response delay in ms (0-30000, default 0)"),
|
|
66
|
+
},
|
|
67
|
+
async ({ method, endpoint, response, statusCode, delay }) => {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(response);
|
|
70
|
+
const result = await mocktailRequest("/core/v1/api", {
|
|
71
|
+
method: "POST",
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
Method: method,
|
|
74
|
+
Endpoint: endpoint,
|
|
75
|
+
Response: parsed,
|
|
76
|
+
StatusCode: statusCode ?? 200,
|
|
77
|
+
Delay: delay ?? 0,
|
|
78
|
+
}),
|
|
79
|
+
});
|
|
80
|
+
const accessUrl = `${MOCKTAIL_URL}/mocktail${endpoint}`;
|
|
81
|
+
return {
|
|
82
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) + `\n\nAccessible at: ${method} ${accessUrl}` }],
|
|
83
|
+
};
|
|
84
|
+
} catch (err) {
|
|
85
|
+
return {
|
|
86
|
+
isError: true,
|
|
87
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// update_mock
|
|
94
|
+
server.tool(
|
|
95
|
+
"update_mock",
|
|
96
|
+
"Update an existing mock endpoint by ID. All fields are required since the entire mock is replaced. The endpoint path is relative — e.g. '/api/users' becomes accessible at MOCKTAIL_URL/mocktail/api/users.",
|
|
97
|
+
{
|
|
98
|
+
id: z.number().describe("Mock endpoint ID"),
|
|
99
|
+
method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).describe("HTTP method"),
|
|
100
|
+
endpoint: z.string().describe("URL path starting with /, e.g. /api/users"),
|
|
101
|
+
response: z.string().describe("JSON string for the response body"),
|
|
102
|
+
statusCode: z.number().optional().describe("HTTP status code (default 200)"),
|
|
103
|
+
delay: z.number().optional().describe("Response delay in ms (0-30000, default 0)"),
|
|
104
|
+
},
|
|
105
|
+
async ({ id, method, endpoint, response, statusCode, delay }) => {
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(response);
|
|
108
|
+
const result = await mocktailRequest(`/core/v1/api/${id}`, {
|
|
109
|
+
method: "PUT",
|
|
110
|
+
body: JSON.stringify({
|
|
111
|
+
Method: method,
|
|
112
|
+
Endpoint: endpoint,
|
|
113
|
+
Response: parsed,
|
|
114
|
+
StatusCode: statusCode ?? 200,
|
|
115
|
+
Delay: delay ?? 0,
|
|
116
|
+
}),
|
|
117
|
+
});
|
|
118
|
+
const accessUrl = `${MOCKTAIL_URL}/mocktail${endpoint}`;
|
|
119
|
+
return {
|
|
120
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) + `\n\nAccessible at: ${method} ${accessUrl}` }],
|
|
121
|
+
};
|
|
122
|
+
} catch (err) {
|
|
123
|
+
return {
|
|
124
|
+
isError: true,
|
|
125
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// delete_mock
|
|
132
|
+
server.tool(
|
|
133
|
+
"delete_mock",
|
|
134
|
+
"Delete a mock endpoint by ID",
|
|
135
|
+
{
|
|
136
|
+
id: z.number().describe("Mock endpoint ID to delete"),
|
|
137
|
+
},
|
|
138
|
+
async ({ id }) => {
|
|
139
|
+
try {
|
|
140
|
+
const result = await mocktailRequest(`/core/v1/api/${id}`, {
|
|
141
|
+
method: "DELETE",
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
145
|
+
};
|
|
146
|
+
} catch (err) {
|
|
147
|
+
return {
|
|
148
|
+
isError: true,
|
|
149
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
// import_mocks
|
|
156
|
+
server.tool(
|
|
157
|
+
"import_mocks",
|
|
158
|
+
"Bulk import multiple mock endpoints. Existing endpoints (same method+path) are skipped. Response fields should be JSON strings.",
|
|
159
|
+
{
|
|
160
|
+
mocks: z.array(
|
|
161
|
+
z.object({
|
|
162
|
+
method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).describe("HTTP method"),
|
|
163
|
+
endpoint: z.string().describe("URL path starting with /, e.g. /api/users"),
|
|
164
|
+
response: z.string().describe("JSON string for the response body"),
|
|
165
|
+
statusCode: z.number().optional().describe("HTTP status code (default 200)"),
|
|
166
|
+
delay: z.number().optional().describe("Response delay in ms (default 0)"),
|
|
167
|
+
})
|
|
168
|
+
).describe("Array of mock endpoints to import"),
|
|
169
|
+
},
|
|
170
|
+
async ({ mocks }) => {
|
|
171
|
+
try {
|
|
172
|
+
const apis = mocks.map((m) => ({
|
|
173
|
+
Method: m.method,
|
|
174
|
+
Endpoint: m.endpoint,
|
|
175
|
+
Response: JSON.parse(m.response),
|
|
176
|
+
StatusCode: m.statusCode ?? 200,
|
|
177
|
+
Delay: m.delay ?? 0,
|
|
178
|
+
}));
|
|
179
|
+
const result = await mocktailRequest("/core/v1/import", {
|
|
180
|
+
method: "POST",
|
|
181
|
+
body: JSON.stringify({ Apis: apis }),
|
|
182
|
+
});
|
|
183
|
+
return {
|
|
184
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
185
|
+
};
|
|
186
|
+
} catch (err) {
|
|
187
|
+
return {
|
|
188
|
+
isError: true,
|
|
189
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const transport = new StdioServerTransport();
|
|
196
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mocktail-mcp",
|
|
3
|
+
"version": "3.1.4",
|
|
4
|
+
"description": "MCP server for managing Mocktail mock API endpoints",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mocktail-mcp": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node index.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"mocktail",
|
|
14
|
+
"mcp",
|
|
15
|
+
"mock",
|
|
16
|
+
"api"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
21
|
+
"zod": "^3.24.2"
|
|
22
|
+
}
|
|
23
|
+
}
|