@ign8t/mcp 0.1.2 → 0.3.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 CHANGED
@@ -7,11 +7,14 @@ Seamlessly integrate ign8t project management into your AI-powered development w
7
7
  Model Context Protocol (MCP) is an open standard that enables AI assistants to securely access external tools and data sources. With the ign8t MCP server, your AI assistant can:
8
8
 
9
9
  - 📋 Access all your ign8t projects without switching contexts
10
- - 📄 Read BRD, PRD, and TRD documents to understand requirements
10
+ - 📄 Read master specs, feature specs, legacy PRP/BRD/PRD/TRD documents, and task specs
11
11
  - 📊 View and manage your backlog in real-time
12
+ - 🧩 See which feature specs exist and which tasks still need specs
12
13
  - ✅ Update task statuses as you complete work
13
14
  - 🔍 Search across all tasks and projects
14
- - 🎯 Get AI-powered task recommendations
15
+ - 🎯 Review, accept, or reject AI/backlog recommendations
16
+ - ➕ Add newly discovered tasks or feature requests from your IDE
17
+ - 📥 Import your local codebase into the project context
15
18
  - 🧠 Access full context including related requirements and documentation
16
19
 
17
20
  ## Quick Start
@@ -24,7 +27,7 @@ pnpm install -g @ign8t/mcp
24
27
 
25
28
  ### 2. Get Your API Key
26
29
 
27
- 1. Log in to [ign8t.com](https://ign8t.com)
30
+ 1. Log in to [ign8t.com](https://www.ign8t.com)
28
31
  2. Navigate to **Settings → API Keys**
29
32
  3. Click **"Create New API Key"**
30
33
  4. Give it a descriptive name (e.g., "Cursor Integration")
@@ -35,7 +38,7 @@ pnpm install -g @ign8t/mcp
35
38
  <details>
36
39
  <summary><strong>Cursor IDE (Recommended - using npx)</strong></summary>
37
40
 
38
- Add to `~/.cursor/mcp/settings.json`:
41
+ Add to `~/.cursor/mcp.json`:
39
42
 
40
43
  ```json
41
44
  {
@@ -140,8 +143,18 @@ Once configured, you can interact with ign8t naturally through your AI assistant
140
143
  ```
141
144
  "Show me all my active projects"
142
145
  "Get details about the [project name] project"
143
- "Show me the BRD for [project name]"
146
+ "Show me the master spec for [project name]"
144
147
  "Display all documents for my current project"
148
+ "Import this codebase into project [id]"
149
+ ```
150
+
151
+ ### Specs Workflow
152
+
153
+ ```
154
+ "What specs are left for this project?"
155
+ "List the master spec, generated task specs, and missing specs"
156
+ "Show me the feature spec for the authentication task"
157
+ "Get all task specs for project [id]"
145
158
  ```
146
159
 
147
160
  ### Task Workflow
@@ -153,6 +166,16 @@ Once configured, you can interact with ign8t naturally through your AI assistant
153
166
  "Update task [id] to in_progress"
154
167
  "Mark task [id] as done with note: 'Implemented OAuth2'"
155
168
  "Get full context for task [id]"
169
+ "Add a task for rate limiting the public API"
170
+ ```
171
+
172
+ ### Recommendation Workflow
173
+
174
+ ```
175
+ "I want to add Stripe billing next"
176
+ "List pending recommendations"
177
+ "Accept recommendation [id] and create a task"
178
+ "Reject recommendation [id]"
156
179
  ```
157
180
 
158
181
  ### Resources
@@ -164,26 +187,57 @@ The MCP server also provides quick access to resources:
164
187
 
165
188
  ## Available Tools Reference
166
189
 
167
- | Tool | Description | Example Use |
168
- | -------------------- | --------------------------------------------- | ------------------------------------------- |
169
- | `list_projects` | List all projects with optional status filter | "Show my archived projects" |
170
- | `get_project` | Get detailed project information | "Get details for project abc123" |
171
- | `get_documents` | Retrieve BRD, PRD, TRD documents | "Show me the PRD for project xyz" |
172
- | `get_backlog` | View epics, stories, and tasks | "Display backlog including completed items" |
173
- | `get_next_task` | AI-recommended next task | "What should I work on next?" |
174
- | `update_task_status` | Change task status with notes | "Set task 123 to blocked: waiting on API" |
175
- | `search_tasks` | Search across tasks | "Find all tasks mentioning 'payment'" |
176
- | `get_task_context` | Full task context with requirements | "Show context for task 456" |
190
+ | Tool | Description | Example Use |
191
+ | ------------------------ | ------------------------------------------------ | -------------------------------------------- |
192
+ | `list_projects` | List all projects with optional status filter | "Show my active projects" |
193
+ | `get_project` | Get detailed project information | "Get details for project abc123" |
194
+ | `get_documents` | Retrieve master spec, task specs, and legacy documents | "Show me the master spec for project xyz" |
195
+ | `get_backlog` | View epics, stories, and tasks | "Display backlog including completed items" |
196
+ | `get_specs` | List master, generated, and missing task specs | "What specs are left?" |
197
+ | `get_next_task` | Next todo task (highest priority, then oldest) plus task spec preview | "What should I work on next?" |
198
+ | `create_task` | Add a concrete task directly to backlog | "Add a task for OAuth callback handling" |
199
+ | `update_task_status` | Change task status with notes | "Set task 123 to blocked: waiting on API" |
200
+ | `search_tasks` | Search across tasks | "Find all tasks mentioning 'payment'" |
201
+ | `get_task_context` | Full task context with requirements | "Show context for task 456" |
202
+ | `add_feature_request` | Queue a feature idea as a backlog recommendation | "I want to add team invites next" |
203
+ | `list_recommendations` | List queued backlog recommendations | "Show pending feature recommendations" |
204
+ | `accept_recommendations` | Convert recommendations into tasks | "Accept recommendations abc and def" |
205
+ | `reject_recommendations` | Reject recommendations | "Reject this duplicate recommendation" |
206
+ | `add_user_persona` | Add or replace a user persona in context | "Add admin operator persona" |
207
+ | `update_success_criteria`| Add or update measurable success criteria | "Add activation-rate target" |
208
+ | `update_context` | Change the tech stack and constraints after you confirm the exact change | "We moved from Firebase to Supabase" |
209
+ | `import_project` | Import the local codebase into project context (28 Sparks) | "Import this codebase into project abc123" |
210
+
211
+ The write tools (`create_task`, `update_task_status`, `accept_recommendations`, `reject_recommendations`, `add_feature_request`, `add_user_persona`, `update_success_criteria`, `update_context`) are free. Only `import_project` costs Sparks.
212
+
213
+ ### Updating the implementation context
214
+
215
+ `update_context` lets the project owner's AI change what ign8t knows about the implementation: the tech stack's platform and framework (set or unset), its databases and AI models, and the project constraints (add or remove items: added values are trimmed, removed values must match the stored text exactly). Vision, features, users and success criteria are not writable through it.
216
+
217
+ - The AI reads the project with `get_project` first, shows you the exact change, and applies it only after you agree. The change is saved immediately and costs no Sparks.
218
+ - All previously generated documents (BRD/PRD/TRD, delivery brief, master spec, task specs) may be marked stale. They are not regenerated automatically, and regenerating them costs Sparks.
219
+ - If the context changed since the AI read it, nothing is saved: the AI reads it again and asks you again.
220
+
221
+ ### Importing a local codebase
222
+
223
+ `import_project` takes the project ID and the **absolute** path of your project folder, and fills the project's context (vision, users, features, tech stack) using ign8t's AI extraction. Results are written in Korean by default; pass `locale: "en"` for English.
224
+
225
+ - **Cost:** 28 Sparks per new import. Re-importing the same content (README, package.json name, description, script and dependency names, notable paths, primary language) is free as long as nothing else has changed the project context since that import; after an edit in ign8t or another import, it runs (and is charged) again so the context is restored. When the import reports a failure, nothing is saved and the Sparks are refunded; the error's `refund_status` says when a refund needs manual follow-up. If the connection drops before any answer arrives, check the project in ign8t before retrying: a retry of an import that was already saved is free, but one whose charge went through without a save is charged again.
226
+ - **What is uploaded:** the README (first 6,000 characters), the `package.json` name (or the folder name when it has none), description, script names and dependency names, up to 120 notable file paths (for example `src/`, `app/`, `supabase/`), the file count and the primary language guessed from file extensions. Nothing else: no other file contents and no environment values.
227
+ - **What is skipped:** hidden folders (`.git`, `.next`, `.venv`, ...), `node_modules`, build output (`dist`, `build`, `out`, ...), `vendor`, `Pods`, Python virtual environments (any folder with `pyvenv.cfg`), folders nested more than 12 levels deep, and folders you cannot read (listed in the result). The walk stops after 20,000 files and says so. Symbolic links inside the folder are not followed.
177
228
 
178
229
  ## Workflow Integration
179
230
 
180
231
  The ign8t MCP server is designed to fit seamlessly into your development workflow:
181
232
 
182
233
  1. **Start your day**: "What are my current tasks?"
183
- 2. **Get context**: "Show me the requirements for this task"
184
- 3. **Update progress**: "Mark this task as in progress"
185
- 4. **Search when stuck**: "Find similar tasks I've completed"
186
- 5. **Complete work**: "Mark task as done and get my next task"
234
+ 2. **Find the next build unit**: "What task should I work on next?"
235
+ 3. **Check spec coverage**: "What specs are left?"
236
+ 4. **Get context**: "Show me the task context and master spec"
237
+ 5. **Add discovered work**: "Queue a recommendation for webhook retry support"
238
+ 6. **Accept planned work**: "Accept that recommendation and create a task"
239
+ 7. **Update progress**: "Mark this task as in progress"
240
+ 8. **Complete work**: "Mark task as done and get my next task"
187
241
 
188
242
  ## Troubleshooting
189
243
 
@@ -218,22 +272,20 @@ The ign8t MCP server is designed to fit seamlessly into your development workflo
218
272
 
219
273
  ### Custom API Endpoint (Optional)
220
274
 
221
- The MCP server connects to `https://ign8t.com` by default. For enterprise deployments, self-hosted instances, or local development, you can override the API endpoint:
222
-
223
- **Using npx (Recommended):**
275
+ The MCP server connects to `https://www.ign8t.com` by default. For enterprise deployments, self-hosted instances, or local development, you can override the API endpoint with `--api-url`:
224
276
 
225
277
  ```json
226
278
  {
227
279
  "mcpServers": {
228
280
  "ign8t": {
229
281
  "command": "npx",
230
- "args": ["-y", "@ign8t/mcp@latest", "--api-key", "your-api-key"]
282
+ "args": ["-y", "@ign8t/mcp@latest", "--api-key", "your-api-key", "--api-url", "http://localhost:3000"]
231
283
  }
232
284
  }
233
285
  }
234
286
  ```
235
287
 
236
- ````
288
+ Or set `IGN8T_API_URL` in the `env` block of the same IDE configuration. A `.env` file in your workspace is not read.
237
289
 
238
290
  **Note**: Most users don't need to set a custom API URL. It's only required if you're:
239
291
 
@@ -250,32 +302,8 @@ For contributors working on the MCP server itself:
250
302
  3. Build the project: `pnpm build`
251
303
  4. Test locally: `pnpm dev --api-key your-key`
252
304
 
253
- ## Contributing
254
-
255
- We welcome contributions! If you'd like to contribute to the ign8t MCP server:
256
-
257
- ### Development Setup
258
-
259
- ```bash
260
- # Clone the repository
261
- git clone https://github.com/ign8t/mcp-server.git
262
- cd mcp-server
263
-
264
- # Install dependencies
265
- pnpm install
266
-
267
- # Run tests
268
- pnpm test
269
-
270
- # Build
271
- pnpm build
272
- ````
273
-
274
305
  ## Support
275
306
 
276
- - 📚 **Documentation**: [docs.ign8t.com/mcp](https://docs.ign8t.com/mcp)
277
- - 🐛 **Issues**: [GitHub Issues](https://github.com/ign8t/mcp-server/issues)
278
- - 💬 **Community**: [Discord](https://discord.gg/ign8t)
279
307
  - 📧 **Email**: support@ign8t.com
280
308
 
281
309
  ## License
package/dist/index.js CHANGED
@@ -2,9 +2,8 @@
2
2
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
- import * as dotenv from "dotenv";
6
- // Load environment variables
7
- dotenv.config();
5
+ import { buildWorkspaceSnapshot } from "./workspace-snapshot.js";
6
+ const VERSION = "0.3.0";
8
7
  // Parse command line arguments
9
8
  function parseArgs() {
10
9
  const args = process.argv.slice(2);
@@ -28,10 +27,10 @@ Usage: ign8t-mcp --api-key <key> [--api-url <url>]
28
27
 
29
28
  Options:
30
29
  --api-key <key> Your ign8t API key (required)
31
- --api-url <url> Custom API URL (optional, defaults to https://ign8t.com)
30
+ --api-url <url> Custom API URL (optional, defaults to https://www.ign8t.com)
32
31
  --help, -h Show this help message
33
32
 
34
- Get your API key from: https://ign8t.com/settings/api-keys
33
+ Get your API key from: https://www.ign8t.com/settings/api-keys
35
34
  `);
36
35
  process.exit(0);
37
36
  break;
@@ -41,19 +40,19 @@ Get your API key from: https://ign8t.com/settings/api-keys
41
40
  }
42
41
  // Configuration
43
42
  const config = parseArgs();
44
- const API_KEY = config.apiKey;
45
- const API_URL = config.apiUrl || "https://ign8t.com";
43
+ const API_KEY = config.apiKey || process.env.IGN8T_API_KEY;
44
+ const API_URL = config.apiUrl || process.env.IGN8T_API_URL || "https://www.ign8t.com";
46
45
  if (!API_KEY) {
47
46
  console.error("Error: API key is required");
48
47
  console.error("Usage: ign8t-mcp --api-key <your-api-key>");
49
48
  console.error("Or set IGN8T_API_KEY environment variable");
50
- console.error("Get your API key from https://ign8t.com/settings/api-keys");
49
+ console.error("Get your API key from https://www.ign8t.com/settings/api-keys");
51
50
  process.exit(1);
52
51
  }
53
52
  // Create MCP server instance
54
53
  const server = new Server({
55
54
  name: "ign8t-mcp",
56
- version: "1.0.0",
55
+ version: VERSION,
57
56
  description: "ign8t project management integration for AI IDEs",
58
57
  author: "nocodecat",
59
58
  homepage: "https://www.ign8t.com",
@@ -69,30 +68,37 @@ const server = new Server({
69
68
  logging: {},
70
69
  },
71
70
  });
71
+ // Turn a non-2xx response into a message the calling AI can act on: the
72
+ // server's `error` plus its structured fields (error_code, refund_status, ...).
73
+ async function describeApiError(response) {
74
+ if (!response.headers.get("content-type")?.includes("application/json")) {
75
+ return `${(await response.text()) || "API error"} (HTTP ${response.status})`;
76
+ }
77
+ const { success: _success, error, ...details } = await response.json();
78
+ const message = `${typeof error === "string" ? error : "API error"} (HTTP ${response.status})`;
79
+ return Object.keys(details).length ? `${message}\n${JSON.stringify(details, null, 2)}` : message;
80
+ }
72
81
  // Helper function to call ign8t API
73
82
  async function callIgn8tAPI(endpoint, data = {}) {
74
- try {
75
- const response = await fetch(`${API_URL}/api/mcp${endpoint}`, {
76
- method: "POST",
77
- headers: {
78
- "Content-Type": "application/json",
79
- "X-API-Key": API_KEY,
80
- },
81
- body: JSON.stringify(data),
82
- });
83
- if (!response.ok) {
84
- const error = await response.text();
85
- throw new Error(error || `API error: ${response.status}`);
86
- }
87
- return response.json();
88
- }
89
- catch (error) {
90
- if (error instanceof Error) {
91
- throw error;
92
- }
93
- throw new Error("Failed to connect to ign8t API");
83
+ const response = await fetch(`${API_URL}/api/mcp${endpoint}`, {
84
+ method: "POST",
85
+ headers: {
86
+ "Content-Type": "application/json",
87
+ "X-API-Key": API_KEY,
88
+ },
89
+ body: JSON.stringify(data),
90
+ });
91
+ if (!response.ok) {
92
+ throw new Error(await describeApiError(response));
94
93
  }
94
+ return response.json();
95
95
  }
96
+ // update_context's add/remove of list items, each at most maxLength characters; the
97
+ // 20-operation cap is on the whole patch (see its description).
98
+ const listOps = (maxLength) => {
99
+ const items = { type: "array", items: { type: "string", minLength: 1, maxLength } };
100
+ return { type: "object", properties: { add: items, remove: items }, additionalProperties: false };
101
+ };
96
102
  // Define available tools
97
103
  server.setRequestHandler(ListToolsRequestSchema, async () => {
98
104
  return {
@@ -120,6 +126,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
120
126
  properties: {
121
127
  project_id: {
122
128
  type: "string",
129
+ format: "uuid",
123
130
  description: "The project ID",
124
131
  },
125
132
  },
@@ -128,17 +135,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
128
135
  },
129
136
  {
130
137
  name: "get_documents",
131
- description: "Get BRD, PRD, and TRD documents for a project",
138
+ description: "Get master specs, task specs, and legacy BRD/PRD/TRD documents for a project",
132
139
  inputSchema: {
133
140
  type: "object",
134
141
  properties: {
135
142
  project_id: {
136
143
  type: "string",
144
+ format: "uuid",
137
145
  description: "The project ID",
138
146
  },
139
147
  document_type: {
140
148
  type: "string",
141
- enum: ["brd", "prd", "trd", "all"],
149
+ enum: ["master_spec", "prp", "brd", "prd", "trd", "task_spec", "all"],
142
150
  description: "Type of document to retrieve",
143
151
  default: "all",
144
152
  },
@@ -154,6 +162,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
154
162
  properties: {
155
163
  project_id: {
156
164
  type: "string",
165
+ format: "uuid",
157
166
  description: "The project ID",
158
167
  },
159
168
  include_completed: {
@@ -166,18 +175,154 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
166
175
  },
167
176
  },
168
177
  {
169
- name: "get_next_task",
170
- description: "Get the next recommended task to work on",
178
+ name: "get_specs",
179
+ description: "List the master spec, generated feature specs, and tasks that still need specs for a project. Use this to answer what specs are left.",
171
180
  inputSchema: {
172
181
  type: "object",
173
182
  properties: {
174
183
  project_id: {
175
184
  type: "string",
185
+ format: "uuid",
176
186
  description: "The project ID",
177
187
  },
178
- context: {
188
+ include_generated: {
189
+ type: "boolean",
190
+ description: "Include specs that already exist",
191
+ default: true,
192
+ },
193
+ include_missing: {
194
+ type: "boolean",
195
+ description: "Include backlog tasks without generated specs",
196
+ default: true,
197
+ },
198
+ },
199
+ required: ["project_id"],
200
+ },
201
+ },
202
+ {
203
+ name: "create_task",
204
+ description: "Add a new implementation task to a project's backlog from the IDE. Use this when you discover missing work or the user asks to add a feature/task.",
205
+ inputSchema: {
206
+ type: "object",
207
+ properties: {
208
+ project_id: {
209
+ type: "string",
210
+ format: "uuid",
211
+ description: "The project ID",
212
+ },
213
+ title: {
214
+ type: "string",
215
+ minLength: 3,
216
+ maxLength: 200,
217
+ description: "Short task title",
218
+ },
219
+ description: {
179
220
  type: "string",
180
- description: "Optional context about what you want to work on",
221
+ maxLength: 10000,
222
+ description: "Detailed task description",
223
+ },
224
+ priority: {
225
+ type: "string",
226
+ enum: ["critical", "high", "medium", "low"],
227
+ description: "Task priority",
228
+ default: "medium",
229
+ },
230
+ status: {
231
+ type: "string",
232
+ enum: ["todo", "in_progress", "review", "done", "blocked"],
233
+ description: "Initial task status",
234
+ default: "todo",
235
+ },
236
+ acceptance_criteria: {
237
+ type: "array",
238
+ items: { type: "string", minLength: 1, maxLength: 500 },
239
+ maxItems: 30,
240
+ description: "Acceptance criteria for the task",
241
+ },
242
+ technical_details: {
243
+ type: "string",
244
+ maxLength: 10000,
245
+ description: "Implementation notes or technical requirements",
246
+ },
247
+ },
248
+ required: ["project_id", "title"],
249
+ },
250
+ },
251
+ {
252
+ name: "list_recommendations",
253
+ description: "List backlog recommendations queued for review. Use this after adding feature requests or when deciding what to build next.",
254
+ inputSchema: {
255
+ type: "object",
256
+ properties: {
257
+ project_id: {
258
+ type: "string",
259
+ format: "uuid",
260
+ description: "The project ID",
261
+ },
262
+ status: {
263
+ type: "string",
264
+ enum: ["pending", "accepted", "rejected", "expired", "all"],
265
+ description: "Recommendation status to list",
266
+ default: "pending",
267
+ },
268
+ },
269
+ required: ["project_id"],
270
+ },
271
+ },
272
+ {
273
+ name: "accept_recommendations",
274
+ description: "Accept pending backlog recommendations and create concrete tasks from them.",
275
+ inputSchema: {
276
+ type: "object",
277
+ properties: {
278
+ project_id: {
279
+ type: "string",
280
+ format: "uuid",
281
+ description: "The project ID",
282
+ },
283
+ recommendation_ids: {
284
+ type: "array",
285
+ items: { type: "string", format: "uuid" },
286
+ minItems: 1,
287
+ maxItems: 50,
288
+ description: "Recommendation IDs to accept",
289
+ },
290
+ },
291
+ required: ["project_id", "recommendation_ids"],
292
+ },
293
+ },
294
+ {
295
+ name: "reject_recommendations",
296
+ description: "Reject pending backlog recommendations so they no longer appear as work candidates.",
297
+ inputSchema: {
298
+ type: "object",
299
+ properties: {
300
+ project_id: {
301
+ type: "string",
302
+ format: "uuid",
303
+ description: "The project ID",
304
+ },
305
+ recommendation_ids: {
306
+ type: "array",
307
+ items: { type: "string", format: "uuid" },
308
+ minItems: 1,
309
+ maxItems: 50,
310
+ description: "Recommendation IDs to reject",
311
+ },
312
+ },
313
+ required: ["project_id", "recommendation_ids"],
314
+ },
315
+ },
316
+ {
317
+ name: "get_next_task",
318
+ description: "Get the next todo task to work on (highest priority, then oldest), including its generated task spec when available.",
319
+ inputSchema: {
320
+ type: "object",
321
+ properties: {
322
+ project_id: {
323
+ type: "string",
324
+ format: "uuid",
325
+ description: "The project ID",
181
326
  },
182
327
  },
183
328
  required: ["project_id"],
@@ -191,6 +336,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
191
336
  properties: {
192
337
  task_id: {
193
338
  type: "string",
339
+ format: "uuid",
194
340
  description: "The task ID",
195
341
  },
196
342
  status: {
@@ -200,6 +346,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
200
346
  },
201
347
  notes: {
202
348
  type: "string",
349
+ maxLength: 10000,
203
350
  description: "Optional notes about the status change",
204
351
  },
205
352
  },
@@ -218,6 +365,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
218
365
  },
219
366
  project_id: {
220
367
  type: "string",
368
+ format: "uuid",
221
369
  description: "Optional: limit search to specific project",
222
370
  },
223
371
  status: {
@@ -232,23 +380,268 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
232
380
  },
233
381
  {
234
382
  name: "get_task_context",
235
- description: "Get full context for a task including related BRD/PRD/TRD sections",
383
+ description: "Get a task with its story, epic, its own task spec and the project's master spec",
236
384
  inputSchema: {
237
385
  type: "object",
238
386
  properties: {
239
387
  task_id: {
240
388
  type: "string",
389
+ format: "uuid",
241
390
  description: "The task ID",
242
391
  },
243
392
  },
244
393
  required: ["task_id"],
245
394
  },
246
395
  },
396
+ {
397
+ name: "import_project",
398
+ description: "Import the local codebase into the ign8t project's context (AI extraction of product vision, users, features and tech stack). Costs 28 Sparks per new import; re-importing the same content (README, package.json name/description/script/dependency names, notable paths, primary language) is free while nothing else has changed the project context since the last import. Uploads only: the README (first 6000 characters), the package.json name (or the folder name when it has none), description, script names and dependency names, up to 120 notable file paths, the file count and the primary language guessed from file extensions — never other file contents or environment values.",
399
+ inputSchema: {
400
+ type: "object",
401
+ properties: {
402
+ project_id: {
403
+ type: "string",
404
+ format: "uuid",
405
+ description: "The ign8t project ID to import into",
406
+ },
407
+ workspace_path: {
408
+ type: "string",
409
+ description: "Absolute path to the project root directory",
410
+ },
411
+ locale: {
412
+ type: "string",
413
+ enum: ["ko", "en"],
414
+ description: "Language of the extracted context",
415
+ default: "ko",
416
+ },
417
+ },
418
+ required: ["project_id", "workspace_path"],
419
+ },
420
+ },
421
+ // ===== BIDIRECTIONAL CONTEXT UPDATE TOOLS =====
422
+ {
423
+ name: "add_feature_request",
424
+ description: "Request a new feature spec candidate discovered during development. This queues the idea for product-owner review before implementation.",
425
+ inputSchema: {
426
+ type: "object",
427
+ properties: {
428
+ project_id: {
429
+ type: "string",
430
+ format: "uuid",
431
+ description: "The project ID",
432
+ },
433
+ feature: {
434
+ type: "object",
435
+ properties: {
436
+ name: {
437
+ type: "string",
438
+ minLength: 1,
439
+ maxLength: 200,
440
+ description: "Feature name (e.g., 'OAuth Social Login')",
441
+ },
442
+ description: {
443
+ type: "string",
444
+ minLength: 1,
445
+ maxLength: 5000,
446
+ description: "Detailed feature description",
447
+ },
448
+ user_story: {
449
+ type: "string",
450
+ minLength: 1,
451
+ maxLength: 2000,
452
+ description: "User story (As a... I want... So that...)",
453
+ },
454
+ priority: {
455
+ type: "string",
456
+ enum: ["critical", "high", "medium", "low"],
457
+ description: "Feature priority",
458
+ },
459
+ estimated_complexity: {
460
+ type: "string",
461
+ enum: ["simple", "moderate", "complex"],
462
+ description: "Estimated implementation complexity",
463
+ },
464
+ },
465
+ required: ["name", "description", "user_story"],
466
+ },
467
+ related_context: {
468
+ type: "object",
469
+ description: "Additional context for spec definition (optional, at most 10000 characters as JSON)",
470
+ },
471
+ },
472
+ required: ["project_id", "feature"],
473
+ },
474
+ },
475
+ {
476
+ name: "add_user_persona",
477
+ description: "Add a new user persona discovered during development. Enriches the project context with detailed user information. Adding a persona with an existing name replaces that persona.",
478
+ inputSchema: {
479
+ type: "object",
480
+ properties: {
481
+ project_id: {
482
+ type: "string",
483
+ format: "uuid",
484
+ description: "The project ID",
485
+ },
486
+ persona: {
487
+ type: "object",
488
+ properties: {
489
+ name: {
490
+ type: "string",
491
+ minLength: 1,
492
+ maxLength: 100,
493
+ description: "Persona name (e.g., 'Sarah - Operations Manager')",
494
+ },
495
+ role: {
496
+ type: "string",
497
+ minLength: 1,
498
+ maxLength: 200,
499
+ description: "User role or job title",
500
+ },
501
+ demographics: {
502
+ type: "object",
503
+ properties: {
504
+ age: { type: "string", maxLength: 100 },
505
+ tech_savviness: {
506
+ type: "string",
507
+ enum: ["beginner", "intermediate", "advanced"],
508
+ },
509
+ industry: { type: "string", maxLength: 100 },
510
+ },
511
+ },
512
+ goals: {
513
+ type: "array",
514
+ items: { type: "string", minLength: 1, maxLength: 500 },
515
+ maxItems: 20,
516
+ description: "User goals and objectives",
517
+ },
518
+ pain_points: {
519
+ type: "array",
520
+ items: { type: "string", minLength: 1, maxLength: 500 },
521
+ maxItems: 20,
522
+ description: "User pain points and frustrations",
523
+ },
524
+ use_cases: {
525
+ type: "array",
526
+ items: { type: "string", minLength: 1, maxLength: 500 },
527
+ maxItems: 20,
528
+ description: "Specific use cases for this persona",
529
+ },
530
+ },
531
+ required: ["name", "role", "goals", "pain_points"],
532
+ },
533
+ },
534
+ required: ["project_id", "persona"],
535
+ },
536
+ },
537
+ {
538
+ name: "update_success_criteria",
539
+ description: "Add or update success criteria and metrics for the project. Helps define how success will be measured. A criterion with an existing metric name replaces that criterion.",
540
+ inputSchema: {
541
+ type: "object",
542
+ properties: {
543
+ project_id: {
544
+ type: "string",
545
+ format: "uuid",
546
+ description: "The project ID",
547
+ },
548
+ criteria: {
549
+ type: "array",
550
+ minItems: 1,
551
+ maxItems: 20,
552
+ items: {
553
+ type: "object",
554
+ properties: {
555
+ metric: {
556
+ type: "string",
557
+ minLength: 1,
558
+ maxLength: 200,
559
+ pattern: "^[^:]*$",
560
+ description: "What to measure, without ':' (e.g., 'User sign-up rate')",
561
+ },
562
+ target: {
563
+ type: "string",
564
+ minLength: 1,
565
+ maxLength: 200,
566
+ description: "Target value (e.g., '80% of visitors')",
567
+ },
568
+ priority: {
569
+ type: "string",
570
+ enum: ["must-have", "nice-to-have"],
571
+ description: "Metric priority",
572
+ },
573
+ measurement_method: {
574
+ type: "string",
575
+ maxLength: 500,
576
+ description: "How this will be measured (optional)",
577
+ },
578
+ },
579
+ required: ["metric", "target", "priority"],
580
+ },
581
+ },
582
+ },
583
+ required: ["project_id", "criteria"],
584
+ },
585
+ },
586
+ {
587
+ name: "update_context",
588
+ description: "Change the project's implementation context: tech_stack platform/framework (set, or list them in tech_stack.unset to clear), tech_stack database/ai_models and constraints (add/remove items: add values are trimmed; remove values must match the stored text exactly). Writes immediately and costs zero Sparks. Call get_project first and pass its context.context_version as expected_context_version. Before calling, show the user the exact change and tell them that all previously generated documents (BRD/PRD/TRD, delivery brief, master spec, task specs) may be marked stale, and that regenerating them costs Sparks; set confirmed_with_user to true only after the user agreed. A CONTEXT_CHANGED error means the context changed since you read it: read it again, reconcile, and ask the user again. Vision, features, users and success criteria are not writable here: use add_user_persona, update_success_criteria or add_feature_request.",
589
+ inputSchema: {
590
+ type: "object",
591
+ properties: {
592
+ project_id: {
593
+ type: "string",
594
+ format: "uuid",
595
+ description: "The project ID",
596
+ },
597
+ expected_context_version: {
598
+ type: "integer",
599
+ minimum: 1,
600
+ description: "context.context_version from the get_project call the user's confirmation was based on",
601
+ },
602
+ reason: {
603
+ type: "string",
604
+ minLength: 10,
605
+ maxLength: 500,
606
+ description: "Why this change is needed (e.g., what was discovered while implementing)",
607
+ },
608
+ confirmed_with_user: {
609
+ type: "boolean",
610
+ description: "Must be true, set only after the user saw the exact change and agreed to it; anything else is rejected",
611
+ },
612
+ patch: {
613
+ type: "object",
614
+ description: "At most 20 operations in total (each set, unset, added or removed item counts). A list can grow to at most 50 items, and the tech stack and constraints to at most 12000 characters together.",
615
+ properties: {
616
+ tech_stack: {
617
+ type: "object",
618
+ properties: {
619
+ platform: { type: "string", minLength: 1, maxLength: 200 },
620
+ framework: { type: "string", minLength: 1, maxLength: 200 },
621
+ unset: {
622
+ type: "array",
623
+ items: { type: "string", enum: ["platform", "framework"] },
624
+ description: "Keys to clear; a key cannot be both set and unset",
625
+ },
626
+ database: listOps(200),
627
+ ai_models: listOps(200),
628
+ },
629
+ additionalProperties: false,
630
+ },
631
+ constraints: listOps(500),
632
+ },
633
+ additionalProperties: false,
634
+ },
635
+ },
636
+ required: ["project_id", "expected_context_version", "reason", "confirmed_with_user", "patch"],
637
+ additionalProperties: false,
638
+ },
639
+ },
247
640
  ],
248
641
  };
249
642
  });
250
643
  // Handle tool calls
251
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
644
+ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
252
645
  const { name, arguments: args } = request.params;
253
646
  try {
254
647
  let result;
@@ -265,6 +658,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
265
658
  case "get_backlog":
266
659
  result = await callIgn8tAPI("/backlog/get", args);
267
660
  break;
661
+ case "get_specs":
662
+ result = await callIgn8tAPI("/specs/list", args);
663
+ break;
664
+ case "create_task":
665
+ result = await callIgn8tAPI("/tasks/create", args);
666
+ break;
667
+ case "list_recommendations":
668
+ result = await callIgn8tAPI("/recommendations/list", args);
669
+ break;
670
+ case "accept_recommendations":
671
+ result = await callIgn8tAPI("/recommendations/accept", args);
672
+ break;
673
+ case "reject_recommendations":
674
+ result = await callIgn8tAPI("/recommendations/reject", args);
675
+ break;
268
676
  case "get_next_task":
269
677
  result = await callIgn8tAPI("/tasks/next", args);
270
678
  break;
@@ -277,6 +685,72 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
277
685
  case "get_task_context":
278
686
  result = await callIgn8tAPI("/tasks/context", args);
279
687
  break;
688
+ case "import_project": {
689
+ const { snapshot, unreadableDirectories, fileLimitReached } = await buildWorkspaceSnapshot(args?.workspace_path);
690
+ // The upload is what charges Sparks: a call the user cancelled while the folder
691
+ // was being read must not go on to charge.
692
+ extra.signal.throwIfAborted();
693
+ // The server's reply states what happened: sparks_charged, already_imported, warning.
694
+ result = await callIgn8tAPI("/projects/import", {
695
+ project_id: args?.project_id,
696
+ locale: args?.locale,
697
+ workspace: snapshot,
698
+ });
699
+ if (unreadableDirectories.length) {
700
+ result = { ...result, skipped_unreadable_directories: unreadableDirectories };
701
+ }
702
+ if (fileLimitReached) {
703
+ result = {
704
+ ...result,
705
+ file_limit_reached: "The folder has more than 20000 files; the count and notable paths cover the first 20000 reached (folders walked depth-first, each sorted by name).",
706
+ };
707
+ }
708
+ break;
709
+ }
710
+ // ===== BIDIRECTIONAL CONTEXT UPDATE HANDLERS =====
711
+ case "add_feature_request": {
712
+ console.error(`Adding feature request for project: ${args.project_id}`);
713
+ result = await callIgn8tAPI("/features/add", {
714
+ project_id: args.project_id,
715
+ feature: args.feature,
716
+ related_context: args.related_context,
717
+ mcp_metadata: {
718
+ ide: "MCP Client",
719
+ timestamp: new Date().toISOString(),
720
+ },
721
+ });
722
+ console.error("Feature request submitted successfully");
723
+ break;
724
+ }
725
+ case "add_user_persona": {
726
+ console.error(`Adding user persona for project: ${args.project_id}`);
727
+ result = await callIgn8tAPI("/personas/add", {
728
+ project_id: args.project_id,
729
+ persona: args.persona,
730
+ mcp_metadata: {
731
+ ide: "MCP Client",
732
+ timestamp: new Date().toISOString(),
733
+ },
734
+ });
735
+ console.error("User persona added successfully");
736
+ break;
737
+ }
738
+ case "update_success_criteria": {
739
+ console.error(`Updating success criteria for project: ${args.project_id}`);
740
+ result = await callIgn8tAPI("/criteria/update", {
741
+ project_id: args.project_id,
742
+ criteria: args.criteria,
743
+ mcp_metadata: {
744
+ ide: "MCP Client",
745
+ timestamp: new Date().toISOString(),
746
+ },
747
+ });
748
+ console.error("Success criteria updated successfully");
749
+ break;
750
+ }
751
+ case "update_context":
752
+ result = await callIgn8tAPI("/context/update", args);
753
+ break;
280
754
  default:
281
755
  throw new Error(`Unknown tool: ${name}`);
282
756
  }
@@ -368,7 +842,7 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
368
842
  async function main() {
369
843
  const transport = new StdioServerTransport();
370
844
  await server.connect(transport);
371
- console.error("ign8t MCP server v1.0.0");
845
+ console.error(`ign8t MCP server v${VERSION}`);
372
846
  console.error(`Connected to: ${API_URL}`);
373
847
  console.error("Ready to serve requests...");
374
848
  }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Workspace Snapshot
3
+ *
4
+ * Builds the bounded, deterministic summary of a local codebase that
5
+ * import_project uploads. Only the README excerpt, package.json metadata,
6
+ * notable file paths and counts leave the machine — never other file contents.
7
+ */
8
+ import { readdir, readFile, stat } from "node:fs/promises";
9
+ import * as path from "node:path";
10
+ // Hidden directories (.git, .next, .venv, .tox, ...) are skipped too: caches there
11
+ // would otherwise fill the file cap before the real code is reached.
12
+ const SKIPPED_DIRECTORIES = new Set([
13
+ "node_modules",
14
+ "dist",
15
+ "build",
16
+ "out",
17
+ "coverage",
18
+ "vendor",
19
+ "target",
20
+ "__pycache__",
21
+ "venv",
22
+ "Pods",
23
+ ]);
24
+ // Same selection the web GitHub import uses (import-github.ts).
25
+ const NOTABLE_FILE = /(^|\/)(package\.json|pnpm-lock\.yaml|yarn\.lock|next\.config|vite\.config|src\/|app\/|pages\/|components\/|supabase\/|prisma\/|drizzle|README|Dockerfile)/i;
26
+ const LANGUAGE_BY_EXTENSION = {
27
+ ".ts": "TypeScript",
28
+ ".tsx": "TypeScript",
29
+ ".js": "JavaScript",
30
+ ".jsx": "JavaScript",
31
+ ".mjs": "JavaScript",
32
+ ".cjs": "JavaScript",
33
+ ".py": "Python",
34
+ ".go": "Go",
35
+ ".rs": "Rust",
36
+ ".java": "Java",
37
+ ".kt": "Kotlin",
38
+ ".swift": "Swift",
39
+ ".rb": "Ruby",
40
+ ".php": "PHP",
41
+ ".cs": "C#",
42
+ ".cpp": "C++",
43
+ ".c": "C",
44
+ ".dart": "Dart",
45
+ ".vue": "Vue",
46
+ ".svelte": "Svelte",
47
+ ".html": "HTML",
48
+ ".css": "CSS",
49
+ };
50
+ const MAX_DEPTH = 12;
51
+ const MAX_FILES = 20_000;
52
+ /**
53
+ * Clip to `max` UTF-16 units without leaving half of a surrogate pair. Unpaired
54
+ * surrogates (possible through package.json escapes) become U+FFFD: the server
55
+ * rejects them, since Postgres cannot store them.
56
+ */
57
+ function clip(value, max) {
58
+ // NUL (possible through "\u0000" escapes) is dropped for the same reason.
59
+ const wellFormed = Buffer.from(value, "utf8").toString("utf8").replace(/\u0000/g, "");
60
+ const clipped = wellFormed.slice(0, max);
61
+ return /[\uD800-\uDBFF]$/.test(clipped) ? clipped.slice(0, -1) : clipped;
62
+ }
63
+ function objectKeys(value) {
64
+ return value && typeof value === "object" && !Array.isArray(value) ? Object.keys(value) : [];
65
+ }
66
+ function byName(a, b) {
67
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
68
+ }
69
+ /**
70
+ * Relative POSIX paths of regular files, in sorted depth-first order.
71
+ * Symlinks are never followed and the walk stops at MAX_FILES. Subdirectories the
72
+ * user cannot read (e.g. a root-owned database volume) are skipped and returned.
73
+ */
74
+ async function listFiles(root) {
75
+ // Collects one file past the cap, so "more than MAX_FILES" is a fact, not a guess.
76
+ const files = [];
77
+ const unreadable = [];
78
+ async function visit(directory, prefix, depth) {
79
+ let entries;
80
+ try {
81
+ entries = (await readdir(directory, { withFileTypes: true })).sort(byName);
82
+ }
83
+ catch (error) {
84
+ const code = error.code;
85
+ // ENOENT/ENOTDIR: removed or replaced while the walk ran, or a name the OS cannot reopen.
86
+ if (prefix && (code === "EACCES" || code === "EPERM" || code === "ENOENT" || code === "ENOTDIR")) {
87
+ unreadable.push(prefix.slice(0, -1));
88
+ return;
89
+ }
90
+ throw error;
91
+ }
92
+ // A Python virtual environment, whatever its folder is called (PEP 405 marker).
93
+ if (prefix && entries.some((entry) => entry.isFile() && entry.name === "pyvenv.cfg"))
94
+ return;
95
+ for (const entry of entries) {
96
+ if (files.length > MAX_FILES)
97
+ return;
98
+ const relativePath = prefix + entry.name;
99
+ if (entry.isFile()) {
100
+ files.push(relativePath);
101
+ }
102
+ else if (entry.isDirectory() &&
103
+ depth < MAX_DEPTH &&
104
+ !entry.name.startsWith(".") &&
105
+ !SKIPPED_DIRECTORIES.has(entry.name)) {
106
+ await visit(path.join(directory, entry.name), `${relativePath}/`, depth + 1);
107
+ }
108
+ }
109
+ }
110
+ await visit(root, "", 0);
111
+ return { files: files.slice(0, MAX_FILES), unreadable, fileLimitReached: files.length > MAX_FILES };
112
+ }
113
+ function primaryLanguage(files) {
114
+ const counts = new Map();
115
+ for (const file of files) {
116
+ const language = LANGUAGE_BY_EXTENSION[path.posix.extname(file).toLowerCase()];
117
+ if (language)
118
+ counts.set(language, (counts.get(language) ?? 0) + 1);
119
+ }
120
+ return [...counts].sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1))[0]?.[0];
121
+ }
122
+ /**
123
+ * The snapshot to upload, plus what it left out (reported to the caller, never sent):
124
+ * unreadable subdirectories and whether the file cap stopped the walk.
125
+ */
126
+ export async function buildWorkspaceSnapshot(root) {
127
+ if (typeof root !== "string" || !path.isAbsolute(root)) {
128
+ throw new Error(`workspace_path must be an absolute directory path, got: ${String(root)}`);
129
+ }
130
+ if (!(await stat(root)).isDirectory()) {
131
+ throw new Error(`workspace_path is not a directory: ${root}`);
132
+ }
133
+ const rootFiles = (await readdir(root, { withFileTypes: true }))
134
+ .filter((entry) => entry.isFile())
135
+ .map((entry) => entry.name)
136
+ .sort();
137
+ let packageJson = {};
138
+ if (rootFiles.includes("package.json")) {
139
+ const packageJsonPath = path.join(root, "package.json");
140
+ const text = await readFile(packageJsonPath, "utf8");
141
+ try {
142
+ // A leading byte-order mark is valid for npm and Node, but not for JSON.parse.
143
+ packageJson = JSON.parse(text.replace(/^\uFEFF/, ""));
144
+ }
145
+ catch {
146
+ // The parser's message quotes the file, which may hold script values; only
147
+ // names leave the machine, so the error must not repeat its contents.
148
+ throw new Error(`Cannot parse ${packageJsonPath}: it is not valid JSON`);
149
+ }
150
+ if (!packageJson || typeof packageJson !== "object" || Array.isArray(packageJson)) {
151
+ throw new Error(`${packageJsonPath} is not a JSON object`);
152
+ }
153
+ }
154
+ const readmeName = ["readme.md", "readme"]
155
+ .map((wanted) => rootFiles.find((name) => name.toLowerCase() === wanted))
156
+ .find(Boolean);
157
+ // NULs (a UTF-16 README read as UTF-8) are dropped by clip(): the server rejects them.
158
+ const readme = readmeName ? await readFile(path.join(root, readmeName), "utf8") : "";
159
+ const { files, unreadable, fileLimitReached } = await listFiles(root);
160
+ // Every value is clipped before the empty checks: a value made only of dropped
161
+ // characters (e.g. NUL) must count as empty, not be sent as "".
162
+ const packageName = clip(typeof packageJson.name === "string" ? packageJson.name : "", 214);
163
+ const description = clip(typeof packageJson.description === "string" ? packageJson.description : "", 1000);
164
+ const readmeExcerpt = clip(readme, 6000);
165
+ const language = primaryLanguage(files);
166
+ const snapshot = {
167
+ name: packageName || clip(path.basename(root), 214),
168
+ ...(description ? { description } : {}),
169
+ ...(language ? { primary_language: language } : {}),
170
+ total_files: files.length,
171
+ scripts: objectKeys(packageJson.scripts)
172
+ .map((script) => clip(script, 100))
173
+ .filter(Boolean)
174
+ .slice(0, 50),
175
+ // Runtime dependencies win the 60 slots over dev tooling (same order as the
176
+ // GitHub import), then the list is sorted so key order cannot change it.
177
+ dependencies: [
178
+ ...new Set([
179
+ ...objectKeys(packageJson.dependencies).sort(),
180
+ ...objectKeys(packageJson.devDependencies).sort(),
181
+ ]),
182
+ ]
183
+ .filter((dependency) => dependency.length <= 214)
184
+ .map((dependency) => clip(dependency, 214))
185
+ .filter(Boolean)
186
+ .slice(0, 60)
187
+ .sort(),
188
+ ...(readmeExcerpt ? { readme_excerpt: readmeExcerpt } : {}),
189
+ // Paths the server would reject (backslash, drive prefix, over 300 chars) are left out.
190
+ notable_files: files
191
+ .filter((file) => file.length <= 300 && !file.includes("\\") && !/^[A-Za-z]:/.test(file))
192
+ .filter((file) => NOTABLE_FILE.test(file))
193
+ .sort()
194
+ .slice(0, 120),
195
+ };
196
+ return { snapshot, unreadableDirectories: unreadable, fileLimitReached };
197
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ign8t/mcp",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "MCP server for ign8t project management - AI IDE integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -11,6 +11,19 @@
11
11
  "dist/",
12
12
  "README.md"
13
13
  ],
14
+ "scripts": {
15
+ "build": "tsc && chmod +x dist/index.js",
16
+ "build:watch": "tsc --watch",
17
+ "start": "node dist/index.js",
18
+ "dev": "tsx src/index.ts",
19
+ "dev:help": "tsx src/index.ts --help",
20
+ "clean": "rm -rf dist",
21
+ "test": "node debug-mcp.js",
22
+ "test:local": "node dist/index.js --help",
23
+ "link:local": "npm link",
24
+ "unlink:local": "npm unlink -g @ign8t/mcp",
25
+ "prepublishOnly": "pnpm clean && pnpm build"
26
+ },
14
27
  "keywords": [
15
28
  "mcp",
16
29
  "model-context-protocol",
@@ -25,34 +38,17 @@
25
38
  "mcp": {
26
39
  "name": "ign8t-mcp",
27
40
  "description": "MCP server for ign8t project management - AI IDE integration",
28
- "version": "1.0.0"
41
+ "version": "0.3.0"
29
42
  },
30
43
  "author": "nocodecat",
31
44
  "license": "MIT",
32
45
  "homepage": "https://www.ign8t.com",
33
- "repository": {
34
- "type": "git",
35
- "url": "https://github.com/ign8t/mcp-server.git"
36
- },
37
46
  "dependencies": {
38
- "@modelcontextprotocol/sdk": "^0.5.0",
39
- "dotenv": "^16.3.1"
47
+ "@modelcontextprotocol/sdk": "^0.5.0"
40
48
  },
41
49
  "devDependencies": {
42
50
  "@types/node": "^20.10.5",
43
51
  "tsx": "^4.7.0",
44
52
  "typescript": "^5.3.3"
45
- },
46
- "scripts": {
47
- "build": "tsc && chmod +x dist/index.js",
48
- "build:watch": "tsc --watch",
49
- "start": "node dist/index.js",
50
- "dev": "tsx src/index.ts",
51
- "dev:help": "tsx src/index.ts --help",
52
- "clean": "rm -rf dist",
53
- "test": "node debug-mcp.js",
54
- "test:local": "node dist/index.js --help",
55
- "link:local": "npm link",
56
- "unlink:local": "npm unlink -g @ign8t/mcp"
57
53
  }
58
- }
54
+ }