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 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,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('../dist/index.js');
@@ -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":";;;;;;AAAA,kDAA6C;AAU7C,MAAa,mBAAmB;IACtB,MAAM,CAAgB;IACtB,MAAM,CAAS;IAEvB,YAAY,MAAc,EAAE,OAAgB;QAC1C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,eAAK,CAAC,MAAM,CAAC;YACzB,OAAO,EAAE,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,4BAA4B;YAClF,OAAO,EAAE;gBACP,WAAW,EAAE,MAAM;gBACnB,cAAc,EAAE,kBAAkB;aACnC;YACD,OAAO,EAAE,KAAK;SACf,CAAC,CAAC;IACL,CAAC;IAEO,eAAe,CAAC,QAAiB;QACvC,OAAO,CACL,OAAO,QAAQ,KAAK,QAAQ;YAC5B,QAAQ,KAAK,IAAI;YACjB,SAAS,IAAI,QAAQ;YACpB,QAA6B,CAAC,OAAO,KAAK,KAAK,CACjD,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,MAA4B;QAC/C,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,IAAI,eAAe,EAAE,CAAC;YAE1C,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACjB,WAAW,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YACxC,CAAC;YACD,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;gBACpB,WAAW,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;YAClD,CAAC;YACD,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC/B,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACpD,CAAC;YACD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAChC,WAAW,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YACtD,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CACpC,2BAA2B,WAAW,CAAC,QAAQ,EAAE,EAAE,CACpD,CAAC;YAEF,OAAO,QAAQ,CAAC,IAAI,CAAC;QACvB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,eAAK,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC;gBACtD,OAAO,KAAK,CAAC,QAAQ,CAAC,IAAwB,CAAC;YACjD,CAAC;YACD,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE;oBACL,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,wBAAwB;oBAC1E,IAAI,EAAE,WAAW;iBAClB;aACF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAAwB;QACvC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CACpC,oBAAoB,MAAM,CAAC,EAAE,EAAE,CAChC,CAAC;YAEF,OAAO,QAAQ,CAAC,IAAI,CAAC;QACvB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,eAAK,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC;gBACtD,OAAO,KAAK,CAAC,QAAQ,CAAC,IAAwB,CAAC;YACjD,CAAC;YACD,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE;oBACL,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,wBAAwB;oBAC1E,IAAI,EAAE,WAAW;iBAClB;aACF,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AAjFD,kDAiFC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -0,0 +1,8 @@
1
+ export declare class GunsnationMcpServer {
2
+ private server;
3
+ private apiClient;
4
+ constructor(apiKey: string, apiUrl?: string);
5
+ private setupHandlers;
6
+ run(): Promise<void>;
7
+ }
8
+ //# sourceMappingURL=server.d.ts.map
@@ -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":";;;AAkEA,gCAaC;AA/ED,6BAAwB;AAIX,QAAA,gBAAgB,GAAG,OAAC,CAAC,MAAM,CAAC;IACvC,EAAE,EAAE,OAAC,CAAC,KAAK,CAAC,CAAC,OAAC,CAAC,MAAM,EAAE,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,mCAAmC,CAAC;CACpF,CAAC,CAAC;AAIH,SAAS,qBAAqB,CAAC,OAAgB;IAC7C,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAChC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,aAAa;IACb,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACnC,KAAK,CAAC,IAAI,CAAC,aAAa,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;IACtC,IAAI,OAAO,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,gBAAgB,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IAC/D,IAAI,OAAO,CAAC,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,kBAAkB,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACrE,IAAI,OAAO,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,iBAAiB,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAClE,IAAI,OAAO,CAAC,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,mBAAmB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IACxE,IAAI,OAAO,CAAC,GAAG;QAAE,KAAK,CAAC,IAAI,CAAC,cAAc,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACzD,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,iBAAiB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,iBAAiB;IACjB,MAAM,KAAK,GAAG,OAAO,CAAC,cAAc,CAAC;IACrC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,CAAC,CAAC;IAE/E,IAAI,QAAQ,EAAE,CAAC;QACb,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAChC,IAAI,KAAK,CAAC,YAAY;YAAE,KAAK,CAAC,IAAI,CAAC,wBAAwB,KAAK,CAAC,YAAY,GAAG,CAAC,CAAC;QAClF,IAAI,KAAK,CAAC,aAAa;YAAE,KAAK,CAAC,IAAI,CAAC,yBAAyB,KAAK,CAAC,aAAa,GAAG,CAAC,CAAC;QACrF,IAAI,KAAK,CAAC,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,iBAAiB,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC;QACjE,IAAI,KAAK,CAAC,QAAQ;YAAE,KAAK,CAAC,IAAI,CAAC,mBAAmB,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QACpE,IAAI,KAAK,CAAC,QAAQ;YAAE,KAAK,CAAC,IAAI,CAAC,mBAAmB,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QACpE,IAAI,KAAK,CAAC,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,iBAAiB,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QAC9D,IAAI,KAAK,CAAC,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,qBAAqB,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;QACxE,IAAI,KAAK,CAAC,cAAc;YAAE,KAAK,CAAC,IAAI,CAAC,0BAA0B,KAAK,CAAC,cAAc,EAAE,CAAC,CAAC;QACvF,IAAI,KAAK,CAAC,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,qBAAqB,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;QACxE,IAAI,KAAK,CAAC,aAAa;YAAE,KAAK,CAAC,IAAI,CAAC,yBAAyB,KAAK,CAAC,aAAa,EAAE,CAAC,CAAC;QACpF,IAAI,KAAK,CAAC,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,qBAAqB,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;QACxE,IAAI,KAAK,CAAC,iBAAiB;YAAE,KAAK,CAAC,IAAI,CAAC,6BAA6B,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC;QAChG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,cAAc;IACd,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,SAAS;IACT,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACxB,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;YACpC,KAAK,CAAC,IAAI,CAAC,WAAW,KAAK,GAAG,CAAC,KAAK,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAEM,KAAK,UAAU,UAAU,CAC9B,MAA2B,EAC3B,KAAsB;IAEtB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC;QACvC,EAAE,EAAE,KAAK,CAAC,EAAE;KACb,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QACtB,OAAO,UAAU,QAAQ,CAAC,KAAK,CAAC,OAAO,KAAK,QAAQ,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC;IACrE,CAAC;IAED,OAAO,qBAAqB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC9C,CAAC"}
@@ -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,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=index.js.map
@@ -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
+ }