@snokam/mcp-api 0.9.0 → 0.10.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 +34 -1
- package/dist/openapi-loader.d.ts +5 -0
- package/dist/openapi-loader.js +15 -0
- package/package.json +1 -1
- package/specs/production/sync.json +181 -0
- package/specs/test/sync.json +181 -0
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
10
10
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
11
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
12
|
-
import { fetchSpecs } from "./openapi-loader.js";
|
|
12
|
+
import { fetchSpecs, fetchSpecFromUrl, } from "./openapi-loader.js";
|
|
13
13
|
import { getAccessToken } from "./auth.js";
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
// State
|
|
@@ -345,6 +345,39 @@ Controls: Sonos speakers, lights, YouTube queue
|
|
|
345
345
|
if (name === SET_URL_TOOL_NAME) {
|
|
346
346
|
const service = String(args.service ?? "");
|
|
347
347
|
const url = String(args.url ?? "");
|
|
348
|
+
const isLocal = url.startsWith("http://localhost") ||
|
|
349
|
+
url.startsWith("http://127.0.0.1");
|
|
350
|
+
// For localhost URLs, fetch the live swagger spec from the local function
|
|
351
|
+
// to pick up any new/changed endpoints during development
|
|
352
|
+
if (isLocal) {
|
|
353
|
+
try {
|
|
354
|
+
const liveEndpoints = await fetchSpecFromUrl(service, url);
|
|
355
|
+
// Remove old endpoints for this service
|
|
356
|
+
endpoints = endpoints.filter((ep) => ep.service !== service);
|
|
357
|
+
// Add the fresh ones
|
|
358
|
+
endpoints.push(...liveEndpoints);
|
|
359
|
+
// Rebuild lookup map
|
|
360
|
+
endpointsByTool = new Map();
|
|
361
|
+
for (const ep of endpoints) {
|
|
362
|
+
endpointsByTool.set(ep.toolName, ep);
|
|
363
|
+
}
|
|
364
|
+
serviceUrlOverrides.set(service, url);
|
|
365
|
+
await server.sendToolListChanged();
|
|
366
|
+
return {
|
|
367
|
+
content: [
|
|
368
|
+
{
|
|
369
|
+
type: "text",
|
|
370
|
+
text: `Overrode ${service} → ${url}. Loaded ${liveEndpoints.length} endpoints from local swagger.`,
|
|
371
|
+
},
|
|
372
|
+
],
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
// Fall through to static override if swagger fetch fails
|
|
377
|
+
console.error(`[snokam-mcp] Failed to fetch swagger from ${url}, falling back to static spec:`, error instanceof Error ? error.message : error);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// Static override: just change the base URL on existing endpoints
|
|
348
381
|
const serviceEndpoints = endpoints.filter((ep) => ep.service === service);
|
|
349
382
|
if (serviceEndpoints.length === 0) {
|
|
350
383
|
const available = [
|
package/dist/openapi-loader.d.ts
CHANGED
|
@@ -42,5 +42,10 @@ interface OpenApiRequestBody {
|
|
|
42
42
|
schema?: Record<string, unknown>;
|
|
43
43
|
}>;
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Fetch a swagger spec from a specific URL (e.g. a locally running function)
|
|
47
|
+
* and return parsed endpoints for that service.
|
|
48
|
+
*/
|
|
49
|
+
export declare function fetchSpecFromUrl(service: string, baseUrl: string): Promise<ApiEndpoint[]>;
|
|
45
50
|
export declare function fetchSpecs(environment: string): Promise<ApiEndpoint[]>;
|
|
46
51
|
export {};
|
package/dist/openapi-loader.js
CHANGED
|
@@ -101,6 +101,21 @@ function parseSpec(spec, service, baseUrl) {
|
|
|
101
101
|
// ---------------------------------------------------------------------------
|
|
102
102
|
// Public API
|
|
103
103
|
// ---------------------------------------------------------------------------
|
|
104
|
+
/**
|
|
105
|
+
* Fetch a swagger spec from a specific URL (e.g. a locally running function)
|
|
106
|
+
* and return parsed endpoints for that service.
|
|
107
|
+
*/
|
|
108
|
+
export async function fetchSpecFromUrl(service, baseUrl) {
|
|
109
|
+
const swaggerUrl = `${baseUrl}/swagger.json`;
|
|
110
|
+
const response = await fetch(swaggerUrl, {
|
|
111
|
+
signal: AbortSignal.timeout(10_000),
|
|
112
|
+
});
|
|
113
|
+
if (!response.ok) {
|
|
114
|
+
throw new Error(`HTTP ${response.status} from ${swaggerUrl}`);
|
|
115
|
+
}
|
|
116
|
+
const spec = (await response.json());
|
|
117
|
+
return parseSpec(spec, service, baseUrl);
|
|
118
|
+
}
|
|
104
119
|
export async function fetchSpecs(environment) {
|
|
105
120
|
// Try to load bundled specs first (for speed)
|
|
106
121
|
try {
|
package/package.json
CHANGED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.0.1",
|
|
3
|
+
"info": {
|
|
4
|
+
"title": "Sync Function",
|
|
5
|
+
"description": "Sync data from one system to another, e.g CvPartner to Sanity",
|
|
6
|
+
"version": "v1.0.0"
|
|
7
|
+
},
|
|
8
|
+
"servers": [
|
|
9
|
+
{
|
|
10
|
+
"url": "https://sync.api.snokam.no"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"paths": {
|
|
14
|
+
"/v1.0/azure-ad-to-sanity": {
|
|
15
|
+
"get": {
|
|
16
|
+
"tags": [
|
|
17
|
+
"Sync"
|
|
18
|
+
],
|
|
19
|
+
"summary": "Syncs Azure AD to Sanity",
|
|
20
|
+
"description": "Synchronizes data from Azure AD to Sanity.",
|
|
21
|
+
"operationId": "SyncAzureAdToSanity",
|
|
22
|
+
"responses": {
|
|
23
|
+
"200": {
|
|
24
|
+
"description": "Data synchronized successfully",
|
|
25
|
+
"content": {
|
|
26
|
+
"application/json": {
|
|
27
|
+
"schema": {
|
|
28
|
+
"type": "string"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"x-ms-summary": "Success"
|
|
33
|
+
},
|
|
34
|
+
"401": {
|
|
35
|
+
"description": "Unauthorized access",
|
|
36
|
+
"x-ms-summary": "Unauthorized"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"security": [
|
|
40
|
+
{
|
|
41
|
+
"Implicit": [
|
|
42
|
+
"api://ff9fe93f-c16c-4d28-ad16-8c379e0815ef/user_impersonation"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"/v1.0/cv-partner-to-sanity": {
|
|
49
|
+
"get": {
|
|
50
|
+
"tags": [
|
|
51
|
+
"Sync"
|
|
52
|
+
],
|
|
53
|
+
"summary": "Syncs CV Partner to Sanity",
|
|
54
|
+
"description": "Synchronizes data from CV Partner to Sanity.",
|
|
55
|
+
"operationId": "SyncCvPartnerToSanity",
|
|
56
|
+
"responses": {
|
|
57
|
+
"200": {
|
|
58
|
+
"description": "Data synchronized successfully",
|
|
59
|
+
"content": {
|
|
60
|
+
"application/json": {
|
|
61
|
+
"schema": {
|
|
62
|
+
"type": "string"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"x-ms-summary": "Success"
|
|
67
|
+
},
|
|
68
|
+
"401": {
|
|
69
|
+
"description": "Unauthorized access",
|
|
70
|
+
"x-ms-summary": "Unauthorized"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"security": [
|
|
74
|
+
{
|
|
75
|
+
"Implicit": [
|
|
76
|
+
"api://ff9fe93f-c16c-4d28-ad16-8c379e0815ef/user_impersonation"
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"/v1.0/power-office-to-sanity": {
|
|
83
|
+
"get": {
|
|
84
|
+
"tags": [
|
|
85
|
+
"Sync"
|
|
86
|
+
],
|
|
87
|
+
"summary": "Sync power office data to sanity",
|
|
88
|
+
"description": "Sync power office data to sanity",
|
|
89
|
+
"operationId": "SyncPowerOfficeToSanity",
|
|
90
|
+
"parameters": [
|
|
91
|
+
{
|
|
92
|
+
"name": "from",
|
|
93
|
+
"in": "query",
|
|
94
|
+
"description": "",
|
|
95
|
+
"required": true,
|
|
96
|
+
"schema": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"format": "date-time"
|
|
99
|
+
},
|
|
100
|
+
"x-ms-summary": "Date from"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "to",
|
|
104
|
+
"in": "query",
|
|
105
|
+
"description": "",
|
|
106
|
+
"required": true,
|
|
107
|
+
"schema": {
|
|
108
|
+
"type": "string",
|
|
109
|
+
"format": "date-time"
|
|
110
|
+
},
|
|
111
|
+
"x-ms-summary": "Date to"
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
"responses": {
|
|
115
|
+
"200": {
|
|
116
|
+
"description": "Data synchronized successfully",
|
|
117
|
+
"content": {
|
|
118
|
+
"application/json": {
|
|
119
|
+
"schema": {
|
|
120
|
+
"type": "string"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"x-ms-summary": "Success"
|
|
125
|
+
},
|
|
126
|
+
"401": {
|
|
127
|
+
"description": "Unauthorized access",
|
|
128
|
+
"x-ms-summary": "Unauthorized"
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"security": [
|
|
132
|
+
{
|
|
133
|
+
"Implicit": [
|
|
134
|
+
"api://ff9fe93f-c16c-4d28-ad16-8c379e0815ef/user_impersonation"
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"/v1.0/okrs-to-sanity": {
|
|
141
|
+
"get": {
|
|
142
|
+
"tags": [
|
|
143
|
+
"Sync"
|
|
144
|
+
],
|
|
145
|
+
"summary": "Sync OKRs",
|
|
146
|
+
"description": "Synchronizes OKRs",
|
|
147
|
+
"operationId": "SyncOkrs",
|
|
148
|
+
"responses": {
|
|
149
|
+
"200": {
|
|
150
|
+
"description": "OKRs synchronized successfully",
|
|
151
|
+
"x-ms-summary": "Success"
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"security": [
|
|
155
|
+
{
|
|
156
|
+
"Implicit": [
|
|
157
|
+
"api://ff9fe93f-c16c-4d28-ad16-8c379e0815ef/user_impersonation"
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
"components": {
|
|
165
|
+
"securitySchemes": {
|
|
166
|
+
"Implicit": {
|
|
167
|
+
"type": "oauth2",
|
|
168
|
+
"flows": {
|
|
169
|
+
"implicit": {
|
|
170
|
+
"authorizationUrl": "https://login.microsoftonline.com/81a4f38d-6712-4b65-868b-c3771f9ba91e/oauth2/v2.0/authorize",
|
|
171
|
+
"tokenUrl": "https://login.microsoftonline.com/81a4f38d-6712-4b65-868b-c3771f9ba91e/oauth2/v2.0/token",
|
|
172
|
+
"refreshUrl": "https://login.microsoftonline.com/81a4f38d-6712-4b65-868b-c3771f9ba91e/oauth2/v2.0/token",
|
|
173
|
+
"scopes": {
|
|
174
|
+
"api://ff9fe93f-c16c-4d28-ad16-8c379e0815ef/user_impersonation": "Default function scope"
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.0.1",
|
|
3
|
+
"info": {
|
|
4
|
+
"title": "Sync Function",
|
|
5
|
+
"description": "Sync data from one system to another, e.g CvPartner to Sanity",
|
|
6
|
+
"version": "v1.0.0"
|
|
7
|
+
},
|
|
8
|
+
"servers": [
|
|
9
|
+
{
|
|
10
|
+
"url": "https://sync.api.test.snokam.no"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"paths": {
|
|
14
|
+
"/v1.0/azure-ad-to-sanity": {
|
|
15
|
+
"get": {
|
|
16
|
+
"tags": [
|
|
17
|
+
"Sync"
|
|
18
|
+
],
|
|
19
|
+
"summary": "Syncs Azure AD to Sanity",
|
|
20
|
+
"description": "Synchronizes data from Azure AD to Sanity.",
|
|
21
|
+
"operationId": "SyncAzureAdToSanity",
|
|
22
|
+
"responses": {
|
|
23
|
+
"200": {
|
|
24
|
+
"description": "Data synchronized successfully",
|
|
25
|
+
"content": {
|
|
26
|
+
"application/json": {
|
|
27
|
+
"schema": {
|
|
28
|
+
"type": "string"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"x-ms-summary": "Success"
|
|
33
|
+
},
|
|
34
|
+
"401": {
|
|
35
|
+
"description": "Unauthorized access",
|
|
36
|
+
"x-ms-summary": "Unauthorized"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"security": [
|
|
40
|
+
{
|
|
41
|
+
"Implicit": [
|
|
42
|
+
"api://2d964377-8322-482d-bd45-3134b48a9da5/user_impersonation"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"/v1.0/cv-partner-to-sanity": {
|
|
49
|
+
"get": {
|
|
50
|
+
"tags": [
|
|
51
|
+
"Sync"
|
|
52
|
+
],
|
|
53
|
+
"summary": "Syncs CV Partner to Sanity",
|
|
54
|
+
"description": "Synchronizes data from CV Partner to Sanity.",
|
|
55
|
+
"operationId": "SyncCvPartnerToSanity",
|
|
56
|
+
"responses": {
|
|
57
|
+
"200": {
|
|
58
|
+
"description": "Data synchronized successfully",
|
|
59
|
+
"content": {
|
|
60
|
+
"application/json": {
|
|
61
|
+
"schema": {
|
|
62
|
+
"type": "string"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"x-ms-summary": "Success"
|
|
67
|
+
},
|
|
68
|
+
"401": {
|
|
69
|
+
"description": "Unauthorized access",
|
|
70
|
+
"x-ms-summary": "Unauthorized"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"security": [
|
|
74
|
+
{
|
|
75
|
+
"Implicit": [
|
|
76
|
+
"api://2d964377-8322-482d-bd45-3134b48a9da5/user_impersonation"
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"/v1.0/power-office-to-sanity": {
|
|
83
|
+
"get": {
|
|
84
|
+
"tags": [
|
|
85
|
+
"Sync"
|
|
86
|
+
],
|
|
87
|
+
"summary": "Sync power office data to sanity",
|
|
88
|
+
"description": "Sync power office data to sanity",
|
|
89
|
+
"operationId": "SyncPowerOfficeToSanity",
|
|
90
|
+
"parameters": [
|
|
91
|
+
{
|
|
92
|
+
"name": "from",
|
|
93
|
+
"in": "query",
|
|
94
|
+
"description": "",
|
|
95
|
+
"required": true,
|
|
96
|
+
"schema": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"format": "date-time"
|
|
99
|
+
},
|
|
100
|
+
"x-ms-summary": "Date from"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "to",
|
|
104
|
+
"in": "query",
|
|
105
|
+
"description": "",
|
|
106
|
+
"required": true,
|
|
107
|
+
"schema": {
|
|
108
|
+
"type": "string",
|
|
109
|
+
"format": "date-time"
|
|
110
|
+
},
|
|
111
|
+
"x-ms-summary": "Date to"
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
"responses": {
|
|
115
|
+
"200": {
|
|
116
|
+
"description": "Data synchronized successfully",
|
|
117
|
+
"content": {
|
|
118
|
+
"application/json": {
|
|
119
|
+
"schema": {
|
|
120
|
+
"type": "string"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"x-ms-summary": "Success"
|
|
125
|
+
},
|
|
126
|
+
"401": {
|
|
127
|
+
"description": "Unauthorized access",
|
|
128
|
+
"x-ms-summary": "Unauthorized"
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"security": [
|
|
132
|
+
{
|
|
133
|
+
"Implicit": [
|
|
134
|
+
"api://2d964377-8322-482d-bd45-3134b48a9da5/user_impersonation"
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"/v1.0/okrs-to-sanity": {
|
|
141
|
+
"get": {
|
|
142
|
+
"tags": [
|
|
143
|
+
"Sync"
|
|
144
|
+
],
|
|
145
|
+
"summary": "Sync OKRs",
|
|
146
|
+
"description": "Synchronizes OKRs",
|
|
147
|
+
"operationId": "SyncOkrs",
|
|
148
|
+
"responses": {
|
|
149
|
+
"200": {
|
|
150
|
+
"description": "OKRs synchronized successfully",
|
|
151
|
+
"x-ms-summary": "Success"
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"security": [
|
|
155
|
+
{
|
|
156
|
+
"Implicit": [
|
|
157
|
+
"api://2d964377-8322-482d-bd45-3134b48a9da5/user_impersonation"
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
"components": {
|
|
165
|
+
"securitySchemes": {
|
|
166
|
+
"Implicit": {
|
|
167
|
+
"type": "oauth2",
|
|
168
|
+
"flows": {
|
|
169
|
+
"implicit": {
|
|
170
|
+
"authorizationUrl": "https://login.microsoftonline.com/a8533784-aa3c-403b-a61a-1533ecc6e3ed/oauth2/v2.0/authorize",
|
|
171
|
+
"tokenUrl": "https://login.microsoftonline.com/a8533784-aa3c-403b-a61a-1533ecc6e3ed/oauth2/v2.0/token",
|
|
172
|
+
"refreshUrl": "https://login.microsoftonline.com/a8533784-aa3c-403b-a61a-1533ecc6e3ed/oauth2/v2.0/token",
|
|
173
|
+
"scopes": {
|
|
174
|
+
"api://2d964377-8322-482d-bd45-3134b48a9da5/user_impersonation": "Default function scope"
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|