feat: add MCP service with 5 multi-level retrieval tools
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,13 +11,15 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@agent-fox/shared": "workspace:*",
|
"@agent-fox/shared": "workspace:*",
|
||||||
"@modelcontextprotocol/sdk": "^1.12.0",
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
||||||
"express": "^5.0.0",
|
"bcrypt": "^6.0.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
"express": "^5.0.0",
|
||||||
"zod": "^3.24.0"
|
"zod": "^3.24.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^5.0.0",
|
"@types/bcrypt": "^6.0.0",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
|
"@types/express": "^5.0.0",
|
||||||
"tsx": "^4.19.0",
|
"tsx": "^4.19.0",
|
||||||
"typescript": "^5.7.0"
|
"typescript": "^5.7.0"
|
||||||
}
|
}
|
||||||
|
|||||||
33
packages/mcp/src/auth.ts
Normal file
33
packages/mcp/src/auth.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import type { Request, Response, NextFunction } from 'express';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
import { prisma } from '@agent-fox/shared';
|
||||||
|
|
||||||
|
export async function mcpAuth(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||||
|
const projectId = req.params['projectId'] as string;
|
||||||
|
const header = req.headers.authorization;
|
||||||
|
|
||||||
|
if (!header?.startsWith('Bearer ')) {
|
||||||
|
res.status(401).json({ error: 'Missing API key' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiKey = header.slice(7);
|
||||||
|
const project = await prisma.project.findUnique({
|
||||||
|
where: { id: projectId },
|
||||||
|
select: { id: true, apiKeyHash: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!project) {
|
||||||
|
res.status(404).json({ error: 'Project not found' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const valid = await bcrypt.compare(apiKey, project.apiKeyHash);
|
||||||
|
if (!valid) {
|
||||||
|
res.status(401).json({ error: 'Invalid API key' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(req as any).projectId = projectId;
|
||||||
|
next();
|
||||||
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
|
import { randomUUID } from 'node:crypto';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
||||||
|
import { mcpAuth } from './auth.js';
|
||||||
|
import { createMcpServer } from './server.js';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
@@ -9,6 +13,60 @@ app.get('/health', (_req, res) => {
|
|||||||
res.json({ status: 'ok' });
|
res.json({ status: 'ok' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Session storage
|
||||||
|
const transports: Record<string, StreamableHTTPServerTransport> = {};
|
||||||
|
|
||||||
|
// MCP Streamable HTTP endpoint
|
||||||
|
app.post('/mcp/:projectId', mcpAuth, async (req, res) => {
|
||||||
|
const projectId = (req as any).projectId as string;
|
||||||
|
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
||||||
|
|
||||||
|
if (sessionId && transports[sessionId]) {
|
||||||
|
await transports[sessionId].handleRequest(req, res, req.body);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// New session — check for initialize request
|
||||||
|
const transport = new StreamableHTTPServerTransport({
|
||||||
|
sessionIdGenerator: () => randomUUID(),
|
||||||
|
onsessioninitialized: (id) => {
|
||||||
|
transports[id] = transport;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
transport.onclose = () => {
|
||||||
|
if (transport.sessionId) {
|
||||||
|
delete transports[transport.sessionId];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const server = createMcpServer(projectId);
|
||||||
|
await server.connect(transport);
|
||||||
|
await transport.handleRequest(req, res, req.body);
|
||||||
|
});
|
||||||
|
|
||||||
|
// SSE endpoint for session resumption
|
||||||
|
app.get('/mcp/:projectId', mcpAuth, async (req, res) => {
|
||||||
|
const sessionId = req.headers['mcp-session-id'] as string;
|
||||||
|
if (sessionId && transports[sessionId]) {
|
||||||
|
await transports[sessionId].handleRequest(req, res);
|
||||||
|
} else {
|
||||||
|
res.status(400).json({ error: 'Invalid session. Start a new session via POST.' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Session termination
|
||||||
|
app.delete('/mcp/:projectId', mcpAuth, async (req, res) => {
|
||||||
|
const sessionId = req.headers['mcp-session-id'] as string;
|
||||||
|
if (sessionId && transports[sessionId]) {
|
||||||
|
await transports[sessionId].close();
|
||||||
|
delete transports[sessionId];
|
||||||
|
res.status(204).end();
|
||||||
|
} else {
|
||||||
|
res.status(400).json({ error: 'Invalid session' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const port = process.env.MCP_PORT || 3001;
|
const port = process.env.MCP_PORT || 3001;
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`MCP service running on port ${port}`);
|
console.log(`MCP service running on port ${port}`);
|
||||||
|
|||||||
54
packages/mcp/src/server.ts
Normal file
54
packages/mcp/src/server.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { getProjectOverview } from './tools/get-project-overview.js';
|
||||||
|
import { listModules } from './tools/list-modules.js';
|
||||||
|
import { listEndpoints } from './tools/list-endpoints.js';
|
||||||
|
import { getEndpointDetail } from './tools/get-endpoint-detail.js';
|
||||||
|
import { searchEndpoints } from './tools/search-endpoints.js';
|
||||||
|
|
||||||
|
export function createMcpServer(projectId: string): McpServer {
|
||||||
|
const server = new McpServer({
|
||||||
|
name: 'agent-fox',
|
||||||
|
version: '0.1.0',
|
||||||
|
});
|
||||||
|
|
||||||
|
server.tool(
|
||||||
|
'get_project_overview',
|
||||||
|
'Get an overview of this API project including its name, version, base URL, and a summary of available modules with endpoint counts. Call this first to understand what the API offers.',
|
||||||
|
{},
|
||||||
|
async () => getProjectOverview(projectId),
|
||||||
|
);
|
||||||
|
|
||||||
|
server.tool(
|
||||||
|
'list_modules',
|
||||||
|
'List all API modules/groups with their descriptions. Each module contains related endpoints. Use this when you need module descriptions to decide which module to explore.',
|
||||||
|
{},
|
||||||
|
async () => listModules(projectId),
|
||||||
|
);
|
||||||
|
|
||||||
|
server.tool(
|
||||||
|
'list_endpoints',
|
||||||
|
'List all endpoints in a specific module. Returns method, path, and summary for each endpoint. Use get_endpoint_detail to get full information about a specific endpoint.',
|
||||||
|
{ moduleId: z.string().describe('The module ID to list endpoints for. Get module IDs from get_project_overview or list_modules.') },
|
||||||
|
async ({ moduleId }) => listEndpoints(projectId, moduleId),
|
||||||
|
);
|
||||||
|
|
||||||
|
server.tool(
|
||||||
|
'get_endpoint_detail',
|
||||||
|
'Get complete details for a specific endpoint including parameters, request body schema, response schemas. Use this when you need to understand exactly how to call an endpoint.',
|
||||||
|
{ endpointId: z.string().describe('The endpoint ID. Get endpoint IDs from list_endpoints or search_endpoints.') },
|
||||||
|
async ({ endpointId }) => getEndpointDetail(projectId, endpointId),
|
||||||
|
);
|
||||||
|
|
||||||
|
server.tool(
|
||||||
|
'search_endpoints',
|
||||||
|
'Search for endpoints by keyword. Searches across path, summary, description, and operationId. Optionally filter by module. Returns matching endpoint summaries.',
|
||||||
|
{
|
||||||
|
keyword: z.string().describe('Search keyword to match against endpoint path, summary, description, and operationId.'),
|
||||||
|
moduleId: z.string().optional().describe('Optional module ID to limit search scope. Omit to search all modules.'),
|
||||||
|
},
|
||||||
|
async ({ keyword, moduleId }) => searchEndpoints(projectId, keyword, moduleId),
|
||||||
|
);
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
22
packages/mcp/src/tools/get-endpoint-detail.ts
Normal file
22
packages/mcp/src/tools/get-endpoint-detail.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { prisma } from '@agent-fox/shared';
|
||||||
|
|
||||||
|
export async function getEndpointDetail(projectId: string, endpointId: string) {
|
||||||
|
const endpoint = await prisma.endpoint.findFirst({
|
||||||
|
where: { id: endpointId, projectId },
|
||||||
|
include: { module: { select: { name: true } } },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!endpoint) {
|
||||||
|
return { content: [{ type: 'text' as const, text: `Endpoint "${endpointId}" not found. Use list_endpoints to see available endpoints.` }], isError: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
const detail = {
|
||||||
|
id: endpoint.id, method: endpoint.method, path: endpoint.path,
|
||||||
|
summary: endpoint.summary, description: endpoint.description,
|
||||||
|
operationId: endpoint.operationId, moduleName: endpoint.module.name,
|
||||||
|
parameters: endpoint.parameters, requestBody: endpoint.requestBody,
|
||||||
|
responses: endpoint.responses, deprecated: endpoint.deprecated,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { content: [{ type: 'text' as const, text: JSON.stringify(detail, null, 2) }] };
|
||||||
|
}
|
||||||
32
packages/mcp/src/tools/get-project-overview.ts
Normal file
32
packages/mcp/src/tools/get-project-overview.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { prisma } from '@agent-fox/shared';
|
||||||
|
|
||||||
|
export async function getProjectOverview(projectId: string) {
|
||||||
|
const project = await prisma.project.findUnique({
|
||||||
|
where: { id: projectId },
|
||||||
|
select: {
|
||||||
|
name: true, description: true, openApiVersion: true, baseUrl: true,
|
||||||
|
modules: {
|
||||||
|
select: { id: true, name: true, _count: { select: { endpoints: true } } },
|
||||||
|
orderBy: { sortOrder: 'asc' },
|
||||||
|
},
|
||||||
|
_count: { select: { endpoints: true } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!project) {
|
||||||
|
return { content: [{ type: 'text' as const, text: 'Project not found' }], isError: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
const overview = {
|
||||||
|
name: project.name,
|
||||||
|
description: project.description,
|
||||||
|
version: project.openApiVersion,
|
||||||
|
baseUrl: project.baseUrl,
|
||||||
|
totalEndpoints: project._count.endpoints,
|
||||||
|
modules: project.modules.map((m) => ({
|
||||||
|
id: m.id, name: m.name, endpointCount: m._count.endpoints,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
return { content: [{ type: 'text' as const, text: JSON.stringify(overview, null, 2) }] };
|
||||||
|
}
|
||||||
16
packages/mcp/src/tools/list-endpoints.ts
Normal file
16
packages/mcp/src/tools/list-endpoints.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { prisma } from '@agent-fox/shared';
|
||||||
|
|
||||||
|
export async function listEndpoints(projectId: string, moduleId: string) {
|
||||||
|
const mod = await prisma.module.findFirst({ where: { id: moduleId, projectId } });
|
||||||
|
if (!mod) {
|
||||||
|
return { content: [{ type: 'text' as const, text: `Module "${moduleId}" not found. Use get_project_overview or list_modules to see available modules.` }], isError: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoints = await prisma.endpoint.findMany({
|
||||||
|
where: { projectId, moduleId },
|
||||||
|
select: { id: true, method: true, path: true, summary: true, deprecated: true },
|
||||||
|
orderBy: [{ path: 'asc' }, { method: 'asc' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
return { content: [{ type: 'text' as const, text: JSON.stringify(endpoints, null, 2) }] };
|
||||||
|
}
|
||||||
15
packages/mcp/src/tools/list-modules.ts
Normal file
15
packages/mcp/src/tools/list-modules.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { prisma } from '@agent-fox/shared';
|
||||||
|
|
||||||
|
export async function listModules(projectId: string) {
|
||||||
|
const modules = await prisma.module.findMany({
|
||||||
|
where: { projectId },
|
||||||
|
select: { id: true, name: true, description: true, _count: { select: { endpoints: true } } },
|
||||||
|
orderBy: { sortOrder: 'asc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = modules.map((m) => ({
|
||||||
|
id: m.id, name: m.name, description: m.description, endpointCount: m._count.endpoints,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] };
|
||||||
|
}
|
||||||
34
packages/mcp/src/tools/search-endpoints.ts
Normal file
34
packages/mcp/src/tools/search-endpoints.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { prisma } from '@agent-fox/shared';
|
||||||
|
|
||||||
|
export async function searchEndpoints(projectId: string, keyword: string, moduleId?: string) {
|
||||||
|
const where: any = { projectId };
|
||||||
|
if (moduleId) where.moduleId = moduleId;
|
||||||
|
|
||||||
|
where.OR = [
|
||||||
|
{ path: { contains: keyword, mode: 'insensitive' } },
|
||||||
|
{ summary: { contains: keyword, mode: 'insensitive' } },
|
||||||
|
{ description: { contains: keyword, mode: 'insensitive' } },
|
||||||
|
{ operationId: { contains: keyword, mode: 'insensitive' } },
|
||||||
|
];
|
||||||
|
|
||||||
|
const endpoints = await prisma.endpoint.findMany({
|
||||||
|
where,
|
||||||
|
select: {
|
||||||
|
id: true, method: true, path: true, summary: true, deprecated: true,
|
||||||
|
module: { select: { name: true } },
|
||||||
|
},
|
||||||
|
orderBy: [{ path: 'asc' }, { method: 'asc' }],
|
||||||
|
take: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (endpoints.length === 0) {
|
||||||
|
return { content: [{ type: 'text' as const, text: `No endpoints found matching "${keyword}". Try a different keyword or use list_modules to browse.` }] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = endpoints.map((e) => ({
|
||||||
|
id: e.id, method: e.method, path: e.path, summary: e.summary,
|
||||||
|
moduleName: e.module.name, deprecated: e.deprecated,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] };
|
||||||
|
}
|
||||||
87
pnpm-lock.yaml
generated
87
pnpm-lock.yaml
generated
@@ -24,6 +24,9 @@ importers:
|
|||||||
'@modelcontextprotocol/sdk':
|
'@modelcontextprotocol/sdk':
|
||||||
specifier: ^1.12.0
|
specifier: ^1.12.0
|
||||||
version: 1.29.0(zod@3.25.76)
|
version: 1.29.0(zod@3.25.76)
|
||||||
|
bcrypt:
|
||||||
|
specifier: ^6.0.0
|
||||||
|
version: 6.0.0
|
||||||
cors:
|
cors:
|
||||||
specifier: ^2.8.5
|
specifier: ^2.8.5
|
||||||
version: 2.8.6
|
version: 2.8.6
|
||||||
@@ -34,6 +37,9 @@ importers:
|
|||||||
specifier: ^3.24.0
|
specifier: ^3.24.0
|
||||||
version: 3.25.76
|
version: 3.25.76
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@types/bcrypt':
|
||||||
|
specifier: ^6.0.0
|
||||||
|
version: 6.0.0
|
||||||
'@types/cors':
|
'@types/cors':
|
||||||
specifier: ^2.8.17
|
specifier: ^2.8.17
|
||||||
version: 2.8.19
|
version: 2.8.19
|
||||||
@@ -52,6 +58,9 @@ importers:
|
|||||||
'@agent-fox/shared':
|
'@agent-fox/shared':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../shared
|
version: link:../shared
|
||||||
|
'@apidevtools/swagger-parser':
|
||||||
|
specifier: ^12.1.0
|
||||||
|
version: 12.1.0(openapi-types@12.1.3)
|
||||||
bcrypt:
|
bcrypt:
|
||||||
specifier: ^6.0.0
|
specifier: ^6.0.0
|
||||||
version: 6.0.0
|
version: 6.0.0
|
||||||
@@ -64,6 +73,9 @@ importers:
|
|||||||
jsonwebtoken:
|
jsonwebtoken:
|
||||||
specifier: ^9.0.3
|
specifier: ^9.0.3
|
||||||
version: 9.0.3
|
version: 9.0.3
|
||||||
|
openapi-types:
|
||||||
|
specifier: ^12.1.3
|
||||||
|
version: 12.1.3
|
||||||
zod:
|
zod:
|
||||||
specifier: ^3.24.0
|
specifier: ^3.24.0
|
||||||
version: 3.25.76
|
version: 3.25.76
|
||||||
@@ -111,6 +123,22 @@ importers:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
|
'@apidevtools/json-schema-ref-parser@14.0.1':
|
||||||
|
resolution: {integrity: sha512-Oc96zvmxx1fqoSEdUmfmvvb59/KDOnUoJ7s2t7bISyAn0XEz57LCCw8k2Y4Pf3mwKaZLMciESALORLgfe2frCw==}
|
||||||
|
engines: {node: '>= 16'}
|
||||||
|
|
||||||
|
'@apidevtools/openapi-schemas@2.1.0':
|
||||||
|
resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
'@apidevtools/swagger-methods@3.0.2':
|
||||||
|
resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==}
|
||||||
|
|
||||||
|
'@apidevtools/swagger-parser@12.1.0':
|
||||||
|
resolution: {integrity: sha512-e5mJoswsnAX0jG+J09xHFYQXb/bUc5S3pLpMxUuRUA2H8T2kni3yEoyz2R3Dltw5f4A6j6rPNMpWTK+iVDFlng==}
|
||||||
|
peerDependencies:
|
||||||
|
openapi-types: '>=7'
|
||||||
|
|
||||||
'@emnapi/core@1.9.1':
|
'@emnapi/core@1.9.1':
|
||||||
resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==}
|
resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==}
|
||||||
|
|
||||||
@@ -456,6 +484,9 @@ packages:
|
|||||||
'@types/http-errors@2.0.5':
|
'@types/http-errors@2.0.5':
|
||||||
resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==}
|
resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==}
|
||||||
|
|
||||||
|
'@types/json-schema@7.0.15':
|
||||||
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
'@types/jsonwebtoken@9.0.10':
|
'@types/jsonwebtoken@9.0.10':
|
||||||
resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==}
|
resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==}
|
||||||
|
|
||||||
@@ -481,6 +512,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
ajv-draft-04@1.0.0:
|
||||||
|
resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==}
|
||||||
|
peerDependencies:
|
||||||
|
ajv: ^8.5.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
ajv:
|
||||||
|
optional: true
|
||||||
|
|
||||||
ajv-formats@3.0.1:
|
ajv-formats@3.0.1:
|
||||||
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
|
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -492,6 +531,9 @@ packages:
|
|||||||
ajv@8.18.0:
|
ajv@8.18.0:
|
||||||
resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
|
resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
|
||||||
|
|
||||||
|
argparse@2.0.1:
|
||||||
|
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||||
|
|
||||||
bcrypt@6.0.0:
|
bcrypt@6.0.0:
|
||||||
resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==}
|
resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==}
|
||||||
engines: {node: '>= 18'}
|
engines: {node: '>= 18'}
|
||||||
@@ -523,6 +565,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
|
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
call-me-maybe@1.0.2:
|
||||||
|
resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==}
|
||||||
|
|
||||||
chokidar@4.0.3:
|
chokidar@4.0.3:
|
||||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||||
engines: {node: '>= 14.16.0'}
|
engines: {node: '>= 14.16.0'}
|
||||||
@@ -763,6 +808,10 @@ packages:
|
|||||||
jose@6.2.2:
|
jose@6.2.2:
|
||||||
resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==}
|
resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==}
|
||||||
|
|
||||||
|
js-yaml@4.1.1:
|
||||||
|
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
json-schema-traverse@1.0.0:
|
json-schema-traverse@1.0.0:
|
||||||
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
||||||
|
|
||||||
@@ -940,6 +989,9 @@ packages:
|
|||||||
once@1.4.0:
|
once@1.4.0:
|
||||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
|
|
||||||
|
openapi-types@12.1.3:
|
||||||
|
resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
|
||||||
|
|
||||||
parseurl@1.3.3:
|
parseurl@1.3.3:
|
||||||
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -1182,6 +1234,25 @@ packages:
|
|||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
|
'@apidevtools/json-schema-ref-parser@14.0.1':
|
||||||
|
dependencies:
|
||||||
|
'@types/json-schema': 7.0.15
|
||||||
|
js-yaml: 4.1.1
|
||||||
|
|
||||||
|
'@apidevtools/openapi-schemas@2.1.0': {}
|
||||||
|
|
||||||
|
'@apidevtools/swagger-methods@3.0.2': {}
|
||||||
|
|
||||||
|
'@apidevtools/swagger-parser@12.1.0(openapi-types@12.1.3)':
|
||||||
|
dependencies:
|
||||||
|
'@apidevtools/json-schema-ref-parser': 14.0.1
|
||||||
|
'@apidevtools/openapi-schemas': 2.1.0
|
||||||
|
'@apidevtools/swagger-methods': 3.0.2
|
||||||
|
ajv: 8.18.0
|
||||||
|
ajv-draft-04: 1.0.0(ajv@8.18.0)
|
||||||
|
call-me-maybe: 1.0.2
|
||||||
|
openapi-types: 12.1.3
|
||||||
|
|
||||||
'@emnapi/core@1.9.1':
|
'@emnapi/core@1.9.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/wasi-threads': 1.2.0
|
'@emnapi/wasi-threads': 1.2.0
|
||||||
@@ -1437,6 +1508,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/http-errors@2.0.5': {}
|
'@types/http-errors@2.0.5': {}
|
||||||
|
|
||||||
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
'@types/jsonwebtoken@9.0.10':
|
'@types/jsonwebtoken@9.0.10':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/ms': 2.1.0
|
'@types/ms': 2.1.0
|
||||||
@@ -1466,6 +1539,10 @@ snapshots:
|
|||||||
mime-types: 3.0.2
|
mime-types: 3.0.2
|
||||||
negotiator: 1.0.0
|
negotiator: 1.0.0
|
||||||
|
|
||||||
|
ajv-draft-04@1.0.0(ajv@8.18.0):
|
||||||
|
optionalDependencies:
|
||||||
|
ajv: 8.18.0
|
||||||
|
|
||||||
ajv-formats@3.0.1(ajv@8.18.0):
|
ajv-formats@3.0.1(ajv@8.18.0):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
ajv: 8.18.0
|
ajv: 8.18.0
|
||||||
@@ -1477,6 +1554,8 @@ snapshots:
|
|||||||
json-schema-traverse: 1.0.0
|
json-schema-traverse: 1.0.0
|
||||||
require-from-string: 2.0.2
|
require-from-string: 2.0.2
|
||||||
|
|
||||||
|
argparse@2.0.1: {}
|
||||||
|
|
||||||
bcrypt@6.0.0:
|
bcrypt@6.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
node-addon-api: 8.7.0
|
node-addon-api: 8.7.0
|
||||||
@@ -1525,6 +1604,8 @@ snapshots:
|
|||||||
call-bind-apply-helpers: 1.0.2
|
call-bind-apply-helpers: 1.0.2
|
||||||
get-intrinsic: 1.3.0
|
get-intrinsic: 1.3.0
|
||||||
|
|
||||||
|
call-me-maybe@1.0.2: {}
|
||||||
|
|
||||||
chokidar@4.0.3:
|
chokidar@4.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
readdirp: 4.1.2
|
readdirp: 4.1.2
|
||||||
@@ -1781,6 +1862,10 @@ snapshots:
|
|||||||
|
|
||||||
jose@6.2.2: {}
|
jose@6.2.2: {}
|
||||||
|
|
||||||
|
js-yaml@4.1.1:
|
||||||
|
dependencies:
|
||||||
|
argparse: 2.0.1
|
||||||
|
|
||||||
json-schema-traverse@1.0.0: {}
|
json-schema-traverse@1.0.0: {}
|
||||||
|
|
||||||
json-schema-typed@8.0.2: {}
|
json-schema-typed@8.0.2: {}
|
||||||
@@ -1916,6 +2001,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
wrappy: 1.0.2
|
wrappy: 1.0.2
|
||||||
|
|
||||||
|
openapi-types@12.1.3: {}
|
||||||
|
|
||||||
parseurl@1.3.3: {}
|
parseurl@1.3.3: {}
|
||||||
|
|
||||||
path-key@3.1.1: {}
|
path-key@3.1.1: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user