patchwork-os 0.2.0-alpha.3 → 0.2.0-alpha.4

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.
Files changed (51) hide show
  1. package/dist/bridge.js +23 -10
  2. package/dist/bridge.js.map +1 -1
  3. package/dist/connectors/github.d.ts +58 -8
  4. package/dist/connectors/github.js +321 -84
  5. package/dist/connectors/github.js.map +1 -1
  6. package/dist/connectors/gmail.js +7 -0
  7. package/dist/connectors/gmail.js.map +1 -1
  8. package/dist/connectors/googleCalendar.d.ts +57 -0
  9. package/dist/connectors/googleCalendar.js +308 -0
  10. package/dist/connectors/googleCalendar.js.map +1 -0
  11. package/dist/connectors/linear.d.ts +52 -19
  12. package/dist/connectors/linear.js +167 -129
  13. package/dist/connectors/linear.js.map +1 -1
  14. package/dist/connectors/mcpClient.d.ts +56 -0
  15. package/dist/connectors/mcpClient.js +189 -0
  16. package/dist/connectors/mcpClient.js.map +1 -0
  17. package/dist/connectors/mcpOAuth.d.ts +73 -0
  18. package/dist/connectors/mcpOAuth.js +338 -0
  19. package/dist/connectors/mcpOAuth.js.map +1 -0
  20. package/dist/connectors/sentry.d.ts +17 -21
  21. package/dist/connectors/sentry.js +124 -131
  22. package/dist/connectors/sentry.js.map +1 -1
  23. package/dist/index.js +1 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/recipes/yamlRunner.js +32 -42
  26. package/dist/recipes/yamlRunner.js.map +1 -1
  27. package/dist/recipesHttp.d.ts +13 -1
  28. package/dist/recipesHttp.js +9 -1
  29. package/dist/recipesHttp.js.map +1 -1
  30. package/dist/server.d.ts +3 -1
  31. package/dist/server.js +220 -49
  32. package/dist/server.js.map +1 -1
  33. package/dist/tools/createLinearIssue.d.ts +84 -0
  34. package/dist/tools/createLinearIssue.js +146 -0
  35. package/dist/tools/createLinearIssue.js.map +1 -0
  36. package/dist/tools/fetchCalendarEvents.d.ts +94 -0
  37. package/dist/tools/fetchCalendarEvents.js +97 -0
  38. package/dist/tools/fetchCalendarEvents.js.map +1 -0
  39. package/dist/tools/fetchGithubIssue.d.ts +80 -0
  40. package/dist/tools/fetchGithubIssue.js +84 -0
  41. package/dist/tools/fetchGithubIssue.js.map +1 -0
  42. package/dist/tools/fetchGithubPR.d.ts +89 -0
  43. package/dist/tools/fetchGithubPR.js +96 -0
  44. package/dist/tools/fetchGithubPR.js.map +1 -0
  45. package/dist/tools/index.js +8 -0
  46. package/dist/tools/index.js.map +1 -1
  47. package/package.json +1 -1
  48. package/scripts/start-all.sh +56 -19
  49. package/templates/recipes/ctx-loop-test.yaml +75 -0
  50. package/templates/recipes/morning-brief.yaml +12 -4
  51. package/templates/recipes/sentry-to-linear.yaml +77 -0
@@ -1,167 +1,184 @@
1
1
  /**
2
- * Linear connector.
2
+ * Linear connector — routes through Linear's official MCP server.
3
3
  *
4
- * Uses Linear's GraphQL API with a personal API key (no OAuth app required).
5
- * Token stored at ~/.patchwork/tokens/linear.json (mode 0600).
6
- * Env var: LINEAR_API_KEY
4
+ * Endpoint: https://mcp.linear.app/mcp
5
+ * Auth: OAuth 2.1 w/ PKCE; dynamic client registration (RFC 7591).
7
6
  *
8
- * HTTP routes registered in server.ts:
9
- * POST /connections/linear/connect store token + verify
10
- * POST /connections/linear/test verify stored token works
11
- * DELETE /connections/linear delete stored token
7
+ * HTTP routes (wired in src/server.ts):
8
+ * GET /connections/linear/authorize returns { url } for popup
9
+ * GET /connections/linear/callback — token exchange
10
+ * POST /connections/linear/test ping MCP server
11
+ * DELETE /connections/linear — revoke + delete token
12
+ *
13
+ * Back-compat: loadTokens() returns a shape compatible with legacy code
14
+ * that expected { api_key }. Set LINEAR_API_KEY to bypass OAuth for CI/headless.
12
15
  */
13
- import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
14
- import { homedir } from "node:os";
15
- import path from "node:path";
16
- const LINEAR_API = "https://api.linear.app/graphql";
17
- const TOKEN_PATH = path.join(homedir(), ".patchwork", "tokens", "linear.json");
18
- // ── Token storage ─────────────────────────────────────────────────────────────
16
+ import { McpClient } from "./mcpClient.js";
17
+ import { completeAuthorize, getAccessToken, loadTokenFile, revoke, startAuthorize, vendorConfig, } from "./mcpOAuth.js";
18
+ const LINEAR_MCP_ENDPOINT = "https://mcp.linear.app/mcp";
19
+ // ── MCP client ───────────────────────────────────────────────────────────────
20
+ let _client = null;
21
+ function client() {
22
+ if (!_client) {
23
+ _client = new McpClient(LINEAR_MCP_ENDPOINT, async () => {
24
+ const envKey = process.env.LINEAR_API_KEY;
25
+ if (envKey)
26
+ return envKey;
27
+ return getAccessToken("linear");
28
+ });
29
+ }
30
+ return _client;
31
+ }
32
+ // ── Back-compat token loader ─────────────────────────────────────────────────
19
33
  export function loadTokens() {
20
34
  const envKey = process.env.LINEAR_API_KEY;
21
35
  if (envKey) {
22
36
  return { api_key: envKey, connected_at: new Date().toISOString() };
23
37
  }
24
- if (!existsSync(TOKEN_PATH))
25
- return null;
26
- try {
27
- return JSON.parse(readFileSync(TOKEN_PATH, "utf-8"));
28
- }
29
- catch {
38
+ const file = loadTokenFile("linear");
39
+ if (!file)
30
40
  return null;
31
- }
32
- }
33
- function saveTokens(tokens) {
34
- mkdirSync(path.dirname(TOKEN_PATH), { recursive: true, mode: 0o700 });
35
- writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2), { mode: 0o600 });
36
- }
37
- function deleteTokens() {
38
- if (existsSync(TOKEN_PATH))
39
- unlinkSync(TOKEN_PATH);
40
- }
41
- export function getStatus() {
42
- const tokens = loadTokens();
43
41
  return {
44
- id: "linear",
45
- status: tokens ? "connected" : "disconnected",
46
- lastSync: tokens?.connected_at,
47
- workspace: tokens?.workspace,
42
+ api_key: file.access_token,
43
+ workspace: file.profile?.workspace,
44
+ connected_at: file.connected_at,
48
45
  };
49
46
  }
50
- // ── GraphQL helpers ───────────────────────────────────────────────────────────
51
- export async function linearQuery(query, variables, apiKey, signal) {
52
- const res = await fetch(LINEAR_API, {
53
- method: "POST",
54
- headers: {
55
- "Content-Type": "application/json",
56
- Authorization: apiKey,
57
- },
58
- body: JSON.stringify({ query, variables }),
59
- signal,
60
- });
61
- if (!res.ok) {
62
- const body = await res.text();
63
- throw new Error(`Linear API error ${res.status}: ${body.slice(0, 200)}`);
64
- }
65
- const json = (await res.json());
66
- if (json.errors?.length) {
67
- throw new Error(`Linear GraphQL error: ${json.errors.map((e) => e.message).join(", ")}`);
47
+ export function getStatus() {
48
+ const envKey = process.env.LINEAR_API_KEY;
49
+ if (envKey) {
50
+ return { id: "linear", status: "connected" };
68
51
  }
69
- return json.data;
70
- }
71
- const VIEWER_QUERY = `query { viewer { id name email organization { name urlKey } } }`;
72
- async function verifyToken(apiKey, signal) {
73
- const data = await linearQuery(VIEWER_QUERY, {}, apiKey, signal);
52
+ const file = loadTokenFile("linear");
74
53
  return {
75
- name: data.viewer.name,
76
- email: data.viewer.email,
77
- workspace: data.viewer.organization.urlKey,
54
+ id: "linear",
55
+ status: file ? "connected" : "disconnected",
56
+ lastSync: file?.connected_at,
57
+ workspace: file?.profile?.workspace,
78
58
  };
79
59
  }
80
- const ISSUE_QUERY = `
81
- query GetIssue($id: String!) {
82
- issue(id: $id) {
83
- id
84
- identifier
85
- title
86
- description
87
- state { name type }
88
- assignee { name email }
89
- priority
90
- priorityLabel
91
- url
92
- createdAt
93
- updatedAt
94
- team { name key }
95
- labels { nodes { name } }
96
- }
97
- }
98
- `;
99
- /**
100
- * Fetch a Linear issue by ID or URL.
101
- * Accepts: "LIN-123", "abc123def456...", "https://linear.app/.../issue/LIN-123/..."
102
- */
103
- export async function fetchIssue(issueIdOrUrl, signal) {
104
- const tokens = loadTokens();
105
- if (!tokens) {
106
- throw new Error("Linear not connected. POST /connections/linear/connect first.");
107
- }
108
- const id = extractIssueId(issueIdOrUrl);
109
- const data = await linearQuery(ISSUE_QUERY, { id }, tokens.api_key, signal);
110
- if (!data.issue) {
111
- throw new Error(`Linear issue not found: ${id}`);
112
- }
113
- return data.issue;
114
- }
115
60
  function extractIssueId(issueIdOrUrl) {
116
- // URL form: https://linear.app/org/issue/LIN-123/title
117
61
  const urlMatch = issueIdOrUrl.match(/\/issue\/([A-Z]+-\d+|[a-f0-9-]{36})/i);
118
62
  if (urlMatch)
119
63
  return urlMatch[1];
120
- // Identifier form: LIN-123, TEAM-456
121
- if (/^[A-Z]+-\d+$/i.test(issueIdOrUrl.trim()))
122
- return issueIdOrUrl.trim();
123
- // UUID form
124
- if (/^[a-f0-9-]{36}$/i.test(issueIdOrUrl.trim()))
125
- return issueIdOrUrl.trim();
64
+ const trimmed = issueIdOrUrl.trim();
65
+ if (/^[A-Z]+-\d+$/i.test(trimmed))
66
+ return trimmed;
67
+ if (/^[a-f0-9-]{36}$/i.test(trimmed))
68
+ return trimmed;
126
69
  throw new Error(`Cannot parse Linear issue ID from: ${issueIdOrUrl}`);
127
70
  }
128
- export async function handleLinearConnect(body) {
129
- const { api_key } = (body ?? {});
130
- if (!api_key || typeof api_key !== "string") {
71
+ export async function fetchIssue(issueIdOrUrl, signal) {
72
+ if (!loadTokens())
73
+ throw new Error("Linear not connected. GET /connections/linear/authorize first.");
74
+ const id = extractIssueId(issueIdOrUrl);
75
+ const res = await client().callTool("get_issue", { id }, { signal });
76
+ const parsed = McpClient.extractJson(res);
77
+ const issue = parsed.issue ?? parsed;
78
+ if (!issue)
79
+ throw new Error(`Linear issue not found: ${id}`);
80
+ return issue;
81
+ }
82
+ export async function listIssues(opts = {}, signal) {
83
+ if (!loadTokens())
84
+ return [];
85
+ const args = {
86
+ limit: Math.min(opts.limit ?? 20, 50),
87
+ };
88
+ if (opts.team)
89
+ args.team = opts.team;
90
+ if (opts.assigneeMe)
91
+ args.assignee = "me";
92
+ if (opts.states?.length)
93
+ args.stateTypes = opts.states;
94
+ try {
95
+ const res = await client().callTool("list_issues", args, {
96
+ signal,
97
+ cacheKey: `linear:issues:${JSON.stringify(args)}`,
98
+ cacheTtlMs: 60_000,
99
+ });
100
+ const parsed = McpClient.extractJson(res);
101
+ if (Array.isArray(parsed))
102
+ return parsed;
103
+ return parsed.issues ?? parsed.nodes ?? [];
104
+ }
105
+ catch (err) {
106
+ if (err instanceof Error && err.message.includes("not connected"))
107
+ throw err;
108
+ return [];
109
+ }
110
+ }
111
+ // ── HTTP handlers ────────────────────────────────────────────────────────────
112
+ export async function handleLinearAuthorize() {
113
+ try {
114
+ const { url } = await startAuthorize(vendorConfig("linear"));
115
+ return { status: 302, body: "", redirect: url };
116
+ }
117
+ catch (err) {
131
118
  return {
132
119
  status: 400,
133
120
  contentType: "application/json",
134
- body: JSON.stringify({ ok: false, error: "api_key required" }),
121
+ body: JSON.stringify({
122
+ ok: false,
123
+ error: err instanceof Error ? err.message : String(err),
124
+ }),
135
125
  };
136
126
  }
137
- try {
138
- const { name, email, workspace } = await verifyToken(api_key);
139
- const tokens = {
140
- api_key,
141
- workspace,
142
- connected_at: new Date().toISOString(),
127
+ }
128
+ export async function handleLinearCallback(code, state, error) {
129
+ if (error) {
130
+ return {
131
+ status: 400,
132
+ contentType: "text/html",
133
+ body: `<html><body><h2>Linear connect failed</h2><pre>${error}</pre></body></html>`,
143
134
  };
144
- saveTokens(tokens);
135
+ }
136
+ if (!code || !state) {
137
+ return {
138
+ status: 400,
139
+ contentType: "text/html",
140
+ body: `<html><body><h2>Linear connect failed</h2><pre>missing code/state</pre></body></html>`,
141
+ };
142
+ }
143
+ try {
144
+ await completeAuthorize(vendorConfig("linear"), code, state);
145
+ // Best-effort profile capture (workspace name)
146
+ try {
147
+ const res = await client().callTool("get_viewer", {}, { timeoutMs: 10_000 });
148
+ const viewer = McpClient.extractJson(res);
149
+ const workspace = viewer.organization?.urlKey ?? viewer.organization?.name ?? "";
150
+ if (workspace) {
151
+ const file = loadTokenFile("linear");
152
+ if (file) {
153
+ const { writeFileSync, mkdirSync } = await import("node:fs");
154
+ const { homedir } = await import("node:os");
155
+ const path = await import("node:path");
156
+ const p = path.join(homedir(), ".patchwork", "tokens", "linear-mcp.json");
157
+ mkdirSync(path.dirname(p), { recursive: true, mode: 0o700 });
158
+ file.profile = { ...(file.profile ?? {}), workspace };
159
+ writeFileSync(p, JSON.stringify(file, null, 2), { mode: 0o600 });
160
+ }
161
+ }
162
+ }
163
+ catch {
164
+ // Profile fetch is best-effort
165
+ }
145
166
  return {
146
167
  status: 200,
147
- contentType: "application/json",
148
- body: JSON.stringify({ ok: true, name, email, workspace }),
168
+ contentType: "text/html",
169
+ body: `<html><body><h2>Linear connected</h2><script>window.close();</script></body></html>`,
149
170
  };
150
171
  }
151
172
  catch (err) {
152
173
  return {
153
174
  status: 400,
154
- contentType: "application/json",
155
- body: JSON.stringify({
156
- ok: false,
157
- error: err instanceof Error ? err.message : String(err),
158
- }),
175
+ contentType: "text/html",
176
+ body: `<html><body><h2>Linear connect failed</h2><pre>${err instanceof Error ? err.message : String(err)}</pre></body></html>`,
159
177
  };
160
178
  }
161
179
  }
162
180
  export async function handleLinearTest() {
163
- const tokens = loadTokens();
164
- if (!tokens) {
181
+ if (!loadTokens()) {
165
182
  return {
166
183
  status: 400,
167
184
  contentType: "application/json",
@@ -169,11 +186,11 @@ export async function handleLinearTest() {
169
186
  };
170
187
  }
171
188
  try {
172
- const { name, email } = await verifyToken(tokens.api_key);
189
+ const ok = await client().ping({ timeoutMs: 10_000 });
173
190
  return {
174
- status: 200,
191
+ status: ok ? 200 : 400,
175
192
  contentType: "application/json",
176
- body: JSON.stringify({ ok: true, name, email }),
193
+ body: JSON.stringify({ ok, message: ok ? "connected" : "ping failed" }),
177
194
  };
178
195
  }
179
196
  catch (err) {
@@ -187,12 +204,33 @@ export async function handleLinearTest() {
187
204
  };
188
205
  }
189
206
  }
190
- export function handleLinearDisconnect() {
191
- deleteTokens();
207
+ export async function handleLinearDisconnect() {
208
+ await revoke("linear");
209
+ _client = null;
192
210
  return {
193
211
  status: 200,
194
212
  contentType: "application/json",
195
213
  body: JSON.stringify({ ok: true }),
196
214
  };
197
215
  }
216
+ export async function listTeams(signal) {
217
+ const res = await client().callTool("list_teams", {}, { signal });
218
+ const parsed = McpClient.extractJson(res);
219
+ if (Array.isArray(parsed))
220
+ return parsed;
221
+ return parsed.teams ?? parsed.nodes ?? [];
222
+ }
223
+ export async function listLabels(signal) {
224
+ const res = await client().callTool("list_issue_labels", {}, { signal });
225
+ const parsed = McpClient.extractJson(res);
226
+ if (Array.isArray(parsed))
227
+ return parsed;
228
+ return parsed.labels ?? parsed.nodes ?? [];
229
+ }
230
+ export async function createIssue(input, signal) {
231
+ const res = await client().callTool("save_issue", input, { signal });
232
+ const parsed = McpClient.extractJson(res);
233
+ const issue = parsed.issue ?? parsed;
234
+ return issue;
235
+ }
198
236
  //# sourceMappingURL=linear.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"linear.js","sourceRoot":"","sources":["../../src/connectors/linear.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"linear.js","sourceRoot":"","sources":["../../src/connectors/linear.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Minimal Streamable-HTTP MCP client for calling upstream MCP servers
3
+ * (GitHub, Linear, Sentry) from Patchwork connectors.
4
+ *
5
+ * Supports:
6
+ * - initialize handshake
7
+ * - tools/list
8
+ * - tools/call (with argument object)
9
+ * - SSE response parsing (when server returns text/event-stream)
10
+ * - Bearer token auth (OAuth access token)
11
+ * - 15s default timeout
12
+ * - Best-effort 60s in-memory result cache (opt-in per call)
13
+ *
14
+ * Wire format reference: MCP spec 2024-11-05 / 2025-03-26, Streamable HTTP transport.
15
+ */
16
+ type JsonValue = string | number | boolean | null | {
17
+ [k: string]: JsonValue;
18
+ } | JsonValue[];
19
+ export interface McpCallOptions {
20
+ signal?: AbortSignal;
21
+ timeoutMs?: number;
22
+ /** If set, cache the result under this key for `cacheTtlMs` ms. */
23
+ cacheKey?: string;
24
+ cacheTtlMs?: number;
25
+ }
26
+ export interface McpToolResult {
27
+ content: Array<{
28
+ type: string;
29
+ text?: string;
30
+ [k: string]: unknown;
31
+ }>;
32
+ isError?: boolean;
33
+ structuredContent?: JsonValue;
34
+ }
35
+ export declare function clearMcpCache(): void;
36
+ export declare class McpClient {
37
+ private readonly endpoint;
38
+ private readonly getAccessToken;
39
+ private sessionId;
40
+ private initialized;
41
+ private nextId;
42
+ constructor(endpoint: string, getAccessToken: () => Promise<string>);
43
+ private post;
44
+ private ensureInitialized;
45
+ listTools(opts?: McpCallOptions): Promise<Array<{
46
+ name: string;
47
+ description?: string;
48
+ inputSchema?: JsonValue;
49
+ }>>;
50
+ callTool(name: string, args: Record<string, unknown>, opts?: McpCallOptions): Promise<McpToolResult>;
51
+ /** Convenience: extract the first `structuredContent` object, or parse the first text block as JSON. */
52
+ static extractJson<T = unknown>(result: McpToolResult): T;
53
+ /** Ping by listing tools; returns true if reachable + authorized. */
54
+ ping(opts?: McpCallOptions): Promise<boolean>;
55
+ }
56
+ export {};
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Minimal Streamable-HTTP MCP client for calling upstream MCP servers
3
+ * (GitHub, Linear, Sentry) from Patchwork connectors.
4
+ *
5
+ * Supports:
6
+ * - initialize handshake
7
+ * - tools/list
8
+ * - tools/call (with argument object)
9
+ * - SSE response parsing (when server returns text/event-stream)
10
+ * - Bearer token auth (OAuth access token)
11
+ * - 15s default timeout
12
+ * - Best-effort 60s in-memory result cache (opt-in per call)
13
+ *
14
+ * Wire format reference: MCP spec 2024-11-05 / 2025-03-26, Streamable HTTP transport.
15
+ */
16
+ const cache = new Map();
17
+ const DEFAULT_TIMEOUT = 15_000;
18
+ function getCached(key) {
19
+ const e = cache.get(key);
20
+ if (!e)
21
+ return null;
22
+ if (Date.now() > e.expiresAt) {
23
+ cache.delete(key);
24
+ return null;
25
+ }
26
+ return e.value;
27
+ }
28
+ function setCached(key, value, ttlMs) {
29
+ cache.set(key, { value, expiresAt: Date.now() + ttlMs });
30
+ }
31
+ export function clearMcpCache() {
32
+ cache.clear();
33
+ }
34
+ /**
35
+ * Parse a Streamable-HTTP response body. The server may reply with:
36
+ * - application/json → single JSON-RPC response
37
+ * - text/event-stream → SSE frames, last `data:` line is the response
38
+ */
39
+ async function parseMcpResponse(res) {
40
+ const ct = res.headers.get("content-type") ?? "";
41
+ const text = await res.text();
42
+ if (ct.includes("text/event-stream")) {
43
+ // Find last `data:` line carrying a JSON payload
44
+ const lines = text.split(/\r?\n/);
45
+ let last = null;
46
+ for (const l of lines) {
47
+ if (l.startsWith("data:")) {
48
+ const payload = l.slice(5).trim();
49
+ if (payload && payload !== "[DONE]")
50
+ last = payload;
51
+ }
52
+ }
53
+ if (!last)
54
+ throw new Error("MCP SSE response had no data frame");
55
+ return JSON.parse(last);
56
+ }
57
+ if (!text)
58
+ return null;
59
+ return JSON.parse(text);
60
+ }
61
+ function withTimeout(signal, ms) {
62
+ const ctl = new AbortController();
63
+ const onAbort = () => ctl.abort(signal?.reason);
64
+ if (signal) {
65
+ if (signal.aborted)
66
+ ctl.abort(signal.reason);
67
+ else
68
+ signal.addEventListener("abort", onAbort, { once: true });
69
+ }
70
+ const t = setTimeout(() => ctl.abort(new Error("MCP request timeout")), ms);
71
+ ctl.signal.addEventListener("abort", () => clearTimeout(t), { once: true });
72
+ return ctl.signal;
73
+ }
74
+ export class McpClient {
75
+ endpoint;
76
+ getAccessToken;
77
+ sessionId = null;
78
+ initialized = false;
79
+ nextId = 1;
80
+ constructor(endpoint, getAccessToken) {
81
+ this.endpoint = endpoint;
82
+ this.getAccessToken = getAccessToken;
83
+ }
84
+ async post(body, opts = {}) {
85
+ const token = await this.getAccessToken();
86
+ const signal = withTimeout(opts.signal, opts.timeoutMs ?? DEFAULT_TIMEOUT);
87
+ const headers = {
88
+ "Content-Type": "application/json",
89
+ Accept: "application/json, text/event-stream",
90
+ Authorization: `Bearer ${token}`,
91
+ };
92
+ if (this.sessionId)
93
+ headers["Mcp-Session-Id"] = this.sessionId;
94
+ const res = await fetch(this.endpoint, {
95
+ method: "POST",
96
+ headers,
97
+ body: JSON.stringify(body),
98
+ signal,
99
+ });
100
+ // Pick up session id if server issued one
101
+ const sid = res.headers.get("mcp-session-id");
102
+ if (sid)
103
+ this.sessionId = sid;
104
+ if (!res.ok) {
105
+ const snippet = (await res.text()).slice(0, 300);
106
+ throw new Error(`MCP HTTP ${res.status} at ${this.endpoint}: ${snippet}`);
107
+ }
108
+ return parseMcpResponse(res);
109
+ }
110
+ async ensureInitialized(opts = {}) {
111
+ if (this.initialized)
112
+ return;
113
+ const id = this.nextId++;
114
+ const resp = (await this.post({
115
+ jsonrpc: "2.0",
116
+ id,
117
+ method: "initialize",
118
+ params: {
119
+ protocolVersion: "2025-03-26",
120
+ capabilities: {},
121
+ clientInfo: { name: "patchwork-os", version: "0.1" },
122
+ },
123
+ }, opts));
124
+ if (resp?.error)
125
+ throw new Error(`MCP initialize failed: ${resp.error.message}`);
126
+ // Notify initialized (fire-and-forget, no id)
127
+ await this.post({ jsonrpc: "2.0", method: "notifications/initialized" }, opts).catch(() => { });
128
+ this.initialized = true;
129
+ }
130
+ async listTools(opts = {}) {
131
+ await this.ensureInitialized(opts);
132
+ const id = this.nextId++;
133
+ const resp = (await this.post({ jsonrpc: "2.0", id, method: "tools/list" }, opts));
134
+ if (resp?.error)
135
+ throw new Error(`tools/list: ${resp.error.message}`);
136
+ return resp.result?.tools ?? [];
137
+ }
138
+ async callTool(name, args, opts = {}) {
139
+ if (opts.cacheKey) {
140
+ const hit = getCached(opts.cacheKey);
141
+ if (hit)
142
+ return hit;
143
+ }
144
+ await this.ensureInitialized(opts);
145
+ const id = this.nextId++;
146
+ const resp = (await this.post({
147
+ jsonrpc: "2.0",
148
+ id,
149
+ method: "tools/call",
150
+ params: { name, arguments: args },
151
+ }, opts));
152
+ if (resp?.error)
153
+ throw new Error(`tools/call ${name}: ${resp.error.message}`);
154
+ const result = resp.result ?? { content: [] };
155
+ if (result.isError) {
156
+ const msg = result.content
157
+ .map((c) => c.text)
158
+ .filter(Boolean)
159
+ .join(" ")
160
+ .slice(0, 300);
161
+ throw new Error(`MCP tool ${name} returned error: ${msg || "unknown"}`);
162
+ }
163
+ if (opts.cacheKey && opts.cacheTtlMs) {
164
+ setCached(opts.cacheKey, result, opts.cacheTtlMs);
165
+ }
166
+ return result;
167
+ }
168
+ /** Convenience: extract the first `structuredContent` object, or parse the first text block as JSON. */
169
+ static extractJson(result) {
170
+ if (result.structuredContent !== undefined) {
171
+ return result.structuredContent;
172
+ }
173
+ const text = result.content.find((c) => c.type === "text")?.text;
174
+ if (!text)
175
+ throw new Error("MCP result had no text content");
176
+ return JSON.parse(text);
177
+ }
178
+ /** Ping by listing tools; returns true if reachable + authorized. */
179
+ async ping(opts = {}) {
180
+ try {
181
+ await this.listTools(opts);
182
+ return true;
183
+ }
184
+ catch {
185
+ return false;
186
+ }
187
+ }
188
+ }
189
+ //# sourceMappingURL=mcpClient.js.map