mcp-sunsama 0.13.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # mcp-sunsama
2
2
 
3
+ ## 0.14.1
4
+
5
+ ### Patch Changes
6
+
7
+ - fix: resolve stdio authentication race condition
8
+
9
+ Fixes critical race condition in stdio authentication that caused "Global Sunsama client not initialized" errors and -32800 request cancelled errors in Raycast and other MCP clients. Implements lazy authentication with promise caching to prevent concurrent auth attempts and adds graceful startup error handling.
10
+
11
+ ## 0.14.0
12
+
13
+ ### Minor Changes
14
+
15
+ - Upgrade FastMCP dependency from 3.3.1 to 3.18.0
16
+
17
+ - Updated FastMCP to latest version for improved MCP protocol support and performance
18
+ - Added comprehensive CONTRIBUTING.md documentation with release process
19
+ - Improved README documentation structure
20
+ - All existing functionality remains compatible
21
+
3
22
  ## 0.13.0
4
23
 
5
24
  ### Minor Changes
@@ -0,0 +1,229 @@
1
+ # Contributing to mcp-sunsama
2
+
3
+ Thank you for your interest in contributing to the Sunsama MCP Server! This document provides guidelines and instructions for contributing to the project.
4
+
5
+ ## Getting Started
6
+
7
+ 1. Fork the repository on GitHub
8
+ 2. Clone your fork locally:
9
+ ```bash
10
+ git clone https://github.com/your-username/mcp-sunsama.git
11
+ cd mcp-sunsama
12
+ ```
13
+ 3. Install dependencies:
14
+ ```bash
15
+ bun install
16
+ ```
17
+ 4. Set up your environment:
18
+ ```bash
19
+ cp .env.example .env
20
+ # Edit .env with your Sunsama credentials for testing
21
+ ```
22
+
23
+ ## Development Workflow
24
+
25
+ ### Branch Naming Convention
26
+
27
+ Use the format `{type}/{short-name}` where `{type}` follows conventional commit naming:
28
+ - `feat/` - New features
29
+ - `fix/` - Bug fixes
30
+ - `chore/` - Maintenance tasks
31
+ - `refactor/` - Code refactoring
32
+ - `docs/` - Documentation updates
33
+ - `test/` - Test additions or fixes
34
+ - `ci/` - CI/CD changes
35
+
36
+ Example: `feat/add-task-labels`
37
+
38
+ ### Making Changes
39
+
40
+ 1. Create a feature branch from `main`:
41
+ ```bash
42
+ git checkout -b feat/your-feature-name
43
+ ```
44
+
45
+ 2. Make your changes following the code style and conventions
46
+
47
+ 3. Run tests and type checking:
48
+ ```bash
49
+ bun test
50
+ bun run typecheck
51
+ ```
52
+
53
+ 4. Build the project to ensure it compiles:
54
+ ```bash
55
+ bun run build
56
+ ```
57
+
58
+ 5. Create a changeset for your changes:
59
+ ```bash
60
+ bun run changeset
61
+ ```
62
+ Follow the prompts to describe your changes.
63
+
64
+ 6. Commit your changes using conventional commit format:
65
+ ```bash
66
+ git add .
67
+ git commit -m "feat: add support for task labels"
68
+ ```
69
+
70
+ 7. Push your branch and create a pull request
71
+
72
+ ## Code Style and Conventions
73
+
74
+ ### TypeScript Guidelines
75
+ - Use TypeScript strict mode
76
+ - Prefer explicit types over `any`
77
+ - Use Zod schemas for all tool parameters and responses
78
+ - Follow the existing modular architecture patterns
79
+
80
+ ### Architecture Patterns
81
+ - Tools should be organized by resource type (user, task, stream)
82
+ - Use the `createToolWrapper` utility for consistent error handling
83
+ - Apply response optimization (filtering and trimming) for large datasets
84
+ - Follow the dual transport pattern for stdio/httpStream compatibility
85
+
86
+ ### Testing
87
+ - Write tests for new Zod schemas in `src/schemas.test.ts`
88
+ - Ensure all existing tests pass before submitting PR
89
+ - Test both stdio and httpStream transports when applicable
90
+
91
+ ## Testing Your Changes
92
+
93
+ ### Unit Tests
94
+ ```bash
95
+ bun test # Run all tests
96
+ bun test:watch # Run tests in watch mode
97
+ ```
98
+
99
+ ### Integration Testing
100
+ ```bash
101
+ # Test with MCP Inspector
102
+ bun run inspect
103
+
104
+ # Test stdio transport directly
105
+ echo '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"1.0.0","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}' | bun src/main.ts
106
+
107
+ # Test compiled output
108
+ bun run build
109
+ node dist/main.js
110
+ ```
111
+
112
+ ### Testing with Raycast
113
+ 1. Build the project: `bun run build`
114
+ 2. Add to Raycast MCP configuration:
115
+ ```json
116
+ {
117
+ "mcpServers": {
118
+ "sunsama-dev": {
119
+ "command": "node",
120
+ "args": ["/path/to/your/mcp-sunsama/dist/main.js"],
121
+ "env": {
122
+ "SUNSAMA_EMAIL": "your-email",
123
+ "SUNSAMA_PASSWORD": "your-password"
124
+ }
125
+ }
126
+ }
127
+ }
128
+ ```
129
+ 3. Test in Raycast using `@mcp` mentions
130
+
131
+ ## For Maintainers
132
+
133
+ ### Release Process
134
+
135
+ This project uses [changesets](https://github.com/changesets/changesets) for version management and npm releases.
136
+
137
+ #### Prerequisites
138
+ - npm authentication configured (`npm login`)
139
+ - Write access to the main branch
140
+ - All tests passing on main branch
141
+
142
+ #### Creating a Release
143
+
144
+ 1. **Preparation**
145
+ ```bash
146
+ git checkout main
147
+ git pull
148
+ bun test # Ensure all tests pass
149
+ bun run typecheck # Check for TypeScript errors
150
+ bun run build # Verify clean build
151
+ ```
152
+
153
+ 2. **Version Update**
154
+ ```bash
155
+ bun run version # Apply changesets and update package.json
156
+ ```
157
+
158
+ **IMPORTANT**: After running this command, manually update the MCP server version in `src/main.ts` (line ~19) to match the new version in `package.json`:
159
+ ```typescript
160
+ const server = new FastMCP({
161
+ name: "Sunsama API Server",
162
+ version: "X.Y.Z", // <-- Update this to match package.json
163
+ ```
164
+
165
+ 3. **Pre-Release Validation**
166
+ ```bash
167
+ bun run typecheck # Ensure no TypeScript errors
168
+ bun test # Verify all tests pass
169
+ bun run build # Ensure clean build
170
+ ```
171
+
172
+ 4. **Commit Version Changes**
173
+ ```bash
174
+ git add .
175
+ git commit -m "chore: release version $(cat package.json | grep '"version"' | cut -d'"' -f4)"
176
+ ```
177
+ Verify the commit includes:
178
+ - `package.json` - version bump
179
+ - `CHANGELOG.md` - generated changelog
180
+ - `src/main.ts` - MCP server version update
181
+ - Removed changeset files from `.changeset/`
182
+
183
+ 5. **Publish to NPM**
184
+ ```bash
185
+ bun run release # Builds and publishes to npm
186
+ ```
187
+ This command runs `bun run build && changeset publish`
188
+
189
+ 6. **Push Changes**
190
+ ```bash
191
+ git push # Push version commit
192
+ git push --tags # Push version tags
193
+ ```
194
+
195
+ 7. **Verify Release**
196
+ - Check npm: https://www.npmjs.com/package/mcp-sunsama
197
+ - Test installation: `npx mcp-sunsama@latest`
198
+ - Verify GitHub release tag appears
199
+
200
+ #### Version Synchronization
201
+
202
+ The MCP server version in `src/main.ts` must always match the version in `package.json`. This ensures clients receive accurate version information during the MCP handshake.
203
+
204
+ #### Troubleshooting Releases
205
+
206
+ - **npm publish fails**: Check `npm whoami` and ensure you're logged in
207
+ - **Version conflict**: Run `npm view mcp-sunsama versions` to check existing versions
208
+ - **Build fails**: Fix TypeScript/build errors before attempting release
209
+ - **Changeset issues**: Ensure all changesets are committed before running `version`
210
+
211
+ ### Dependency Updates
212
+
213
+ When updating dependencies, especially FastMCP:
214
+ 1. Update the dependency: `bun add fastmcp@X.Y.Z`
215
+ 2. Run full test suite: `bun test`
216
+ 3. Test with MCP Inspector: `bun run inspect`
217
+ 4. Test stdio transport functionality
218
+ 5. Create a changeset describing the update
219
+
220
+ ## Questions or Issues?
221
+
222
+ - Open an issue on [GitHub](https://github.com/robertn702/mcp-sunsama/issues)
223
+ - Check existing issues before creating a new one
224
+ - Provide reproduction steps for bugs
225
+ - Include your environment details (OS, Node version, Bun version)
226
+
227
+ ## License
228
+
229
+ By contributing, you agree that your contributions will be licensed under the project's MIT License.
package/README.md CHANGED
@@ -128,6 +128,9 @@ bun run typecheck # Run TypeScript type checking
128
128
  bun run typecheck:watch # Watch mode type checking
129
129
  ```
130
130
 
131
+ ### Release Process
132
+ For information on creating releases and publishing to npm, see [CONTRIBUTING.md](CONTRIBUTING.md#release-process).
133
+
131
134
  ### Code Architecture
132
135
 
133
136
  The server is organized with a modular, resource-based architecture:
@@ -163,10 +166,17 @@ src/
163
166
 
164
167
  ## Contributing
165
168
 
166
- 1. Fork the repository
167
- 2. Create a feature branch
169
+ We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines on:
170
+ - Development workflow
171
+ - Code style and conventions
172
+ - Testing requirements
173
+ - Release process (for maintainers)
174
+
175
+ Quick start:
176
+ 1. Fork and clone the repository
177
+ 2. Install dependencies: `bun install`
168
178
  3. Make your changes
169
- 4. Add tests if applicable
179
+ 4. Create a changeset: `bun run changeset`
170
180
  5. Submit a pull request
171
181
 
172
182
  ## License
package/bun.lock CHANGED
@@ -5,13 +5,13 @@
5
5
  "name": "mcp-sunsama",
6
6
  "dependencies": {
7
7
  "@types/papaparse": "^5.3.16",
8
- "fastmcp": "3.3.1",
8
+ "fastmcp": "3.18.0",
9
9
  "papaparse": "^5.5.3",
10
10
  "sunsama-api": "0.11.0",
11
11
  "zod": "3.24.4",
12
12
  },
13
13
  "devDependencies": {
14
- "@changesets/cli": "^2.29.4",
14
+ "@changesets/cli": "^2.29.7",
15
15
  "@types/bun": "latest",
16
16
  "typescript": "^5",
17
17
  },
@@ -20,13 +20,13 @@
20
20
  "packages": {
21
21
  "@babel/runtime": ["@babel/runtime@7.27.6", "", {}, "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q=="],
22
22
 
23
- "@changesets/apply-release-plan": ["@changesets/apply-release-plan@7.0.12", "", { "dependencies": { "@changesets/config": "^3.1.1", "@changesets/get-version-range-type": "^0.4.0", "@changesets/git": "^3.0.4", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "detect-indent": "^6.0.0", "fs-extra": "^7.0.1", "lodash.startcase": "^4.4.0", "outdent": "^0.5.0", "prettier": "^2.7.1", "resolve-from": "^5.0.0", "semver": "^7.5.3" } }, "sha512-EaET7As5CeuhTzvXTQCRZeBUcisoYPDDcXvgTE/2jmmypKp0RC7LxKj/yzqeh/1qFTZI7oDGFcL1PHRuQuketQ=="],
23
+ "@changesets/apply-release-plan": ["@changesets/apply-release-plan@7.0.13", "", { "dependencies": { "@changesets/config": "^3.1.1", "@changesets/get-version-range-type": "^0.4.0", "@changesets/git": "^3.0.4", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "detect-indent": "^6.0.0", "fs-extra": "^7.0.1", "lodash.startcase": "^4.4.0", "outdent": "^0.5.0", "prettier": "^2.7.1", "resolve-from": "^5.0.0", "semver": "^7.5.3" } }, "sha512-BIW7bofD2yAWoE8H4V40FikC+1nNFEKBisMECccS16W1rt6qqhNTBDmIw5HaqmMgtLNz9e7oiALiEUuKrQ4oHg=="],
24
24
 
25
- "@changesets/assemble-release-plan": ["@changesets/assemble-release-plan@6.0.8", "", { "dependencies": { "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.3", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "semver": "^7.5.3" } }, "sha512-y8+8LvZCkKJdbUlpXFuqcavpzJR80PN0OIfn8HZdwK7Sh6MgLXm4hKY5vu6/NDoKp8lAlM4ERZCqRMLxP4m+MQ=="],
25
+ "@changesets/assemble-release-plan": ["@changesets/assemble-release-plan@6.0.9", "", { "dependencies": { "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.3", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "semver": "^7.5.3" } }, "sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ=="],
26
26
 
27
27
  "@changesets/changelog-git": ["@changesets/changelog-git@0.2.1", "", { "dependencies": { "@changesets/types": "^6.1.0" } }, "sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q=="],
28
28
 
29
- "@changesets/cli": ["@changesets/cli@2.29.4", "", { "dependencies": { "@changesets/apply-release-plan": "^7.0.12", "@changesets/assemble-release-plan": "^6.0.8", "@changesets/changelog-git": "^0.2.1", "@changesets/config": "^3.1.1", "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.3", "@changesets/get-release-plan": "^4.0.12", "@changesets/git": "^3.0.4", "@changesets/logger": "^0.1.1", "@changesets/pre": "^2.0.2", "@changesets/read": "^0.6.5", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@changesets/write": "^0.4.0", "@manypkg/get-packages": "^1.1.3", "ansi-colors": "^4.1.3", "ci-info": "^3.7.0", "enquirer": "^2.4.1", "external-editor": "^3.1.0", "fs-extra": "^7.0.1", "mri": "^1.2.0", "p-limit": "^2.2.0", "package-manager-detector": "^0.2.0", "picocolors": "^1.1.0", "resolve-from": "^5.0.0", "semver": "^7.5.3", "spawndamnit": "^3.0.1", "term-size": "^2.1.0" }, "bin": { "changeset": "bin.js" } }, "sha512-VW30x9oiFp/un/80+5jLeWgEU6Btj8IqOgI+X/zAYu4usVOWXjPIK5jSSlt5jsCU7/6Z7AxEkarxBxGUqkAmNg=="],
29
+ "@changesets/cli": ["@changesets/cli@2.29.7", "", { "dependencies": { "@changesets/apply-release-plan": "^7.0.13", "@changesets/assemble-release-plan": "^6.0.9", "@changesets/changelog-git": "^0.2.1", "@changesets/config": "^3.1.1", "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.3", "@changesets/get-release-plan": "^4.0.13", "@changesets/git": "^3.0.4", "@changesets/logger": "^0.1.1", "@changesets/pre": "^2.0.2", "@changesets/read": "^0.6.5", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@changesets/write": "^0.4.0", "@inquirer/external-editor": "^1.0.0", "@manypkg/get-packages": "^1.1.3", "ansi-colors": "^4.1.3", "ci-info": "^3.7.0", "enquirer": "^2.4.1", "fs-extra": "^7.0.1", "mri": "^1.2.0", "p-limit": "^2.2.0", "package-manager-detector": "^0.2.0", "picocolors": "^1.1.0", "resolve-from": "^5.0.0", "semver": "^7.5.3", "spawndamnit": "^3.0.1", "term-size": "^2.1.0" }, "bin": { "changeset": "bin.js" } }, "sha512-R7RqWoaksyyKXbKXBTbT4REdy22yH81mcFK6sWtqSanxUCbUi9Uf+6aqxZtDQouIqPdem2W56CdxXgsxdq7FLQ=="],
30
30
 
31
31
  "@changesets/config": ["@changesets/config@3.1.1", "", { "dependencies": { "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.3", "@changesets/logger": "^0.1.1", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "fs-extra": "^7.0.1", "micromatch": "^4.0.8" } }, "sha512-bd+3Ap2TKXxljCggI0mKPfzCQKeV/TU4yO2h2C6vAihIo8tzseAn2e7klSuiyYYXvgu53zMN1OeYMIQkaQoWnA=="],
32
32
 
@@ -34,7 +34,7 @@
34
34
 
35
35
  "@changesets/get-dependents-graph": ["@changesets/get-dependents-graph@2.1.3", "", { "dependencies": { "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "picocolors": "^1.1.0", "semver": "^7.5.3" } }, "sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ=="],
36
36
 
37
- "@changesets/get-release-plan": ["@changesets/get-release-plan@4.0.12", "", { "dependencies": { "@changesets/assemble-release-plan": "^6.0.8", "@changesets/config": "^3.1.1", "@changesets/pre": "^2.0.2", "@changesets/read": "^0.6.5", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3" } }, "sha512-KukdEgaafnyGryUwpHG2kZ7xJquOmWWWk5mmoeQaSvZTWH1DC5D/Sw6ClgGFYtQnOMSQhgoEbDxAbpIIayKH1g=="],
37
+ "@changesets/get-release-plan": ["@changesets/get-release-plan@4.0.13", "", { "dependencies": { "@changesets/assemble-release-plan": "^6.0.9", "@changesets/config": "^3.1.1", "@changesets/pre": "^2.0.2", "@changesets/read": "^0.6.5", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3" } }, "sha512-DWG1pus72FcNeXkM12tx+xtExyH/c9I1z+2aXlObH3i9YA7+WZEVaiHzHl03thpvAgWTRaH64MpfHxozfF7Dvg=="],
38
38
 
39
39
  "@changesets/get-version-range-type": ["@changesets/get-version-range-type@0.4.0", "", {}, "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ=="],
40
40
 
@@ -54,13 +54,15 @@
54
54
 
55
55
  "@changesets/write": ["@changesets/write@0.4.0", "", { "dependencies": { "@changesets/types": "^6.1.0", "fs-extra": "^7.0.1", "human-id": "^4.1.1", "prettier": "^2.7.1" } }, "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q=="],
56
56
 
57
+ "@inquirer/external-editor": ["@inquirer/external-editor@1.0.2", "", { "dependencies": { "chardet": "^2.1.0", "iconv-lite": "^0.7.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ=="],
58
+
57
59
  "@manypkg/find-root": ["@manypkg/find-root@1.1.0", "", { "dependencies": { "@babel/runtime": "^7.5.5", "@types/node": "^12.7.1", "find-up": "^4.1.0", "fs-extra": "^8.1.0" } }, "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA=="],
58
60
 
59
61
  "@manypkg/get-packages": ["@manypkg/get-packages@1.1.3", "", { "dependencies": { "@babel/runtime": "^7.5.5", "@changesets/types": "^4.0.1", "@manypkg/find-root": "^1.1.0", "fs-extra": "^8.1.0", "globby": "^11.0.0", "read-yaml-file": "^1.1.0" } }, "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A=="],
60
62
 
61
63
  "@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
62
64
 
63
- "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.12.1", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-KG1CZhZfWg+u8pxeM/mByJDScJSrjjxLc8fwQqbsS8xCjBmQfMNEBTotYdNanKekepnfRI85GtgQlctLFpcYPw=="],
65
+ "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.18.2", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-beedclIvFcCnPrYgHsylqiYJVJ/CI47Vyc4tY8no1/Li/O8U4BTlJfy6ZwxkYwx+Mx10nrgwSVrA7VBbhh4slg=="],
64
66
 
65
67
  "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
66
68
 
@@ -112,7 +114,7 @@
112
114
 
113
115
  "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
114
116
 
115
- "chardet": ["chardet@0.7.0", "", {}, "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="],
117
+ "chardet": ["chardet@2.1.0", "", {}, "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA=="],
116
118
 
117
119
  "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
118
120
 
@@ -174,15 +176,13 @@
174
176
 
175
177
  "extendable-error": ["extendable-error@0.1.7", "", {}, "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg=="],
176
178
 
177
- "external-editor": ["external-editor@3.1.0", "", { "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" } }, "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew=="],
178
-
179
179
  "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
180
180
 
181
181
  "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
182
182
 
183
183
  "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
184
184
 
185
- "fastmcp": ["fastmcp@3.3.1", "", { "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", "@standard-schema/spec": "^1.0.0", "execa": "^9.6.0", "file-type": "^21.0.0", "fuse.js": "^7.1.0", "mcp-proxy": "^5.0.0", "strict-event-emitter-types": "^2.0.0", "undici": "^7.10.0", "uri-templates": "^0.2.0", "xsschema": "0.3.0-beta.3", "yargs": "^18.0.0", "zod": "^3.25.56", "zod-to-json-schema": "^3.24.5" }, "bin": { "fastmcp": "dist/bin/fastmcp.js" } }, "sha512-08tcvSJoWUpimxEX0DBf9uGPDb//5BPNImfpScPIUGobcaA4CABnLzbJYvO5KPwozOyFk1Qi/9QSAHE/dzSHMw=="],
185
+ "fastmcp": ["fastmcp@3.18.0", "", { "dependencies": { "@modelcontextprotocol/sdk": "^1.17.2", "@standard-schema/spec": "^1.0.0", "execa": "^9.6.0", "file-type": "^21.0.0", "fuse.js": "^7.1.0", "mcp-proxy": "^5.5.4", "strict-event-emitter-types": "^2.0.0", "undici": "^7.13.0", "uri-templates": "^0.2.0", "xsschema": "0.3.5", "yargs": "^18.0.0", "zod": "^3.25.76", "zod-to-json-schema": "^3.24.6" }, "bin": { "fastmcp": "dist/bin/fastmcp.js" } }, "sha512-Td8+QMHmA8WeLTczlMIvV+/Xvk164d/047Hku3kwExuwm3tgMTrwmTI10euP04lNE5WCFZ1xdF9XISnMxeJ3yw=="],
186
186
 
187
187
  "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
188
188
 
@@ -240,7 +240,7 @@
240
240
 
241
241
  "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="],
242
242
 
243
- "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
243
+ "iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="],
244
244
 
245
245
  "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
246
246
 
@@ -288,7 +288,7 @@
288
288
 
289
289
  "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
290
290
 
291
- "mcp-proxy": ["mcp-proxy@5.1.0", "", { "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", "eventsource": "^4.0.0", "yargs": "^18.0.0" }, "bin": { "mcp-proxy": "dist/bin/mcp-proxy.js" } }, "sha512-6pfAdXFOvfpqse9dMLuQo8WTSFdD0al8vhr0Nx5EWv+dR6aTAHphdQj9NUP2xej2bhaWzJ5p8BRwbvXufOjJHA=="],
291
+ "mcp-proxy": ["mcp-proxy@5.6.1", "", { "bin": { "mcp-proxy": "dist/bin/mcp-proxy.js" } }, "sha512-307KBxoJ4YS4fsa26MFkHLcuWYUoy/bTj/VNG/Km8/elgydEh0o+YpJiG7ywUrHhSsaYKpBc9OgMRxibUCjKKA=="],
292
292
 
293
293
  "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
294
294
 
@@ -318,8 +318,6 @@
318
318
 
319
319
  "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
320
320
 
321
- "os-tmpdir": ["os-tmpdir@1.0.2", "", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="],
322
-
323
321
  "outdent": ["outdent@0.5.0", "", {}, "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q=="],
324
322
 
325
323
  "p-filter": ["p-filter@2.1.0", "", { "dependencies": { "p-map": "^2.0.0" } }, "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw=="],
@@ -438,8 +436,6 @@
438
436
 
439
437
  "tldts-core": ["tldts-core@6.1.86", "", {}, "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA=="],
440
438
 
441
- "tmp": ["tmp@0.0.33", "", { "dependencies": { "os-tmpdir": "~1.0.2" } }, "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="],
442
-
443
439
  "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
444
440
 
445
441
  "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
@@ -458,7 +454,7 @@
458
454
 
459
455
  "uint8array-extras": ["uint8array-extras@1.4.0", "", {}, "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ=="],
460
456
 
461
- "undici": ["undici@7.10.0", "", {}, "sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw=="],
457
+ "undici": ["undici@7.16.0", "", {}, "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g=="],
462
458
 
463
459
  "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
464
460
 
@@ -480,7 +476,7 @@
480
476
 
481
477
  "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
482
478
 
483
- "xsschema": ["xsschema@0.3.0-beta.3", "", { "peerDependencies": { "@valibot/to-json-schema": "^1.0.0", "arktype": "^2.1.16", "effect": "^3.14.5", "sury": "^10.0.0-rc", "zod": "^3.25.0", "zod-to-json-schema": "^3.24.5" }, "optionalPeers": ["@valibot/to-json-schema", "arktype", "effect", "sury", "zod", "zod-to-json-schema"] }, "sha512-8fKI0Kqxs7npz3ElebNCeGdS0HDuS2qL3IqHK5O53yCdh419hcr3GQillwN39TNFasHjbMLQ+DjSwpY0NONdnQ=="],
479
+ "xsschema": ["xsschema@0.3.5", "", { "peerDependencies": { "@valibot/to-json-schema": "^1.0.0", "arktype": "^2.1.20", "effect": "^3.16.0", "sury": "^10.0.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.5" }, "optionalPeers": ["@valibot/to-json-schema", "arktype", "effect", "sury", "zod", "zod-to-json-schema"] }, "sha512-f+dcy11dTv7aBEEfTbXWnrm/1b/Ds2k4taN0TpN6KCtPAD58kyE/R1znUdrHdBnhIFexj9k+pS1fm6FgtSISrQ=="],
484
480
 
485
481
  "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
486
482
 
@@ -494,7 +490,7 @@
494
490
 
495
491
  "zod": ["zod@3.24.4", "", {}, "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="],
496
492
 
497
- "zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
493
+ "zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="],
498
494
 
499
495
  "@manypkg/find-root/@types/node": ["@types/node@12.20.55", "", {}, "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="],
500
496
 
@@ -504,18 +500,16 @@
504
500
 
505
501
  "@manypkg/get-packages/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
506
502
 
507
- "@modelcontextprotocol/sdk/zod": ["zod@3.25.56", "", {}, "sha512-rd6eEF3BTNvQnR2e2wwolfTmUTnp70aUTqr0oaGbHifzC3BKJsoV+Gat8vxUMR1hwOKBs6El+qWehrHbCpW6SQ=="],
503
+ "@modelcontextprotocol/sdk/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
508
504
 
509
505
  "body-parser/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
510
506
 
511
507
  "cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
512
508
 
513
- "fastmcp/zod": ["zod@3.25.64", "", {}, "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g=="],
509
+ "fastmcp/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
514
510
 
515
511
  "http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
516
512
 
517
- "mcp-proxy/eventsource": ["eventsource@4.0.0", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-fvIkb9qZzdMxgZrEQDyll+9oJsyaVvY92I2Re+qK0qEJ+w5s0X3dtz+M0VAPOjP1gtU3iqWyjQ0G3nvd5CLZ2g=="],
518
-
519
513
  "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
520
514
 
521
515
  "raw-body/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
@@ -3,11 +3,11 @@ import { SunsamaClient } from "sunsama-api";
3
3
  * Initialize stdio authentication using environment variables
4
4
  * @throws {Error} If credentials are missing or authentication fails
5
5
  */
6
- export declare function initializeStdioAuth(): Promise<void>;
6
+ export declare function initializeStdioAuth(): Promise<SunsamaClient>;
7
7
  /**
8
8
  * Get the global Sunsama client instance for stdio transport
9
- * @returns {SunsamaClient} The authenticated global client
10
- * @throws {Error} If global client is not initialized
9
+ * @returns {Promise<SunsamaClient>} The authenticated global client
10
+ * @throws {Error} If credentials are missing or authentication fails
11
11
  */
12
- export declare function getGlobalSunsamaClient(): SunsamaClient;
12
+ export declare function getGlobalSunsamaClient(): Promise<SunsamaClient>;
13
13
  //# sourceMappingURL=stdio.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/auth/stdio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAO5C;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CASzD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,aAAa,CAKtD"}
1
+ {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/auth/stdio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAO5C;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,aAAa,CAAC,CAWlE;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,aAAa,CAAC,CAMrE"}
@@ -1,8 +1,8 @@
1
1
  import { SunsamaClient } from "sunsama-api";
2
2
  /**
3
- * Global Sunsama client instance for stdio transport
3
+ * Cached authentication promise to prevent concurrent auth attempts
4
4
  */
5
- let globalSunsamaClient = null;
5
+ let authenticationPromise = null;
6
6
  /**
7
7
  * Initialize stdio authentication using environment variables
8
8
  * @throws {Error} If credentials are missing or authentication fails
@@ -11,17 +11,18 @@ export async function initializeStdioAuth() {
11
11
  if (!process.env.SUNSAMA_EMAIL || !process.env.SUNSAMA_PASSWORD) {
12
12
  throw new Error("Sunsama credentials not configured. Please set SUNSAMA_EMAIL and SUNSAMA_PASSWORD environment variables.");
13
13
  }
14
- globalSunsamaClient = new SunsamaClient();
15
- await globalSunsamaClient.login(process.env.SUNSAMA_EMAIL, process.env.SUNSAMA_PASSWORD);
14
+ const sunsamaClient = new SunsamaClient();
15
+ await sunsamaClient.login(process.env.SUNSAMA_EMAIL, process.env.SUNSAMA_PASSWORD);
16
+ return sunsamaClient;
16
17
  }
17
18
  /**
18
19
  * Get the global Sunsama client instance for stdio transport
19
- * @returns {SunsamaClient} The authenticated global client
20
- * @throws {Error} If global client is not initialized
20
+ * @returns {Promise<SunsamaClient>} The authenticated global client
21
+ * @throws {Error} If credentials are missing or authentication fails
21
22
  */
22
- export function getGlobalSunsamaClient() {
23
- if (!globalSunsamaClient) {
24
- throw new Error("Global Sunsama client not initialized.");
23
+ export async function getGlobalSunsamaClient() {
24
+ if (!authenticationPromise) {
25
+ authenticationPromise = initializeStdioAuth();
25
26
  }
26
- return globalSunsamaClient;
27
+ return authenticationPromise;
27
28
  }
package/dist/main.js CHANGED
@@ -7,13 +7,20 @@ import { allTools } from "./tools/index.js";
7
7
  import { apiDocumentationResource } from "./resources/index.js";
8
8
  // Get transport configuration with validation
9
9
  const transportConfig = getTransportConfig();
10
- // For stdio transport, authenticate at startup with environment variables
10
+ // For stdio transport, attempt authentication at startup with environment variables
11
11
  if (transportConfig.transportType === "stdio") {
12
- await initializeStdioAuth();
12
+ try {
13
+ await initializeStdioAuth();
14
+ console.log("Sunsama authentication successful");
15
+ }
16
+ catch (error) {
17
+ console.error("Failed to initialize Sunsama authentication:", error instanceof Error ? error.message : 'Unknown error');
18
+ console.error("Server will start but tools will retry authentication on first use");
19
+ }
13
20
  }
14
21
  const server = new FastMCP({
15
22
  name: "Sunsama API Server",
16
- version: "0.13.0",
23
+ version: "0.14.1",
17
24
  instructions: `
18
25
  This MCP server provides access to the Sunsama API for task and project management.
19
26
 
@@ -21,10 +21,6 @@ export declare function createToolWrapper<T>(config: {
21
21
  parameters: any;
22
22
  execute: (args: T, context: ToolContext) => Promise<any>;
23
23
  };
24
- /**
25
- * Gets the Sunsama client for the current session
26
- */
27
- export declare function getClient(session: SessionData | undefined | null): import("sunsama-api").SunsamaClient;
28
24
  /**
29
25
  * Formats data as JSON response
30
26
  */
@@ -1 +1 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/tools/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIpD,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;AAE/C,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;CACJ;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,GAAG,CAAC;IAChB,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CAC1D;;;;oBAKyB,CAAC,WAAW,WAAW;EAchD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,uCAEhE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,GAAG,GAAG,YAAY,CAS1D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,YAAY,CAS3D;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,GAAG,EAAE,EACX,UAAU,EAAE;IACV,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GACA,YAAY,CAYd"}
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/tools/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;AAE/C,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;CACJ;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,GAAG,CAAC;IAChB,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CAC1D;;;;oBAKyB,CAAC,WAAW,WAAW;EAchD;AAGD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,GAAG,GAAG,YAAY,CAS1D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,YAAY,CAS3D;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,GAAG,EAAE,EACX,UAAU,EAAE;IACV,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GACA,YAAY,CAYd"}
@@ -1,4 +1,3 @@
1
- import { getSunsamaClient } from "../utils/client-resolver.js";
2
1
  import { toTsv } from "../utils/to-tsv.js";
3
2
  /**
4
3
  * Creates a standardized tool execution wrapper with error handling and logging
@@ -24,12 +23,6 @@ export function createToolWrapper(config) {
24
23
  }
25
24
  };
26
25
  }
27
- /**
28
- * Gets the Sunsama client for the current session
29
- */
30
- export function getClient(session) {
31
- return getSunsamaClient(session);
32
- }
33
26
  /**
34
27
  * Formats data as JSON response
35
28
  */
@@ -1 +1 @@
1
- {"version":3,"file":"stream-tools.d.ts","sourceRoot":"","sources":["../../src/tools/stream-tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAmD,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAEhG,eAAO,MAAM,cAAc;;;;;CAczB,CAAC;AAEH,eAAO,MAAM,WAAW;;;;;GAAmB,CAAC"}
1
+ {"version":3,"file":"stream-tools.d.ts","sourceRoot":"","sources":["../../src/tools/stream-tools.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAErF,eAAO,MAAM,cAAc;;;;;CAczB,CAAC;AAEH,eAAO,MAAM,WAAW;;;;;GAAmB,CAAC"}
@@ -1,12 +1,13 @@
1
1
  import { getStreamsSchema } from "../schemas.js";
2
- import { createToolWrapper, getClient, formatTsvResponse } from "./shared.js";
2
+ import { getSunsamaClient } from "../utils/client-resolver.js";
3
+ import { createToolWrapper, formatTsvResponse } from "./shared.js";
3
4
  export const getStreamsTool = createToolWrapper({
4
5
  name: "get-streams",
5
6
  description: "Get streams for the user's group (streams are called 'channels' in the Sunsama UI)",
6
7
  parameters: getStreamsSchema,
7
8
  execute: async (_args, context) => {
8
9
  context.log.info("Getting streams for user's group");
9
- const sunsamaClient = getClient(context.session);
10
+ const sunsamaClient = await getSunsamaClient(context.session);
10
11
  const streams = await sunsamaClient.getStreamsByGroupId();
11
12
  context.log.info("Successfully retrieved streams", { count: streams.length });
12
13
  return formatTsvResponse(streams);
@@ -1 +1 @@
1
- {"version":3,"file":"task-tools.d.ts","sourceRoot":"","sources":["../../src/tools/task-tools.ts"],"names":[],"mappings":"AAiCA,OAAO,EAML,KAAK,WAAW,EACjB,MAAM,aAAa,CAAC;AAGrB,eAAO,MAAM,mBAAmB;;;;;CAe9B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;CAkC5B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;CAsC/B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;CAqB1B,CAAC;AAGH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;CA4CzB,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;;CA2BzB,CAAC;AAGH,eAAO,MAAM,sBAAsB;;;;;;;;;CA4BjC,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;;;;;;;;CAkCnC,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;CAgChC,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;;;;;;;CA4BpC,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;CAsC9B,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;CA6BhC,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;CAmC7B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;CAiC/B,CAAC;AAGH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoBrB,CAAC"}
1
+ {"version":3,"file":"task-tools.d.ts","sourceRoot":"","sources":["../../src/tools/task-tools.ts"],"names":[],"mappings":"AAkCA,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,aAAa,CAAC;AAGrB,eAAO,MAAM,mBAAmB;;;;;CAe9B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;CAkC5B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;CAsC/B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;CAqB1B,CAAC;AAGH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;CA4CzB,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;;CA2BzB,CAAC;AAGH,eAAO,MAAM,sBAAsB;;;;;;;;;CA4BjC,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;;;;;;;;CAkCnC,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;CAgChC,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;;;;;;;CA4BpC,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;CAsC9B,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;;;;CA6BhC,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;CAmC7B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;CAiC/B,CAAC;AAGH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoBrB,CAAC"}
@@ -1,7 +1,8 @@
1
1
  import { createTaskSchema, deleteTaskSchema, getArchivedTasksSchema, getTaskByIdSchema, getTasksBacklogSchema, getTasksByDaySchema, updateTaskBacklogSchema, updateTaskCompleteSchema, updateTaskDueDateSchema, updateTaskNotesSchema, updateTaskPlannedTimeSchema, updateTaskSnoozeDateSchema, updateTaskStreamSchema, updateTaskTextSchema } from "../schemas.js";
2
2
  import { filterTasksByCompletion } from "../utils/task-filters.js";
3
3
  import { trimTasksForResponse } from "../utils/task-trimmer.js";
4
- import { createToolWrapper, getClient, formatJsonResponse, formatTsvResponse, formatPaginatedTsvResponse } from "./shared.js";
4
+ import { getSunsamaClient } from "../utils/client-resolver.js";
5
+ import { createToolWrapper, formatJsonResponse, formatTsvResponse, formatPaginatedTsvResponse } from "./shared.js";
5
6
  // Task Query Tools
6
7
  export const getTasksBacklogTool = createToolWrapper({
7
8
  name: "get-tasks-backlog",
@@ -9,7 +10,7 @@ export const getTasksBacklogTool = createToolWrapper({
9
10
  parameters: getTasksBacklogSchema,
10
11
  execute: async (_args, context) => {
11
12
  context.log.info("Getting backlog tasks");
12
- const sunsamaClient = getClient(context.session);
13
+ const sunsamaClient = await getSunsamaClient(context.session);
13
14
  const tasks = await sunsamaClient.getTasksBacklog();
14
15
  const trimmedTasks = trimTasksForResponse(tasks);
15
16
  context.log.info("Successfully retrieved backlog tasks", { count: tasks.length });
@@ -26,7 +27,7 @@ export const getTasksByDayTool = createToolWrapper({
26
27
  timezone,
27
28
  completionFilter
28
29
  });
29
- const sunsamaClient = getClient(context.session);
30
+ const sunsamaClient = await getSunsamaClient(context.session);
30
31
  // If no timezone provided, get the user's default timezone
31
32
  let resolvedTimezone = timezone;
32
33
  if (!resolvedTimezone) {
@@ -58,7 +59,7 @@ export const getArchivedTasksTool = createToolWrapper({
58
59
  requestedLimit,
59
60
  fetchLimit
60
61
  });
61
- const sunsamaClient = getClient(context.session);
62
+ const sunsamaClient = await getSunsamaClient(context.session);
62
63
  const allTasks = await sunsamaClient.getArchivedTasks(offset, fetchLimit);
63
64
  const hasMore = allTasks.length > requestedLimit;
64
65
  const tasks = hasMore ? allTasks.slice(0, requestedLimit) : allTasks;
@@ -85,7 +86,7 @@ export const getTaskByIdTool = createToolWrapper({
85
86
  parameters: getTaskByIdSchema,
86
87
  execute: async ({ taskId }, context) => {
87
88
  context.log.info("Getting task by ID", { taskId });
88
- const sunsamaClient = getClient(context.session);
89
+ const sunsamaClient = await getSunsamaClient(context.session);
89
90
  const task = await sunsamaClient.getTaskById(taskId);
90
91
  if (task) {
91
92
  context.log.info("Successfully retrieved task by ID", {
@@ -116,7 +117,7 @@ export const createTaskTool = createToolWrapper({
116
117
  isPrivate: isPrivate,
117
118
  customTaskId: !!taskId
118
119
  });
119
- const sunsamaClient = getClient(context.session);
120
+ const sunsamaClient = await getSunsamaClient(context.session);
120
121
  const options = {};
121
122
  if (notes)
122
123
  options.notes = notes;
@@ -157,7 +158,7 @@ export const deleteTaskTool = createToolWrapper({
157
158
  limitResponsePayload,
158
159
  wasTaskMerged
159
160
  });
160
- const sunsamaClient = getClient(context.session);
161
+ const sunsamaClient = await getSunsamaClient(context.session);
161
162
  const result = await sunsamaClient.deleteTask(taskId, limitResponsePayload, wasTaskMerged);
162
163
  context.log.info("Successfully deleted task", {
163
164
  taskId,
@@ -182,7 +183,7 @@ export const updateTaskCompleteTool = createToolWrapper({
182
183
  hasCustomCompleteOn: !!completeOn,
183
184
  limitResponsePayload
184
185
  });
185
- const sunsamaClient = getClient(context.session);
186
+ const sunsamaClient = await getSunsamaClient(context.session);
186
187
  const result = await sunsamaClient.updateTaskComplete(taskId, completeOn, limitResponsePayload);
187
188
  context.log.info("Successfully marked task as complete", {
188
189
  taskId,
@@ -208,7 +209,7 @@ export const updateTaskSnoozeDateTool = createToolWrapper({
208
209
  timezone,
209
210
  limitResponsePayload
210
211
  });
211
- const sunsamaClient = getClient(context.session);
212
+ const sunsamaClient = await getSunsamaClient(context.session);
212
213
  const options = {};
213
214
  if (timezone)
214
215
  options.timezone = timezone;
@@ -238,7 +239,7 @@ export const updateTaskBacklogTool = createToolWrapper({
238
239
  timezone,
239
240
  limitResponsePayload
240
241
  });
241
- const sunsamaClient = getClient(context.session);
242
+ const sunsamaClient = await getSunsamaClient(context.session);
242
243
  const options = {};
243
244
  if (timezone)
244
245
  options.timezone = timezone;
@@ -267,7 +268,7 @@ export const updateTaskPlannedTimeTool = createToolWrapper({
267
268
  timeEstimateMinutes,
268
269
  limitResponsePayload
269
270
  });
270
- const sunsamaClient = getClient(context.session);
271
+ const sunsamaClient = await getSunsamaClient(context.session);
271
272
  const result = await sunsamaClient.updateTaskPlannedTime(taskId, timeEstimateMinutes, limitResponsePayload);
272
273
  context.log.info("Successfully updated task planned time", {
273
274
  taskId,
@@ -296,7 +297,7 @@ export const updateTaskNotesTool = createToolWrapper({
296
297
  contentLength: content.value.length,
297
298
  limitResponsePayload
298
299
  });
299
- const sunsamaClient = getClient(context.session);
300
+ const sunsamaClient = await getSunsamaClient(context.session);
300
301
  const options = {};
301
302
  if (limitResponsePayload !== undefined)
302
303
  options.limitResponsePayload = limitResponsePayload;
@@ -325,7 +326,7 @@ export const updateTaskDueDateTool = createToolWrapper({
325
326
  dueDate,
326
327
  limitResponsePayload
327
328
  });
328
- const sunsamaClient = getClient(context.session);
329
+ const sunsamaClient = await getSunsamaClient(context.session);
329
330
  const result = await sunsamaClient.updateTaskDueDate(taskId, dueDate, limitResponsePayload);
330
331
  context.log.info("Successfully updated task due date", {
331
332
  taskId,
@@ -352,7 +353,7 @@ export const updateTaskTextTool = createToolWrapper({
352
353
  recommendedStreamId,
353
354
  limitResponsePayload
354
355
  });
355
- const sunsamaClient = getClient(context.session);
356
+ const sunsamaClient = await getSunsamaClient(context.session);
356
357
  const options = {};
357
358
  if (recommendedStreamId !== undefined)
358
359
  options.recommendedStreamId = recommendedStreamId;
@@ -383,7 +384,7 @@ export const updateTaskStreamTool = createToolWrapper({
383
384
  streamId,
384
385
  limitResponsePayload
385
386
  });
386
- const sunsamaClient = getClient(context.session);
387
+ const sunsamaClient = await getSunsamaClient(context.session);
387
388
  const result = await sunsamaClient.updateTaskStream(taskId, streamId, limitResponsePayload !== undefined ? limitResponsePayload : true);
388
389
  context.log.info("Successfully updated task stream assignment", {
389
390
  taskId,
@@ -1 +1 @@
1
- {"version":3,"file":"user-tools.d.ts","sourceRoot":"","sources":["../../src/tools/user-tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAoD,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAEjG,eAAO,MAAM,WAAW;;;;;CActB,CAAC;AAEH,eAAO,MAAM,SAAS;;;;;GAAgB,CAAC"}
1
+ {"version":3,"file":"user-tools.d.ts","sourceRoot":"","sources":["../../src/tools/user-tools.ts"],"names":[],"mappings":"AAEA,OAAO,EAAyC,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtF,eAAO,MAAM,WAAW;;;;;CActB,CAAC;AAEH,eAAO,MAAM,SAAS;;;;;GAAgB,CAAC"}
@@ -1,12 +1,13 @@
1
1
  import { getUserSchema } from "../schemas.js";
2
- import { createToolWrapper, getClient, formatJsonResponse } from "./shared.js";
2
+ import { getSunsamaClient } from "../utils/client-resolver.js";
3
+ import { createToolWrapper, formatJsonResponse } from "./shared.js";
3
4
  export const getUserTool = createToolWrapper({
4
5
  name: "get-user",
5
6
  description: "Get current user information including profile, timezone, and group details",
6
7
  parameters: getUserSchema,
7
8
  execute: async (_args, context) => {
8
9
  context.log.info("Getting user information");
9
- const sunsamaClient = getClient(context.session);
10
+ const sunsamaClient = await getSunsamaClient(context.session);
10
11
  const user = await sunsamaClient.getUser();
11
12
  context.log.info("Successfully retrieved user information", { userId: user._id });
12
13
  return formatJsonResponse(user);
@@ -2,9 +2,9 @@ import { SunsamaClient } from "sunsama-api";
2
2
  import type { SessionData } from "../auth/types.js";
3
3
  /**
4
4
  * Gets the appropriate SunsamaClient instance based on transport type
5
- * @param session - Session data for HTTP transport (null for stdio)
6
- * @returns Authenticated SunsamaClient instance
5
+ * @param session - Session data for HTTP transport (undefined/null for stdio)
6
+ * @returns Promise<SunsamaClient> Authenticated SunsamaClient instance
7
7
  * @throws {Error} If session is not available for HTTP transport or global client not initialized for stdio
8
8
  */
9
- export declare function getSunsamaClient(session: SessionData | null): SunsamaClient;
9
+ export declare function getSunsamaClient(session?: SessionData | null): Promise<SunsamaClient>;
10
10
  //# sourceMappingURL=client-resolver.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client-resolver.d.ts","sourceRoot":"","sources":["../../src/utils/client-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,aAAa,CAW3E"}
1
+ {"version":3,"file":"client-resolver.d.ts","sourceRoot":"","sources":["../../src/utils/client-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,CAW3F"}
@@ -3,11 +3,11 @@ import { getGlobalSunsamaClient } from "../auth/stdio.js";
3
3
  import { getTransportConfig } from "../config/transport.js";
4
4
  /**
5
5
  * Gets the appropriate SunsamaClient instance based on transport type
6
- * @param session - Session data for HTTP transport (null for stdio)
7
- * @returns Authenticated SunsamaClient instance
6
+ * @param session - Session data for HTTP transport (undefined/null for stdio)
7
+ * @returns Promise<SunsamaClient> Authenticated SunsamaClient instance
8
8
  * @throws {Error} If session is not available for HTTP transport or global client not initialized for stdio
9
9
  */
10
- export function getSunsamaClient(session) {
10
+ export async function getSunsamaClient(session) {
11
11
  const transportConfig = getTransportConfig();
12
12
  if (transportConfig.transportType === "httpStream") {
13
13
  if (!session?.sunsamaClient) {
@@ -15,5 +15,5 @@ export function getSunsamaClient(session) {
15
15
  }
16
16
  return session.sunsamaClient;
17
17
  }
18
- return getGlobalSunsamaClient();
18
+ return await getGlobalSunsamaClient();
19
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-sunsama",
3
- "version": "0.13.0",
3
+ "version": "0.14.1",
4
4
  "description": "MCP server for Sunsama API integration",
5
5
  "type": "module",
6
6
  "private": false,
@@ -24,13 +24,13 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@types/papaparse": "^5.3.16",
27
- "fastmcp": "3.3.1",
27
+ "fastmcp": "3.18.0",
28
28
  "papaparse": "^5.5.3",
29
29
  "sunsama-api": "0.11.0",
30
30
  "zod": "3.24.4"
31
31
  },
32
32
  "devDependencies": {
33
- "@changesets/cli": "^2.29.4",
33
+ "@changesets/cli": "^2.29.7",
34
34
  "@types/bun": "latest",
35
35
  "typescript": "^5"
36
36
  },
package/src/auth/stdio.ts CHANGED
@@ -1,33 +1,36 @@
1
1
  import { SunsamaClient } from "sunsama-api";
2
2
 
3
3
  /**
4
- * Global Sunsama client instance for stdio transport
4
+ * Cached authentication promise to prevent concurrent auth attempts
5
5
  */
6
- let globalSunsamaClient: SunsamaClient | null = null;
6
+ let authenticationPromise: Promise<SunsamaClient> | null = null;
7
7
 
8
8
  /**
9
9
  * Initialize stdio authentication using environment variables
10
10
  * @throws {Error} If credentials are missing or authentication fails
11
11
  */
12
- export async function initializeStdioAuth(): Promise<void> {
12
+ export async function initializeStdioAuth(): Promise<SunsamaClient> {
13
13
  if (!process.env.SUNSAMA_EMAIL || !process.env.SUNSAMA_PASSWORD) {
14
14
  throw new Error(
15
15
  "Sunsama credentials not configured. Please set SUNSAMA_EMAIL and SUNSAMA_PASSWORD environment variables."
16
16
  );
17
17
  }
18
18
 
19
- globalSunsamaClient = new SunsamaClient();
20
- await globalSunsamaClient.login(process.env.SUNSAMA_EMAIL, process.env.SUNSAMA_PASSWORD);
19
+ const sunsamaClient = new SunsamaClient();
20
+ await sunsamaClient.login(process.env.SUNSAMA_EMAIL, process.env.SUNSAMA_PASSWORD);
21
+
22
+ return sunsamaClient;
21
23
  }
22
24
 
23
25
  /**
24
26
  * Get the global Sunsama client instance for stdio transport
25
- * @returns {SunsamaClient} The authenticated global client
26
- * @throws {Error} If global client is not initialized
27
+ * @returns {Promise<SunsamaClient>} The authenticated global client
28
+ * @throws {Error} If credentials are missing or authentication fails
27
29
  */
28
- export function getGlobalSunsamaClient(): SunsamaClient {
29
- if (!globalSunsamaClient) {
30
- throw new Error("Global Sunsama client not initialized.");
30
+ export async function getGlobalSunsamaClient(): Promise<SunsamaClient> {
31
+ if (!authenticationPromise) {
32
+ authenticationPromise = initializeStdioAuth();
31
33
  }
32
- return globalSunsamaClient;
34
+
35
+ return authenticationPromise;
33
36
  }
package/src/main.ts CHANGED
@@ -9,14 +9,20 @@ import { apiDocumentationResource } from "./resources/index.js";
9
9
  // Get transport configuration with validation
10
10
  const transportConfig = getTransportConfig();
11
11
 
12
- // For stdio transport, authenticate at startup with environment variables
12
+ // For stdio transport, attempt authentication at startup with environment variables
13
13
  if (transportConfig.transportType === "stdio") {
14
- await initializeStdioAuth();
14
+ try {
15
+ await initializeStdioAuth();
16
+ console.log("Sunsama authentication successful");
17
+ } catch (error) {
18
+ console.error("Failed to initialize Sunsama authentication:", error instanceof Error ? error.message : 'Unknown error');
19
+ console.error("Server will start but tools will retry authentication on first use");
20
+ }
15
21
  }
16
22
 
17
23
  const server = new FastMCP({
18
24
  name: "Sunsama API Server",
19
- version: "0.13.0",
25
+ version: "0.14.1",
20
26
  instructions: `
21
27
  This MCP server provides access to the Sunsama API for task and project management.
22
28
 
@@ -1,6 +1,5 @@
1
1
  import type { Context } from "fastmcp";
2
2
  import type { SessionData } from "../auth/types.js";
3
- import { getSunsamaClient } from "../utils/client-resolver.js";
4
3
  import { toTsv } from "../utils/to-tsv.js";
5
4
 
6
5
  export type ToolContext = Context<SessionData>;
@@ -41,12 +40,6 @@ export function createToolWrapper<T>(config: {
41
40
  };
42
41
  }
43
42
 
44
- /**
45
- * Gets the Sunsama client for the current session
46
- */
47
- export function getClient(session: SessionData | undefined | null) {
48
- return getSunsamaClient(session as SessionData | null);
49
- }
50
43
 
51
44
  /**
52
45
  * Formats data as JSON response
@@ -1,5 +1,6 @@
1
1
  import { getStreamsSchema, type GetStreamsInput } from "../schemas.js";
2
- import { createToolWrapper, getClient, formatTsvResponse, type ToolContext } from "./shared.js";
2
+ import { getSunsamaClient } from "../utils/client-resolver.js";
3
+ import { createToolWrapper, formatTsvResponse, type ToolContext } from "./shared.js";
3
4
 
4
5
  export const getStreamsTool = createToolWrapper({
5
6
  name: "get-streams",
@@ -8,7 +9,7 @@ export const getStreamsTool = createToolWrapper({
8
9
  execute: async (_args: GetStreamsInput, context: ToolContext) => {
9
10
  context.log.info("Getting streams for user's group");
10
11
 
11
- const sunsamaClient = getClient(context.session);
12
+ const sunsamaClient = await getSunsamaClient(context.session);
12
13
  const streams = await sunsamaClient.getStreamsByGroupId();
13
14
 
14
15
  context.log.info("Successfully retrieved streams", { count: streams.length });
@@ -31,9 +31,9 @@ import {
31
31
  } from "../schemas.js";
32
32
  import { filterTasksByCompletion } from "../utils/task-filters.js";
33
33
  import { trimTasksForResponse } from "../utils/task-trimmer.js";
34
+ import { getSunsamaClient } from "../utils/client-resolver.js";
34
35
  import {
35
36
  createToolWrapper,
36
- getClient,
37
37
  formatJsonResponse,
38
38
  formatTsvResponse,
39
39
  formatPaginatedTsvResponse,
@@ -48,7 +48,7 @@ export const getTasksBacklogTool = createToolWrapper({
48
48
  execute: async (_args: GetTasksBacklogInput, context: ToolContext) => {
49
49
  context.log.info("Getting backlog tasks");
50
50
 
51
- const sunsamaClient = getClient(context.session);
51
+ const sunsamaClient = await getSunsamaClient(context.session);
52
52
  const tasks = await sunsamaClient.getTasksBacklog();
53
53
  const trimmedTasks = trimTasksForResponse(tasks);
54
54
 
@@ -69,7 +69,7 @@ export const getTasksByDayTool = createToolWrapper({
69
69
  completionFilter
70
70
  });
71
71
 
72
- const sunsamaClient = getClient(context.session);
72
+ const sunsamaClient = await getSunsamaClient(context.session);
73
73
 
74
74
  // If no timezone provided, get the user's default timezone
75
75
  let resolvedTimezone = timezone;
@@ -108,7 +108,7 @@ export const getArchivedTasksTool = createToolWrapper({
108
108
  fetchLimit
109
109
  });
110
110
 
111
- const sunsamaClient = getClient(context.session);
111
+ const sunsamaClient = await getSunsamaClient(context.session);
112
112
  const allTasks = await sunsamaClient.getArchivedTasks(offset, fetchLimit);
113
113
 
114
114
  const hasMore = allTasks.length > requestedLimit;
@@ -141,7 +141,7 @@ export const getTaskByIdTool = createToolWrapper({
141
141
  execute: async ({ taskId }: GetTaskByIdInput, context: ToolContext) => {
142
142
  context.log.info("Getting task by ID", { taskId });
143
143
 
144
- const sunsamaClient = getClient(context.session);
144
+ const sunsamaClient = await getSunsamaClient(context.session);
145
145
  const task = await sunsamaClient.getTaskById(taskId);
146
146
 
147
147
  if (task) {
@@ -175,7 +175,7 @@ export const createTaskTool = createToolWrapper({
175
175
  customTaskId: !!taskId
176
176
  });
177
177
 
178
- const sunsamaClient = getClient(context.session);
178
+ const sunsamaClient = await getSunsamaClient(context.session);
179
179
 
180
180
  const options: CreateTaskOptions = {};
181
181
  if (notes) options.notes = notes;
@@ -216,7 +216,7 @@ export const deleteTaskTool = createToolWrapper({
216
216
  wasTaskMerged
217
217
  });
218
218
 
219
- const sunsamaClient = getClient(context.session);
219
+ const sunsamaClient = await getSunsamaClient(context.session);
220
220
  const result = await sunsamaClient.deleteTask(taskId, limitResponsePayload, wasTaskMerged);
221
221
 
222
222
  context.log.info("Successfully deleted task", {
@@ -246,7 +246,7 @@ export const updateTaskCompleteTool = createToolWrapper({
246
246
  limitResponsePayload
247
247
  });
248
248
 
249
- const sunsamaClient = getClient(context.session);
249
+ const sunsamaClient = await getSunsamaClient(context.session);
250
250
  const result = await sunsamaClient.updateTaskComplete(taskId, completeOn, limitResponsePayload);
251
251
 
252
252
  context.log.info("Successfully marked task as complete", {
@@ -277,7 +277,7 @@ export const updateTaskSnoozeDateTool = createToolWrapper({
277
277
  limitResponsePayload
278
278
  });
279
279
 
280
- const sunsamaClient = getClient(context.session);
280
+ const sunsamaClient = await getSunsamaClient(context.session);
281
281
 
282
282
  const options: { timezone?: string; limitResponsePayload?: boolean } = {};
283
283
  if (timezone) options.timezone = timezone;
@@ -312,7 +312,7 @@ export const updateTaskBacklogTool = createToolWrapper({
312
312
  limitResponsePayload
313
313
  });
314
314
 
315
- const sunsamaClient = getClient(context.session);
315
+ const sunsamaClient = await getSunsamaClient(context.session);
316
316
 
317
317
  const options: { timezone?: string; limitResponsePayload?: boolean } = {};
318
318
  if (timezone) options.timezone = timezone;
@@ -346,7 +346,7 @@ export const updateTaskPlannedTimeTool = createToolWrapper({
346
346
  limitResponsePayload
347
347
  });
348
348
 
349
- const sunsamaClient = getClient(context.session);
349
+ const sunsamaClient = await getSunsamaClient(context.session);
350
350
  const result = await sunsamaClient.updateTaskPlannedTime(taskId, timeEstimateMinutes, limitResponsePayload);
351
351
 
352
352
  context.log.info("Successfully updated task planned time", {
@@ -381,7 +381,7 @@ export const updateTaskNotesTool = createToolWrapper({
381
381
  limitResponsePayload
382
382
  });
383
383
 
384
- const sunsamaClient = getClient(context.session);
384
+ const sunsamaClient = await getSunsamaClient(context.session);
385
385
 
386
386
  const options: { limitResponsePayload?: boolean } = {};
387
387
  if (limitResponsePayload !== undefined) options.limitResponsePayload = limitResponsePayload;
@@ -416,7 +416,7 @@ export const updateTaskDueDateTool = createToolWrapper({
416
416
  limitResponsePayload
417
417
  });
418
418
 
419
- const sunsamaClient = getClient(context.session);
419
+ const sunsamaClient = await getSunsamaClient(context.session);
420
420
  const result = await sunsamaClient.updateTaskDueDate(taskId, dueDate, limitResponsePayload);
421
421
 
422
422
  context.log.info("Successfully updated task due date", {
@@ -448,7 +448,7 @@ export const updateTaskTextTool = createToolWrapper({
448
448
  limitResponsePayload
449
449
  });
450
450
 
451
- const sunsamaClient = getClient(context.session);
451
+ const sunsamaClient = await getSunsamaClient(context.session);
452
452
 
453
453
  const options: { recommendedStreamId?: string | null; limitResponsePayload?: boolean } = {};
454
454
  if (recommendedStreamId !== undefined) options.recommendedStreamId = recommendedStreamId;
@@ -484,7 +484,7 @@ export const updateTaskStreamTool = createToolWrapper({
484
484
  limitResponsePayload
485
485
  });
486
486
 
487
- const sunsamaClient = getClient(context.session);
487
+ const sunsamaClient = await getSunsamaClient(context.session);
488
488
  const result = await sunsamaClient.updateTaskStream(
489
489
  taskId,
490
490
  streamId,
@@ -1,5 +1,6 @@
1
1
  import { getUserSchema, type GetUserInput } from "../schemas.js";
2
- import { createToolWrapper, getClient, formatJsonResponse, type ToolContext } from "./shared.js";
2
+ import { getSunsamaClient } from "../utils/client-resolver.js";
3
+ import { createToolWrapper, formatJsonResponse, type ToolContext } from "./shared.js";
3
4
 
4
5
  export const getUserTool = createToolWrapper({
5
6
  name: "get-user",
@@ -8,7 +9,7 @@ export const getUserTool = createToolWrapper({
8
9
  execute: async (_args: GetUserInput, context: ToolContext) => {
9
10
  context.log.info("Getting user information");
10
11
 
11
- const sunsamaClient = getClient(context.session);
12
+ const sunsamaClient = await getSunsamaClient(context.session);
12
13
  const user = await sunsamaClient.getUser();
13
14
 
14
15
  context.log.info("Successfully retrieved user information", { userId: user._id });
@@ -5,19 +5,19 @@ import { getTransportConfig } from "../config/transport.js";
5
5
 
6
6
  /**
7
7
  * Gets the appropriate SunsamaClient instance based on transport type
8
- * @param session - Session data for HTTP transport (null for stdio)
9
- * @returns Authenticated SunsamaClient instance
8
+ * @param session - Session data for HTTP transport (undefined/null for stdio)
9
+ * @returns Promise<SunsamaClient> Authenticated SunsamaClient instance
10
10
  * @throws {Error} If session is not available for HTTP transport or global client not initialized for stdio
11
11
  */
12
- export function getSunsamaClient(session: SessionData | null): SunsamaClient {
12
+ export async function getSunsamaClient(session?: SessionData | null): Promise<SunsamaClient> {
13
13
  const transportConfig = getTransportConfig();
14
-
14
+
15
15
  if (transportConfig.transportType === "httpStream") {
16
16
  if (!session?.sunsamaClient) {
17
17
  throw new Error("Session not available. Authentication may have failed.");
18
18
  }
19
19
  return session.sunsamaClient;
20
20
  }
21
-
22
- return getGlobalSunsamaClient();
21
+
22
+ return await getGlobalSunsamaClient();
23
23
  }