apigrip 0.2.3 → 0.3.1
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 +22 -6
- package/cli/commands/mcp.js +23 -1
- package/cli/commands/serve.js +8 -1
- package/client/dist/assets/{index-B6sjbmFk.js → index--ZL-pHkt.js} +17 -17
- package/client/dist/index.html +1 -1
- package/mcp/server.js +21 -26
- package/package.json +1 -1
- package/server/index.js +3 -0
- package/server/spec-watcher.js +9 -9
package/client/dist/index.html
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
if (t) document.documentElement.setAttribute('data-theme', t);
|
|
11
11
|
} catch (e) {}
|
|
12
12
|
</script>
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index--ZL-pHkt.js"></script>
|
|
14
14
|
<link rel="stylesheet" crossorigin href="/assets/index-kzeRjfI8.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
package/mcp/server.js
CHANGED
|
@@ -14,15 +14,10 @@ import { saveLastResponse, loadLastResponse } from '../core/response-store.js';
|
|
|
14
14
|
/**
|
|
15
15
|
* Create and configure the MCP server.
|
|
16
16
|
*
|
|
17
|
-
* @param {object}
|
|
18
|
-
* @param {string} options.projectDir - Absolute path to the project directory
|
|
19
|
-
* @param {string} options.specPath - Absolute path to the spec file
|
|
20
|
-
* @param {object} options.spec - Parsed and dereferenced OpenAPI spec object
|
|
17
|
+
* @param {object} state - Mutable state object with projectDir, specPath, spec
|
|
21
18
|
* @returns {Promise<McpServer>}
|
|
22
19
|
*/
|
|
23
|
-
export async function createMcpServer(
|
|
24
|
-
const { projectDir, specPath, spec } = options;
|
|
25
|
-
|
|
20
|
+
export async function createMcpServer(state) {
|
|
26
21
|
const server = new McpServer({
|
|
27
22
|
name: 'apigrip',
|
|
28
23
|
version: '0.1.0',
|
|
@@ -35,20 +30,20 @@ export async function createMcpServer(options = {}) {
|
|
|
35
30
|
contents: [{
|
|
36
31
|
uri: uri.href,
|
|
37
32
|
mimeType: 'application/json',
|
|
38
|
-
text: JSON.stringify(spec, null, 2),
|
|
33
|
+
text: JSON.stringify(state.spec, null, 2),
|
|
39
34
|
}],
|
|
40
35
|
};
|
|
41
36
|
});
|
|
42
37
|
|
|
43
38
|
server.resource('project-info', 'project://info', async (uri) => {
|
|
44
|
-
const git = await getGitInfo(projectDir);
|
|
39
|
+
const git = await getGitInfo(state.projectDir);
|
|
45
40
|
return {
|
|
46
41
|
contents: [{
|
|
47
42
|
uri: uri.href,
|
|
48
43
|
mimeType: 'application/json',
|
|
49
44
|
text: JSON.stringify({
|
|
50
|
-
path: projectDir,
|
|
51
|
-
spec_file: specPath,
|
|
45
|
+
path: state.projectDir,
|
|
46
|
+
spec_file: state.specPath,
|
|
52
47
|
git_branch: git?.branch || null,
|
|
53
48
|
git_commit: git?.commit || null,
|
|
54
49
|
git_dirty: git?.dirty || null,
|
|
@@ -60,22 +55,22 @@ export async function createMcpServer(options = {}) {
|
|
|
60
55
|
// ── Tools ──────────────────────────────────────────────────────────
|
|
61
56
|
|
|
62
57
|
server.tool('get_project_path', 'Get the project directory path', {}, async () => {
|
|
63
|
-
return { content: [{ type: 'text', text: projectDir }] };
|
|
58
|
+
return { content: [{ type: 'text', text: state.projectDir }] };
|
|
64
59
|
});
|
|
65
60
|
|
|
66
61
|
server.tool('get_spec_path', 'Get the spec file path', {}, async () => {
|
|
67
|
-
return { content: [{ type: 'text', text: specPath }] };
|
|
62
|
+
return { content: [{ type: 'text', text: state.specPath }] };
|
|
68
63
|
});
|
|
69
64
|
|
|
70
65
|
server.tool('list_endpoints', 'List all API endpoints', {}, async () => {
|
|
71
|
-
const endpoints = extractEndpoints(spec);
|
|
66
|
+
const endpoints = extractEndpoints(state.spec);
|
|
72
67
|
return { content: [{ type: 'text', text: JSON.stringify(endpoints, null, 2) }] };
|
|
73
68
|
});
|
|
74
69
|
|
|
75
70
|
server.tool('get_endpoint', 'Get endpoint details',
|
|
76
71
|
{ method: z.string(), path: z.string() },
|
|
77
72
|
async ({ method, path }) => {
|
|
78
|
-
const details = getEndpointDetails(spec, method.toUpperCase(), path);
|
|
73
|
+
const details = getEndpointDetails(state.spec, method.toUpperCase(), path);
|
|
79
74
|
if (!details) {
|
|
80
75
|
return {
|
|
81
76
|
content: [{ type: 'text', text: `Endpoint not found: ${method} ${path}` }],
|
|
@@ -89,7 +84,7 @@ export async function createMcpServer(options = {}) {
|
|
|
89
84
|
server.tool('get_schema', 'Get a named schema',
|
|
90
85
|
{ name: z.string() },
|
|
91
86
|
async ({ name }) => {
|
|
92
|
-
const schemas = spec.components?.schemas || spec.definitions || {};
|
|
87
|
+
const schemas = state.spec.components?.schemas || state.spec.definitions || {};
|
|
93
88
|
const schema = schemas[name];
|
|
94
89
|
if (!schema) {
|
|
95
90
|
const available = Object.keys(schemas);
|
|
@@ -103,7 +98,7 @@ export async function createMcpServer(options = {}) {
|
|
|
103
98
|
);
|
|
104
99
|
|
|
105
100
|
server.tool('list_environments', 'List environments', {}, async () => {
|
|
106
|
-
const envData = loadEnvironments(projectDir);
|
|
101
|
+
const envData = loadEnvironments(state.projectDir);
|
|
107
102
|
return {
|
|
108
103
|
content: [{
|
|
109
104
|
type: 'text',
|
|
@@ -119,10 +114,10 @@ export async function createMcpServer(options = {}) {
|
|
|
119
114
|
{ name: z.string().optional() },
|
|
120
115
|
async ({ name } = {}) => {
|
|
121
116
|
if (!name) {
|
|
122
|
-
const resolved = resolveEnvironment(projectDir);
|
|
117
|
+
const resolved = resolveEnvironment(state.projectDir);
|
|
123
118
|
return { content: [{ type: 'text', text: JSON.stringify(resolved, null, 2) }] };
|
|
124
119
|
}
|
|
125
|
-
const envData = loadEnvironments(projectDir);
|
|
120
|
+
const envData = loadEnvironments(state.projectDir);
|
|
126
121
|
const env = envData.environments[name] || envData.base;
|
|
127
122
|
return { content: [{ type: 'text', text: JSON.stringify(env, null, 2) }] };
|
|
128
123
|
}
|
|
@@ -131,7 +126,7 @@ export async function createMcpServer(options = {}) {
|
|
|
131
126
|
server.tool('validate_request_body', 'Validate a request body against the spec',
|
|
132
127
|
{ method: z.string(), path: z.string(), body: z.string() },
|
|
133
128
|
async ({ method, path, body }) => {
|
|
134
|
-
const details = getEndpointDetails(spec, method.toUpperCase(), path);
|
|
129
|
+
const details = getEndpointDetails(state.spec, method.toUpperCase(), path);
|
|
135
130
|
if (!details) {
|
|
136
131
|
return {
|
|
137
132
|
content: [{ type: 'text', text: `Endpoint not found: ${method} ${path}` }],
|
|
@@ -158,7 +153,7 @@ export async function createMcpServer(options = {}) {
|
|
|
158
153
|
async ({ method, path: endpointPath, params_json }) => {
|
|
159
154
|
const params = params_json ? JSON.parse(params_json) : {};
|
|
160
155
|
const upperMethod = method.toUpperCase();
|
|
161
|
-
const details = getEndpointDetails(spec, upperMethod, endpointPath);
|
|
156
|
+
const details = getEndpointDetails(state.spec, upperMethod, endpointPath);
|
|
162
157
|
if (!details) {
|
|
163
158
|
return {
|
|
164
159
|
content: [{ type: 'text', text: `Endpoint not found: ${method} ${endpointPath}` }],
|
|
@@ -169,7 +164,7 @@ export async function createMcpServer(options = {}) {
|
|
|
169
164
|
// Resolve environment
|
|
170
165
|
let env = {};
|
|
171
166
|
try {
|
|
172
|
-
const envData = loadEnvironments(projectDir);
|
|
167
|
+
const envData = loadEnvironments(state.projectDir);
|
|
173
168
|
env = resolveEnvironment(envData);
|
|
174
169
|
} catch {
|
|
175
170
|
// Continue without environment
|
|
@@ -183,7 +178,7 @@ export async function createMcpServer(options = {}) {
|
|
|
183
178
|
const body = resolvedParams.body != null ? resolvedParams.body : null;
|
|
184
179
|
|
|
185
180
|
// Build URL
|
|
186
|
-
const servers = details.servers || spec.servers || [];
|
|
181
|
+
const servers = details.servers || state.spec.servers || [];
|
|
187
182
|
const url = buildUrl(servers, 0, {}, endpointPath, pathParams, queryParams, env.base_url);
|
|
188
183
|
|
|
189
184
|
// Build curl args
|
|
@@ -204,7 +199,7 @@ export async function createMcpServer(options = {}) {
|
|
|
204
199
|
|
|
205
200
|
// Validate response against spec
|
|
206
201
|
const validation = validateResponse(
|
|
207
|
-
spec,
|
|
202
|
+
state.spec,
|
|
208
203
|
upperMethod,
|
|
209
204
|
endpointPath,
|
|
210
205
|
result.status,
|
|
@@ -224,7 +219,7 @@ export async function createMcpServer(options = {}) {
|
|
|
224
219
|
|
|
225
220
|
// Cache response for later retrieval
|
|
226
221
|
try {
|
|
227
|
-
saveLastResponse(projectDir, upperMethod, endpointPath, response);
|
|
222
|
+
saveLastResponse(state.projectDir, upperMethod, endpointPath, response);
|
|
228
223
|
} catch {
|
|
229
224
|
// Non-critical
|
|
230
225
|
}
|
|
@@ -242,7 +237,7 @@ export async function createMcpServer(options = {}) {
|
|
|
242
237
|
server.tool('get_last_response', 'Get the last cached response for an endpoint',
|
|
243
238
|
{ method: z.string(), path: z.string() },
|
|
244
239
|
async ({ method, path: endpointPath }) => {
|
|
245
|
-
const cached = loadLastResponse(projectDir, method.toUpperCase(), endpointPath);
|
|
240
|
+
const cached = loadLastResponse(state.projectDir, method.toUpperCase(), endpointPath);
|
|
246
241
|
if (!cached) {
|
|
247
242
|
return {
|
|
248
243
|
content: [{ type: 'text', text: `No cached response for ${method} ${endpointPath}` }],
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -40,6 +40,9 @@ export function createServer(options = {}) {
|
|
|
40
40
|
}
|
|
41
41
|
app.use('/api', createEventsRoutes(state));
|
|
42
42
|
|
|
43
|
+
// No favicon — return empty 204 to avoid SPA fallback noise
|
|
44
|
+
app.get('/favicon.ico', (req, res) => res.status(204).end());
|
|
45
|
+
|
|
43
46
|
// Serve frontend static files
|
|
44
47
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
45
48
|
const clientDist = path.resolve(__dirname, '../client/dist');
|
package/server/spec-watcher.js
CHANGED
|
@@ -92,20 +92,20 @@ function diffEndpoints(oldEndpoints, newEndpoints) {
|
|
|
92
92
|
* @param {Function} broadcast - broadcast(state, event, data) function.
|
|
93
93
|
*/
|
|
94
94
|
export async function createSpecWatcher(state, broadcast) {
|
|
95
|
-
if (!state.
|
|
95
|
+
if (!state.specPath) return;
|
|
96
96
|
|
|
97
|
-
const filesToWatch = [state.
|
|
97
|
+
const filesToWatch = [state.specPath];
|
|
98
98
|
|
|
99
99
|
// Add $ref dependency files
|
|
100
100
|
try {
|
|
101
|
-
const deps = await getRefDeps(state.
|
|
101
|
+
const deps = await getRefDeps(state.specPath);
|
|
102
102
|
filesToWatch.push(...deps);
|
|
103
103
|
} catch {
|
|
104
104
|
// If we can't resolve deps, just watch the main file
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
// Git metadata files
|
|
108
|
-
const gitDir = path.join(state.
|
|
108
|
+
const gitDir = path.join(state.projectDir, '.git');
|
|
109
109
|
const gitPaths = [
|
|
110
110
|
path.join(gitDir, 'HEAD'),
|
|
111
111
|
path.join(gitDir, 'index'),
|
|
@@ -133,7 +133,7 @@ export async function createSpecWatcher(state, broadcast) {
|
|
|
133
133
|
// Debounced spec reload handler
|
|
134
134
|
const handleSpecChange = debounce(async () => {
|
|
135
135
|
try {
|
|
136
|
-
const newSpec = await parseSpec(state.
|
|
136
|
+
const newSpec = await parseSpec(state.specPath);
|
|
137
137
|
const newEndpoints = extractEndpoints(newSpec);
|
|
138
138
|
const changed = diffEndpoints(currentEndpoints, newEndpoints);
|
|
139
139
|
|
|
@@ -147,7 +147,7 @@ export async function createSpecWatcher(state, broadcast) {
|
|
|
147
147
|
|
|
148
148
|
// Re-watch any new $ref deps
|
|
149
149
|
try {
|
|
150
|
-
const newDeps = await getRefDeps(state.
|
|
150
|
+
const newDeps = await getRefDeps(state.specPath);
|
|
151
151
|
for (const dep of newDeps) {
|
|
152
152
|
watcher.add(dep);
|
|
153
153
|
}
|
|
@@ -165,7 +165,7 @@ export async function createSpecWatcher(state, broadcast) {
|
|
|
165
165
|
// Debounced git change handler
|
|
166
166
|
const handleGitChange = debounce(async () => {
|
|
167
167
|
try {
|
|
168
|
-
const newGit = await getGitInfo(state.
|
|
168
|
+
const newGit = await getGitInfo(state.projectDir);
|
|
169
169
|
state.git = newGit;
|
|
170
170
|
broadcast(state, 'git:changed', newGit);
|
|
171
171
|
} catch {
|
|
@@ -199,10 +199,10 @@ export async function createSpecWatcher(state, broadcast) {
|
|
|
199
199
|
// Handle unlink (deleted files)
|
|
200
200
|
watcher.on('unlink', (changedPath) => {
|
|
201
201
|
const resolved = path.resolve(changedPath);
|
|
202
|
-
if (resolved === path.resolve(state.
|
|
202
|
+
if (resolved === path.resolve(state.specPath)) {
|
|
203
203
|
// Main spec file deleted — broadcast error and tear down watcher
|
|
204
204
|
broadcast(state, 'spec:error', {
|
|
205
|
-
errors: ['Spec file deleted: ' + state.
|
|
205
|
+
errors: ['Spec file deleted: ' + state.specPath],
|
|
206
206
|
partial: false,
|
|
207
207
|
});
|
|
208
208
|
state.spec = null;
|