@snokam/mcp-api 0.5.1 → 0.7.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 (2) hide show
  1. package/dist/index.js +184 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12,9 +12,86 @@ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSche
12
12
  import { fetchSpecs } from "./openapi-loader.js";
13
13
  import { getAccessToken } from "./auth.js";
14
14
  // ---------------------------------------------------------------------------
15
- // Config
15
+ // State
16
16
  // ---------------------------------------------------------------------------
17
- const ENVIRONMENT = process.env.SNOKAM_ENVIRONMENT ?? "production";
17
+ let currentEnvironment = process.env.SNOKAM_ENVIRONMENT ?? "production";
18
+ let endpoints = [];
19
+ let endpointsByTool = new Map();
20
+ const serviceUrlOverrides = new Map();
21
+ async function loadEndpoints(environment) {
22
+ currentEnvironment = environment;
23
+ endpoints = await fetchSpecs(environment);
24
+ endpointsByTool = new Map();
25
+ for (const ep of endpoints) {
26
+ endpointsByTool.set(ep.toolName, ep);
27
+ }
28
+ // Re-apply any active URL overrides
29
+ applyUrlOverrides();
30
+ }
31
+ function applyUrlOverrides() {
32
+ for (const ep of endpoints) {
33
+ const override = serviceUrlOverrides.get(ep.service);
34
+ if (override) {
35
+ ep.baseUrl = override;
36
+ }
37
+ }
38
+ }
39
+ // ---------------------------------------------------------------------------
40
+ // Built-in tool: SwitchEnvironment
41
+ // ---------------------------------------------------------------------------
42
+ const SWITCH_TOOL_NAME = "SwitchEnvironment";
43
+ const VALID_ENVIRONMENTS = ["production", "test"];
44
+ const switchToolDef = {
45
+ name: SWITCH_TOOL_NAME,
46
+ description: "Switch the Snokam MCP server between environments (production/test). Reloads all API specs for the new environment.",
47
+ inputSchema: {
48
+ type: "object",
49
+ properties: {
50
+ environment: {
51
+ type: "string",
52
+ enum: VALID_ENVIRONMENTS,
53
+ description: "The environment to switch to",
54
+ },
55
+ },
56
+ required: ["environment"],
57
+ },
58
+ };
59
+ // ---------------------------------------------------------------------------
60
+ // Built-in tool: SetServiceUrl
61
+ // ---------------------------------------------------------------------------
62
+ const SET_URL_TOOL_NAME = "SetServiceUrl";
63
+ const RESET_URL_TOOL_NAME = "ResetServiceUrl";
64
+ const setUrlToolDef = {
65
+ name: SET_URL_TOOL_NAME,
66
+ description: "Override a service's base URL, e.g. to point to a locally running function. Auth is skipped for localhost URLs. Use ResetServiceUrl to revert.",
67
+ inputSchema: {
68
+ type: "object",
69
+ properties: {
70
+ service: {
71
+ type: "string",
72
+ description: "The service name (e.g. employees, notifications, events)",
73
+ },
74
+ url: {
75
+ type: "string",
76
+ description: "The base URL to use (e.g. http://localhost:7071)",
77
+ },
78
+ },
79
+ required: ["service", "url"],
80
+ },
81
+ };
82
+ const resetUrlToolDef = {
83
+ name: RESET_URL_TOOL_NAME,
84
+ description: "Reset a service's base URL back to the environment default. Call without arguments to reset all overrides.",
85
+ inputSchema: {
86
+ type: "object",
87
+ properties: {
88
+ service: {
89
+ type: "string",
90
+ description: "The service name to reset. Omit to reset all overrides.",
91
+ },
92
+ },
93
+ },
94
+ };
18
95
  // ---------------------------------------------------------------------------
19
96
  // JSON Schema builder for tool inputs
20
97
  // ---------------------------------------------------------------------------
@@ -84,9 +161,12 @@ async function executeCall(endpoint, args) {
84
161
  const headers = {
85
162
  Accept: "application/json",
86
163
  };
87
- const token = await getAccessToken(endpoint.scope);
88
- if (token) {
89
- headers.Authorization = `Bearer ${token}`;
164
+ const isLocal = url.startsWith("http://localhost") || url.startsWith("http://127.0.0.1");
165
+ if (!isLocal) {
166
+ const token = await getAccessToken(endpoint.scope);
167
+ if (token) {
168
+ headers.Authorization = `Bearer ${token}`;
169
+ }
90
170
  }
91
171
  let fetchBody;
92
172
  if (args.body !== undefined && endpoint.method !== "GET") {
@@ -112,17 +192,13 @@ async function executeCall(endpoint, args) {
112
192
  // Server setup
113
193
  // ---------------------------------------------------------------------------
114
194
  async function main() {
115
- const endpoints = await fetchSpecs(ENVIRONMENT);
195
+ await loadEndpoints(currentEnvironment);
116
196
  if (endpoints.length === 0) {
117
197
  console.error("[snokam-mcp] No endpoints loaded. Ensure specs/*.json files exist.");
118
198
  }
119
- const endpointsByTool = new Map();
120
- for (const ep of endpoints) {
121
- endpointsByTool.set(ep.toolName, ep);
122
- }
123
199
  const server = new Server({
124
200
  name: "snokam",
125
- version: "0.2.0",
201
+ version: "0.3.0",
126
202
  }, {
127
203
  capabilities: {
128
204
  tools: {},
@@ -170,7 +246,7 @@ async function main() {
170
246
 
171
247
  ## Environment
172
248
 
173
- Currently connected to: **${ENVIRONMENT}**
249
+ Currently connected to: **${currentEnvironment}**
174
250
 
175
251
  ## Available Services
176
252
 
@@ -218,15 +294,106 @@ Controls: Sonos speakers, lights, YouTube queue
218
294
  });
219
295
  // List tools
220
296
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
221
- tools: endpoints.map((ep) => ({
222
- name: ep.toolName,
223
- description: ep.description || ep.summary || `${ep.method} ${ep.path}`,
224
- inputSchema: buildInputSchema(ep),
225
- })),
297
+ tools: [
298
+ switchToolDef,
299
+ setUrlToolDef,
300
+ resetUrlToolDef,
301
+ ...endpoints.map((ep) => ({
302
+ name: ep.toolName,
303
+ description: ep.description || ep.summary || `${ep.method} ${ep.path}`,
304
+ inputSchema: buildInputSchema(ep),
305
+ })),
306
+ ],
226
307
  }));
227
308
  // Call tool
228
309
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
229
310
  const { name, arguments: args = {} } = request.params;
311
+ // Handle SwitchEnvironment
312
+ if (name === SWITCH_TOOL_NAME) {
313
+ const env = String(args.environment ?? "");
314
+ if (!VALID_ENVIRONMENTS.includes(env)) {
315
+ return {
316
+ content: [
317
+ {
318
+ type: "text",
319
+ text: `Invalid environment: ${env}. Must be one of: ${VALID_ENVIRONMENTS.join(", ")}`,
320
+ },
321
+ ],
322
+ isError: true,
323
+ };
324
+ }
325
+ if (env === currentEnvironment) {
326
+ return {
327
+ content: [
328
+ {
329
+ type: "text",
330
+ text: `Already connected to ${env} (${endpoints.length} endpoints)`,
331
+ },
332
+ ],
333
+ };
334
+ }
335
+ await loadEndpoints(env);
336
+ // Notify client that the tool list has changed
337
+ await server.sendToolListChanged();
338
+ return {
339
+ content: [
340
+ {
341
+ type: "text",
342
+ text: `Switched to ${env} environment. Loaded ${endpoints.length} endpoints.`,
343
+ },
344
+ ],
345
+ };
346
+ }
347
+ // Handle SetServiceUrl
348
+ if (name === SET_URL_TOOL_NAME) {
349
+ const service = String(args.service ?? "");
350
+ const url = String(args.url ?? "");
351
+ const serviceEndpoints = endpoints.filter((ep) => ep.service === service);
352
+ if (serviceEndpoints.length === 0) {
353
+ const available = [
354
+ ...new Set(endpoints.map((ep) => ep.service)),
355
+ ].sort();
356
+ return {
357
+ content: [
358
+ {
359
+ type: "text",
360
+ text: `Unknown service: ${service}. Available: ${available.join(", ")}`,
361
+ },
362
+ ],
363
+ isError: true,
364
+ };
365
+ }
366
+ serviceUrlOverrides.set(service, url);
367
+ for (const ep of serviceEndpoints) {
368
+ ep.baseUrl = url;
369
+ }
370
+ return {
371
+ content: [
372
+ {
373
+ type: "text",
374
+ text: `Overrode ${service} → ${url} (${serviceEndpoints.length} endpoints). Auth ${url.startsWith("http://localhost") || url.startsWith("http://127.0.0.1") ? "skipped" : "active"}.`,
375
+ },
376
+ ],
377
+ };
378
+ }
379
+ // Handle ResetServiceUrl
380
+ if (name === RESET_URL_TOOL_NAME) {
381
+ const service = args.service ? String(args.service) : undefined;
382
+ if (service) {
383
+ serviceUrlOverrides.delete(service);
384
+ }
385
+ else {
386
+ serviceUrlOverrides.clear();
387
+ }
388
+ // Reload to restore original URLs
389
+ await loadEndpoints(currentEnvironment);
390
+ const msg = service
391
+ ? `Reset ${service} to environment default`
392
+ : `Reset all service URL overrides`;
393
+ return {
394
+ content: [{ type: "text", text: msg }],
395
+ };
396
+ }
230
397
  const endpoint = endpointsByTool.get(name);
231
398
  if (!endpoint) {
232
399
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snokam/mcp-api",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
4
4
  "description": "MCP server exposing Snokam backend APIs as tools for Claude Code and other MCP clients",
5
5
  "type": "module",
6
6
  "bin": {