mcp-zenskar 1.0.7 → 1.0.8

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 (3) hide show
  1. package/README.md +30 -1
  2. package/package.json +1 -1
  3. package/src/server.js +51 -14
package/README.md CHANGED
@@ -32,6 +32,8 @@ Add to your Claude Desktop configuration (`~/Library/Application Support/Claude/
32
32
  }
33
33
  ```
34
34
 
35
+ > You can omit one or both environment variables from the config, but the server will error until Claude supplies them in a tool call. Keeping them in the env block prevents repeated credential prompts.
36
+
35
37
  ### For Other AI Applications
36
38
 
37
39
  Install globally:
@@ -56,6 +58,8 @@ This MCP server requires two authentication parameters for every request:
56
58
  1. **Organization ID**: Available in your Zenskar dashboard settings
57
59
  2. **API Token**: Generate from Zenskar dashboard → Settings → API Keys
58
60
 
61
+ At runtime the server looks for these values in the tool invocation first, then falls back to the `ZENSKAR_ORGANIZATION` and `ZENSKAR_AUTH_TOKEN` environment variables. Tokens that look like JWTs are sent as `Authorization: Bearer ...`; everything else is sent as an `x-api-key` header automatically.
62
+
59
63
  ## Usage
60
64
 
61
65
  ### In Claude Desktop
@@ -114,6 +118,31 @@ npm install
114
118
  npm start
115
119
  ```
116
120
 
121
+ ### Developing Locally Without Publishing
122
+
123
+ If you want Claude Desktop to use a local checkout instead of the npm package:
124
+
125
+ ```bash
126
+ # Install dependencies once
127
+ npm install
128
+
129
+ # Optional: install the local build globally
130
+ npm install -g .
131
+ ```
132
+
133
+ Then either point Claude to the globally-installed binary (usually `$(npm bin -g)/mcp-zenskar`) or call the repo copy directly:
134
+
135
+ ```json
136
+ {
137
+ "command": "node",
138
+ "args": ["/absolute/path/to/mcp-zenskar/src/server.js"],
139
+ "env": {
140
+ "ZENSKAR_ORGANIZATION": "your-org-id",
141
+ "ZENSKAR_AUTH_TOKEN": "your-token"
142
+ }
143
+ }
144
+ ```
145
+
117
146
  ## Configuration
118
147
 
119
148
  The server uses `src/mcp-config.json` to define available tools and API endpoints. This file contains the complete mapping of MCP tools to Zenskar API operations.
@@ -126,4 +155,4 @@ MIT
126
155
 
127
156
  For issues and support:
128
157
  - GitHub Issues: https://github.com/zenskar/mcp-zenskar/issues
129
- - Zenskar Documentation: https://docs.zenskar.com
158
+ - Zenskar Documentation: https://docs.zenskar.com
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-zenskar",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "Model Context Protocol (MCP) server for Zenskar API - customer management, invoicing, and billing operations",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
package/src/server.js CHANGED
@@ -64,11 +64,44 @@ function normalizeUsageEventPayload(eventPayload) {
64
64
  return normalized;
65
65
  }
66
66
 
67
- // User context validation schema
68
- const userContextSchema = z.object({
69
- organization: z.string().describe('Organization ID for multi-tenant API access'),
70
- authorization: z.string().describe('Bearer token for API authentication'),
71
- }).describe('Required authentication context for Zenskar API');
67
+ function resolveCredentialValue(providedValue, envValue) {
68
+ if (typeof providedValue === 'string' && providedValue.trim().length > 0) {
69
+ return providedValue.trim();
70
+ }
71
+
72
+ if (typeof envValue === 'string' && envValue.trim().length > 0) {
73
+ return envValue.trim();
74
+ }
75
+
76
+ return null;
77
+ }
78
+
79
+ function buildAuthHeaders(rawToken) {
80
+ if (!rawToken) {
81
+ return {};
82
+ }
83
+
84
+ const token = rawToken.trim();
85
+ if (!token) {
86
+ return {};
87
+ }
88
+
89
+ if (token.toLowerCase().startsWith('bearer ')) {
90
+ return { 'Authorization': token };
91
+ }
92
+
93
+ // Basic JWT heuristic: three dot-separated segments
94
+ const isLikelyJwt = token.split('.').length === 3;
95
+ if (isLikelyJwt) {
96
+ return { 'Authorization': `Bearer ${token}` };
97
+ }
98
+
99
+ if (token.toLowerCase().startsWith('x-api-key ')) {
100
+ return { 'x-api-key': token.substring('x-api-key '.length).trim() };
101
+ }
102
+
103
+ return { 'x-api-key': token };
104
+ }
72
105
 
73
106
  class ZenskarMcpServer {
74
107
  constructor() {
@@ -107,8 +140,8 @@ class ZenskarMcpServer {
107
140
 
108
141
  generateInputSchema(tool) {
109
142
  const schemaObj = {
110
- organization: z.string().describe("Organization ID for multi-tenant API access (required)"),
111
- authorization: z.string().describe("Bearer token for API authentication (required)")
143
+ organization: z.string().describe("Organization ID for multi-tenant API access (defaults to ZENSKAR_ORGANIZATION env when omitted)").optional(),
144
+ authorization: z.string().describe("Auth token (JWT -> Authorization Bearer, API key -> x-api-key). Defaults to ZENSKAR_AUTH_TOKEN env when omitted").optional()
112
145
  };
113
146
 
114
147
  // Add tool-specific arguments
@@ -151,24 +184,28 @@ class ZenskarMcpServer {
151
184
  // Extract authentication from arguments
152
185
  const { organization, authorization, ...toolArgs } = args;
153
186
 
187
+ const resolvedOrganization = resolveCredentialValue(organization, process.env.ZENSKAR_ORGANIZATION);
188
+ const resolvedAuthorization = resolveCredentialValue(authorization, process.env.ZENSKAR_AUTH_TOKEN);
189
+
154
190
  // Validate required authentication
155
- if (!organization) {
156
- throw new Error('Organization ID is required for API access');
191
+ if (!resolvedOrganization) {
192
+ throw new Error('Organization ID is required for API access. Provide it in the tool input or set ZENSKAR_ORGANIZATION in the environment.');
157
193
  }
158
- if (!authorization) {
159
- throw new Error('Authorization token is required for API access');
194
+ if (!resolvedAuthorization) {
195
+ throw new Error('Authorization token is required for API access. Provide it in the tool input or set ZENSKAR_AUTH_TOKEN in the environment.');
160
196
  }
161
197
 
162
- logger.info(`[${tool.name}] Executing with organization: ${organization.substring(0, 10)}...`);
198
+ logger.info(`[${tool.name}] Executing with organization: ${resolvedOrganization.substring(0, 10)}...`);
163
199
 
164
200
  // Build headers
165
201
  const headers = {
166
202
  'Content-Type': 'application/json',
167
203
  'Accept': 'application/json',
168
- 'organisation': organization, // Note: API uses 'organisation' not 'organization'
169
- 'Authorization': authorization.startsWith('Bearer ') ? authorization : `Bearer ${authorization}`
204
+ 'organisation': resolvedOrganization // Note: API uses 'organisation' not 'organization'
170
205
  };
171
206
 
207
+ Object.assign(headers, buildAuthHeaders(resolvedAuthorization));
208
+
172
209
  // Build URL from requestTemplate
173
210
  let url = `${config.server.baseUrl}${tool.requestTemplate.url}`;
174
211
  const method = tool.requestTemplate.method;