@gitscrum-studio/mcp-server 1.0.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.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +250 -0
  3. package/dist/auth/DeviceAuthenticator.d.ts +51 -0
  4. package/dist/auth/DeviceAuthenticator.d.ts.map +1 -0
  5. package/dist/auth/DeviceAuthenticator.js +89 -0
  6. package/dist/auth/DeviceAuthenticator.js.map +1 -0
  7. package/dist/auth/RateLimiter.d.ts +58 -0
  8. package/dist/auth/RateLimiter.d.ts.map +1 -0
  9. package/dist/auth/RateLimiter.js +181 -0
  10. package/dist/auth/RateLimiter.js.map +1 -0
  11. package/dist/auth/TokenManager.d.ts +62 -0
  12. package/dist/auth/TokenManager.d.ts.map +1 -0
  13. package/dist/auth/TokenManager.js +164 -0
  14. package/dist/auth/TokenManager.js.map +1 -0
  15. package/dist/client/GitScrumClient.d.ts +1002 -0
  16. package/dist/client/GitScrumClient.d.ts.map +1 -0
  17. package/dist/client/GitScrumClient.js +1835 -0
  18. package/dist/client/GitScrumClient.js.map +1 -0
  19. package/dist/context/ActiveContext.d.ts +79 -0
  20. package/dist/context/ActiveContext.d.ts.map +1 -0
  21. package/dist/context/ActiveContext.js +151 -0
  22. package/dist/context/ActiveContext.js.map +1 -0
  23. package/dist/index.d.ts +15 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +152 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/tools/activity.d.ts +12 -0
  28. package/dist/tools/activity.d.ts.map +1 -0
  29. package/dist/tools/activity.js +106 -0
  30. package/dist/tools/activity.js.map +1 -0
  31. package/dist/tools/analytics.d.ts +11 -0
  32. package/dist/tools/analytics.d.ts.map +1 -0
  33. package/dist/tools/analytics.js +117 -0
  34. package/dist/tools/analytics.js.map +1 -0
  35. package/dist/tools/auth.d.ts +17 -0
  36. package/dist/tools/auth.d.ts.map +1 -0
  37. package/dist/tools/auth.js +173 -0
  38. package/dist/tools/auth.js.map +1 -0
  39. package/dist/tools/budget.d.ts +12 -0
  40. package/dist/tools/budget.d.ts.map +1 -0
  41. package/dist/tools/budget.js +109 -0
  42. package/dist/tools/budget.js.map +1 -0
  43. package/dist/tools/clientflow.d.ts +12 -0
  44. package/dist/tools/clientflow.d.ts.map +1 -0
  45. package/dist/tools/clientflow.js +476 -0
  46. package/dist/tools/clientflow.js.map +1 -0
  47. package/dist/tools/comments.d.ts +11 -0
  48. package/dist/tools/comments.d.ts.map +1 -0
  49. package/dist/tools/comments.js +99 -0
  50. package/dist/tools/comments.js.map +1 -0
  51. package/dist/tools/discussions.d.ts +12 -0
  52. package/dist/tools/discussions.d.ts.map +1 -0
  53. package/dist/tools/discussions.js +200 -0
  54. package/dist/tools/discussions.js.map +1 -0
  55. package/dist/tools/epics.d.ts +11 -0
  56. package/dist/tools/epics.d.ts.map +1 -0
  57. package/dist/tools/epics.js +122 -0
  58. package/dist/tools/epics.js.map +1 -0
  59. package/dist/tools/labels.d.ts +11 -0
  60. package/dist/tools/labels.d.ts.map +1 -0
  61. package/dist/tools/labels.js +138 -0
  62. package/dist/tools/labels.js.map +1 -0
  63. package/dist/tools/notes.d.ts +11 -0
  64. package/dist/tools/notes.d.ts.map +1 -0
  65. package/dist/tools/notes.js +201 -0
  66. package/dist/tools/notes.js.map +1 -0
  67. package/dist/tools/projects.d.ts +12 -0
  68. package/dist/tools/projects.d.ts.map +1 -0
  69. package/dist/tools/projects.js +272 -0
  70. package/dist/tools/projects.js.map +1 -0
  71. package/dist/tools/search.d.ts +17 -0
  72. package/dist/tools/search.d.ts.map +1 -0
  73. package/dist/tools/search.js +56 -0
  74. package/dist/tools/search.js.map +1 -0
  75. package/dist/tools/shared/actionHandler.d.ts +67 -0
  76. package/dist/tools/shared/actionHandler.d.ts.map +1 -0
  77. package/dist/tools/shared/actionHandler.js +98 -0
  78. package/dist/tools/shared/actionHandler.js.map +1 -0
  79. package/dist/tools/shared/initModules.d.ts +6 -0
  80. package/dist/tools/shared/initModules.d.ts.map +1 -0
  81. package/dist/tools/shared/initModules.js +166 -0
  82. package/dist/tools/shared/initModules.js.map +1 -0
  83. package/dist/tools/shared/toolRegistry.d.ts +45 -0
  84. package/dist/tools/shared/toolRegistry.d.ts.map +1 -0
  85. package/dist/tools/shared/toolRegistry.js +59 -0
  86. package/dist/tools/shared/toolRegistry.js.map +1 -0
  87. package/dist/tools/sprints.d.ts +11 -0
  88. package/dist/tools/sprints.d.ts.map +1 -0
  89. package/dist/tools/sprints.js +271 -0
  90. package/dist/tools/sprints.js.map +1 -0
  91. package/dist/tools/standup.d.ts +11 -0
  92. package/dist/tools/standup.d.ts.map +1 -0
  93. package/dist/tools/standup.js +89 -0
  94. package/dist/tools/standup.js.map +1 -0
  95. package/dist/tools/taskTypes.d.ts +11 -0
  96. package/dist/tools/taskTypes.d.ts.map +1 -0
  97. package/dist/tools/taskTypes.js +132 -0
  98. package/dist/tools/taskTypes.js.map +1 -0
  99. package/dist/tools/tasks.d.ts +12 -0
  100. package/dist/tools/tasks.d.ts.map +1 -0
  101. package/dist/tools/tasks.js +470 -0
  102. package/dist/tools/tasks.js.map +1 -0
  103. package/dist/tools/timeTracking.d.ts +11 -0
  104. package/dist/tools/timeTracking.d.ts.map +1 -0
  105. package/dist/tools/timeTracking.js +174 -0
  106. package/dist/tools/timeTracking.js.map +1 -0
  107. package/dist/tools/userStories.d.ts +11 -0
  108. package/dist/tools/userStories.d.ts.map +1 -0
  109. package/dist/tools/userStories.js +166 -0
  110. package/dist/tools/userStories.js.map +1 -0
  111. package/dist/tools/wiki.d.ts +11 -0
  112. package/dist/tools/wiki.d.ts.map +1 -0
  113. package/dist/tools/wiki.js +154 -0
  114. package/dist/tools/wiki.js.map +1 -0
  115. package/dist/tools/workflows.d.ts +11 -0
  116. package/dist/tools/workflows.d.ts.map +1 -0
  117. package/dist/tools/workflows.js +144 -0
  118. package/dist/tools/workflows.js.map +1 -0
  119. package/package.json +89 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GitScrum
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,250 @@
1
+ <p align="center">
2
+ <img src="https://site-assets.gitscrum.com/vscode/gitscrum-white.png" alt="GitScrum" width="160"/>
3
+ </p>
4
+
5
+ <h1 align="center">GitScrum Studio MCP Server</h1>
6
+
7
+ <p align="center">
8
+ Model Context Protocol server for GitScrum.<br/>
9
+ Ship faster with AI-powered project management.
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-000?style=flat-square" alt="MIT License"></a>
14
+ <a href="https://github.com/gitscrum-core/mcp-server/actions"><img src="https://img.shields.io/badge/tests-378_passing-000?style=flat-square" alt="Tests"></a>
15
+ </p>
16
+
17
+ <br/>
18
+
19
+ ## Overview
20
+
21
+ GitScrum Studio MCP Server connects AI assistants to your [GitScrum](https://gitscrum.com) workspace via the [Model Context Protocol](https://modelcontextprotocol.io). It gives Claude, GitHub Copilot, Cursor, and any MCP-compatible client full operational access to your project management stack — tasks, sprints, time tracking, user stories, epics, kanban workflows, team discussions, wiki, notes, client CRM, invoicing, proposals, budget tracking, analytics dashboards, standup reports, and activity feeds.
22
+
23
+ Everything your team does in the GitScrum web app, your AI assistant can now do through conversation.
24
+
25
+ ```
26
+ You: "What's on my plate today?"
27
+ Assistant: Fetches your tasks due today across all projects.
28
+
29
+ You: "Create a sprint for next week with the top 5 backlog items"
30
+ Assistant: Creates the sprint, assigns tasks, and sets the timeline.
31
+
32
+ You: "Show me which projects are over budget"
33
+ Assistant: Returns burn-down data and flags at-risk projects.
34
+
35
+ You: "Send the Q1 proposal to Acme Corp"
36
+ Assistant: Creates the proposal, attaches the client, and sends it.
37
+
38
+ You: "What did the team ship this week?"
39
+ Assistant: Generates a standup digest with completed work and blockers.
40
+ ```
41
+
42
+ **29 tools. 160+ operations. Zero context switching.**
43
+
44
+ ---
45
+
46
+ ## Quick start
47
+
48
+ ### Install
49
+
50
+ ```bash
51
+ npx -y @gitscrum-studio/mcp-server
52
+ ```
53
+
54
+ ### Configure your client
55
+
56
+ <details>
57
+ <summary><strong>Claude Desktop</strong></summary>
58
+
59
+ Edit the configuration file:
60
+
61
+ - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
62
+ - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
63
+
64
+ ```json
65
+ {
66
+ "mcpServers": {
67
+ "gitscrum": {
68
+ "command": "npx",
69
+ "args": ["-y", "@gitscrum-studio/mcp-server"]
70
+ }
71
+ }
72
+ }
73
+ ```
74
+ </details>
75
+
76
+ <details>
77
+ <summary><strong>VS Code / Cursor</strong></summary>
78
+
79
+ Add to `.vscode/mcp.json` or your MCP settings:
80
+
81
+ ```json
82
+ {
83
+ "servers": {
84
+ "gitscrum": {
85
+ "command": "npx",
86
+ "args": ["-y", "@gitscrum-studio/mcp-server"]
87
+ }
88
+ }
89
+ }
90
+ ```
91
+ </details>
92
+
93
+ ### Authenticate
94
+
95
+ Tell your AI assistant: **"Login to GitScrum"**
96
+
97
+ The server initiates an [OAuth 2.0 Device Authorization Grant](https://datatracker.ietf.org/doc/html/rfc8628) flow. You authorize in the browser — credentials are never shared with the MCP server.
98
+
99
+ ---
100
+
101
+ ## Tools
102
+
103
+ Each tool uses a consolidated `action` parameter, reducing LLM context tokens by ~80% compared to individual tool definitions.
104
+
105
+ ### Core
106
+
107
+ | Tool | Actions | Docs |
108
+ |:-----|:--------|:-----|
109
+ | `task` | `my` `today` `get` `create` `update` `complete` `subtasks` `filter` `by_code` `duplicate` `move` `notifications` | [tasks](docs/tools/tasks.md) |
110
+ | `sprint` | `list` `all` `get` `kpis` `create` `update` `stats` `reports` `progress` `metrics` | [sprints](docs/tools/sprints.md) |
111
+ | `workspace` | `list` `get` | [projects](docs/tools/projects.md) |
112
+ | `project` | `list` `get` `stats` `tasks` `workflows` `types` `efforts` `labels` `members` | [projects](docs/tools/projects.md) |
113
+ | `time` | `active` `start` `stop` `logs` `analytics` `team` `reports` `productivity` `timeline` | [time-tracking](docs/tools/time-tracking.md) |
114
+
115
+ ### Planning
116
+
117
+ | Tool | Actions | Docs |
118
+ |:-----|:--------|:-----|
119
+ | `user_story` | `list` `get` `create` `update` `all` | [user-stories](docs/tools/user-stories.md) |
120
+ | `epic` | `list` `create` `update` | [epics](docs/tools/epics.md) |
121
+ | `label` | `list` `create` `update` `attach` `detach` `toggle` | [labels](docs/tools/labels.md) |
122
+ | `task_type` | `list` `create` `update` `assign` | [task-types](docs/tools/task-types.md) |
123
+ | `workflow` | `create` `update` | [workflows](docs/tools/workflows.md) |
124
+
125
+ ### Collaboration
126
+
127
+ | Tool | Actions | Docs |
128
+ |:-----|:--------|:-----|
129
+ | `discussion` | `all` `channels` `channel` `messages` `send` `search` `unread` `mark_read` `create_channel` `update_channel` | [discussions](docs/tools/discussions.md) |
130
+ | `comment` | `list` `add` `update` | [comments](docs/tools/comments.md) |
131
+ | `wiki` | `list` `get` `create` `update` `search` | [wiki](docs/tools/wiki.md) |
132
+ | `note` | `list` `get` `create` `update` `share` `revisions` | [notevault](docs/tools/notevault.md) |
133
+ | `note_folder` | `list` `create` `update` `move` | [notevault](docs/tools/notevault.md) |
134
+ | `search` | — | [search](docs/tools/search.md) |
135
+
136
+ ### ClientFlow CRM
137
+
138
+ | Tool | Actions | Docs |
139
+ |:-----|:--------|:-----|
140
+ | `client` | `list` `get` `create` `update` `contacts` `interactions` `add_interaction` | [clientflow](docs/tools/clientflow.md) |
141
+ | `invoice` | `list` `get` `stats` `create` `update` `issue` `send` `mark_paid` | [clientflow](docs/tools/clientflow.md) |
142
+ | `proposal` | `list` `get` `stats` `create` `update` `send` `approve` `reject` `convert` | [clientflow](docs/tools/clientflow.md) |
143
+ | `clientflow_dashboard` | 8 reports | [clientflow](docs/tools/clientflow.md) |
144
+ | `clientflow_cross_workspace` | 4 reports | [clientflow](docs/tools/clientflow.md) |
145
+
146
+ ### Insights <sup>PRO</sup>
147
+
148
+ | Tool | Actions | Docs |
149
+ |:-----|:--------|:-----|
150
+ | `standup` | `summary` `completed` `blockers` `team` `stuck` `digest` `contributors` | [standup](docs/tools/standup.md) |
151
+ | `analytics` | 10 reports | [analytics](docs/tools/analytics.md) |
152
+ | `activity` | `feed` `user_feed` `notifications` `activities` `task_workflow` | [activity](docs/tools/activity.md) |
153
+ | `budget` | `projects_at_risk` `overview` `consumption` `burn_down` `alerts` `events` | [budget](docs/tools/budget.md) |
154
+
155
+ ### Authentication
156
+
157
+ | Tool | Description | Docs |
158
+ |:-----|:------------|:-----|
159
+ | `auth_login` | Initiate device code flow | [auth](docs/tools/auth.md) |
160
+ | `auth_complete` | Complete authorization | [auth](docs/tools/auth.md) |
161
+ | `auth_status` | Check session status | [auth](docs/tools/auth.md) |
162
+ | `auth_logout` | Clear stored credentials | [auth](docs/tools/auth.md) |
163
+
164
+ Full reference: [docs/TOOLS.md](docs/TOOLS.md)
165
+
166
+ ---
167
+
168
+ ## Security
169
+
170
+ The server is designed around the **principle of least privilege**.
171
+
172
+ | Layer | Protection |
173
+ |:------|:-----------|
174
+ | **Operations** | Only CREATE, READ, UPDATE. DELETE is blocked at MCP and API layers. |
175
+ | **Authentication** | OAuth 2.0 Device Grant — credentials never touch the server. |
176
+ | **Token storage** | Local filesystem with restricted permissions. |
177
+ | **Rate limiting** | Automatic lockout after failed auth attempts. |
178
+
179
+ Destructive operations must be performed in the [GitScrum Studio](https://studio.gitscrum.com).
180
+
181
+ Full details: [docs/SECURITY.md](docs/SECURITY.md)
182
+
183
+ Found a vulnerability? Report privately to **security@gitscrum.com**.
184
+
185
+ ---
186
+
187
+ ## Documentation
188
+
189
+ | | |
190
+ |:--|:--|
191
+ | **[Usage Guide](docs/USAGE.md)** | Practical examples and common workflows |
192
+ | **[Tools Reference](docs/TOOLS.md)** | All 29 tools with parameters and response shapes |
193
+ | **[Per-Tool Guides](docs/tools/)** | Deep-dive into each tool module |
194
+ | **[Security](docs/SECURITY.md)** | Security model, token handling, threat mitigations |
195
+ | **[Development](docs/DEVELOPMENT.md)** | Local setup, architecture, testing, contribution |
196
+ | **[Changelog](CHANGELOG.md)** | Version history and migration notes |
197
+
198
+ ---
199
+
200
+ ## Development
201
+
202
+ ```bash
203
+ git clone https://github.com/gitscrum-core/mcp-server.git
204
+ cd mcp-server
205
+ npm install
206
+ npm run build
207
+ npm test # 378 tests across 22 suites
208
+ ```
209
+
210
+ Inspect locally with the MCP Inspector:
211
+
212
+ ```bash
213
+ npx @modelcontextprotocol/inspector node dist/index.js
214
+ ```
215
+
216
+ | Requirement | Version |
217
+ |:------------|:--------|
218
+ | Node.js | >= 18.0.0 |
219
+ | npm | >= 8.0.0 |
220
+
221
+ Full guide: [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)
222
+
223
+ ---
224
+
225
+ ## Contributing
226
+
227
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
228
+
229
+ ```bash
230
+ git checkout -b feature/my-feature
231
+ # make changes, add tests
232
+ npm test
233
+ git commit -m "feat: describe your change"
234
+ ```
235
+
236
+ ---
237
+
238
+ ## License
239
+
240
+ MIT — see [LICENSE](LICENSE).
241
+
242
+ ---
243
+
244
+ <p align="center">
245
+ <a href="https://gitscrum.com">Website</a>&nbsp;&nbsp;·&nbsp;&nbsp;<a href="https://docs.gitscrum.com/en/mcp">Docs</a>&nbsp;&nbsp;·&nbsp;&nbsp;<a href="https://github.com/gitscrum-core/mcp-server/issues">Issues</a>&nbsp;&nbsp;·&nbsp;&nbsp;<a href="CHANGELOG.md">Changelog</a>
246
+ </p>
247
+
248
+ <p align="center">
249
+ Built by <a href="https://gitscrum.com/en">GitScrum</a>
250
+ </p>
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Device Authorization Flow
3
+ *
4
+ * Implements OAuth 2.0 Device Authorization Grant (RFC 8628)
5
+ * Allows MCP to authenticate users via browser login.
6
+ *
7
+ * Flow:
8
+ * 1. MCP requests device code from API
9
+ * 2. User opens verification URL in browser (contains device_code)
10
+ * 3. User logs in (Google, GitHub, or email/password)
11
+ * 4. User clicks "Authorize" - no manual code entry needed
12
+ * 5. MCP polls for token until authorized
13
+ *
14
+ * @module @gitscrum-studio/mcp-server/auth
15
+ */
16
+ export interface DeviceCodeResponse {
17
+ device_code: string;
18
+ user_code: string;
19
+ verification_uri: string;
20
+ verification_uri_complete: string;
21
+ expires_in: number;
22
+ interval: number;
23
+ }
24
+ export interface TokenResponse {
25
+ access_token: string;
26
+ token_type: string;
27
+ expires_in: number;
28
+ scope: string;
29
+ }
30
+ /**
31
+ * Handles Device Authorization Flow for CLI/MCP authentication
32
+ */
33
+ export declare class DeviceAuthenticator {
34
+ private apiUrl;
35
+ constructor(apiUrl?: string);
36
+ /**
37
+ * MCP Client ID - must match OAuthClientRegistry on the API
38
+ */
39
+ private static readonly MCP_CLIENT_ID;
40
+ /**
41
+ * Start the device authorization flow
42
+ * Returns the device code info that should be shown to the user
43
+ */
44
+ requestDeviceCode(): Promise<DeviceCodeResponse>;
45
+ /**
46
+ * Poll for the access token
47
+ * Should be called repeatedly until success or error (other than pending)
48
+ */
49
+ pollForToken(deviceCode: string): Promise<TokenResponse | null>;
50
+ }
51
+ //# sourceMappingURL=DeviceAuthenticator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeviceAuthenticator.d.ts","sourceRoot":"","sources":["../../src/auth/DeviceAuthenticator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,yBAAyB,EAAE,MAAM,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAOD;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,CAAC,EAAE,MAAM;IAI3B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAA0C;IAE/E;;;OAGG;IACG,iBAAiB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IA4BtD;;;OAGG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;CAgCtE"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Device Authorization Flow
3
+ *
4
+ * Implements OAuth 2.0 Device Authorization Grant (RFC 8628)
5
+ * Allows MCP to authenticate users via browser login.
6
+ *
7
+ * Flow:
8
+ * 1. MCP requests device code from API
9
+ * 2. User opens verification URL in browser (contains device_code)
10
+ * 3. User logs in (Google, GitHub, or email/password)
11
+ * 4. User clicks "Authorize" - no manual code entry needed
12
+ * 5. MCP polls for token until authorized
13
+ *
14
+ * @module @gitscrum-studio/mcp-server/auth
15
+ */
16
+ /**
17
+ * Handles Device Authorization Flow for CLI/MCP authentication
18
+ */
19
+ export class DeviceAuthenticator {
20
+ apiUrl;
21
+ constructor(apiUrl) {
22
+ this.apiUrl = apiUrl || process.env.GITSCRUM_API_URL || "https://services.gitscrum.com";
23
+ }
24
+ /**
25
+ * MCP Client ID - must match OAuthClientRegistry on the API
26
+ */
27
+ static MCP_CLIENT_ID = "9e8d7c6b-5a4f-3e2d-1c0b-a9b8c7d6e5f4";
28
+ /**
29
+ * Start the device authorization flow
30
+ * Returns the device code info that should be shown to the user
31
+ */
32
+ async requestDeviceCode() {
33
+ const url = `${this.apiUrl}/oauth/device/code`;
34
+ try {
35
+ const response = await fetch(url, {
36
+ method: "POST",
37
+ headers: {
38
+ "Content-Type": "application/json",
39
+ "Accept": "application/json",
40
+ "X-Client-Source": "mcp-server",
41
+ },
42
+ body: JSON.stringify({ client_id: DeviceAuthenticator.MCP_CLIENT_ID }),
43
+ });
44
+ if (!response.ok) {
45
+ const error = await response.json();
46
+ throw new Error(error.error_description || `API error: ${response.status} ${response.statusText}`);
47
+ }
48
+ return response.json();
49
+ }
50
+ catch (error) {
51
+ if (error instanceof TypeError && error.message.includes("fetch")) {
52
+ throw new Error(`Network error: Unable to connect to ${url}. Is the API server running?`);
53
+ }
54
+ throw error;
55
+ }
56
+ }
57
+ /**
58
+ * Poll for the access token
59
+ * Should be called repeatedly until success or error (other than pending)
60
+ */
61
+ async pollForToken(deviceCode) {
62
+ const response = await fetch(`${this.apiUrl}/oauth/device/token`, {
63
+ method: "POST",
64
+ headers: {
65
+ "Content-Type": "application/json",
66
+ "Accept": "application/json",
67
+ "X-Client-Source": "mcp-server",
68
+ },
69
+ body: JSON.stringify({
70
+ device_code: deviceCode,
71
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
72
+ }),
73
+ });
74
+ if (!response.ok) {
75
+ const error = await response.json();
76
+ // These are expected while waiting for user authorization
77
+ if (error.error === "authorization_pending") {
78
+ return null; // Keep polling
79
+ }
80
+ if (error.error === "slow_down") {
81
+ return null; // Keep polling, but slower
82
+ }
83
+ // These are terminal errors
84
+ throw new Error(error.error_description || error.error);
85
+ }
86
+ return response.json();
87
+ }
88
+ }
89
+ //# sourceMappingURL=DeviceAuthenticator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeviceAuthenticator.js","sourceRoot":"","sources":["../../src/auth/DeviceAuthenticator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAuBH;;GAEG;AACH,MAAM,OAAO,mBAAmB;IACtB,MAAM,CAAS;IAEvB,YAAY,MAAe;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,+BAA+B,CAAC;IAC1F,CAAC;IAED;;OAEG;IACK,MAAM,CAAU,aAAa,GAAG,sCAAsC,CAAC;IAE/E;;;OAGG;IACH,KAAK,CAAC,iBAAiB;QACrB,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,oBAAoB,CAAC;QAE/C,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAChC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,QAAQ,EAAE,kBAAkB;oBAC5B,iBAAiB,EAAE,YAAY;iBAChC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,mBAAmB,CAAC,aAAa,EAAE,CAAC;aACvE,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAe,CAAC;gBACjD,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,iBAAiB,IAAI,cAAc,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YACrG,CAAC;YAED,OAAO,QAAQ,CAAC,IAAI,EAAiC,CAAC;QACxD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBAClE,MAAM,IAAI,KAAK,CAAC,uCAAuC,GAAG,8BAA8B,CAAC,CAAC;YAC5F,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,YAAY,CAAC,UAAkB;QACnC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,qBAAqB,EAAE;YAChE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,QAAQ,EAAE,kBAAkB;gBAC5B,iBAAiB,EAAE,YAAY;aAChC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,WAAW,EAAE,UAAU;gBACvB,UAAU,EAAE,8CAA8C;aAC3D,CAAC;SACH,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAe,CAAC;YAEjD,0DAA0D;YAC1D,IAAI,KAAK,CAAC,KAAK,KAAK,uBAAuB,EAAE,CAAC;gBAC5C,OAAO,IAAI,CAAC,CAAC,eAAe;YAC9B,CAAC;YAED,IAAI,KAAK,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;gBAChC,OAAO,IAAI,CAAC,CAAC,2BAA2B;YAC1C,CAAC;YAED,4BAA4B;YAC5B,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,iBAAiB,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1D,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,EAA4B,CAAC;IACnD,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Rate Limiter for Authentication
3
+ *
4
+ * Protects against brute force attacks by limiting login attempts.
5
+ * Uses in-memory storage (resets on server restart).
6
+ */
7
+ export declare class RateLimiter {
8
+ private static readonly MAX_ATTEMPTS;
9
+ private static readonly WINDOW_MS;
10
+ private static readonly LOCKOUT_MS;
11
+ private static readonly CLEANUP_INTERVAL_MS;
12
+ private storage;
13
+ private storageFile;
14
+ constructor();
15
+ /**
16
+ * Load rate limit data from disk
17
+ */
18
+ private loadStorage;
19
+ /**
20
+ * Save rate limit data to disk
21
+ */
22
+ private saveStorage;
23
+ /**
24
+ * Normalize identifier (lowercase email)
25
+ */
26
+ private normalizeKey;
27
+ /**
28
+ * Clean up old records
29
+ */
30
+ private cleanup;
31
+ /**
32
+ * Check if login is allowed for the given identifier
33
+ * Returns: { allowed: boolean, retryAfter?: number, message?: string }
34
+ */
35
+ check(identifier: string): {
36
+ allowed: boolean;
37
+ retryAfter?: number;
38
+ remainingAttempts?: number;
39
+ message?: string;
40
+ };
41
+ /**
42
+ * Record a failed login attempt
43
+ */
44
+ recordFailure(identifier: string): void;
45
+ /**
46
+ * Clear attempts on successful login
47
+ */
48
+ recordSuccess(identifier: string): void;
49
+ /**
50
+ * Get rate limit info for display
51
+ */
52
+ getInfo(): {
53
+ maxAttempts: number;
54
+ windowMinutes: number;
55
+ lockoutMinutes: number;
56
+ };
57
+ }
58
+ //# sourceMappingURL=RateLimiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RateLimiter.d.ts","sourceRoot":"","sources":["../../src/auth/RateLimiter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAiBH,qBAAa,WAAW;IAEtB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAK;IACzC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAkB;IACnD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAkB;IACpD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAkB;IAE7D,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,WAAW,CAAS;;IAS5B;;OAEG;IACH,OAAO,CAAC,WAAW;IAenB;;OAEG;IACH,OAAO,CAAC,WAAW;IAgBnB;;OAEG;IACH,OAAO,CAAC,YAAY;IAIpB;;OAEG;IACH,OAAO,CAAC,OAAO;IAwBf;;;OAGG;IACH,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG;QACzB,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB;IA0DD;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IA0BvC;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAMvC;;OAEG;IACH,OAAO,IAAI;QACT,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;KACxB;CAOF"}
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Rate Limiter for Authentication
3
+ *
4
+ * Protects against brute force attacks by limiting login attempts.
5
+ * Uses in-memory storage (resets on server restart).
6
+ */
7
+ import * as fs from "fs";
8
+ import * as path from "path";
9
+ import * as os from "os";
10
+ export class RateLimiter {
11
+ // Configuration
12
+ static MAX_ATTEMPTS = 5;
13
+ static WINDOW_MS = 15 * 60 * 1000; // 15 minutes
14
+ static LOCKOUT_MS = 30 * 60 * 1000; // 30 minutes lockout
15
+ static CLEANUP_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
16
+ storage;
17
+ storageFile;
18
+ constructor() {
19
+ // Persist rate limit data to prevent bypass via restart
20
+ const configDir = path.join(os.homedir(), ".gitscrum");
21
+ this.storageFile = path.join(configDir, "rate-limit.json");
22
+ this.storage = this.loadStorage();
23
+ }
24
+ /**
25
+ * Load rate limit data from disk
26
+ */
27
+ loadStorage() {
28
+ try {
29
+ if (fs.existsSync(this.storageFile)) {
30
+ const data = JSON.parse(fs.readFileSync(this.storageFile, "utf-8"));
31
+ return {
32
+ attempts: data.attempts || {},
33
+ lastCleanup: data.lastCleanup || Date.now(),
34
+ };
35
+ }
36
+ }
37
+ catch {
38
+ // Ignore errors, start fresh
39
+ }
40
+ return { attempts: {}, lastCleanup: Date.now() };
41
+ }
42
+ /**
43
+ * Save rate limit data to disk
44
+ */
45
+ saveStorage() {
46
+ try {
47
+ const configDir = path.dirname(this.storageFile);
48
+ if (!fs.existsSync(configDir)) {
49
+ fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
50
+ }
51
+ fs.writeFileSync(this.storageFile, JSON.stringify(this.storage, null, 2), { mode: 0o600 });
52
+ }
53
+ catch {
54
+ // Ignore save errors - rate limiting still works in memory
55
+ }
56
+ }
57
+ /**
58
+ * Normalize identifier (lowercase email)
59
+ */
60
+ normalizeKey(identifier) {
61
+ return identifier.toLowerCase().trim();
62
+ }
63
+ /**
64
+ * Clean up old records
65
+ */
66
+ cleanup() {
67
+ const now = Date.now();
68
+ // Only cleanup once per hour
69
+ if (now - this.storage.lastCleanup < RateLimiter.CLEANUP_INTERVAL_MS) {
70
+ return;
71
+ }
72
+ const windowStart = now - RateLimiter.WINDOW_MS;
73
+ for (const key of Object.keys(this.storage.attempts)) {
74
+ const record = this.storage.attempts[key];
75
+ // Remove if outside window AND not locked
76
+ if (record.firstAttempt < windowStart &&
77
+ (!record.lockedUntil || record.lockedUntil < now)) {
78
+ delete this.storage.attempts[key];
79
+ }
80
+ }
81
+ this.storage.lastCleanup = now;
82
+ this.saveStorage();
83
+ }
84
+ /**
85
+ * Check if login is allowed for the given identifier
86
+ * Returns: { allowed: boolean, retryAfter?: number, message?: string }
87
+ */
88
+ check(identifier) {
89
+ this.cleanup();
90
+ const key = this.normalizeKey(identifier);
91
+ const now = Date.now();
92
+ const record = this.storage.attempts[key];
93
+ // No previous attempts
94
+ if (!record) {
95
+ return {
96
+ allowed: true,
97
+ remainingAttempts: RateLimiter.MAX_ATTEMPTS
98
+ };
99
+ }
100
+ // Check if currently locked out
101
+ if (record.lockedUntil && record.lockedUntil > now) {
102
+ const retryAfter = Math.ceil((record.lockedUntil - now) / 1000);
103
+ const minutes = Math.ceil(retryAfter / 60);
104
+ return {
105
+ allowed: false,
106
+ retryAfter,
107
+ message: `Too many failed login attempts. Please try again in ${minutes} minute${minutes > 1 ? 's' : ''}.`,
108
+ };
109
+ }
110
+ // Check if window has expired (reset counter)
111
+ if (now - record.firstAttempt > RateLimiter.WINDOW_MS) {
112
+ delete this.storage.attempts[key];
113
+ this.saveStorage();
114
+ return {
115
+ allowed: true,
116
+ remainingAttempts: RateLimiter.MAX_ATTEMPTS
117
+ };
118
+ }
119
+ // Check remaining attempts
120
+ const remainingAttempts = RateLimiter.MAX_ATTEMPTS - record.count;
121
+ if (remainingAttempts <= 0) {
122
+ // Lock the account
123
+ record.lockedUntil = now + RateLimiter.LOCKOUT_MS;
124
+ this.saveStorage();
125
+ const minutes = Math.ceil(RateLimiter.LOCKOUT_MS / 1000 / 60);
126
+ return {
127
+ allowed: false,
128
+ retryAfter: Math.ceil(RateLimiter.LOCKOUT_MS / 1000),
129
+ message: `Too many failed login attempts. Account locked for ${minutes} minutes.`,
130
+ };
131
+ }
132
+ return {
133
+ allowed: true,
134
+ remainingAttempts
135
+ };
136
+ }
137
+ /**
138
+ * Record a failed login attempt
139
+ */
140
+ recordFailure(identifier) {
141
+ const key = this.normalizeKey(identifier);
142
+ const now = Date.now();
143
+ let record = this.storage.attempts[key];
144
+ if (!record || now - record.firstAttempt > RateLimiter.WINDOW_MS) {
145
+ // Start new window
146
+ record = {
147
+ count: 1,
148
+ firstAttempt: now,
149
+ lockedUntil: null,
150
+ };
151
+ }
152
+ else {
153
+ record.count++;
154
+ // Check if should lock
155
+ if (record.count >= RateLimiter.MAX_ATTEMPTS) {
156
+ record.lockedUntil = now + RateLimiter.LOCKOUT_MS;
157
+ }
158
+ }
159
+ this.storage.attempts[key] = record;
160
+ this.saveStorage();
161
+ }
162
+ /**
163
+ * Clear attempts on successful login
164
+ */
165
+ recordSuccess(identifier) {
166
+ const key = this.normalizeKey(identifier);
167
+ delete this.storage.attempts[key];
168
+ this.saveStorage();
169
+ }
170
+ /**
171
+ * Get rate limit info for display
172
+ */
173
+ getInfo() {
174
+ return {
175
+ maxAttempts: RateLimiter.MAX_ATTEMPTS,
176
+ windowMinutes: Math.ceil(RateLimiter.WINDOW_MS / 1000 / 60),
177
+ lockoutMinutes: Math.ceil(RateLimiter.LOCKOUT_MS / 1000 / 60),
178
+ };
179
+ }
180
+ }
181
+ //# sourceMappingURL=RateLimiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RateLimiter.js","sourceRoot":"","sources":["../../src/auth/RateLimiter.ts"],"names":[],"mappings":"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"}