pinpointmcp 0.1.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 +270 -0
- package/dist/client-holder.d.ts +8 -0
- package/dist/client-holder.js +18 -0
- package/dist/client.d.ts +10 -0
- package/dist/client.js +63 -0
- package/dist/config-store.d.ts +7 -0
- package/dist/config-store.js +27 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +25 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +35 -0
- package/dist/prompts/index.d.ts +1 -0
- package/dist/prompts/index.js +1 -0
- package/dist/prompts/solve-bug.d.ts +3 -0
- package/dist/prompts/solve-bug.js +29 -0
- package/dist/resources/bugs.d.ts +4 -0
- package/dist/resources/bugs.js +43 -0
- package/dist/resources/index.d.ts +1 -0
- package/dist/resources/index.js +1 -0
- package/dist/setup-message.d.ts +24 -0
- package/dist/setup-message.js +34 -0
- package/dist/tools/configure.d.ts +3 -0
- package/dist/tools/configure.js +49 -0
- package/dist/tools/get-bug.d.ts +3 -0
- package/dist/tools/get-bug.js +30 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/list-bugs.d.ts +3 -0
- package/dist/tools/list-bugs.js +23 -0
- package/dist/tools/update-bug-status.d.ts +3 -0
- package/dist/tools/update-bug-status.js +25 -0
- package/dist/types.d.ts +54 -0
- package/dist/types.js +33 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Pinpoint MCP Server
|
|
2
|
+
|
|
3
|
+
MCP server for the [Pinpoint](https://testwithpinpoint.com) bug tracking platform. Enables AI agents in Claude Code, Cursor, and other MCP-compatible environments to list bugs, inspect details, update status, and autonomously resolve reported issues.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### Install from npm
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx -y pinpointmcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
That's it. The server starts without any pre-configuration and exposes a `configure` tool so your AI agent can set up credentials interactively. You can also pre-configure via environment variables or a dotfile (see [Configuration](#configuration) below).
|
|
14
|
+
|
|
15
|
+
### Build from source
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cd code/mcp
|
|
19
|
+
npm install
|
|
20
|
+
npm run build
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Configuration
|
|
24
|
+
|
|
25
|
+
The server resolves credentials in this order:
|
|
26
|
+
|
|
27
|
+
1. **Environment variables** (highest precedence)
|
|
28
|
+
2. **Dotfile** at `~/.pinpoint/config.json`
|
|
29
|
+
3. **Unconfigured** (server starts, all tools return setup guidance until configured)
|
|
30
|
+
|
|
31
|
+
### Option 1: Environment Variables
|
|
32
|
+
|
|
33
|
+
| Variable | Required | Default | Description |
|
|
34
|
+
|----------|----------|---------|-------------|
|
|
35
|
+
| `PINPOINT_TOKEN` | No | n/a | API authentication token |
|
|
36
|
+
| `PINPOINT_API_URL` | No | `https://api.testwithpinpoint.com` | API base URL |
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export PINPOINT_TOKEN=your-token-here
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Option 2: Interactive Configuration (First-Boot)
|
|
43
|
+
|
|
44
|
+
Launch the server without any token set. The agent can then call the `configure` tool:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{ "token": "your-pinpoint-api-token" }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The server validates the token against the API, swaps in a live client, and persists the credentials to `~/.pinpoint/config.json` (with `0600` file permissions) for future sessions.
|
|
51
|
+
|
|
52
|
+
### Option 3: Dotfile
|
|
53
|
+
|
|
54
|
+
Create `~/.pinpoint/config.json` manually:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"token": "your-pinpoint-api-token",
|
|
59
|
+
"apiUrl": "https://api.testwithpinpoint.com"
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Claude Code / Cursor / Windsurf
|
|
64
|
+
|
|
65
|
+
Add to your project's `.mcp.json` (or the equivalent for your platform):
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"pinpoint": {
|
|
71
|
+
"command": "npx",
|
|
72
|
+
"args": ["-y", "pinpointmcp"]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
If you have a token ready, you can pass it as an environment variable to skip the interactive setup:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"mcpServers": {
|
|
83
|
+
"pinpoint": {
|
|
84
|
+
"command": "npx",
|
|
85
|
+
"args": ["-y", "pinpointmcp"],
|
|
86
|
+
"env": {
|
|
87
|
+
"PINPOINT_TOKEN": "your-token-here"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Claude Desktop
|
|
95
|
+
|
|
96
|
+
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, `%APPDATA%\Claude\claude_desktop_config.json` on Windows):
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"mcpServers": {
|
|
101
|
+
"pinpoint": {
|
|
102
|
+
"command": "npx",
|
|
103
|
+
"args": ["-y", "pinpointmcp"]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Tools
|
|
110
|
+
|
|
111
|
+
### configure
|
|
112
|
+
|
|
113
|
+
Configure the server with an API token at runtime. Validates the token before persisting.
|
|
114
|
+
|
|
115
|
+
| Parameter | Type | Required | Default | Description |
|
|
116
|
+
|-----------|------|----------|---------|-------------|
|
|
117
|
+
| `token` | string | Yes | n/a | Your Pinpoint API token |
|
|
118
|
+
| `api_url` | string | No | `https://api.testwithpinpoint.com` | API base URL for self-hosted instances |
|
|
119
|
+
|
|
120
|
+
**Behavior:**
|
|
121
|
+
- Validates the token by making a lightweight API call
|
|
122
|
+
- On success: swaps the live client, persists to `~/.pinpoint/config.json`, returns confirmation
|
|
123
|
+
- On validation failure: returns an error without persisting
|
|
124
|
+
- On write failure: configures the current session and warns about persistence
|
|
125
|
+
|
|
126
|
+
### list_bugs
|
|
127
|
+
|
|
128
|
+
List bug reports filtered by status and project.
|
|
129
|
+
|
|
130
|
+
| Parameter | Type | Required | Default | Description |
|
|
131
|
+
|-----------|------|----------|---------|-------------|
|
|
132
|
+
| `status` | string | No | `"open"` | Filter: `open`, `in_progress`, `resolved`, `closed` |
|
|
133
|
+
| `project` | string | No | n/a | Filter by project name |
|
|
134
|
+
| `page` | number | No | `0` | Page number (0-indexed) |
|
|
135
|
+
| `size` | number | No | `20` | Page size (max 200) |
|
|
136
|
+
|
|
137
|
+
### get_bug
|
|
138
|
+
|
|
139
|
+
Get detailed information about a specific bug report, including description, reproduction steps, expected/actual behavior, and environment.
|
|
140
|
+
|
|
141
|
+
| Parameter | Type | Required | Description |
|
|
142
|
+
|-----------|------|----------|-------------|
|
|
143
|
+
| `id` | string | Yes | Bug report UUID |
|
|
144
|
+
|
|
145
|
+
### update_bug_status
|
|
146
|
+
|
|
147
|
+
Update the status of a bug report.
|
|
148
|
+
|
|
149
|
+
| Parameter | Type | Required | Description |
|
|
150
|
+
|-----------|------|----------|-------------|
|
|
151
|
+
| `id` | string | Yes | Bug report UUID |
|
|
152
|
+
| `status` | enum | Yes | New status: `open`, `in_progress`, `resolved`, `closed` |
|
|
153
|
+
| `resolution` | string | No | Resolution notes (recommended when resolving) |
|
|
154
|
+
|
|
155
|
+
## Resources
|
|
156
|
+
|
|
157
|
+
| URI | Format | Description |
|
|
158
|
+
|-----|--------|-------------|
|
|
159
|
+
| `pinpoint://bugs` | JSON | All open bugs as a JSON array |
|
|
160
|
+
| `pinpoint://bugs/{id}` | Markdown | Detailed bug report rendered as Markdown |
|
|
161
|
+
|
|
162
|
+
## Prompts
|
|
163
|
+
|
|
164
|
+
### solve_bug
|
|
165
|
+
|
|
166
|
+
Structured prompt for analyzing and fixing a specific bug. Assembles the title, description, reproduction steps, expected/actual behavior, and environment into a context block, then asks the agent to identify the root cause, implement a fix, and create a merge request.
|
|
167
|
+
|
|
168
|
+
| Argument | Type | Required | Description |
|
|
169
|
+
|----------|------|----------|-------------|
|
|
170
|
+
| `bug_id` | string | Yes | UUID of the bug to solve |
|
|
171
|
+
|
|
172
|
+
## Development
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
npm run dev # Watch mode (recompiles on save)
|
|
176
|
+
npm run build # Compile TypeScript to dist/
|
|
177
|
+
npm test # Run all tests
|
|
178
|
+
npm start # Start the server on stdio
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Test Suite
|
|
182
|
+
|
|
183
|
+
77 tests across 10 files:
|
|
184
|
+
|
|
185
|
+
| File | Tests | Coverage |
|
|
186
|
+
|------|-------|----------|
|
|
187
|
+
| `client.test.ts` | 16 | HTTP client behavior, URL construction, headers, error hierarchy |
|
|
188
|
+
| `client-holder.test.ts` | 7 | Mutable client wrapper: configured/unconfigured states, swap |
|
|
189
|
+
| `config.test.ts` | 6 | Config precedence: env var > dotfile > unconfigured |
|
|
190
|
+
| `config-store.test.ts` | 7 | Dotfile read/write, permissions, invalid JSON handling |
|
|
191
|
+
| `configure-tool.test.ts` | 6 | Token validation, persistence, failure modes, truncation |
|
|
192
|
+
| `setup-message.test.ts` | 5 | Setup guidance text for tools, resources, prompts |
|
|
193
|
+
| `tools.test.ts` | 11 | Tool output formatting, pagination, error wrapping, unconfigured guard |
|
|
194
|
+
| `resources.test.ts` | 8 | Static and templated resources, array variables, unconfigured guard |
|
|
195
|
+
| `prompts.test.ts` | 3 | Prompt assembly, null section omission, unconfigured guard |
|
|
196
|
+
| `integration.test.ts` | 8 | Full MCP handshake via `InMemoryTransport`, schema validation |
|
|
197
|
+
|
|
198
|
+
**Coverage:** 100% statements, 100% lines, 100% functions, 93% branches.
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
npx vitest run --coverage # Run with coverage report
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Architecture
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
src/
|
|
208
|
+
index.ts Entry point: loads config, creates holder, registers handlers
|
|
209
|
+
config.ts Async config loader (env var > dotfile > unconfigured)
|
|
210
|
+
config-store.ts ~/.pinpoint/config.json read/write with 0600 permissions
|
|
211
|
+
client.ts PinpointClient HTTP client for the Pinpoint API
|
|
212
|
+
client-holder.ts Mutable wrapper so the client can be swapped at runtime
|
|
213
|
+
setup-message.ts Shared setup guidance for unconfigured state
|
|
214
|
+
types.ts TypeScript types and error class hierarchy
|
|
215
|
+
tools/
|
|
216
|
+
configure.ts configure tool (token validation + persistence)
|
|
217
|
+
list-bugs.ts list_bugs tool handler
|
|
218
|
+
get-bug.ts get_bug tool handler
|
|
219
|
+
update-bug-status.ts update_bug_status tool handler
|
|
220
|
+
index.ts Barrel export
|
|
221
|
+
resources/
|
|
222
|
+
bugs.ts Static bug list and templated bug detail resources
|
|
223
|
+
index.ts Barrel export
|
|
224
|
+
prompts/
|
|
225
|
+
solve-bug.ts solve_bug prompt handler
|
|
226
|
+
index.ts Barrel export
|
|
227
|
+
__tests__/
|
|
228
|
+
client.test.ts
|
|
229
|
+
client-holder.test.ts
|
|
230
|
+
config.test.ts
|
|
231
|
+
config-store.test.ts
|
|
232
|
+
configure-tool.test.ts
|
|
233
|
+
setup-message.test.ts
|
|
234
|
+
tools.test.ts
|
|
235
|
+
resources.test.ts
|
|
236
|
+
prompts.test.ts
|
|
237
|
+
integration.test.ts
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### First-Boot Flow
|
|
241
|
+
|
|
242
|
+
When no token is available (no env var, no dotfile), the server starts in an unconfigured state:
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
┌───────────────┐ ┌──────────────┐ ┌───────────────────┐
|
|
246
|
+
│ loadConfig() │────>│ ClientHolder │────>│ McpServer starts │
|
|
247
|
+
│ token = null │ │ client = null│ │ on stdio transport │
|
|
248
|
+
└───────────────┘ └──────────────┘ └───────────────────┘
|
|
249
|
+
│
|
|
250
|
+
┌────────────────────────┴──────────────────────┐
|
|
251
|
+
│ │
|
|
252
|
+
Agent calls any tool Agent calls "configure"
|
|
253
|
+
(list_bugs, get_bug, ...) with { token: "..." }
|
|
254
|
+
│ │
|
|
255
|
+
Returns setup guidance Validates token via API
|
|
256
|
+
with instructions to Swaps client on holder
|
|
257
|
+
call "configure" Persists to dotfile
|
|
258
|
+
Returns confirmation
|
|
259
|
+
│
|
|
260
|
+
All tools now work normally
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Troubleshooting
|
|
264
|
+
|
|
265
|
+
- **Server starts but tools return setup guidance:** No token is configured. Either set `PINPOINT_TOKEN`, create the dotfile, or ask the agent to call the `configure` tool.
|
|
266
|
+
- **"Token validation failed":** The token was rejected by the API. Verify it is valid and has not expired. Check the Pinpoint dashboard under Settings > API Tokens.
|
|
267
|
+
- **"Could not save configuration to disk":** The server configured successfully for this session but could not write `~/.pinpoint/config.json`. Check directory permissions on `~/.pinpoint/`.
|
|
268
|
+
- **"Authentication failed" on tool calls:** Your stored or environment token may have expired. Re-run the `configure` tool with a fresh token.
|
|
269
|
+
- **Connection errors:** Check `PINPOINT_API_URL` and confirm network connectivity to the API.
|
|
270
|
+
- **Server not discovered by Claude Code:** Ensure `.mcp.json` exists in the project root and that `npm run build` has been executed so `dist/index.js` is present.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export class ClientHolder {
|
|
2
|
+
client;
|
|
3
|
+
constructor(client = null) {
|
|
4
|
+
this.client = client;
|
|
5
|
+
}
|
|
6
|
+
isConfigured() {
|
|
7
|
+
return this.client !== null;
|
|
8
|
+
}
|
|
9
|
+
getClient() {
|
|
10
|
+
if (!this.client) {
|
|
11
|
+
throw new Error("Pinpoint client is not configured. Call the configure tool first.");
|
|
12
|
+
}
|
|
13
|
+
return this.client;
|
|
14
|
+
}
|
|
15
|
+
setClient(client) {
|
|
16
|
+
this.client = client;
|
|
17
|
+
}
|
|
18
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BugReport, PaginatedResponse, ListBugsOptions } from "./types.js";
|
|
2
|
+
export declare class PinpointClient {
|
|
3
|
+
private baseUrl;
|
|
4
|
+
private token;
|
|
5
|
+
constructor(baseUrl: string, token: string);
|
|
6
|
+
listBugs(options?: ListBugsOptions): Promise<PaginatedResponse<BugReport>>;
|
|
7
|
+
getBug(id: string): Promise<BugReport>;
|
|
8
|
+
updateBugStatus(id: string, status: string, resolution?: string): Promise<BugReport>;
|
|
9
|
+
private request;
|
|
10
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { AuthenticationError, NotFoundError, ServerError, ConnectionError, } from "./types.js";
|
|
2
|
+
export class PinpointClient {
|
|
3
|
+
baseUrl;
|
|
4
|
+
token;
|
|
5
|
+
constructor(baseUrl, token) {
|
|
6
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
7
|
+
this.token = token;
|
|
8
|
+
}
|
|
9
|
+
async listBugs(options) {
|
|
10
|
+
const params = new URLSearchParams();
|
|
11
|
+
params.set("status", options?.status ?? "open");
|
|
12
|
+
if (options?.project)
|
|
13
|
+
params.set("project", options.project);
|
|
14
|
+
params.set("page", String(options?.page ?? 0));
|
|
15
|
+
params.set("size", String(options?.size ?? 20));
|
|
16
|
+
return this.request(`/api/v1/bugs?${params.toString()}`);
|
|
17
|
+
}
|
|
18
|
+
async getBug(id) {
|
|
19
|
+
return this.request(`/api/v1/bugs/${encodeURIComponent(id)}`);
|
|
20
|
+
}
|
|
21
|
+
async updateBugStatus(id, status, resolution) {
|
|
22
|
+
const body = { status };
|
|
23
|
+
if (resolution !== undefined) {
|
|
24
|
+
body.resolution = resolution;
|
|
25
|
+
}
|
|
26
|
+
return this.request(`/api/v1/bugs/${encodeURIComponent(id)}/status`, {
|
|
27
|
+
method: "PATCH",
|
|
28
|
+
headers: { "Content-Type": "application/json" },
|
|
29
|
+
body: JSON.stringify(body),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async request(path, init) {
|
|
33
|
+
const url = `${this.baseUrl}${path}`;
|
|
34
|
+
let response;
|
|
35
|
+
try {
|
|
36
|
+
response = await fetch(url, {
|
|
37
|
+
...init,
|
|
38
|
+
headers: {
|
|
39
|
+
Authorization: `Bearer ${this.token}`,
|
|
40
|
+
Accept: "application/json",
|
|
41
|
+
...init?.headers,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
throw new ConnectionError(this.baseUrl);
|
|
47
|
+
}
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
switch (response.status) {
|
|
50
|
+
case 401:
|
|
51
|
+
throw new AuthenticationError();
|
|
52
|
+
case 404:
|
|
53
|
+
throw new NotFoundError();
|
|
54
|
+
default:
|
|
55
|
+
if (response.status >= 500) {
|
|
56
|
+
throw new ServerError(`Server error: ${response.status} ${response.statusText}`, response.status);
|
|
57
|
+
}
|
|
58
|
+
throw new ServerError(`Request failed: ${response.status} ${response.statusText}`, response.status);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return response.json();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface StoredConfig {
|
|
2
|
+
token: string;
|
|
3
|
+
apiUrl: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function getConfigPath(): string;
|
|
6
|
+
export declare function readStoredConfig(): Promise<StoredConfig | null>;
|
|
7
|
+
export declare function writeStoredConfig(config: StoredConfig): Promise<void>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
export function getConfigPath() {
|
|
5
|
+
return join(homedir(), ".pinpoint", "config.json");
|
|
6
|
+
}
|
|
7
|
+
export async function readStoredConfig() {
|
|
8
|
+
try {
|
|
9
|
+
const raw = await readFile(getConfigPath(), "utf-8");
|
|
10
|
+
const parsed = JSON.parse(raw);
|
|
11
|
+
if (typeof parsed.token === "string" && typeof parsed.apiUrl === "string") {
|
|
12
|
+
return { token: parsed.token, apiUrl: parsed.apiUrl };
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function writeStoredConfig(config) {
|
|
21
|
+
const configPath = getConfigPath();
|
|
22
|
+
const dir = join(homedir(), ".pinpoint");
|
|
23
|
+
await mkdir(dir, { recursive: true });
|
|
24
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", {
|
|
25
|
+
mode: 0o600,
|
|
26
|
+
});
|
|
27
|
+
}
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readStoredConfig } from "./config-store.js";
|
|
2
|
+
export async function loadConfig() {
|
|
3
|
+
// Env var takes highest precedence
|
|
4
|
+
const envToken = process.env.PINPOINT_TOKEN ?? null;
|
|
5
|
+
const envUrl = process.env.PINPOINT_API_URL;
|
|
6
|
+
if (envToken) {
|
|
7
|
+
return {
|
|
8
|
+
token: envToken,
|
|
9
|
+
apiUrl: envUrl || "https://api.testwithpinpoint.com",
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
// Fall back to dotfile
|
|
13
|
+
const stored = await readStoredConfig();
|
|
14
|
+
if (stored) {
|
|
15
|
+
return {
|
|
16
|
+
token: stored.token,
|
|
17
|
+
apiUrl: envUrl || stored.apiUrl,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// Start unconfigured
|
|
21
|
+
return {
|
|
22
|
+
token: null,
|
|
23
|
+
apiUrl: envUrl || "https://api.testwithpinpoint.com",
|
|
24
|
+
};
|
|
25
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { loadConfig } from "./config.js";
|
|
5
|
+
import { PinpointClient } from "./client.js";
|
|
6
|
+
import { ClientHolder } from "./client-holder.js";
|
|
7
|
+
import { registerConfigureTool, registerListBugsTool, registerGetBugTool, registerUpdateBugStatusTool } from "./tools/index.js";
|
|
8
|
+
import { registerBugListResource, registerBugDetailResource } from "./resources/index.js";
|
|
9
|
+
import { registerSolveBugPrompt } from "./prompts/index.js";
|
|
10
|
+
async function main() {
|
|
11
|
+
const config = await loadConfig();
|
|
12
|
+
const holder = new ClientHolder(config.token ? new PinpointClient(config.apiUrl, config.token) : null);
|
|
13
|
+
const server = new McpServer({
|
|
14
|
+
name: "pinpoint",
|
|
15
|
+
version: "0.1.0",
|
|
16
|
+
});
|
|
17
|
+
// Register configure tool first (always available)
|
|
18
|
+
registerConfigureTool(server, holder);
|
|
19
|
+
// Register bug tools, resources, and prompts
|
|
20
|
+
registerListBugsTool(server, holder);
|
|
21
|
+
registerGetBugTool(server, holder);
|
|
22
|
+
registerUpdateBugStatusTool(server, holder);
|
|
23
|
+
registerBugListResource(server, holder);
|
|
24
|
+
registerBugDetailResource(server, holder);
|
|
25
|
+
registerSolveBugPrompt(server, holder);
|
|
26
|
+
const transport = new StdioServerTransport();
|
|
27
|
+
await server.connect(transport);
|
|
28
|
+
if (holder.isConfigured()) {
|
|
29
|
+
console.error("Pinpoint MCP server running on stdio");
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
console.error("Pinpoint MCP server running on stdio (unconfigured, use the configure tool to set your API token)");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
main().catch(console.error);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerSolveBugPrompt } from "./solve-bug.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerSolveBugPrompt } from "./solve-bug.js";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { unconfiguredPromptResult } from "../setup-message.js";
|
|
3
|
+
export function registerSolveBugPrompt(server, holder) {
|
|
4
|
+
server.prompt("solve_bug", "Structured prompt for analyzing and fixing a specific bug", { bug_id: z.string().describe("UUID of the bug to solve") }, async ({ bug_id }) => {
|
|
5
|
+
if (!holder.isConfigured())
|
|
6
|
+
return unconfiguredPromptResult();
|
|
7
|
+
const bug = await holder.getClient().getBug(bug_id);
|
|
8
|
+
const text = [
|
|
9
|
+
`Please help me fix the following bug:\n`,
|
|
10
|
+
`## ${bug.title}`,
|
|
11
|
+
`**Severity:** ${bug.severity} | **Component:** ${bug.component || "Unknown"}`,
|
|
12
|
+
bug.description ? `\n### Description\n${bug.description}` : "",
|
|
13
|
+
bug.stepsToReproduce ? `\n### Steps to Reproduce\n${bug.stepsToReproduce}` : "",
|
|
14
|
+
bug.expectedBehavior ? `\n### Expected Behavior\n${bug.expectedBehavior}` : "",
|
|
15
|
+
bug.actualBehavior ? `\n### Actual Behavior\n${bug.actualBehavior}` : "",
|
|
16
|
+
bug.environment ? `\n### Environment\n${bug.environment}` : "",
|
|
17
|
+
`\nPlease analyze the bug, identify the root cause, implement a fix, and create a merge request.`,
|
|
18
|
+
].filter(Boolean).join("\n");
|
|
19
|
+
return {
|
|
20
|
+
messages: [{
|
|
21
|
+
role: "user",
|
|
22
|
+
content: {
|
|
23
|
+
type: "text",
|
|
24
|
+
text,
|
|
25
|
+
},
|
|
26
|
+
}],
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { ClientHolder } from "../client-holder.js";
|
|
3
|
+
export declare function registerBugListResource(server: McpServer, holder: ClientHolder): void;
|
|
4
|
+
export declare function registerBugDetailResource(server: McpServer, holder: ClientHolder): void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { unconfiguredResourceResult } from "../setup-message.js";
|
|
3
|
+
export function registerBugListResource(server, holder) {
|
|
4
|
+
server.resource("bug-list", "pinpoint://bugs", async (uri) => {
|
|
5
|
+
if (!holder.isConfigured())
|
|
6
|
+
return unconfiguredResourceResult(uri.href);
|
|
7
|
+
const result = await holder.getClient().listBugs({ status: "open" });
|
|
8
|
+
return {
|
|
9
|
+
contents: [{
|
|
10
|
+
uri: uri.href,
|
|
11
|
+
mimeType: "application/json",
|
|
12
|
+
text: JSON.stringify(result.content, null, 2),
|
|
13
|
+
}],
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export function registerBugDetailResource(server, holder) {
|
|
18
|
+
server.resource("bug-detail", new ResourceTemplate("pinpoint://bugs/{id}", { list: undefined }), async (uri, variables) => {
|
|
19
|
+
if (!holder.isConfigured())
|
|
20
|
+
return unconfiguredResourceResult(uri.href);
|
|
21
|
+
const id = Array.isArray(variables.id) ? variables.id[0] : variables.id;
|
|
22
|
+
const bug = await holder.getClient().getBug(id);
|
|
23
|
+
const markdown = [
|
|
24
|
+
`# ${bug.title}`,
|
|
25
|
+
`**Severity:** ${bug.severity} | **Status:** ${bug.status} | **Component:** ${bug.component || "N/A"}`,
|
|
26
|
+
bug.description ? `## Description\n${bug.description}` : "",
|
|
27
|
+
bug.stepsToReproduce ? `## Steps to Reproduce\n${bug.stepsToReproduce}` : "",
|
|
28
|
+
bug.expectedBehavior ? `## Expected Behavior\n${bug.expectedBehavior}` : "",
|
|
29
|
+
bug.actualBehavior ? `## Actual Behavior\n${bug.actualBehavior}` : "",
|
|
30
|
+
bug.environment ? `## Environment\n${bug.environment}` : "",
|
|
31
|
+
bug.rootCause ? `## Root Cause\n${bug.rootCause}` : "",
|
|
32
|
+
bug.resolution ? `## Resolution\n${bug.resolution}` : "",
|
|
33
|
+
`\n**Reporter:** ${bug.reporterName} | **Created:** ${bug.createdAt}`,
|
|
34
|
+
].filter(Boolean).join("\n\n");
|
|
35
|
+
return {
|
|
36
|
+
contents: [{
|
|
37
|
+
uri: uri.href,
|
|
38
|
+
mimeType: "text/markdown",
|
|
39
|
+
text: markdown,
|
|
40
|
+
}],
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerBugListResource, registerBugDetailResource } from "./bugs.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerBugListResource, registerBugDetailResource } from "./bugs.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare function getSetupText(): string;
|
|
2
|
+
export declare function unconfiguredToolResult(): {
|
|
3
|
+
content: Array<{
|
|
4
|
+
type: "text";
|
|
5
|
+
text: string;
|
|
6
|
+
}>;
|
|
7
|
+
isError: true;
|
|
8
|
+
};
|
|
9
|
+
export declare function unconfiguredResourceResult(uri: string): {
|
|
10
|
+
contents: Array<{
|
|
11
|
+
uri: string;
|
|
12
|
+
mimeType: string;
|
|
13
|
+
text: string;
|
|
14
|
+
}>;
|
|
15
|
+
};
|
|
16
|
+
export declare function unconfiguredPromptResult(): {
|
|
17
|
+
messages: Array<{
|
|
18
|
+
role: "user";
|
|
19
|
+
content: {
|
|
20
|
+
type: "text";
|
|
21
|
+
text: string;
|
|
22
|
+
};
|
|
23
|
+
}>;
|
|
24
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const SETUP_TEXT = [
|
|
2
|
+
"Pinpoint MCP server is not yet configured.",
|
|
3
|
+
"",
|
|
4
|
+
"To get started, call the `configure` tool with your API token:",
|
|
5
|
+
"",
|
|
6
|
+
' { "token": "your-pinpoint-api-token" }',
|
|
7
|
+
"",
|
|
8
|
+
"You can find your token in the Pinpoint dashboard under Settings > API Tokens.",
|
|
9
|
+
"Optionally pass `api_url` if you use a self-hosted instance.",
|
|
10
|
+
].join("\n");
|
|
11
|
+
export function getSetupText() {
|
|
12
|
+
return SETUP_TEXT;
|
|
13
|
+
}
|
|
14
|
+
export function unconfiguredToolResult() {
|
|
15
|
+
return {
|
|
16
|
+
content: [{ type: "text", text: SETUP_TEXT }],
|
|
17
|
+
isError: true,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function unconfiguredResourceResult(uri) {
|
|
21
|
+
return {
|
|
22
|
+
contents: [{ uri, mimeType: "text/plain", text: SETUP_TEXT }],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function unconfiguredPromptResult() {
|
|
26
|
+
return {
|
|
27
|
+
messages: [
|
|
28
|
+
{
|
|
29
|
+
role: "user",
|
|
30
|
+
content: { type: "text", text: SETUP_TEXT },
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { PinpointClient } from "../client.js";
|
|
3
|
+
import { writeStoredConfig } from "../config-store.js";
|
|
4
|
+
const DEFAULT_API_URL = "https://api.testwithpinpoint.com";
|
|
5
|
+
export function registerConfigureTool(server, holder) {
|
|
6
|
+
server.tool("configure", "Configure the Pinpoint MCP server with your API token", {
|
|
7
|
+
token: z.string().describe("Your Pinpoint API token"),
|
|
8
|
+
api_url: z.string().optional().describe("Pinpoint API URL (defaults to production)"),
|
|
9
|
+
}, async (params) => {
|
|
10
|
+
const apiUrl = params.api_url || DEFAULT_API_URL;
|
|
11
|
+
const client = new PinpointClient(apiUrl, params.token);
|
|
12
|
+
// Validate the token with a lightweight call
|
|
13
|
+
try {
|
|
14
|
+
await client.listBugs({ size: 1 });
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: "text", text: `Token validation failed: ${message}\n\nPlease check your token and try again.` }],
|
|
20
|
+
isError: true,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// Token is valid, swap the client
|
|
24
|
+
holder.setClient(client);
|
|
25
|
+
// Persist to dotfile
|
|
26
|
+
let persisted = true;
|
|
27
|
+
try {
|
|
28
|
+
await writeStoredConfig({ token: params.token, apiUrl });
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
persisted = false;
|
|
32
|
+
}
|
|
33
|
+
const lines = [
|
|
34
|
+
"Pinpoint MCP server configured successfully!",
|
|
35
|
+
"",
|
|
36
|
+
`API URL: ${apiUrl}`,
|
|
37
|
+
`Token: ${params.token.slice(0, 8)}...`,
|
|
38
|
+
];
|
|
39
|
+
if (persisted) {
|
|
40
|
+
lines.push("", "Configuration saved to ~/.pinpoint/config.json for future sessions.");
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
lines.push("", "Warning: Could not save configuration to disk. The token will only be available for this session.");
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { unconfiguredToolResult } from "../setup-message.js";
|
|
3
|
+
export function registerGetBugTool(server, holder) {
|
|
4
|
+
server.tool("get_bug", "Get detailed information about a specific bug report", {
|
|
5
|
+
id: z.string().describe("Bug report UUID"),
|
|
6
|
+
}, async (params) => {
|
|
7
|
+
if (!holder.isConfigured())
|
|
8
|
+
return unconfiguredToolResult();
|
|
9
|
+
try {
|
|
10
|
+
const bug = await holder.getClient().getBug(params.id);
|
|
11
|
+
const sections = [
|
|
12
|
+
`# ${bug.title}`,
|
|
13
|
+
`**Severity:** ${bug.severity} | **Status:** ${bug.status} | **Component:** ${bug.component || "N/A"}`,
|
|
14
|
+
bug.description ? `## Description\n${bug.description}` : null,
|
|
15
|
+
bug.stepsToReproduce ? `## Steps to Reproduce\n${bug.stepsToReproduce}` : null,
|
|
16
|
+
bug.expectedBehavior ? `## Expected Behavior\n${bug.expectedBehavior}` : null,
|
|
17
|
+
bug.actualBehavior ? `## Actual Behavior\n${bug.actualBehavior}` : null,
|
|
18
|
+
bug.environment ? `## Environment\n${bug.environment}` : null,
|
|
19
|
+
bug.rootCause ? `## Root Cause\n${bug.rootCause}` : null,
|
|
20
|
+
bug.resolution ? `## Resolution\n${bug.resolution}` : null,
|
|
21
|
+
`\n**Reporter:** ${bug.reporterName} | **Created:** ${bug.createdAt}`,
|
|
22
|
+
].filter(Boolean).join("\n\n");
|
|
23
|
+
return { content: [{ type: "text", text: sections }] };
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
27
|
+
return { content: [{ type: "text", text: `Error fetching bug: ${message}` }], isError: true };
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { unconfiguredToolResult } from "../setup-message.js";
|
|
3
|
+
export function registerListBugsTool(server, holder) {
|
|
4
|
+
server.tool("list_bugs", "List bug reports filtered by status and project", {
|
|
5
|
+
status: z.string().optional().default("open").describe("Filter by bug status (open, in_progress, resolved, closed)"),
|
|
6
|
+
project: z.string().optional().describe("Filter by project name"),
|
|
7
|
+
page: z.number().optional().default(0).describe("Page number (0-indexed)"),
|
|
8
|
+
size: z.number().optional().default(20).describe("Page size (max 200)"),
|
|
9
|
+
}, async (params) => {
|
|
10
|
+
if (!holder.isConfigured())
|
|
11
|
+
return unconfiguredToolResult();
|
|
12
|
+
try {
|
|
13
|
+
const result = await holder.getClient().listBugs(params);
|
|
14
|
+
const formatted = result.content.map(bug => `[${bug.severity.toUpperCase()}] ${bug.title} (${bug.id})\n Status: ${bug.status} | Component: ${bug.component || "N/A"}`).join("\n\n");
|
|
15
|
+
const summary = `Found ${result.totalElements} bugs (page ${result.page + 1} of ${result.totalPages})`;
|
|
16
|
+
return { content: [{ type: "text", text: `${summary}\n\n${formatted}` }] };
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
+
return { content: [{ type: "text", text: `Error listing bugs: ${message}` }], isError: true };
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { unconfiguredToolResult } from "../setup-message.js";
|
|
3
|
+
export function registerUpdateBugStatusTool(server, holder) {
|
|
4
|
+
server.tool("update_bug_status", "Update the status of a bug report", {
|
|
5
|
+
id: z.string().describe("Bug report UUID"),
|
|
6
|
+
status: z.enum(["open", "in_progress", "resolved", "closed"]).describe("New status"),
|
|
7
|
+
resolution: z.string().optional().describe("Resolution notes (recommended when setting status to resolved)"),
|
|
8
|
+
}, async (params) => {
|
|
9
|
+
if (!holder.isConfigured())
|
|
10
|
+
return unconfiguredToolResult();
|
|
11
|
+
try {
|
|
12
|
+
const bug = await holder.getClient().updateBugStatus(params.id, params.status, params.resolution);
|
|
13
|
+
return {
|
|
14
|
+
content: [{
|
|
15
|
+
type: "text",
|
|
16
|
+
text: `Bug "${bug.title}" status updated to ${bug.status}${bug.resolution ? `\nResolution: ${bug.resolution}` : ""}`,
|
|
17
|
+
}],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
22
|
+
return { content: [{ type: "text", text: `Error updating bug status: ${message}` }], isError: true };
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface BugReport {
|
|
2
|
+
id: string;
|
|
3
|
+
dispatchEventId: string | null;
|
|
4
|
+
reporterName: string;
|
|
5
|
+
title: string;
|
|
6
|
+
severity: string;
|
|
7
|
+
status: string;
|
|
8
|
+
component: string | null;
|
|
9
|
+
description: string | null;
|
|
10
|
+
stepsToReproduce: string | null;
|
|
11
|
+
expectedBehavior: string | null;
|
|
12
|
+
actualBehavior: string | null;
|
|
13
|
+
environment: string | null;
|
|
14
|
+
rootCause: string | null;
|
|
15
|
+
resolution: string | null;
|
|
16
|
+
fileName: string | null;
|
|
17
|
+
fileSize: number | null;
|
|
18
|
+
downloadUrl: string | null;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
updatedAt: string;
|
|
21
|
+
}
|
|
22
|
+
export interface PaginatedResponse<T> {
|
|
23
|
+
content: T[];
|
|
24
|
+
page: number;
|
|
25
|
+
size: number;
|
|
26
|
+
totalElements: number;
|
|
27
|
+
totalPages: number;
|
|
28
|
+
}
|
|
29
|
+
export interface ListBugsOptions {
|
|
30
|
+
status?: string;
|
|
31
|
+
project?: string;
|
|
32
|
+
page?: number;
|
|
33
|
+
size?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface UpdateBugStatusRequest {
|
|
36
|
+
status: string;
|
|
37
|
+
resolution?: string;
|
|
38
|
+
}
|
|
39
|
+
export declare class PinpointError extends Error {
|
|
40
|
+
statusCode?: number | undefined;
|
|
41
|
+
constructor(message: string, statusCode?: number | undefined);
|
|
42
|
+
}
|
|
43
|
+
export declare class AuthenticationError extends PinpointError {
|
|
44
|
+
constructor(message?: string);
|
|
45
|
+
}
|
|
46
|
+
export declare class NotFoundError extends PinpointError {
|
|
47
|
+
constructor(message?: string);
|
|
48
|
+
}
|
|
49
|
+
export declare class ServerError extends PinpointError {
|
|
50
|
+
constructor(message?: string, statusCode?: number);
|
|
51
|
+
}
|
|
52
|
+
export declare class ConnectionError extends PinpointError {
|
|
53
|
+
constructor(url: string);
|
|
54
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Error hierarchy
|
|
2
|
+
export class PinpointError extends Error {
|
|
3
|
+
statusCode;
|
|
4
|
+
constructor(message, statusCode) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.statusCode = statusCode;
|
|
7
|
+
this.name = "PinpointError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export class AuthenticationError extends PinpointError {
|
|
11
|
+
constructor(message = "Authentication failed. Check your API token.") {
|
|
12
|
+
super(message, 401);
|
|
13
|
+
this.name = "AuthenticationError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export class NotFoundError extends PinpointError {
|
|
17
|
+
constructor(message = "Resource not found.") {
|
|
18
|
+
super(message, 404);
|
|
19
|
+
this.name = "NotFoundError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class ServerError extends PinpointError {
|
|
23
|
+
constructor(message = "Server error", statusCode = 500) {
|
|
24
|
+
super(message, statusCode);
|
|
25
|
+
this.name = "ServerError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class ConnectionError extends PinpointError {
|
|
29
|
+
constructor(url) {
|
|
30
|
+
super(`Could not connect to ${url}`);
|
|
31
|
+
this.name = "ConnectionError";
|
|
32
|
+
}
|
|
33
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pinpointmcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Pinpoint bug tracking. Enables AI agents to list, inspect, and resolve bugs.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"pinpointmcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"dev": "tsc --watch",
|
|
18
|
+
"start": "node dist/index.js",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"prepublishOnly": "npm run build && npm test"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"mcp",
|
|
25
|
+
"model-context-protocol",
|
|
26
|
+
"pinpoint",
|
|
27
|
+
"bug-tracking",
|
|
28
|
+
"ai-agent",
|
|
29
|
+
"claude",
|
|
30
|
+
"testing"
|
|
31
|
+
],
|
|
32
|
+
"author": "Pinpoint",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/testwithpinpoint/pinpoint",
|
|
37
|
+
"directory": "code/mcp"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://testwithpinpoint.com",
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
45
|
+
"zod": "^4.3.6"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^25.3.2",
|
|
49
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
50
|
+
"typescript": "^5.9.3",
|
|
51
|
+
"vitest": "^4.0.18"
|
|
52
|
+
}
|
|
53
|
+
}
|