@jetpump/mcp 0.1.0-beta.1 → 0.1.0-beta.3

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/dist/index.js CHANGED
@@ -1,19 +1,49 @@
1
1
  #!/usr/bin/env node
2
+ // Boot order matters:
3
+ // 1. Sentry.init() as early as possible so unhandled errors during
4
+ // subsequent imports are still captured.
5
+ // 2. logger stderr adapter.
6
+ // 3. MCP server + transport.
7
+ import { initSentryMcp, captureException, flushSentry } from './sentry.js';
8
+ initSentryMcp();
2
9
  import './logger.js';
3
10
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
11
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
12
  import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
6
13
  import { tools } from './tools.js';
7
14
  import { handleDeploy, handleListDeploys, handleTeardown } from './handlers.js';
8
- const server = new Server({ name: 'jetpump', version: '0.0.1' }, { capabilities: { tools: {} } });
15
+ import { createRequire } from 'node:module';
16
+ const require = createRequire(import.meta.url);
17
+ const pkg = require('../package.json');
18
+ const server = new Server({ name: 'jetpump', version: pkg.version }, { capabilities: { tools: {} } });
9
19
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
10
20
  server.setRequestHandler(CallToolRequestSchema, async (req) => {
11
21
  const { name, arguments: args } = req.params;
12
- switch (name) {
13
- case 'deploy': return handleDeploy(args);
14
- case 'list_deploys': return handleListDeploys();
15
- case 'teardown': return handleTeardown(args);
16
- default: throw new Error(`Unknown tool: ${name}`);
22
+ try {
23
+ switch (name) {
24
+ case 'deploy': return await handleDeploy(args);
25
+ case 'list_deploys': return await handleListDeploys();
26
+ case 'teardown': return await handleTeardown(args);
27
+ default: throw new Error(`Unknown tool: ${name}`);
28
+ }
29
+ }
30
+ catch (err) {
31
+ // Report to Sentry with scrubbed context then rethrow so the
32
+ // MCP client still sees a tool error.
33
+ captureException(err, {
34
+ tool: name,
35
+ mcp_version: pkg.version,
36
+ // arguments deliberately NOT included — may contain user paths.
37
+ // Sentry's beforeSend scrubs anything that does make it through.
38
+ });
39
+ throw err;
17
40
  }
18
41
  });
42
+ // Best-effort flush on graceful shutdown so in-flight events reach Sentry.
43
+ for (const sig of ['SIGINT', 'SIGTERM']) {
44
+ process.on(sig, async () => {
45
+ await flushSentry(2000);
46
+ process.exit(0);
47
+ });
48
+ }
19
49
  await server.connect(new StdioServerTransport());
package/dist/sentry.js ADDED
@@ -0,0 +1,112 @@
1
+ // Sentry wiring for @jetpump/mcp — runs on the user's machine under
2
+ // Claude Code / Cursor / Windsurf. Every tool invocation is wrapped so
3
+ // any crash in deploy/list/teardown lands in the jetpump-mcp Sentry
4
+ // inbox with the MCP package version as the release tag.
5
+ //
6
+ // The DSN is baked into the published source because Sentry DSNs are
7
+ // designed to be public ingestion keys (they allow writes, not reads).
8
+ // Override via JETPUMP_MCP_SENTRY_DSN for testing or if you fork the
9
+ // package. Setting the env var to the empty string disables Sentry
10
+ // entirely.
11
+ //
12
+ // beforeSend strips anything that smells like a JWT or an absolute
13
+ // filesystem path so user source code filenames don't leak into our
14
+ // inbox. It's a light scrubber — a paranoid user can set
15
+ // JETPUMP_MCP_SENTRY_DSN="" to turn Sentry off.
16
+ import * as Sentry from '@sentry/node';
17
+ import { createRequire } from 'node:module';
18
+ const BAKED_DSN = 'https://6dcaba3d4523d78fe23bd2cd45083724@o4511219499859968.ingest.us.sentry.io/4511219510542336';
19
+ let initialised = false;
20
+ function pkgVersion() {
21
+ try {
22
+ const require = createRequire(import.meta.url);
23
+ const pkg = require('../package.json');
24
+ return pkg.version || 'unknown';
25
+ }
26
+ catch {
27
+ return 'unknown';
28
+ }
29
+ }
30
+ function scrub(obj) {
31
+ if (obj == null)
32
+ return obj;
33
+ if (typeof obj === 'string') {
34
+ // Strip bearer/JWT-ish blobs
35
+ return obj
36
+ .replace(/(Bearer\s+)[A-Za-z0-9._~\-+/=]{20,}/g, '$1<redacted>')
37
+ .replace(/eyJ[A-Za-z0-9._\-]{30,}/g, '<redacted-jwt>')
38
+ // Drop the user portion of absolute home paths
39
+ .replace(/\/Users\/[^/]+/g, '/Users/<user>')
40
+ .replace(/\/home\/[^/]+/g, '/home/<user>');
41
+ }
42
+ if (Array.isArray(obj))
43
+ return obj.map(scrub);
44
+ if (typeof obj === 'object') {
45
+ const out = {};
46
+ for (const [k, v] of Object.entries(obj)) {
47
+ if (/authorization|x-anon-token|jwt|password|secret/i.test(k)) {
48
+ out[k] = '<redacted>';
49
+ }
50
+ else {
51
+ out[k] = scrub(v);
52
+ }
53
+ }
54
+ return out;
55
+ }
56
+ return obj;
57
+ }
58
+ export function initSentryMcp() {
59
+ if (initialised)
60
+ return;
61
+ initialised = true;
62
+ const rawDsn = process.env.JETPUMP_MCP_SENTRY_DSN;
63
+ const dsn = rawDsn === undefined ? BAKED_DSN : rawDsn; // explicit empty string disables
64
+ if (!dsn)
65
+ return;
66
+ try {
67
+ Sentry.init({
68
+ dsn,
69
+ release: `@jetpump/mcp@${pkgVersion()}`,
70
+ environment: process.env.NODE_ENV || 'production',
71
+ tracesSampleRate: 0,
72
+ beforeSend(event) {
73
+ try {
74
+ if (event.extra)
75
+ event.extra = scrub(event.extra);
76
+ if (event.tags)
77
+ event.tags = scrub(event.tags);
78
+ if (event.breadcrumbs) {
79
+ event.breadcrumbs = event.breadcrumbs.map((b) => ({
80
+ ...b,
81
+ data: scrub(b.data),
82
+ message: typeof b.message === 'string' ? scrub(b.message) : b.message,
83
+ }));
84
+ }
85
+ }
86
+ catch {
87
+ // If scrubbing itself throws, drop the event defensively.
88
+ return null;
89
+ }
90
+ return event;
91
+ },
92
+ });
93
+ }
94
+ catch (err) {
95
+ // Never fail the MCP startup because of Sentry.
96
+ process.stderr.write(`[jetpump-mcp] sentry init failed: ${err.message}\n`);
97
+ }
98
+ }
99
+ export function captureException(err, extra = {}) {
100
+ try {
101
+ Sentry.captureException(err, { extra: scrub(extra) });
102
+ }
103
+ catch {
104
+ // swallow — Sentry transport is best-effort
105
+ }
106
+ }
107
+ export async function flushSentry(timeoutMs = 2000) {
108
+ try {
109
+ await Sentry.flush(timeoutMs);
110
+ }
111
+ catch { /* ignore */ }
112
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetpump/mcp",
3
- "version": "0.1.0-beta.1",
3
+ "version": "0.1.0-beta.3",
4
4
  "description": "Jetpump MCP server — deploy the current directory from Claude Code.",
5
5
  "homepage": "https://jetpump.dev",
6
6
  "license": "MIT",
@@ -24,6 +24,7 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@modelcontextprotocol/sdk": "^1.0.4",
27
+ "@sentry/node": "^8.47.0",
27
28
  "archiver": "^7.0.1",
28
29
  "ignore": "^6.0.2"
29
30
  },