patchwork-os 0.2.0-alpha.2 → 0.2.0-alpha.22

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 (281) hide show
  1. package/README.bridge.md +6 -0
  2. package/README.md +40 -15
  3. package/deploy/bootstrap-vps.sh +184 -0
  4. package/dist/approvalHttp.d.ts +11 -2
  5. package/dist/approvalHttp.js +98 -10
  6. package/dist/approvalHttp.js.map +1 -1
  7. package/dist/approvalQueue.d.ts +12 -1
  8. package/dist/approvalQueue.js +25 -3
  9. package/dist/approvalQueue.js.map +1 -1
  10. package/dist/automation.d.ts +20 -0
  11. package/dist/automation.js +35 -0
  12. package/dist/automation.js.map +1 -1
  13. package/dist/bridge.js +145 -23
  14. package/dist/bridge.js.map +1 -1
  15. package/dist/bridgeToken.js +57 -19
  16. package/dist/bridgeToken.js.map +1 -1
  17. package/dist/claudeDriver.d.ts +3 -1
  18. package/dist/claudeDriver.js +48 -0
  19. package/dist/claudeDriver.js.map +1 -1
  20. package/dist/claudeOrchestrator.d.ts +1 -1
  21. package/dist/claudeOrchestrator.js +14 -8
  22. package/dist/claudeOrchestrator.js.map +1 -1
  23. package/dist/commands/launchd.d.ts +2 -0
  24. package/dist/commands/launchd.js +94 -0
  25. package/dist/commands/launchd.js.map +1 -0
  26. package/dist/commands/recipe.d.ts +256 -0
  27. package/dist/commands/recipe.js +1313 -0
  28. package/dist/commands/recipe.js.map +1 -0
  29. package/dist/config.d.ts +15 -2
  30. package/dist/config.js +94 -8
  31. package/dist/config.js.map +1 -1
  32. package/dist/connectors/baseConnector.d.ts +117 -0
  33. package/dist/connectors/baseConnector.js +213 -0
  34. package/dist/connectors/baseConnector.js.map +1 -0
  35. package/dist/connectors/confluence.d.ts +111 -0
  36. package/dist/connectors/confluence.js +406 -0
  37. package/dist/connectors/confluence.js.map +1 -0
  38. package/dist/connectors/fixtureLibrary.d.ts +21 -0
  39. package/dist/connectors/fixtureLibrary.js +70 -0
  40. package/dist/connectors/fixtureLibrary.js.map +1 -0
  41. package/dist/connectors/fixtureRecorder.d.ts +1 -0
  42. package/dist/connectors/fixtureRecorder.js +35 -0
  43. package/dist/connectors/fixtureRecorder.js.map +1 -0
  44. package/dist/connectors/github.d.ts +58 -8
  45. package/dist/connectors/github.js +312 -84
  46. package/dist/connectors/github.js.map +1 -1
  47. package/dist/connectors/gmail.d.ts +4 -1
  48. package/dist/connectors/gmail.js +93 -16
  49. package/dist/connectors/gmail.js.map +1 -1
  50. package/dist/connectors/googleCalendar.d.ts +60 -0
  51. package/dist/connectors/googleCalendar.js +345 -0
  52. package/dist/connectors/googleCalendar.js.map +1 -0
  53. package/dist/connectors/jira.d.ts +98 -0
  54. package/dist/connectors/jira.js +379 -0
  55. package/dist/connectors/jira.js.map +1 -0
  56. package/dist/connectors/linear.d.ts +117 -0
  57. package/dist/connectors/linear.js +239 -0
  58. package/dist/connectors/linear.js.map +1 -0
  59. package/dist/connectors/mcpClient.d.ts +56 -0
  60. package/dist/connectors/mcpClient.js +189 -0
  61. package/dist/connectors/mcpClient.js.map +1 -0
  62. package/dist/connectors/mcpOAuth.d.ts +84 -0
  63. package/dist/connectors/mcpOAuth.js +389 -0
  64. package/dist/connectors/mcpOAuth.js.map +1 -0
  65. package/dist/connectors/mockConnector.d.ts +28 -0
  66. package/dist/connectors/mockConnector.js +81 -0
  67. package/dist/connectors/mockConnector.js.map +1 -0
  68. package/dist/connectors/notion.d.ts +143 -0
  69. package/dist/connectors/notion.js +424 -0
  70. package/dist/connectors/notion.js.map +1 -0
  71. package/dist/connectors/sentry.d.ts +43 -0
  72. package/dist/connectors/sentry.js +188 -0
  73. package/dist/connectors/sentry.js.map +1 -0
  74. package/dist/connectors/slack.d.ts +50 -0
  75. package/dist/connectors/slack.js +324 -0
  76. package/dist/connectors/slack.js.map +1 -0
  77. package/dist/connectors/tokenStorage.d.ts +35 -0
  78. package/dist/connectors/tokenStorage.js +394 -0
  79. package/dist/connectors/tokenStorage.js.map +1 -0
  80. package/dist/connectors/zendesk.d.ts +104 -0
  81. package/dist/connectors/zendesk.js +424 -0
  82. package/dist/connectors/zendesk.js.map +1 -0
  83. package/dist/drivers/claude/api.d.ts +11 -0
  84. package/dist/drivers/claude/api.js +54 -0
  85. package/dist/drivers/claude/api.js.map +1 -0
  86. package/dist/drivers/claude/envSanitizer.d.ts +7 -0
  87. package/dist/drivers/claude/envSanitizer.js +18 -0
  88. package/dist/drivers/claude/envSanitizer.js.map +1 -0
  89. package/dist/drivers/claude/streamParser.d.ts +38 -0
  90. package/dist/drivers/claude/streamParser.js +34 -0
  91. package/dist/drivers/claude/streamParser.js.map +1 -0
  92. package/dist/drivers/claude/subprocess.d.ts +19 -0
  93. package/dist/drivers/claude/subprocess.js +216 -0
  94. package/dist/drivers/claude/subprocess.js.map +1 -0
  95. package/dist/drivers/claude/subprocessSettings.d.ts +9 -0
  96. package/dist/drivers/claude/subprocessSettings.js +55 -0
  97. package/dist/drivers/claude/subprocessSettings.js.map +1 -0
  98. package/dist/drivers/gemini/index.d.ts +18 -0
  99. package/dist/drivers/gemini/index.js +210 -0
  100. package/dist/drivers/gemini/index.js.map +1 -0
  101. package/dist/drivers/grok/index.d.ts +11 -0
  102. package/dist/drivers/grok/index.js +22 -0
  103. package/dist/drivers/grok/index.js.map +1 -0
  104. package/dist/drivers/index.d.ts +23 -0
  105. package/dist/drivers/index.js +31 -0
  106. package/dist/drivers/index.js.map +1 -0
  107. package/dist/drivers/openai/index.d.ts +24 -0
  108. package/dist/drivers/openai/index.js +110 -0
  109. package/dist/drivers/openai/index.js.map +1 -0
  110. package/dist/drivers/types.d.ts +72 -0
  111. package/dist/drivers/types.js +30 -0
  112. package/dist/drivers/types.js.map +1 -0
  113. package/dist/featureFlags.d.ts +73 -0
  114. package/dist/featureFlags.js +203 -0
  115. package/dist/featureFlags.js.map +1 -0
  116. package/dist/fp/automationInterpreter.js +1 -0
  117. package/dist/fp/automationInterpreter.js.map +1 -1
  118. package/dist/fp/automationProgram.d.ts +1 -1
  119. package/dist/fp/automationProgram.js.map +1 -1
  120. package/dist/fp/policyParser.js +17 -0
  121. package/dist/fp/policyParser.js.map +1 -1
  122. package/dist/index.js +543 -37
  123. package/dist/index.js.map +1 -1
  124. package/dist/installGuard.d.ts +25 -0
  125. package/dist/installGuard.js +48 -0
  126. package/dist/installGuard.js.map +1 -0
  127. package/dist/oauth.d.ts +4 -1
  128. package/dist/oauth.js +50 -14
  129. package/dist/oauth.js.map +1 -1
  130. package/dist/patchworkConfig.d.ts +9 -0
  131. package/dist/patchworkConfig.js.map +1 -1
  132. package/dist/recipes/chainedRunner.d.ts +104 -0
  133. package/dist/recipes/chainedRunner.js +359 -0
  134. package/dist/recipes/chainedRunner.js.map +1 -0
  135. package/dist/recipes/dependencyGraph.d.ts +39 -0
  136. package/dist/recipes/dependencyGraph.js +199 -0
  137. package/dist/recipes/dependencyGraph.js.map +1 -0
  138. package/dist/recipes/legacyRecipeCompat.d.ts +1 -0
  139. package/dist/recipes/legacyRecipeCompat.js +97 -0
  140. package/dist/recipes/legacyRecipeCompat.js.map +1 -0
  141. package/dist/recipes/nestedRecipeStep.d.ts +58 -0
  142. package/dist/recipes/nestedRecipeStep.js +95 -0
  143. package/dist/recipes/nestedRecipeStep.js.map +1 -0
  144. package/dist/recipes/outputRegistry.d.ts +28 -0
  145. package/dist/recipes/outputRegistry.js +52 -0
  146. package/dist/recipes/outputRegistry.js.map +1 -0
  147. package/dist/recipes/scheduler.d.ts +23 -7
  148. package/dist/recipes/scheduler.js +135 -41
  149. package/dist/recipes/scheduler.js.map +1 -1
  150. package/dist/recipes/schemaGenerator.d.ts +28 -0
  151. package/dist/recipes/schemaGenerator.js +484 -0
  152. package/dist/recipes/schemaGenerator.js.map +1 -0
  153. package/dist/recipes/templateEngine.d.ts +62 -0
  154. package/dist/recipes/templateEngine.js +182 -0
  155. package/dist/recipes/templateEngine.js.map +1 -0
  156. package/dist/recipes/toolRegistry.d.ts +181 -0
  157. package/dist/recipes/toolRegistry.js +300 -0
  158. package/dist/recipes/toolRegistry.js.map +1 -0
  159. package/dist/recipes/tools/calendar.d.ts +6 -0
  160. package/dist/recipes/tools/calendar.js +61 -0
  161. package/dist/recipes/tools/calendar.js.map +1 -0
  162. package/dist/recipes/tools/confluence.d.ts +6 -0
  163. package/dist/recipes/tools/confluence.js +254 -0
  164. package/dist/recipes/tools/confluence.js.map +1 -0
  165. package/dist/recipes/tools/diagnostics.d.ts +6 -0
  166. package/dist/recipes/tools/diagnostics.js +36 -0
  167. package/dist/recipes/tools/diagnostics.js.map +1 -0
  168. package/dist/recipes/tools/file.d.ts +6 -0
  169. package/dist/recipes/tools/file.js +170 -0
  170. package/dist/recipes/tools/file.js.map +1 -0
  171. package/dist/recipes/tools/git.d.ts +6 -0
  172. package/dist/recipes/tools/git.js +63 -0
  173. package/dist/recipes/tools/git.js.map +1 -0
  174. package/dist/recipes/tools/github.d.ts +6 -0
  175. package/dist/recipes/tools/github.js +91 -0
  176. package/dist/recipes/tools/github.js.map +1 -0
  177. package/dist/recipes/tools/gmail.d.ts +6 -0
  178. package/dist/recipes/tools/gmail.js +210 -0
  179. package/dist/recipes/tools/gmail.js.map +1 -0
  180. package/dist/recipes/tools/index.d.ts +18 -0
  181. package/dist/recipes/tools/index.js +21 -0
  182. package/dist/recipes/tools/index.js.map +1 -0
  183. package/dist/recipes/tools/linear.d.ts +6 -0
  184. package/dist/recipes/tools/linear.js +83 -0
  185. package/dist/recipes/tools/linear.js.map +1 -0
  186. package/dist/recipes/tools/notion.d.ts +6 -0
  187. package/dist/recipes/tools/notion.js +278 -0
  188. package/dist/recipes/tools/notion.js.map +1 -0
  189. package/dist/recipes/tools/slack.d.ts +6 -0
  190. package/dist/recipes/tools/slack.js +72 -0
  191. package/dist/recipes/tools/slack.js.map +1 -0
  192. package/dist/recipes/tools/zendesk.d.ts +6 -0
  193. package/dist/recipes/tools/zendesk.js +245 -0
  194. package/dist/recipes/tools/zendesk.js.map +1 -0
  195. package/dist/recipes/yamlRunner.d.ts +79 -0
  196. package/dist/recipes/yamlRunner.js +612 -346
  197. package/dist/recipes/yamlRunner.js.map +1 -1
  198. package/dist/recipesHttp.d.ts +14 -1
  199. package/dist/recipesHttp.js +21 -4
  200. package/dist/recipesHttp.js.map +1 -1
  201. package/dist/riskTier.js +1 -0
  202. package/dist/riskTier.js.map +1 -1
  203. package/dist/runLog.d.ts +23 -0
  204. package/dist/runLog.js +56 -1
  205. package/dist/runLog.js.map +1 -1
  206. package/dist/server.d.ts +19 -1
  207. package/dist/server.js +682 -31
  208. package/dist/server.js.map +1 -1
  209. package/dist/streamableHttp.js +2 -0
  210. package/dist/streamableHttp.js.map +1 -1
  211. package/dist/tools/addLinearComment.d.ts +55 -0
  212. package/dist/tools/addLinearComment.js +72 -0
  213. package/dist/tools/addLinearComment.js.map +1 -0
  214. package/dist/tools/bridgeDoctor.js +2 -2
  215. package/dist/tools/bridgeDoctor.js.map +1 -1
  216. package/dist/tools/createLinearIssue.d.ts +84 -0
  217. package/dist/tools/createLinearIssue.js +146 -0
  218. package/dist/tools/createLinearIssue.js.map +1 -0
  219. package/dist/tools/ctxGetTaskContext.d.ts +4 -1
  220. package/dist/tools/ctxGetTaskContext.js +45 -2
  221. package/dist/tools/ctxGetTaskContext.js.map +1 -1
  222. package/dist/tools/fetchCalendarEvents.d.ts +94 -0
  223. package/dist/tools/fetchCalendarEvents.js +97 -0
  224. package/dist/tools/fetchCalendarEvents.js.map +1 -0
  225. package/dist/tools/fetchGithubIssue.d.ts +80 -0
  226. package/dist/tools/fetchGithubIssue.js +84 -0
  227. package/dist/tools/fetchGithubIssue.js.map +1 -0
  228. package/dist/tools/fetchGithubPR.d.ts +89 -0
  229. package/dist/tools/fetchGithubPR.js +96 -0
  230. package/dist/tools/fetchGithubPR.js.map +1 -0
  231. package/dist/tools/fetchLinearIssue.d.ts +112 -0
  232. package/dist/tools/fetchLinearIssue.js +129 -0
  233. package/dist/tools/fetchLinearIssue.js.map +1 -0
  234. package/dist/tools/fetchSentryIssue.d.ts +143 -0
  235. package/dist/tools/fetchSentryIssue.js +150 -0
  236. package/dist/tools/fetchSentryIssue.js.map +1 -0
  237. package/dist/tools/fetchSlackProfile.d.ts +43 -0
  238. package/dist/tools/fetchSlackProfile.js +46 -0
  239. package/dist/tools/fetchSlackProfile.js.map +1 -0
  240. package/dist/tools/getConnectorStatus.d.ts +58 -0
  241. package/dist/tools/getConnectorStatus.js +56 -0
  242. package/dist/tools/getConnectorStatus.js.map +1 -0
  243. package/dist/tools/github/actions.js +4 -2
  244. package/dist/tools/github/actions.js.map +1 -1
  245. package/dist/tools/github/composite.d.ts +339 -0
  246. package/dist/tools/github/composite.js +343 -0
  247. package/dist/tools/github/composite.js.map +1 -0
  248. package/dist/tools/github/index.d.ts +2 -1
  249. package/dist/tools/github/index.js +2 -1
  250. package/dist/tools/github/index.js.map +1 -1
  251. package/dist/tools/github/issues.js +8 -4
  252. package/dist/tools/github/issues.js.map +1 -1
  253. package/dist/tools/github/pr.d.ts +122 -0
  254. package/dist/tools/github/pr.js +195 -5
  255. package/dist/tools/github/pr.js.map +1 -1
  256. package/dist/tools/index.js +36 -1
  257. package/dist/tools/index.js.map +1 -1
  258. package/dist/tools/searchTools.js +1 -1
  259. package/dist/tools/searchTools.js.map +1 -1
  260. package/dist/tools/slackListChannels.d.ts +65 -0
  261. package/dist/tools/slackListChannels.js +70 -0
  262. package/dist/tools/slackListChannels.js.map +1 -0
  263. package/dist/tools/slackPostMessage.d.ts +57 -0
  264. package/dist/tools/slackPostMessage.js +77 -0
  265. package/dist/tools/slackPostMessage.js.map +1 -0
  266. package/dist/tools/updateLinearIssue.d.ts +89 -0
  267. package/dist/tools/updateLinearIssue.js +117 -0
  268. package/dist/tools/updateLinearIssue.js.map +1 -0
  269. package/dist/transport.d.ts +7 -1
  270. package/dist/transport.js +85 -11
  271. package/dist/transport.js.map +1 -1
  272. package/package.json +4 -2
  273. package/scripts/start-all.sh +56 -19
  274. package/templates/automation-policies/recipe-authoring.json +25 -0
  275. package/templates/automation-policy.example.json +6 -0
  276. package/templates/co.patchwork-os.bridge.plist +34 -0
  277. package/templates/recipes/ctx-loop-test.yaml +75 -0
  278. package/templates/recipes/lint-on-save.yaml +1 -2
  279. package/templates/recipes/morning-brief-slack.yaml +57 -0
  280. package/templates/recipes/morning-brief.yaml +21 -5
  281. package/templates/recipes/sentry-to-linear.yaml +77 -0
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Linear connector — routes through Linear's official MCP server.
3
+ *
4
+ * Endpoint: https://mcp.linear.app/mcp
5
+ * Auth: OAuth 2.1 w/ PKCE; dynamic client registration (RFC 7591).
6
+ *
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.
15
+ */
16
+ import { McpClient } from "./mcpClient.js";
17
+ import { completeAuthorize, getAccessToken, loadTokenFile, revoke, startAuthorize, updateTokenProfile, 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 ─────────────────────────────────────────────────
33
+ export function loadTokens() {
34
+ const envKey = process.env.LINEAR_API_KEY;
35
+ if (envKey) {
36
+ return { api_key: envKey, connected_at: new Date().toISOString() };
37
+ }
38
+ const file = loadTokenFile("linear");
39
+ if (!file)
40
+ return null;
41
+ return {
42
+ api_key: file.access_token,
43
+ workspace: file.profile?.workspace,
44
+ connected_at: file.connected_at,
45
+ };
46
+ }
47
+ export function getStatus() {
48
+ const envKey = process.env.LINEAR_API_KEY;
49
+ if (envKey) {
50
+ return { id: "linear", status: "connected" };
51
+ }
52
+ const file = loadTokenFile("linear");
53
+ return {
54
+ id: "linear",
55
+ status: file ? "connected" : "disconnected",
56
+ lastSync: file?.connected_at,
57
+ workspace: file?.profile?.workspace,
58
+ };
59
+ }
60
+ function extractIssueId(issueIdOrUrl) {
61
+ const urlMatch = issueIdOrUrl.match(/\/issue\/([A-Z]+-\d+|[a-f0-9-]{36})/i);
62
+ if (urlMatch)
63
+ return urlMatch[1];
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;
69
+ throw new Error(`Cannot parse Linear issue ID from: ${issueIdOrUrl}`);
70
+ }
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) {
118
+ return {
119
+ status: 400,
120
+ contentType: "application/json",
121
+ body: JSON.stringify({
122
+ ok: false,
123
+ error: err instanceof Error ? err.message : String(err),
124
+ }),
125
+ };
126
+ }
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>`,
134
+ };
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
+ updateTokenProfile("linear", { workspace });
152
+ }
153
+ }
154
+ catch {
155
+ // Profile fetch is best-effort
156
+ }
157
+ return {
158
+ status: 200,
159
+ contentType: "text/html",
160
+ body: `<html><body><h2>Linear connected</h2><script>window.close();</script></body></html>`,
161
+ };
162
+ }
163
+ catch (err) {
164
+ return {
165
+ status: 400,
166
+ contentType: "text/html",
167
+ body: `<html><body><h2>Linear connect failed</h2><pre>${err instanceof Error ? err.message : String(err)}</pre></body></html>`,
168
+ };
169
+ }
170
+ }
171
+ export async function handleLinearTest() {
172
+ if (!loadTokens()) {
173
+ return {
174
+ status: 400,
175
+ contentType: "application/json",
176
+ body: JSON.stringify({ ok: false, error: "Linear not connected" }),
177
+ };
178
+ }
179
+ try {
180
+ const ok = await client().ping({ timeoutMs: 10_000 });
181
+ return {
182
+ status: ok ? 200 : 400,
183
+ contentType: "application/json",
184
+ body: JSON.stringify({ ok, message: ok ? "connected" : "ping failed" }),
185
+ };
186
+ }
187
+ catch (err) {
188
+ return {
189
+ status: 400,
190
+ contentType: "application/json",
191
+ body: JSON.stringify({
192
+ ok: false,
193
+ error: err instanceof Error ? err.message : String(err),
194
+ }),
195
+ };
196
+ }
197
+ }
198
+ export async function handleLinearDisconnect() {
199
+ await revoke("linear");
200
+ _client = null;
201
+ return {
202
+ status: 200,
203
+ contentType: "application/json",
204
+ body: JSON.stringify({ ok: true }),
205
+ };
206
+ }
207
+ export async function listTeams(signal) {
208
+ const res = await client().callTool("list_teams", {}, { signal });
209
+ const parsed = McpClient.extractJson(res);
210
+ if (Array.isArray(parsed))
211
+ return parsed;
212
+ return parsed.teams ?? parsed.nodes ?? [];
213
+ }
214
+ export async function listLabels(signal) {
215
+ const res = await client().callTool("list_issue_labels", {}, { signal });
216
+ const parsed = McpClient.extractJson(res);
217
+ if (Array.isArray(parsed))
218
+ return parsed;
219
+ return parsed.labels ?? parsed.nodes ?? [];
220
+ }
221
+ async function callSaveIssue(args, signal) {
222
+ const res = await client().callTool("save_issue", args, { signal });
223
+ const parsed = McpClient.extractJson(res);
224
+ return (parsed.issue ?? parsed);
225
+ }
226
+ export async function createIssue(input, signal) {
227
+ return callSaveIssue(input, signal);
228
+ }
229
+ export async function updateIssue(input, signal) {
230
+ return callSaveIssue(input, signal);
231
+ }
232
+ export async function addComment(issueId, body, signal) {
233
+ const id = extractIssueId(issueId);
234
+ const res = await client().callTool("create_comment", { issueId: id, body }, { signal });
235
+ const parsed = McpClient.extractJson(res);
236
+ return (parsed
237
+ .comment ?? parsed);
238
+ }
239
+ //# sourceMappingURL=linear.js.map
@@ -0,0 +1 @@
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcpClient.js","sourceRoot":"","sources":["../../src/connectors/mcpClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * OAuth 2.1 PKCE helper for upstream MCP servers.
3
+ *
4
+ * Supports two vendor modes:
5
+ * - dyn-reg (Linear, Sentry): RFC 7591 dynamic client registration.
6
+ * Registration data cached alongside tokens so we don't re-register every run.
7
+ * - preregistered (GitHub): uses a hardcoded client_id + PKCE-only flow.
8
+ *
9
+ * Token files: ~/.patchwork/tokens/<vendor>-mcp.json (mode 0600)
10
+ *
11
+ * Flow:
12
+ * 1. startAuthorize({ vendor, config }) -> { url, state }
13
+ * Dashboard opens `url` in a popup; stores `state` to correlate callback.
14
+ * 2. server.ts callback route calls completeAuthorize({ vendor, config, code, state })
15
+ * -> persisted token file.
16
+ * 3. getAccessToken({ vendor }) reads token, refreshes if needed.
17
+ * 4. revoke({ vendor }) hits the revocation endpoint + deletes file.
18
+ */
19
+ export type VendorId = "github" | "linear" | "sentry";
20
+ export interface VendorConfig {
21
+ vendor: VendorId;
22
+ /** Base issuer (authorization server), used for discovery. */
23
+ issuer: string;
24
+ /** Explicit endpoints (overrides discovery). */
25
+ authorizationEndpoint?: string;
26
+ tokenEndpoint?: string;
27
+ registrationEndpoint?: string;
28
+ revocationEndpoint?: string;
29
+ /** Scopes requested in authorize URL. */
30
+ scopes: string[];
31
+ /** Redirect URI — must match what's registered / what the dashboard uses. */
32
+ redirectUri: string;
33
+ /** If true, use RFC 7591 dynamic client registration. */
34
+ useDynamicRegistration: boolean;
35
+ /** If useDynamicRegistration=false, this client_id is used. */
36
+ preregisteredClientId?: string;
37
+ /** Client secret for pre-registered clients (e.g. GitHub OAuth Apps). */
38
+ preregisteredClientSecret?: string;
39
+ /** Human-friendly client name for dyn-reg. */
40
+ clientName?: string;
41
+ }
42
+ export declare function vendorConfig(vendor: VendorId): VendorConfig;
43
+ export interface McpTokenFile {
44
+ vendor: VendorId;
45
+ client_id: string;
46
+ client_secret?: string;
47
+ access_token: string;
48
+ refresh_token?: string;
49
+ expires_at?: number;
50
+ scope?: string;
51
+ connected_at: string;
52
+ /** Vendor-specific profile info captured at connect-time for UI display. */
53
+ profile?: Record<string, string>;
54
+ }
55
+ export declare function loadTokenFile(vendor: VendorId): McpTokenFile | null;
56
+ export declare function updateTokenProfile(vendor: VendorId, profile: Record<string, string>): void;
57
+ /**
58
+ * Returns the authorize URL for the popup, and a `state` cookie value
59
+ * the callback must match. For dyn-reg vendors, registers a fresh client
60
+ * if we don't have one yet (re-uses existing one from token file on reconnect).
61
+ */
62
+ export declare function startAuthorize(config: VendorConfig): Promise<{
63
+ url: string;
64
+ state: string;
65
+ }>;
66
+ export interface CompleteResult {
67
+ ok: true;
68
+ profile?: Record<string, string>;
69
+ }
70
+ /** Complete the authorize flow. Persists token file. */
71
+ export declare function completeAuthorize(config: VendorConfig, code: string, state: string, profile?: Record<string, string>): Promise<CompleteResult>;
72
+ export interface ConnectorStatus {
73
+ vendor: VendorId;
74
+ connected: boolean;
75
+ expiresAt?: number;
76
+ expiresInMs?: number;
77
+ needsReauth: boolean;
78
+ profile?: Record<string, string>;
79
+ }
80
+ export declare function getConnectorStatus(vendor: VendorId): ConnectorStatus;
81
+ export declare function getAllConnectorStatuses(): ConnectorStatus[];
82
+ export declare function getAccessToken(vendor: VendorId): Promise<string>;
83
+ export declare function revoke(vendor: VendorId): Promise<void>;
84
+ export declare function isConnected(vendor: VendorId): boolean;