@snokam/mcp-api 0.108.3 → 0.110.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.
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@ import { getAccessToken } from "./auth.js";
15
15
  // State
16
16
  // ---------------------------------------------------------------------------
17
17
  let currentEnvironment = process.env.SNOKAM_ENVIRONMENT ?? "production";
18
+ let currentApiDomain = null;
18
19
  let endpoints = [];
19
20
  let endpointsByTool = new Map();
20
21
  const serviceUrlOverrides = new Map();
@@ -25,9 +26,18 @@ async function loadEndpoints(environment) {
25
26
  for (const ep of endpoints) {
26
27
  endpointsByTool.set(ep.toolName, ep);
27
28
  }
28
- // Re-apply any active URL overrides
29
+ // Re-target every service at the active business domain (if set), then layer
30
+ // per-service overrides on top (most specific wins).
31
+ applyDomainOverride();
29
32
  applyUrlOverrides();
30
33
  }
34
+ function applyDomainOverride() {
35
+ if (!currentApiDomain)
36
+ return;
37
+ for (const ep of endpoints) {
38
+ ep.baseUrl = `https://${ep.service}.${currentApiDomain}`;
39
+ }
40
+ }
31
41
  function applyUrlOverrides() {
32
42
  for (const ep of endpoints) {
33
43
  const override = serviceUrlOverrides.get(ep.service);
@@ -36,6 +46,12 @@ function applyUrlOverrides() {
36
46
  }
37
47
  }
38
48
  }
49
+ function normalizeDomain(raw) {
50
+ return raw
51
+ .trim()
52
+ .replace(/^https?:\/\//, "")
53
+ .replace(/\/$/, "");
54
+ }
39
55
  // ---------------------------------------------------------------------------
40
56
  // Built-in tool: SwitchEnvironment
41
57
  // ---------------------------------------------------------------------------
@@ -92,6 +108,21 @@ const resetUrlToolDef = {
92
108
  },
93
109
  },
94
110
  };
111
+ const SWITCH_DOMAIN_TOOL_NAME = "SwitchDomain";
112
+ const switchDomainToolDef = {
113
+ name: SWITCH_DOMAIN_TOOL_NAME,
114
+ description: "Point all Snokam API calls at a specific business's API domain, so the same MCP can serve any tenant. Pass the business's API domain (custom, e.g. api.acme.com, or its snosky fallback, e.g. api.acme.snosky.no); calls then go to {service}.{domain}. Pass an empty string to reset to the environment default (snokam).",
115
+ inputSchema: {
116
+ type: "object",
117
+ properties: {
118
+ domain: {
119
+ type: "string",
120
+ description: "The business's API base domain (e.g. api.acme.com). Empty string resets to the snokam default.",
121
+ },
122
+ },
123
+ required: ["domain"],
124
+ },
125
+ };
95
126
  // ---------------------------------------------------------------------------
96
127
  // JSON Schema builder for tool inputs
97
128
  // ---------------------------------------------------------------------------
@@ -293,6 +324,7 @@ Controls: Sonos speakers, lights, YouTube queue
293
324
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
294
325
  tools: [
295
326
  switchToolDef,
327
+ switchDomainToolDef,
296
328
  setUrlToolDef,
297
329
  resetUrlToolDef,
298
330
  ...endpoints.map((ep) => ({
@@ -341,6 +373,22 @@ Controls: Sonos speakers, lights, YouTube queue
341
373
  ],
342
374
  };
343
375
  }
376
+ // Handle SwitchDomain
377
+ if (name === SWITCH_DOMAIN_TOOL_NAME) {
378
+ currentApiDomain = normalizeDomain(String(args.domain ?? "")) || null;
379
+ await loadEndpoints(currentEnvironment);
380
+ await server.sendToolListChanged();
381
+ return {
382
+ content: [
383
+ {
384
+ type: "text",
385
+ text: currentApiDomain
386
+ ? `API calls now target ${currentApiDomain} ({service}.${currentApiDomain}).`
387
+ : "API domain reset to the environment default.",
388
+ },
389
+ ],
390
+ };
391
+ }
344
392
  // Handle SetServiceUrl
345
393
  if (name === SET_URL_TOOL_NAME) {
346
394
  const service = String(args.service ?? "");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snokam/mcp-api",
3
- "version": "0.108.3",
3
+ "version": "0.110.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": {
@@ -4449,6 +4449,9 @@
4449
4449
  "isExternal": {
4450
4450
  "type": "boolean"
4451
4451
  },
4452
+ "isPublic": {
4453
+ "type": "boolean"
4454
+ },
4452
4455
  "isSeekingProject": {
4453
4456
  "type": "boolean"
4454
4457
  },
@@ -736,6 +736,52 @@
736
736
  ]
737
737
  }
738
738
  },
739
+ "/v1.0/protected/me/photo": {
740
+ "post": {
741
+ "tags": [
742
+ "Protected"
743
+ ],
744
+ "summary": "Uploads the authenticated user's profile photo",
745
+ "description": "Stores the photo and a blurred variant on the employee. Body is the raw image bytes.",
746
+ "operationId": "SetMyPhoto",
747
+ "requestBody": {
748
+ "description": "Raw image bytes",
749
+ "content": {
750
+ "application/octet-stream": {
751
+ "schema": {
752
+ "type": "string",
753
+ "format": "binary"
754
+ }
755
+ }
756
+ },
757
+ "required": true
758
+ },
759
+ "responses": {
760
+ "200": {
761
+ "description": "Photo updated successfully",
762
+ "content": {
763
+ "application/json": {
764
+ "schema": {
765
+ "$ref": "#/components/schemas/protectedEmployee"
766
+ }
767
+ }
768
+ },
769
+ "x-ms-summary": "Success"
770
+ },
771
+ "401": {
772
+ "description": "User is not authorized",
773
+ "x-ms-summary": "Unauthorized"
774
+ }
775
+ },
776
+ "security": [
777
+ {
778
+ "Implicit": [
779
+ "api://bb21a8d0-366b-4a95-aa59-dbbf708322d1/.default"
780
+ ]
781
+ }
782
+ ]
783
+ }
784
+ },
739
785
  "/v1.0/protected/{id}": {
740
786
  "put": {
741
787
  "tags": [
@@ -841,6 +887,63 @@
841
887
  ]
842
888
  }
843
889
  },
890
+ "/v1.0/protected/{id}/photo": {
891
+ "post": {
892
+ "tags": [
893
+ "Protected"
894
+ ],
895
+ "summary": "Uploads a profile photo for an employee (admin)",
896
+ "description": "Stores the photo and a blurred variant on the given employee. Body is the raw image bytes.",
897
+ "operationId": "SetEmployeePhoto",
898
+ "parameters": [
899
+ {
900
+ "name": "id",
901
+ "in": "path",
902
+ "required": true,
903
+ "schema": {
904
+ "type": "string"
905
+ },
906
+ "x-ms-summary": "The employee Sanity id"
907
+ }
908
+ ],
909
+ "requestBody": {
910
+ "description": "Raw image bytes",
911
+ "content": {
912
+ "application/octet-stream": {
913
+ "schema": {
914
+ "type": "string",
915
+ "format": "binary"
916
+ }
917
+ }
918
+ },
919
+ "required": true
920
+ },
921
+ "responses": {
922
+ "200": {
923
+ "description": "Photo updated successfully",
924
+ "content": {
925
+ "application/json": {
926
+ "schema": {
927
+ "$ref": "#/components/schemas/protectedEmployee"
928
+ }
929
+ }
930
+ },
931
+ "x-ms-summary": "Success"
932
+ },
933
+ "401": {
934
+ "description": "User is not authorized",
935
+ "x-ms-summary": "Unauthorized"
936
+ }
937
+ },
938
+ "security": [
939
+ {
940
+ "Implicit": [
941
+ "api://bb21a8d0-366b-4a95-aa59-dbbf708322d1/.default"
942
+ ]
943
+ }
944
+ ]
945
+ }
946
+ },
844
947
  "/v1.0/protected/me/gadgetbudget": {
845
948
  "get": {
846
949
  "tags": [
@@ -1722,6 +1825,10 @@
1722
1825
  "type": "boolean",
1723
1826
  "nullable": true
1724
1827
  },
1828
+ "isPublic": {
1829
+ "type": "boolean",
1830
+ "nullable": true
1831
+ },
1725
1832
  "isSeekingProject": {
1726
1833
  "type": "boolean",
1727
1834
  "nullable": true
@@ -1918,6 +2025,9 @@
1918
2025
  "isExternal": {
1919
2026
  "type": "boolean"
1920
2027
  },
2028
+ "isPublic": {
2029
+ "type": "boolean"
2030
+ },
1921
2031
  "isSeekingProject": {
1922
2032
  "type": "boolean"
1923
2033
  },
@@ -2003,6 +2113,10 @@
2003
2113
  "type": "boolean",
2004
2114
  "nullable": true
2005
2115
  },
2116
+ "isPublic": {
2117
+ "type": "boolean",
2118
+ "nullable": true
2119
+ },
2006
2120
  "image": {
2007
2121
  "$ref": "#/components/schemas/sanityImage"
2008
2122
  },
@@ -30305,6 +30305,9 @@
30305
30305
  "isSeekingProject": {
30306
30306
  "type": "boolean"
30307
30307
  },
30308
+ "isPublic": {
30309
+ "type": "boolean"
30310
+ },
30308
30311
  "active": {
30309
30312
  "type": "boolean"
30310
30313
  },
@@ -30664,6 +30667,9 @@
30664
30667
  "isSeekingProject": {
30665
30668
  "type": "boolean"
30666
30669
  },
30670
+ "isPublic": {
30671
+ "type": "boolean"
30672
+ },
30667
30673
  "active": {
30668
30674
  "type": "boolean"
30669
30675
  },
@@ -30932,6 +30938,10 @@
30932
30938
  "type": "boolean",
30933
30939
  "nullable": true
30934
30940
  },
30941
+ "isPublic": {
30942
+ "type": "boolean",
30943
+ "nullable": true
30944
+ },
30935
30945
  "isSeekingProject": {
30936
30946
  "type": "boolean",
30937
30947
  "nullable": true
@@ -50,40 +50,6 @@
50
50
  ]
51
51
  }
52
52
  },
53
- "/v1.0/azure-ad-to-sanity": {
54
- "get": {
55
- "tags": [
56
- "Sync"
57
- ],
58
- "summary": "Syncs Azure AD to Sanity",
59
- "description": "Synchronizes data from Azure AD to Sanity.",
60
- "operationId": "SyncAzureAdToSanity",
61
- "responses": {
62
- "200": {
63
- "description": "Data synchronized successfully",
64
- "content": {
65
- "application/json": {
66
- "schema": {
67
- "type": "string"
68
- }
69
- }
70
- },
71
- "x-ms-summary": "Success"
72
- },
73
- "401": {
74
- "description": "Unauthorized access",
75
- "x-ms-summary": "Unauthorized"
76
- }
77
- },
78
- "security": [
79
- {
80
- "Implicit": [
81
- "api://bb21a8d0-366b-4a95-aa59-dbbf708322d1/.default"
82
- ]
83
- }
84
- ]
85
- }
86
- },
87
53
  "/v1.0/cv-partner-to-sanity": {
88
54
  "get": {
89
55
  "tags": [
@@ -4449,6 +4449,9 @@
4449
4449
  "isExternal": {
4450
4450
  "type": "boolean"
4451
4451
  },
4452
+ "isPublic": {
4453
+ "type": "boolean"
4454
+ },
4452
4455
  "isSeekingProject": {
4453
4456
  "type": "boolean"
4454
4457
  },
@@ -736,6 +736,52 @@
736
736
  ]
737
737
  }
738
738
  },
739
+ "/v1.0/protected/me/photo": {
740
+ "post": {
741
+ "tags": [
742
+ "Protected"
743
+ ],
744
+ "summary": "Uploads the authenticated user's profile photo",
745
+ "description": "Stores the photo and a blurred variant on the employee. Body is the raw image bytes.",
746
+ "operationId": "SetMyPhoto",
747
+ "requestBody": {
748
+ "description": "Raw image bytes",
749
+ "content": {
750
+ "application/octet-stream": {
751
+ "schema": {
752
+ "type": "string",
753
+ "format": "binary"
754
+ }
755
+ }
756
+ },
757
+ "required": true
758
+ },
759
+ "responses": {
760
+ "200": {
761
+ "description": "Photo updated successfully",
762
+ "content": {
763
+ "application/json": {
764
+ "schema": {
765
+ "$ref": "#/components/schemas/protectedEmployee"
766
+ }
767
+ }
768
+ },
769
+ "x-ms-summary": "Success"
770
+ },
771
+ "401": {
772
+ "description": "User is not authorized",
773
+ "x-ms-summary": "Unauthorized"
774
+ }
775
+ },
776
+ "security": [
777
+ {
778
+ "Implicit": [
779
+ "api://b2c47b20-df4a-49b5-a7ee-87f3647ab227/.default"
780
+ ]
781
+ }
782
+ ]
783
+ }
784
+ },
739
785
  "/v1.0/protected/{id}": {
740
786
  "put": {
741
787
  "tags": [
@@ -841,6 +887,63 @@
841
887
  ]
842
888
  }
843
889
  },
890
+ "/v1.0/protected/{id}/photo": {
891
+ "post": {
892
+ "tags": [
893
+ "Protected"
894
+ ],
895
+ "summary": "Uploads a profile photo for an employee (admin)",
896
+ "description": "Stores the photo and a blurred variant on the given employee. Body is the raw image bytes.",
897
+ "operationId": "SetEmployeePhoto",
898
+ "parameters": [
899
+ {
900
+ "name": "id",
901
+ "in": "path",
902
+ "required": true,
903
+ "schema": {
904
+ "type": "string"
905
+ },
906
+ "x-ms-summary": "The employee Sanity id"
907
+ }
908
+ ],
909
+ "requestBody": {
910
+ "description": "Raw image bytes",
911
+ "content": {
912
+ "application/octet-stream": {
913
+ "schema": {
914
+ "type": "string",
915
+ "format": "binary"
916
+ }
917
+ }
918
+ },
919
+ "required": true
920
+ },
921
+ "responses": {
922
+ "200": {
923
+ "description": "Photo updated successfully",
924
+ "content": {
925
+ "application/json": {
926
+ "schema": {
927
+ "$ref": "#/components/schemas/protectedEmployee"
928
+ }
929
+ }
930
+ },
931
+ "x-ms-summary": "Success"
932
+ },
933
+ "401": {
934
+ "description": "User is not authorized",
935
+ "x-ms-summary": "Unauthorized"
936
+ }
937
+ },
938
+ "security": [
939
+ {
940
+ "Implicit": [
941
+ "api://b2c47b20-df4a-49b5-a7ee-87f3647ab227/.default"
942
+ ]
943
+ }
944
+ ]
945
+ }
946
+ },
844
947
  "/v1.0/protected/me/gadgetbudget": {
845
948
  "get": {
846
949
  "tags": [
@@ -1722,6 +1825,10 @@
1722
1825
  "type": "boolean",
1723
1826
  "nullable": true
1724
1827
  },
1828
+ "isPublic": {
1829
+ "type": "boolean",
1830
+ "nullable": true
1831
+ },
1725
1832
  "isSeekingProject": {
1726
1833
  "type": "boolean",
1727
1834
  "nullable": true
@@ -1918,6 +2025,9 @@
1918
2025
  "isExternal": {
1919
2026
  "type": "boolean"
1920
2027
  },
2028
+ "isPublic": {
2029
+ "type": "boolean"
2030
+ },
1921
2031
  "isSeekingProject": {
1922
2032
  "type": "boolean"
1923
2033
  },
@@ -2003,6 +2113,10 @@
2003
2113
  "type": "boolean",
2004
2114
  "nullable": true
2005
2115
  },
2116
+ "isPublic": {
2117
+ "type": "boolean",
2118
+ "nullable": true
2119
+ },
2006
2120
  "image": {
2007
2121
  "$ref": "#/components/schemas/sanityImage"
2008
2122
  },
@@ -30305,6 +30305,9 @@
30305
30305
  "isSeekingProject": {
30306
30306
  "type": "boolean"
30307
30307
  },
30308
+ "isPublic": {
30309
+ "type": "boolean"
30310
+ },
30308
30311
  "active": {
30309
30312
  "type": "boolean"
30310
30313
  },
@@ -30664,6 +30667,9 @@
30664
30667
  "isSeekingProject": {
30665
30668
  "type": "boolean"
30666
30669
  },
30670
+ "isPublic": {
30671
+ "type": "boolean"
30672
+ },
30667
30673
  "active": {
30668
30674
  "type": "boolean"
30669
30675
  },
@@ -30932,6 +30938,10 @@
30932
30938
  "type": "boolean",
30933
30939
  "nullable": true
30934
30940
  },
30941
+ "isPublic": {
30942
+ "type": "boolean",
30943
+ "nullable": true
30944
+ },
30935
30945
  "isSeekingProject": {
30936
30946
  "type": "boolean",
30937
30947
  "nullable": true
@@ -50,40 +50,6 @@
50
50
  ]
51
51
  }
52
52
  },
53
- "/v1.0/azure-ad-to-sanity": {
54
- "get": {
55
- "tags": [
56
- "Sync"
57
- ],
58
- "summary": "Syncs Azure AD to Sanity",
59
- "description": "Synchronizes data from Azure AD to Sanity.",
60
- "operationId": "SyncAzureAdToSanity",
61
- "responses": {
62
- "200": {
63
- "description": "Data synchronized successfully",
64
- "content": {
65
- "application/json": {
66
- "schema": {
67
- "type": "string"
68
- }
69
- }
70
- },
71
- "x-ms-summary": "Success"
72
- },
73
- "401": {
74
- "description": "Unauthorized access",
75
- "x-ms-summary": "Unauthorized"
76
- }
77
- },
78
- "security": [
79
- {
80
- "Implicit": [
81
- "api://b2c47b20-df4a-49b5-a7ee-87f3647ab227/.default"
82
- ]
83
- }
84
- ]
85
- }
86
- },
87
53
  "/v1.0/cv-partner-to-sanity": {
88
54
  "get": {
89
55
  "tags": [