@snokam/mcp-api 0.8.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
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
|
|
@@ -161,12 +161,9 @@ async function executeCall(endpoint, args) {
|
|
|
161
161
|
const headers = {
|
|
162
162
|
Accept: "application/json",
|
|
163
163
|
};
|
|
164
|
-
const
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
if (token) {
|
|
168
|
-
headers.Authorization = `Bearer ${token}`;
|
|
169
|
-
}
|
|
164
|
+
const token = await getAccessToken(endpoint.scope);
|
|
165
|
+
if (token) {
|
|
166
|
+
headers.Authorization = `Bearer ${token}`;
|
|
170
167
|
}
|
|
171
168
|
let fetchBody;
|
|
172
169
|
if (args.body !== undefined && endpoint.method !== "GET") {
|
|
@@ -348,6 +345,39 @@ Controls: Sonos speakers, lights, YouTube queue
|
|
|
348
345
|
if (name === SET_URL_TOOL_NAME) {
|
|
349
346
|
const service = String(args.service ?? "");
|
|
350
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
|
|
351
381
|
const serviceEndpoints = endpoints.filter((ep) => ep.service === service);
|
|
352
382
|
if (serviceEndpoints.length === 0) {
|
|
353
383
|
const available = [
|
|
@@ -371,7 +401,7 @@ Controls: Sonos speakers, lights, YouTube queue
|
|
|
371
401
|
content: [
|
|
372
402
|
{
|
|
373
403
|
type: "text",
|
|
374
|
-
text: `Overrode ${service} → ${url} (${serviceEndpoints.length} endpoints)
|
|
404
|
+
text: `Overrode ${service} → ${url} (${serviceEndpoints.length} endpoints).`,
|
|
375
405
|
},
|
|
376
406
|
],
|
|
377
407
|
};
|
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.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
|
+
}
|