gunsnation-mcp 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/README.md +115 -0
- package/bin/mcp-server-gunsnation.js +3 -0
- package/dist/api/client.d.ts +10 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +79 -0
- package/dist/api/client.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +134 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/get-firearm.d.ts +12 -0
- package/dist/tools/get-firearm.d.ts.map +1 -0
- package/dist/tools/get-firearm.js +84 -0
- package/dist/tools/get-firearm.js.map +1 -0
- package/dist/tools/search-firearms.d.ts +21 -0
- package/dist/tools/search-firearms.d.ts.map +1 -0
- package/dist/tools/search-firearms.js +63 -0
- package/dist/tools/search-firearms.js.map +1 -0
- package/dist/types/index.d.ts +64 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +40 -0
- package/src/api/client.ts +92 -0
- package/src/index.ts +36 -0
- package/src/server.ts +149 -0
- package/src/tools/get-firearm.ts +80 -0
- package/src/tools/search-firearms.ts +77 -0
- package/src/types/index.ts +71 -0
- package/tsconfig.json +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Gunsnation MCP Server
|
|
2
|
+
|
|
3
|
+
The Gunsnation MCP Server is a lightweight integration layer that exposes the Gunsnation firearms catalog to AI assistants through the Model Context Protocol (MCP). It allows compatible AI clients to search, filter, and retrieve detailed firearm information using structured tool calls instead of manual API integration.
|
|
4
|
+
|
|
5
|
+
Designed for speed and simplicity, the server connects directly to the Gunsnation API and provides a clean, standardized interface for querying products by brand, model, UPC, or category. Assistants can also fetch full specifications, images, and metadata for individual firearms, making it ideal for retail, comparison, and product discovery workflows.
|
|
6
|
+
|
|
7
|
+
Built in TypeScript and distributed as an npm package, the server is easy to install and run locally or in hosted environments. With just an API key and an MCP-compatible client, developers can quickly add real-time firearm data access to their AI tools.
|
|
8
|
+
|
|
9
|
+
Key features
|
|
10
|
+
• MCP-compatible firearm search and lookup tools
|
|
11
|
+
• Real-time access to the Gunsnation product catalog
|
|
12
|
+
• Simple installation via npm or npx
|
|
13
|
+
• Lightweight, developer-friendly TypeScript codebase
|
|
14
|
+
• Secure API-key authentication
|
|
15
|
+
|
|
16
|
+
This project is ideal for developers building AI shopping assistants, retail tools, or product discovery experiences that require up-to-date firearm data from Gunsnation.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- **Search Firearms**: Search the firearms database by name, brand, model, UPC, or category
|
|
21
|
+
- **Get Firearm Details**: Retrieve comprehensive details about a specific firearm including specifications and images
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install gunsnation-mcp
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or use directly with npx:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx gunsnation-mcp
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
### Environment Variables
|
|
38
|
+
|
|
39
|
+
- `GUNSNATION_API_KEY` (required): Your Gunsnation API key
|
|
40
|
+
- `GUNSNATION_API_URL` (optional): Custom API URL (defaults to https://api.gunsnation.com)
|
|
41
|
+
|
|
42
|
+
### Claude Desktop Configuration
|
|
43
|
+
|
|
44
|
+
Add to your Claude Desktop config file (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"gunsnation": {
|
|
50
|
+
"command": "npx",
|
|
51
|
+
"args": ["gunsnation-mcp"],
|
|
52
|
+
"env": {
|
|
53
|
+
"GUNSNATION_API_KEY": "your_api_key_here"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Available Tools
|
|
61
|
+
|
|
62
|
+
### search_firearms
|
|
63
|
+
|
|
64
|
+
Search the Gunsnation firearms database.
|
|
65
|
+
|
|
66
|
+
**Parameters:**
|
|
67
|
+
- `query` (optional): Search query for firearm name, brand, model, or UPC
|
|
68
|
+
- `category` (optional): Category filter (e.g., "Handguns", "Rifles", "Shotguns")
|
|
69
|
+
- `limit` (optional): Maximum number of results (1-100, default: 20)
|
|
70
|
+
- `offset` (optional): Number of results to skip for pagination
|
|
71
|
+
|
|
72
|
+
**Example:**
|
|
73
|
+
```
|
|
74
|
+
Search for Glock handguns: { "query": "glock", "category": "Handguns", "limit": 10 }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### get_firearm
|
|
78
|
+
|
|
79
|
+
Get detailed information about a specific firearm.
|
|
80
|
+
|
|
81
|
+
**Parameters:**
|
|
82
|
+
- `id` (required): The ID of the firearm to retrieve
|
|
83
|
+
|
|
84
|
+
**Example:**
|
|
85
|
+
```
|
|
86
|
+
Get firearm details: { "id": 12345 }
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Getting an API Key
|
|
90
|
+
|
|
91
|
+
1. Create an account at [gunsnation.com](https://gunsnation.com)
|
|
92
|
+
2. Go to Settings
|
|
93
|
+
3. Click "Generate API Key" in the API Key section
|
|
94
|
+
4. Copy your API key and keep it secure
|
|
95
|
+
|
|
96
|
+
## Rate Limits
|
|
97
|
+
|
|
98
|
+
- 60 requests per minute per API key
|
|
99
|
+
|
|
100
|
+
## Development
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Install dependencies
|
|
104
|
+
npm install
|
|
105
|
+
|
|
106
|
+
# Build
|
|
107
|
+
npm run build
|
|
108
|
+
|
|
109
|
+
# Run in development mode
|
|
110
|
+
GUNSNATION_API_KEY=your_key npm run dev
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SearchFirearmsParams, SearchFirearmsResponse, GetFirearmParams, GetFirearmResponse, ApiResponse } from '../types';
|
|
2
|
+
export declare class GunsnationApiClient {
|
|
3
|
+
private client;
|
|
4
|
+
private apiKey;
|
|
5
|
+
constructor(apiKey: string, baseUrl?: string);
|
|
6
|
+
private isErrorResponse;
|
|
7
|
+
searchFirearms(params: SearchFirearmsParams): Promise<ApiResponse<SearchFirearmsResponse>>;
|
|
8
|
+
getFirearm(params: GetFirearmParams): Promise<ApiResponse<GetFirearmResponse>>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EAClB,WAAW,EAEZ,MAAM,UAAU,CAAC;AAElB,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;IAY5C,OAAO,CAAC,eAAe;IASjB,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC;IAoC1F,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC;CAoBrF"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.GunsnationApiClient = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
class GunsnationApiClient {
|
|
9
|
+
client;
|
|
10
|
+
apiKey;
|
|
11
|
+
constructor(apiKey, baseUrl) {
|
|
12
|
+
this.apiKey = apiKey;
|
|
13
|
+
this.client = axios_1.default.create({
|
|
14
|
+
baseURL: baseUrl || process.env.GUNSNATION_API_URL || 'https://api.gunsnation.com',
|
|
15
|
+
headers: {
|
|
16
|
+
'X-API-Key': apiKey,
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
},
|
|
19
|
+
timeout: 30000,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
isErrorResponse(response) {
|
|
23
|
+
return (typeof response === 'object' &&
|
|
24
|
+
response !== null &&
|
|
25
|
+
'success' in response &&
|
|
26
|
+
response.success === false);
|
|
27
|
+
}
|
|
28
|
+
async searchFirearms(params) {
|
|
29
|
+
try {
|
|
30
|
+
const queryParams = new URLSearchParams();
|
|
31
|
+
if (params.query) {
|
|
32
|
+
queryParams.append('q', params.query);
|
|
33
|
+
}
|
|
34
|
+
if (params.category) {
|
|
35
|
+
queryParams.append('category', params.category);
|
|
36
|
+
}
|
|
37
|
+
if (params.limit !== undefined) {
|
|
38
|
+
queryParams.append('limit', String(params.limit));
|
|
39
|
+
}
|
|
40
|
+
if (params.offset !== undefined) {
|
|
41
|
+
queryParams.append('offset', String(params.offset));
|
|
42
|
+
}
|
|
43
|
+
const response = await this.client.get(`/public/firearms/search?${queryParams.toString()}`);
|
|
44
|
+
return response.data;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (axios_1.default.isAxiosError(error) && error.response?.data) {
|
|
48
|
+
return error.response.data;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
error: {
|
|
53
|
+
message: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
54
|
+
code: 'API_ERROR',
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async getFirearm(params) {
|
|
60
|
+
try {
|
|
61
|
+
const response = await this.client.get(`/public/firearms/${params.id}`);
|
|
62
|
+
return response.data;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (axios_1.default.isAxiosError(error) && error.response?.data) {
|
|
66
|
+
return error.response.data;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
error: {
|
|
71
|
+
message: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
72
|
+
code: 'API_ERROR',
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.GunsnationApiClient = GunsnationApiClient;
|
|
79
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const server_1 = require("./server");
|
|
5
|
+
async function main() {
|
|
6
|
+
const apiKey = process.env.GUNSNATION_API_KEY;
|
|
7
|
+
if (!apiKey) {
|
|
8
|
+
console.error('Error: GUNSNATION_API_KEY environment variable is required');
|
|
9
|
+
console.error('');
|
|
10
|
+
console.error('Usage:');
|
|
11
|
+
console.error(' GUNSNATION_API_KEY=your_key npx gunsnation-mcp');
|
|
12
|
+
console.error('');
|
|
13
|
+
console.error('Or in Claude Desktop config:');
|
|
14
|
+
console.error(JSON.stringify({
|
|
15
|
+
mcpServers: {
|
|
16
|
+
gunsnation: {
|
|
17
|
+
command: 'npx',
|
|
18
|
+
args: ['gunsnation-mcp'],
|
|
19
|
+
env: { GUNSNATION_API_KEY: 'your_key_here' },
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
}, null, 2));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const apiUrl = process.env.GUNSNATION_API_URL;
|
|
26
|
+
const server = new server_1.GunsnationMcpServer(apiKey, apiUrl);
|
|
27
|
+
await server.run();
|
|
28
|
+
}
|
|
29
|
+
main().catch((error) => {
|
|
30
|
+
console.error('Fatal error:', error);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
});
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAEA,qCAA+C;AAE/C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAE9C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,CAAC,4DAA4D,CAAC,CAAC;QAC5E,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACxB,OAAO,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;QAClE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAC9C,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC;YAC3B,UAAU,EAAE;gBACV,UAAU,EAAE;oBACV,OAAO,EAAE,KAAK;oBACd,IAAI,EAAE,CAAC,gBAAgB,CAAC;oBACxB,GAAG,EAAE,EAAE,kBAAkB,EAAE,eAAe,EAAE;iBAC7C;aACF;SACF,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAE9C,MAAM,MAAM,GAAG,IAAI,4BAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvD,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC;AACrB,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAYA,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAsB;gBAE3B,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;IAkB3C,OAAO,CAAC,aAAa;IA6Gf,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAK3B"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GunsnationMcpServer = void 0;
|
|
4
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
5
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
7
|
+
const client_1 = require("./api/client");
|
|
8
|
+
const search_firearms_1 = require("./tools/search-firearms");
|
|
9
|
+
const get_firearm_1 = require("./tools/get-firearm");
|
|
10
|
+
class GunsnationMcpServer {
|
|
11
|
+
server;
|
|
12
|
+
apiClient;
|
|
13
|
+
constructor(apiKey, apiUrl) {
|
|
14
|
+
this.server = new index_js_1.Server({
|
|
15
|
+
name: 'gunsnation-mcp',
|
|
16
|
+
version: '1.0.0',
|
|
17
|
+
}, {
|
|
18
|
+
capabilities: {
|
|
19
|
+
tools: {},
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
this.apiClient = new client_1.GunsnationApiClient(apiKey, apiUrl);
|
|
23
|
+
this.setupHandlers();
|
|
24
|
+
}
|
|
25
|
+
setupHandlers() {
|
|
26
|
+
// List available tools
|
|
27
|
+
this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
28
|
+
return {
|
|
29
|
+
tools: [
|
|
30
|
+
{
|
|
31
|
+
name: 'search_firearms',
|
|
32
|
+
description: 'Search the Gunsnation firearms database. Returns a list of firearms matching the search criteria with basic details including name, brand, caliber, action type, and price.',
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
query: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
description: 'Search query for firearm name, brand, model, or UPC',
|
|
39
|
+
},
|
|
40
|
+
category: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
description: 'Category filter (e.g., "Handguns", "Rifles", "Shotguns")',
|
|
43
|
+
},
|
|
44
|
+
limit: {
|
|
45
|
+
type: 'number',
|
|
46
|
+
description: 'Maximum number of results (1-100, default: 20)',
|
|
47
|
+
minimum: 1,
|
|
48
|
+
maximum: 100,
|
|
49
|
+
default: 20,
|
|
50
|
+
},
|
|
51
|
+
offset: {
|
|
52
|
+
type: 'number',
|
|
53
|
+
description: 'Number of results to skip for pagination',
|
|
54
|
+
minimum: 0,
|
|
55
|
+
default: 0,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'get_firearm',
|
|
62
|
+
description: 'Get detailed information about a specific firearm by its ID. Returns comprehensive details including specifications, description, and images.',
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {
|
|
66
|
+
id: {
|
|
67
|
+
type: ['string', 'number'],
|
|
68
|
+
description: 'The ID of the firearm to retrieve',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
required: ['id'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
// Handle tool calls
|
|
78
|
+
this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
79
|
+
const { name, arguments: args } = request.params;
|
|
80
|
+
try {
|
|
81
|
+
switch (name) {
|
|
82
|
+
case 'search_firearms': {
|
|
83
|
+
const input = search_firearms_1.searchFirearmsSchema.parse(args);
|
|
84
|
+
const result = await (0, search_firearms_1.searchFirearms)(this.apiClient, input);
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: 'text',
|
|
89
|
+
text: result,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
case 'get_firearm': {
|
|
95
|
+
const input = get_firearm_1.getFirearmSchema.parse(args);
|
|
96
|
+
const result = await (0, get_firearm_1.getFirearm)(this.apiClient, input);
|
|
97
|
+
return {
|
|
98
|
+
content: [
|
|
99
|
+
{
|
|
100
|
+
type: 'text',
|
|
101
|
+
text: result,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
default:
|
|
107
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (error instanceof types_js_1.McpError) {
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
const message = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
115
|
+
return {
|
|
116
|
+
content: [
|
|
117
|
+
{
|
|
118
|
+
type: 'text',
|
|
119
|
+
text: `Error: ${message}`,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
isError: true,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
async run() {
|
|
128
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
129
|
+
await this.server.connect(transport);
|
|
130
|
+
console.error('Gunsnation MCP server running on stdio');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.GunsnationMcpServer = GunsnationMcpServer;
|
|
134
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { GunsnationApiClient } from '../api/client';
|
|
3
|
+
export declare const getFirearmSchema: z.ZodObject<{
|
|
4
|
+
id: z.ZodUnion<[z.ZodString, z.ZodNumber]>;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
id: string | number;
|
|
7
|
+
}, {
|
|
8
|
+
id: string | number;
|
|
9
|
+
}>;
|
|
10
|
+
export type GetFirearmInput = z.infer<typeof getFirearmSchema>;
|
|
11
|
+
export declare function getFirearm(client: GunsnationApiClient, input: GetFirearmInput): Promise<string>;
|
|
12
|
+
//# sourceMappingURL=get-firearm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-firearm.d.ts","sourceRoot":"","sources":["../../src/tools/get-firearm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAGpD,eAAO,MAAM,gBAAgB;;;;;;EAE3B,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AA0D/D,wBAAsB,UAAU,CAC9B,MAAM,EAAE,mBAAmB,EAC3B,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,MAAM,CAAC,CAUjB"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getFirearmSchema = void 0;
|
|
4
|
+
exports.getFirearm = getFirearm;
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
exports.getFirearmSchema = zod_1.z.object({
|
|
7
|
+
id: zod_1.z.union([zod_1.z.string(), zod_1.z.number()]).describe('The ID of the firearm to retrieve'),
|
|
8
|
+
});
|
|
9
|
+
function formatDetailedFirearm(firearm) {
|
|
10
|
+
const lines = [];
|
|
11
|
+
lines.push(`# ${firearm.name}`);
|
|
12
|
+
lines.push('');
|
|
13
|
+
// Basic Info
|
|
14
|
+
lines.push('## Basic Information');
|
|
15
|
+
lines.push(`- **ID**: ${firearm.id}`);
|
|
16
|
+
if (firearm.brand)
|
|
17
|
+
lines.push(`- **Brand**: ${firearm.brand}`);
|
|
18
|
+
if (firearm.caliber)
|
|
19
|
+
lines.push(`- **Caliber**: ${firearm.caliber}`);
|
|
20
|
+
if (firearm.action)
|
|
21
|
+
lines.push(`- **Action**: ${firearm.action}`);
|
|
22
|
+
if (firearm.category)
|
|
23
|
+
lines.push(`- **Category**: ${firearm.category}`);
|
|
24
|
+
if (firearm.upc)
|
|
25
|
+
lines.push(`- **UPC**: ${firearm.upc}`);
|
|
26
|
+
if (firearm.price !== null)
|
|
27
|
+
lines.push(`- **Price**: $${firearm.price.toFixed(2)}`);
|
|
28
|
+
lines.push('');
|
|
29
|
+
// Specifications
|
|
30
|
+
const specs = firearm.specifications;
|
|
31
|
+
const hasSpecs = Object.values(specs).some(v => v !== null && v !== undefined);
|
|
32
|
+
if (hasSpecs) {
|
|
33
|
+
lines.push('## Specifications');
|
|
34
|
+
if (specs.barrelLength)
|
|
35
|
+
lines.push(`- **Barrel Length**: ${specs.barrelLength}"`);
|
|
36
|
+
if (specs.overallLength)
|
|
37
|
+
lines.push(`- **Overall Length**: ${specs.overallLength}"`);
|
|
38
|
+
if (specs.weight)
|
|
39
|
+
lines.push(`- **Weight**: ${specs.weight} oz`);
|
|
40
|
+
if (specs.capacity)
|
|
41
|
+
lines.push(`- **Capacity**: ${specs.capacity}`);
|
|
42
|
+
if (specs.material)
|
|
43
|
+
lines.push(`- **Material**: ${specs.material}`);
|
|
44
|
+
if (specs.finish)
|
|
45
|
+
lines.push(`- **Finish**: ${specs.finish}`);
|
|
46
|
+
if (specs.sightType)
|
|
47
|
+
lines.push(`- **Sight Type**: ${specs.sightType}`);
|
|
48
|
+
if (specs.safetyFeatures)
|
|
49
|
+
lines.push(`- **Safety Features**: ${specs.safetyFeatures}`);
|
|
50
|
+
if (specs.frameSize)
|
|
51
|
+
lines.push(`- **Frame Size**: ${specs.frameSize}`);
|
|
52
|
+
if (specs.stockMaterial)
|
|
53
|
+
lines.push(`- **Stock Material**: ${specs.stockMaterial}`);
|
|
54
|
+
if (specs.stockType)
|
|
55
|
+
lines.push(`- **Stock Type**: ${specs.stockType}`);
|
|
56
|
+
if (specs.magazinesIncluded)
|
|
57
|
+
lines.push(`- **Magazines Included**: ${specs.magazinesIncluded}`);
|
|
58
|
+
lines.push('');
|
|
59
|
+
}
|
|
60
|
+
// Description
|
|
61
|
+
if (firearm.description) {
|
|
62
|
+
lines.push('## Description');
|
|
63
|
+
lines.push(firearm.description);
|
|
64
|
+
lines.push('');
|
|
65
|
+
}
|
|
66
|
+
// Images
|
|
67
|
+
if (firearm.images.length > 0) {
|
|
68
|
+
lines.push('## Images');
|
|
69
|
+
firearm.images.forEach((img, index) => {
|
|
70
|
+
lines.push(`- Image ${index + 1}: ${img.original}`);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return lines.join('\n');
|
|
74
|
+
}
|
|
75
|
+
async function getFirearm(client, input) {
|
|
76
|
+
const response = await client.getFirearm({
|
|
77
|
+
id: input.id,
|
|
78
|
+
});
|
|
79
|
+
if (!response.success) {
|
|
80
|
+
return `Error: ${response.error.message} (${response.error.code})`;
|
|
81
|
+
}
|
|
82
|
+
return formatDetailedFirearm(response.data);
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=get-firearm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-firearm.js","sourceRoot":"","sources":["../../src/tools/get-firearm.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { GunsnationApiClient } from '../api/client';
|
|
3
|
+
export declare const searchFirearmsSchema: z.ZodObject<{
|
|
4
|
+
query: z.ZodOptional<z.ZodString>;
|
|
5
|
+
category: z.ZodOptional<z.ZodString>;
|
|
6
|
+
limit: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
7
|
+
offset: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
8
|
+
}, "strip", z.ZodTypeAny, {
|
|
9
|
+
limit: number;
|
|
10
|
+
offset: number;
|
|
11
|
+
category?: string | undefined;
|
|
12
|
+
query?: string | undefined;
|
|
13
|
+
}, {
|
|
14
|
+
category?: string | undefined;
|
|
15
|
+
limit?: number | undefined;
|
|
16
|
+
offset?: number | undefined;
|
|
17
|
+
query?: string | undefined;
|
|
18
|
+
}>;
|
|
19
|
+
export type SearchFirearmsInput = z.infer<typeof searchFirearmsSchema>;
|
|
20
|
+
export declare function searchFirearms(client: GunsnationApiClient, input: SearchFirearmsInput): Promise<string>;
|
|
21
|
+
//# sourceMappingURL=search-firearms.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-firearms.d.ts","sourceRoot":"","sources":["../../src/tools/search-firearms.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAGpD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;EAK/B,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAiCvE,wBAAsB,cAAc,CAClC,MAAM,EAAE,mBAAmB,EAC3B,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,MAAM,CAAC,CA6BjB"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.searchFirearmsSchema = void 0;
|
|
4
|
+
exports.searchFirearms = searchFirearms;
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
exports.searchFirearmsSchema = zod_1.z.object({
|
|
7
|
+
query: zod_1.z.string().optional().describe('Search query for firearm name, brand, model, or UPC'),
|
|
8
|
+
category: zod_1.z.string().optional().describe('Category filter (e.g., "Handguns", "Rifles", "Shotguns")'),
|
|
9
|
+
limit: zod_1.z.number().min(1).max(100).optional().default(20).describe('Maximum number of results (1-100, default: 20)'),
|
|
10
|
+
offset: zod_1.z.number().min(0).optional().default(0).describe('Number of results to skip for pagination'),
|
|
11
|
+
});
|
|
12
|
+
function formatFirearmForDisplay(firearm) {
|
|
13
|
+
const lines = [];
|
|
14
|
+
lines.push(`**${firearm.name}**`);
|
|
15
|
+
lines.push(`- ID: ${firearm.id}`);
|
|
16
|
+
if (firearm.brand) {
|
|
17
|
+
lines.push(`- Brand: ${firearm.brand}`);
|
|
18
|
+
}
|
|
19
|
+
if (firearm.caliber) {
|
|
20
|
+
lines.push(`- Caliber: ${firearm.caliber}`);
|
|
21
|
+
}
|
|
22
|
+
if (firearm.action) {
|
|
23
|
+
lines.push(`- Action: ${firearm.action}`);
|
|
24
|
+
}
|
|
25
|
+
if (firearm.price !== null) {
|
|
26
|
+
lines.push(`- Price: $${firearm.price.toFixed(2)}`);
|
|
27
|
+
}
|
|
28
|
+
if (firearm.specifications.barrelLength) {
|
|
29
|
+
lines.push(`- Barrel Length: ${firearm.specifications.barrelLength}"`);
|
|
30
|
+
}
|
|
31
|
+
if (firearm.specifications.weight) {
|
|
32
|
+
lines.push(`- Weight: ${firearm.specifications.weight} oz`);
|
|
33
|
+
}
|
|
34
|
+
if (firearm.images.length > 0) {
|
|
35
|
+
lines.push(`- Image: ${firearm.images[0].original}`);
|
|
36
|
+
}
|
|
37
|
+
return lines.join('\n');
|
|
38
|
+
}
|
|
39
|
+
async function searchFirearms(client, input) {
|
|
40
|
+
const response = await client.searchFirearms({
|
|
41
|
+
query: input.query,
|
|
42
|
+
category: input.category,
|
|
43
|
+
limit: input.limit,
|
|
44
|
+
offset: input.offset,
|
|
45
|
+
});
|
|
46
|
+
if (!response.success) {
|
|
47
|
+
return `Error: ${response.error.message} (${response.error.code})`;
|
|
48
|
+
}
|
|
49
|
+
if (response.data.length === 0) {
|
|
50
|
+
return 'No firearms found matching your search criteria.';
|
|
51
|
+
}
|
|
52
|
+
const results = [];
|
|
53
|
+
results.push(`Found ${response.meta.total} firearms (showing ${response.data.length}):\n`);
|
|
54
|
+
for (const firearm of response.data) {
|
|
55
|
+
results.push(formatFirearmForDisplay(firearm));
|
|
56
|
+
results.push(''); // Empty line between results
|
|
57
|
+
}
|
|
58
|
+
if (response.meta.offset + response.data.length < response.meta.total) {
|
|
59
|
+
results.push(`\n---\nShowing ${response.meta.offset + 1}-${response.meta.offset + response.data.length} of ${response.meta.total}. Use offset parameter to see more results.`);
|
|
60
|
+
}
|
|
61
|
+
return results.join('\n');
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=search-firearms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-firearms.js","sourceRoot":"","sources":["../../src/tools/search-firearms.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export interface FirearmImage {
|
|
2
|
+
original: string;
|
|
3
|
+
thumbnail?: string;
|
|
4
|
+
medium?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface FirearmSpecifications {
|
|
7
|
+
weight: string | null;
|
|
8
|
+
barrelLength: string | null;
|
|
9
|
+
overallLength?: string | null;
|
|
10
|
+
material?: string | null;
|
|
11
|
+
finish?: string | null;
|
|
12
|
+
sightType?: string | null;
|
|
13
|
+
safetyFeatures?: string | null;
|
|
14
|
+
frameSize?: string | null;
|
|
15
|
+
stockMaterial?: string | null;
|
|
16
|
+
stockType?: string | null;
|
|
17
|
+
magazinesIncluded?: number | null;
|
|
18
|
+
capacity?: string | null;
|
|
19
|
+
}
|
|
20
|
+
export interface Firearm {
|
|
21
|
+
id: number;
|
|
22
|
+
name: string;
|
|
23
|
+
brand: string | null;
|
|
24
|
+
caliber: string | null;
|
|
25
|
+
action: string | null;
|
|
26
|
+
price: number | null;
|
|
27
|
+
images: FirearmImage[];
|
|
28
|
+
specifications: FirearmSpecifications;
|
|
29
|
+
upc?: string;
|
|
30
|
+
productHash?: string;
|
|
31
|
+
category?: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface SearchFirearmsResponse {
|
|
35
|
+
success: true;
|
|
36
|
+
data: Firearm[];
|
|
37
|
+
meta: {
|
|
38
|
+
total: number;
|
|
39
|
+
limit: number;
|
|
40
|
+
offset: number;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface GetFirearmResponse {
|
|
44
|
+
success: true;
|
|
45
|
+
data: Firearm;
|
|
46
|
+
}
|
|
47
|
+
export interface ApiErrorResponse {
|
|
48
|
+
success: false;
|
|
49
|
+
error: {
|
|
50
|
+
message: string;
|
|
51
|
+
code: string;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export type ApiResponse<T> = T | ApiErrorResponse;
|
|
55
|
+
export interface SearchFirearmsParams {
|
|
56
|
+
query?: string;
|
|
57
|
+
category?: string;
|
|
58
|
+
limit?: number;
|
|
59
|
+
offset?: number;
|
|
60
|
+
}
|
|
61
|
+
export interface GetFirearmParams {
|
|
62
|
+
id: string | number;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,cAAc,EAAE,qBAAqB,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,IAAI,CAAC;IACd,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,IAAI,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE;QACL,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,gBAAgB,CAAC;AAElD,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;CACrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gunsnation-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Gunsnation firearms database API",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"gunsnation-mcp": "./bin/mcp-server-gunsnation.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "node dist/index.js",
|
|
12
|
+
"dev": "ts-node src/index.ts",
|
|
13
|
+
"prepare": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"model-context-protocol",
|
|
18
|
+
"gunsnation",
|
|
19
|
+
"firearms",
|
|
20
|
+
"api"
|
|
21
|
+
],
|
|
22
|
+
"author": "",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
26
|
+
"axios": "^1.7.2",
|
|
27
|
+
"zod": "^3.23.8"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/debug": "^4.1.13",
|
|
31
|
+
"@types/ms": "^2.1.0",
|
|
32
|
+
"@types/node": "^20.14.10",
|
|
33
|
+
"ts-node": "^10.9.2",
|
|
34
|
+
"typescript": "^5.5.3"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18.0.0"
|
|
38
|
+
},
|
|
39
|
+
"mcpName": "io.github.DynamicDeals/gunsnation"
|
|
40
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import axios, { AxiosInstance } from 'axios';
|
|
2
|
+
import {
|
|
3
|
+
SearchFirearmsParams,
|
|
4
|
+
SearchFirearmsResponse,
|
|
5
|
+
GetFirearmParams,
|
|
6
|
+
GetFirearmResponse,
|
|
7
|
+
ApiResponse,
|
|
8
|
+
ApiErrorResponse,
|
|
9
|
+
} from '../types';
|
|
10
|
+
|
|
11
|
+
export class GunsnationApiClient {
|
|
12
|
+
private client: AxiosInstance;
|
|
13
|
+
private apiKey: string;
|
|
14
|
+
|
|
15
|
+
constructor(apiKey: string, baseUrl?: string) {
|
|
16
|
+
this.apiKey = apiKey;
|
|
17
|
+
this.client = axios.create({
|
|
18
|
+
baseURL: baseUrl || process.env.GUNSNATION_API_URL || 'https://api.gunsnation.com',
|
|
19
|
+
headers: {
|
|
20
|
+
'X-API-Key': apiKey,
|
|
21
|
+
'Content-Type': 'application/json',
|
|
22
|
+
},
|
|
23
|
+
timeout: 30000,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private isErrorResponse(response: unknown): response is ApiErrorResponse {
|
|
28
|
+
return (
|
|
29
|
+
typeof response === 'object' &&
|
|
30
|
+
response !== null &&
|
|
31
|
+
'success' in response &&
|
|
32
|
+
(response as ApiErrorResponse).success === false
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async searchFirearms(params: SearchFirearmsParams): Promise<ApiResponse<SearchFirearmsResponse>> {
|
|
37
|
+
try {
|
|
38
|
+
const queryParams = new URLSearchParams();
|
|
39
|
+
|
|
40
|
+
if (params.query) {
|
|
41
|
+
queryParams.append('q', params.query);
|
|
42
|
+
}
|
|
43
|
+
if (params.category) {
|
|
44
|
+
queryParams.append('category', params.category);
|
|
45
|
+
}
|
|
46
|
+
if (params.limit !== undefined) {
|
|
47
|
+
queryParams.append('limit', String(params.limit));
|
|
48
|
+
}
|
|
49
|
+
if (params.offset !== undefined) {
|
|
50
|
+
queryParams.append('offset', String(params.offset));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const response = await this.client.get<SearchFirearmsResponse>(
|
|
54
|
+
`/public/firearms/search?${queryParams.toString()}`
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return response.data;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (axios.isAxiosError(error) && error.response?.data) {
|
|
60
|
+
return error.response.data as ApiErrorResponse;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
error: {
|
|
65
|
+
message: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
66
|
+
code: 'API_ERROR',
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async getFirearm(params: GetFirearmParams): Promise<ApiResponse<GetFirearmResponse>> {
|
|
73
|
+
try {
|
|
74
|
+
const response = await this.client.get<GetFirearmResponse>(
|
|
75
|
+
`/public/firearms/${params.id}`
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return response.data;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (axios.isAxiosError(error) && error.response?.data) {
|
|
81
|
+
return error.response.data as ApiErrorResponse;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
error: {
|
|
86
|
+
message: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
87
|
+
code: 'API_ERROR',
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { GunsnationMcpServer } from './server';
|
|
4
|
+
|
|
5
|
+
async function main() {
|
|
6
|
+
const apiKey = process.env.GUNSNATION_API_KEY;
|
|
7
|
+
|
|
8
|
+
if (!apiKey) {
|
|
9
|
+
console.error('Error: GUNSNATION_API_KEY environment variable is required');
|
|
10
|
+
console.error('');
|
|
11
|
+
console.error('Usage:');
|
|
12
|
+
console.error(' GUNSNATION_API_KEY=your_key npx gunsnation-mcp');
|
|
13
|
+
console.error('');
|
|
14
|
+
console.error('Or in Claude Desktop config:');
|
|
15
|
+
console.error(JSON.stringify({
|
|
16
|
+
mcpServers: {
|
|
17
|
+
gunsnation: {
|
|
18
|
+
command: 'npx',
|
|
19
|
+
args: ['gunsnation-mcp'],
|
|
20
|
+
env: { GUNSNATION_API_KEY: 'your_key_here' },
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
}, null, 2));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const apiUrl = process.env.GUNSNATION_API_URL;
|
|
28
|
+
|
|
29
|
+
const server = new GunsnationMcpServer(apiKey, apiUrl);
|
|
30
|
+
await server.run();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
main().catch((error) => {
|
|
34
|
+
console.error('Fatal error:', error);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import {
|
|
4
|
+
CallToolRequestSchema,
|
|
5
|
+
ListToolsRequestSchema,
|
|
6
|
+
ErrorCode,
|
|
7
|
+
McpError,
|
|
8
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
import { GunsnationApiClient } from './api/client';
|
|
10
|
+
import { searchFirearms, searchFirearmsSchema } from './tools/search-firearms';
|
|
11
|
+
import { getFirearm, getFirearmSchema } from './tools/get-firearm';
|
|
12
|
+
|
|
13
|
+
export class GunsnationMcpServer {
|
|
14
|
+
private server: Server;
|
|
15
|
+
private apiClient: GunsnationApiClient;
|
|
16
|
+
|
|
17
|
+
constructor(apiKey: string, apiUrl?: string) {
|
|
18
|
+
this.server = new Server(
|
|
19
|
+
{
|
|
20
|
+
name: 'gunsnation-mcp',
|
|
21
|
+
version: '1.0.0',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
capabilities: {
|
|
25
|
+
tools: {},
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
this.apiClient = new GunsnationApiClient(apiKey, apiUrl);
|
|
31
|
+
|
|
32
|
+
this.setupHandlers();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private setupHandlers(): void {
|
|
36
|
+
// List available tools
|
|
37
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
38
|
+
return {
|
|
39
|
+
tools: [
|
|
40
|
+
{
|
|
41
|
+
name: 'search_firearms',
|
|
42
|
+
description:
|
|
43
|
+
'Search the Gunsnation firearms database. Returns a list of firearms matching the search criteria with basic details including name, brand, caliber, action type, and price.',
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: 'object',
|
|
46
|
+
properties: {
|
|
47
|
+
query: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
description: 'Search query for firearm name, brand, model, or UPC',
|
|
50
|
+
},
|
|
51
|
+
category: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'Category filter (e.g., "Handguns", "Rifles", "Shotguns")',
|
|
54
|
+
},
|
|
55
|
+
limit: {
|
|
56
|
+
type: 'number',
|
|
57
|
+
description: 'Maximum number of results (1-100, default: 20)',
|
|
58
|
+
minimum: 1,
|
|
59
|
+
maximum: 100,
|
|
60
|
+
default: 20,
|
|
61
|
+
},
|
|
62
|
+
offset: {
|
|
63
|
+
type: 'number',
|
|
64
|
+
description: 'Number of results to skip for pagination',
|
|
65
|
+
minimum: 0,
|
|
66
|
+
default: 0,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'get_firearm',
|
|
73
|
+
description:
|
|
74
|
+
'Get detailed information about a specific firearm by its ID. Returns comprehensive details including specifications, description, and images.',
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: 'object',
|
|
77
|
+
properties: {
|
|
78
|
+
id: {
|
|
79
|
+
type: ['string', 'number'],
|
|
80
|
+
description: 'The ID of the firearm to retrieve',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
required: ['id'],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Handle tool calls
|
|
91
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
92
|
+
const { name, arguments: args } = request.params;
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
switch (name) {
|
|
96
|
+
case 'search_firearms': {
|
|
97
|
+
const input = searchFirearmsSchema.parse(args);
|
|
98
|
+
const result = await searchFirearms(this.apiClient, input);
|
|
99
|
+
return {
|
|
100
|
+
content: [
|
|
101
|
+
{
|
|
102
|
+
type: 'text',
|
|
103
|
+
text: result,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
case 'get_firearm': {
|
|
110
|
+
const input = getFirearmSchema.parse(args);
|
|
111
|
+
const result = await getFirearm(this.apiClient, input);
|
|
112
|
+
return {
|
|
113
|
+
content: [
|
|
114
|
+
{
|
|
115
|
+
type: 'text',
|
|
116
|
+
text: result,
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
default:
|
|
123
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
124
|
+
}
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (error instanceof McpError) {
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const message = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
131
|
+
return {
|
|
132
|
+
content: [
|
|
133
|
+
{
|
|
134
|
+
type: 'text',
|
|
135
|
+
text: `Error: ${message}`,
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
isError: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async run(): Promise<void> {
|
|
145
|
+
const transport = new StdioServerTransport();
|
|
146
|
+
await this.server.connect(transport);
|
|
147
|
+
console.error('Gunsnation MCP server running on stdio');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { GunsnationApiClient } from '../api/client';
|
|
3
|
+
import { Firearm } from '../types';
|
|
4
|
+
|
|
5
|
+
export const getFirearmSchema = z.object({
|
|
6
|
+
id: z.union([z.string(), z.number()]).describe('The ID of the firearm to retrieve'),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export type GetFirearmInput = z.infer<typeof getFirearmSchema>;
|
|
10
|
+
|
|
11
|
+
function formatDetailedFirearm(firearm: Firearm): string {
|
|
12
|
+
const lines: string[] = [];
|
|
13
|
+
|
|
14
|
+
lines.push(`# ${firearm.name}`);
|
|
15
|
+
lines.push('');
|
|
16
|
+
|
|
17
|
+
// Basic Info
|
|
18
|
+
lines.push('## Basic Information');
|
|
19
|
+
lines.push(`- **ID**: ${firearm.id}`);
|
|
20
|
+
if (firearm.brand) lines.push(`- **Brand**: ${firearm.brand}`);
|
|
21
|
+
if (firearm.caliber) lines.push(`- **Caliber**: ${firearm.caliber}`);
|
|
22
|
+
if (firearm.action) lines.push(`- **Action**: ${firearm.action}`);
|
|
23
|
+
if (firearm.category) lines.push(`- **Category**: ${firearm.category}`);
|
|
24
|
+
if (firearm.upc) lines.push(`- **UPC**: ${firearm.upc}`);
|
|
25
|
+
if (firearm.price !== null) lines.push(`- **Price**: $${firearm.price.toFixed(2)}`);
|
|
26
|
+
lines.push('');
|
|
27
|
+
|
|
28
|
+
// Specifications
|
|
29
|
+
const specs = firearm.specifications;
|
|
30
|
+
const hasSpecs = Object.values(specs).some(v => v !== null && v !== undefined);
|
|
31
|
+
|
|
32
|
+
if (hasSpecs) {
|
|
33
|
+
lines.push('## Specifications');
|
|
34
|
+
if (specs.barrelLength) lines.push(`- **Barrel Length**: ${specs.barrelLength}"`);
|
|
35
|
+
if (specs.overallLength) lines.push(`- **Overall Length**: ${specs.overallLength}"`);
|
|
36
|
+
if (specs.weight) lines.push(`- **Weight**: ${specs.weight} oz`);
|
|
37
|
+
if (specs.capacity) lines.push(`- **Capacity**: ${specs.capacity}`);
|
|
38
|
+
if (specs.material) lines.push(`- **Material**: ${specs.material}`);
|
|
39
|
+
if (specs.finish) lines.push(`- **Finish**: ${specs.finish}`);
|
|
40
|
+
if (specs.sightType) lines.push(`- **Sight Type**: ${specs.sightType}`);
|
|
41
|
+
if (specs.safetyFeatures) lines.push(`- **Safety Features**: ${specs.safetyFeatures}`);
|
|
42
|
+
if (specs.frameSize) lines.push(`- **Frame Size**: ${specs.frameSize}`);
|
|
43
|
+
if (specs.stockMaterial) lines.push(`- **Stock Material**: ${specs.stockMaterial}`);
|
|
44
|
+
if (specs.stockType) lines.push(`- **Stock Type**: ${specs.stockType}`);
|
|
45
|
+
if (specs.magazinesIncluded) lines.push(`- **Magazines Included**: ${specs.magazinesIncluded}`);
|
|
46
|
+
lines.push('');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Description
|
|
50
|
+
if (firearm.description) {
|
|
51
|
+
lines.push('## Description');
|
|
52
|
+
lines.push(firearm.description);
|
|
53
|
+
lines.push('');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Images
|
|
57
|
+
if (firearm.images.length > 0) {
|
|
58
|
+
lines.push('## Images');
|
|
59
|
+
firearm.images.forEach((img, index) => {
|
|
60
|
+
lines.push(`- Image ${index + 1}: ${img.original}`);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return lines.join('\n');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function getFirearm(
|
|
68
|
+
client: GunsnationApiClient,
|
|
69
|
+
input: GetFirearmInput
|
|
70
|
+
): Promise<string> {
|
|
71
|
+
const response = await client.getFirearm({
|
|
72
|
+
id: input.id,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!response.success) {
|
|
76
|
+
return `Error: ${response.error.message} (${response.error.code})`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return formatDetailedFirearm(response.data);
|
|
80
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { GunsnationApiClient } from '../api/client';
|
|
3
|
+
import { Firearm } from '../types';
|
|
4
|
+
|
|
5
|
+
export const searchFirearmsSchema = z.object({
|
|
6
|
+
query: z.string().optional().describe('Search query for firearm name, brand, model, or UPC'),
|
|
7
|
+
category: z.string().optional().describe('Category filter (e.g., "Handguns", "Rifles", "Shotguns")'),
|
|
8
|
+
limit: z.number().min(1).max(100).optional().default(20).describe('Maximum number of results (1-100, default: 20)'),
|
|
9
|
+
offset: z.number().min(0).optional().default(0).describe('Number of results to skip for pagination'),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export type SearchFirearmsInput = z.infer<typeof searchFirearmsSchema>;
|
|
13
|
+
|
|
14
|
+
function formatFirearmForDisplay(firearm: Firearm): string {
|
|
15
|
+
const lines: string[] = [];
|
|
16
|
+
|
|
17
|
+
lines.push(`**${firearm.name}**`);
|
|
18
|
+
lines.push(`- ID: ${firearm.id}`);
|
|
19
|
+
|
|
20
|
+
if (firearm.brand) {
|
|
21
|
+
lines.push(`- Brand: ${firearm.brand}`);
|
|
22
|
+
}
|
|
23
|
+
if (firearm.caliber) {
|
|
24
|
+
lines.push(`- Caliber: ${firearm.caliber}`);
|
|
25
|
+
}
|
|
26
|
+
if (firearm.action) {
|
|
27
|
+
lines.push(`- Action: ${firearm.action}`);
|
|
28
|
+
}
|
|
29
|
+
if (firearm.price !== null) {
|
|
30
|
+
lines.push(`- Price: $${firearm.price.toFixed(2)}`);
|
|
31
|
+
}
|
|
32
|
+
if (firearm.specifications.barrelLength) {
|
|
33
|
+
lines.push(`- Barrel Length: ${firearm.specifications.barrelLength}"`);
|
|
34
|
+
}
|
|
35
|
+
if (firearm.specifications.weight) {
|
|
36
|
+
lines.push(`- Weight: ${firearm.specifications.weight} oz`);
|
|
37
|
+
}
|
|
38
|
+
if (firearm.images.length > 0) {
|
|
39
|
+
lines.push(`- Image: ${firearm.images[0].original}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return lines.join('\n');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function searchFirearms(
|
|
46
|
+
client: GunsnationApiClient,
|
|
47
|
+
input: SearchFirearmsInput
|
|
48
|
+
): Promise<string> {
|
|
49
|
+
const response = await client.searchFirearms({
|
|
50
|
+
query: input.query,
|
|
51
|
+
category: input.category,
|
|
52
|
+
limit: input.limit,
|
|
53
|
+
offset: input.offset,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (!response.success) {
|
|
57
|
+
return `Error: ${response.error.message} (${response.error.code})`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (response.data.length === 0) {
|
|
61
|
+
return 'No firearms found matching your search criteria.';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const results: string[] = [];
|
|
65
|
+
results.push(`Found ${response.meta.total} firearms (showing ${response.data.length}):\n`);
|
|
66
|
+
|
|
67
|
+
for (const firearm of response.data) {
|
|
68
|
+
results.push(formatFirearmForDisplay(firearm));
|
|
69
|
+
results.push(''); // Empty line between results
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (response.meta.offset + response.data.length < response.meta.total) {
|
|
73
|
+
results.push(`\n---\nShowing ${response.meta.offset + 1}-${response.meta.offset + response.data.length} of ${response.meta.total}. Use offset parameter to see more results.`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return results.join('\n');
|
|
77
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export interface FirearmImage {
|
|
2
|
+
original: string;
|
|
3
|
+
thumbnail?: string;
|
|
4
|
+
medium?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface FirearmSpecifications {
|
|
8
|
+
weight: string | null;
|
|
9
|
+
barrelLength: string | null;
|
|
10
|
+
overallLength?: string | null;
|
|
11
|
+
material?: string | null;
|
|
12
|
+
finish?: string | null;
|
|
13
|
+
sightType?: string | null;
|
|
14
|
+
safetyFeatures?: string | null;
|
|
15
|
+
frameSize?: string | null;
|
|
16
|
+
stockMaterial?: string | null;
|
|
17
|
+
stockType?: string | null;
|
|
18
|
+
magazinesIncluded?: number | null;
|
|
19
|
+
capacity?: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Firearm {
|
|
23
|
+
id: number;
|
|
24
|
+
name: string;
|
|
25
|
+
brand: string | null;
|
|
26
|
+
caliber: string | null;
|
|
27
|
+
action: string | null;
|
|
28
|
+
price: number | null;
|
|
29
|
+
images: FirearmImage[];
|
|
30
|
+
specifications: FirearmSpecifications;
|
|
31
|
+
upc?: string;
|
|
32
|
+
productHash?: string;
|
|
33
|
+
category?: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface SearchFirearmsResponse {
|
|
38
|
+
success: true;
|
|
39
|
+
data: Firearm[];
|
|
40
|
+
meta: {
|
|
41
|
+
total: number;
|
|
42
|
+
limit: number;
|
|
43
|
+
offset: number;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface GetFirearmResponse {
|
|
48
|
+
success: true;
|
|
49
|
+
data: Firearm;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ApiErrorResponse {
|
|
53
|
+
success: false;
|
|
54
|
+
error: {
|
|
55
|
+
message: string;
|
|
56
|
+
code: string;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type ApiResponse<T> = T | ApiErrorResponse;
|
|
61
|
+
|
|
62
|
+
export interface SearchFirearmsParams {
|
|
63
|
+
query?: string;
|
|
64
|
+
category?: string;
|
|
65
|
+
limit?: number;
|
|
66
|
+
offset?: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface GetFirearmParams {
|
|
70
|
+
id: string | number;
|
|
71
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2022"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|