@ampeco/public-api-mcp 0.2.2 → 0.4.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 (76) hide show
  1. package/README.md +296 -4
  2. package/dist/build/index.js +52 -7
  3. package/dist/build/index.js.map +1 -1
  4. package/dist/build/optimizer.d.ts.map +1 -1
  5. package/dist/build/optimizer.js +82 -21
  6. package/dist/build/optimizer.js.map +1 -1
  7. package/dist/build/parser.d.ts.map +1 -1
  8. package/dist/build/parser.js +74 -1
  9. package/dist/build/parser.js.map +1 -1
  10. package/dist/build/types.d.ts +24 -2
  11. package/dist/build/types.d.ts.map +1 -1
  12. package/dist/cli/index.js +25 -11
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/generated/build-stats.json +11 -5
  15. package/dist/generated/endpoints.json +29219 -20895
  16. package/dist/generated/response-schemas.json +84437 -0
  17. package/dist/generated/schemas.d.ts +7157 -2416
  18. package/dist/generated/schemas.d.ts.map +1 -1
  19. package/dist/generated/schemas.js +1443 -590
  20. package/dist/generated/schemas.js.map +1 -1
  21. package/dist/generated/schemas.ts +1447 -596
  22. package/dist/server/factory.d.ts +3 -2
  23. package/dist/server/factory.d.ts.map +1 -1
  24. package/dist/server/factory.js +144 -231
  25. package/dist/server/factory.js.map +1 -1
  26. package/dist/server/factory.test.js +62 -4
  27. package/dist/server/factory.test.js.map +1 -1
  28. package/dist/server/http.d.ts +2 -1
  29. package/dist/server/http.d.ts.map +1 -1
  30. package/dist/server/http.js +11 -3
  31. package/dist/server/http.js.map +1 -1
  32. package/dist/server/index.d.ts +2 -0
  33. package/dist/server/index.d.ts.map +1 -1
  34. package/dist/server/index.js +12 -1388
  35. package/dist/server/index.js.map +1 -1
  36. package/dist/server/instructions.d.ts +13 -0
  37. package/dist/server/instructions.d.ts.map +1 -0
  38. package/dist/server/instructions.js +146 -0
  39. package/dist/server/instructions.js.map +1 -0
  40. package/dist/server/loader.d.ts +6 -1
  41. package/dist/server/loader.d.ts.map +1 -1
  42. package/dist/server/loader.js +9 -0
  43. package/dist/server/loader.js.map +1 -1
  44. package/dist/server/resources/handlers.d.ts +31 -0
  45. package/dist/server/resources/handlers.d.ts.map +1 -0
  46. package/dist/server/resources/handlers.js +113 -0
  47. package/dist/server/resources/handlers.js.map +1 -0
  48. package/dist/server/resources/handlers.test.d.ts +6 -0
  49. package/dist/server/resources/handlers.test.d.ts.map +1 -0
  50. package/dist/server/resources/handlers.test.js +203 -0
  51. package/dist/server/resources/handlers.test.js.map +1 -0
  52. package/dist/server/resources/native.d.ts +11 -0
  53. package/dist/server/resources/native.d.ts.map +1 -0
  54. package/dist/server/resources/native.js +31 -0
  55. package/dist/server/resources/native.js.map +1 -0
  56. package/dist/server/resources/tools.d.ts +11 -0
  57. package/dist/server/resources/tools.d.ts.map +1 -0
  58. package/dist/server/resources/tools.js +41 -0
  59. package/dist/server/resources/tools.js.map +1 -0
  60. package/dist/server/stdio.d.ts +2 -1
  61. package/dist/server/stdio.d.ts.map +1 -1
  62. package/dist/server/stdio.js +11 -3
  63. package/dist/server/stdio.js.map +1 -1
  64. package/dist/server/types.d.ts +36 -0
  65. package/dist/server/types.d.ts.map +1 -0
  66. package/dist/server/types.js +6 -0
  67. package/dist/server/types.js.map +1 -0
  68. package/dist/server/utils/response-transformer.d.ts +78 -0
  69. package/dist/server/utils/response-transformer.d.ts.map +1 -0
  70. package/dist/server/utils/response-transformer.js +423 -0
  71. package/dist/server/utils/response-transformer.js.map +1 -0
  72. package/dist/server/utils/response-transformer.test.d.ts +2 -0
  73. package/dist/server/utils/response-transformer.test.d.ts.map +1 -0
  74. package/dist/server/utils/response-transformer.test.js +103 -0
  75. package/dist/server/utils/response-transformer.test.js.map +1 -0
  76. package/package.json +4 -2
@@ -3,1397 +3,14 @@
3
3
  *
4
4
  * This server exposes pre-processed OpenAPI endpoints as MCP resources and provides
5
5
  * a tool for making authenticated HTTP requests to any endpoint.
6
+ *
7
+ * This is the legacy standalone server. Consider using http.ts or stdio.ts instead.
6
8
  */
7
9
  import express from 'express';
8
10
  import cors from 'cors';
9
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10
11
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
11
- import { ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
12
- import { z } from 'zod';
13
- import { readFileSync } from 'fs';
14
- import { join, dirname } from 'path';
15
- import { fileURLToPath } from 'url';
16
- import fetch from 'node-fetch';
17
- // Get the directory name for ES modules
18
- const __filename = fileURLToPath(import.meta.url);
19
- const __dirname = dirname(__filename);
20
- /**
21
- * Load pre-processed endpoints from build artifacts
22
- */
23
- function loadEndpoints() {
24
- const endpointsPath = join(__dirname, '../generated/endpoints.json');
25
- const content = readFileSync(endpointsPath, 'utf-8');
26
- return JSON.parse(content);
27
- }
28
- /**
29
- * Create and configure the MCP server
30
- */
31
- function createServer(endpoints, bearerToken, hostname) {
32
- const server = new McpServer({
33
- name: 'public-api-mcp',
34
- version: '0.1.0',
35
- }, {
36
- capabilities: {
37
- resources: {},
38
- tools: {},
39
- prompts: {},
40
- logging: {},
41
- },
42
- instructions: `This server provides access to the ${endpoints.info.title} API (v${endpoints.info.version}).
43
-
44
- It exposes ${endpoints.endpoints.filter(e => !e.deprecated).length} active API endpoints through a hierarchical resource structure (deprecated endpoints are automatically filtered out) and provides a tool for making authenticated HTTP requests.
45
-
46
- Target API: ${hostname}
47
-
48
- Resource Structure (3-tier hierarchy):
49
- - Tier 1 (Discovery): List resources returns API tags (e.g., tag://Users, tag://ChargingSessions)
50
- - Tier 2 (Navigation): Read a tag resource to see all endpoints within that tag
51
- - Tier 3 (Details): Read an individual endpoint resource for full specification
52
-
53
- Usage:
54
- 1. List available resources to discover API tags (returns ~10-30 tags instead of 443 endpoints)
55
- 2. Read a tag resource (e.g., tag://Users) to see all endpoints within that tag
56
- 3. Read an endpoint resource (e.g., api://GET/users/{id}) to get detailed endpoint information
57
- 4. Use the 'api_request' tool to make HTTP calls to any endpoint
58
-
59
- URI Formats:
60
- - Tags: tag://{TagName} (e.g., tag://Users)
61
- - Endpoints: api://{METHOD}{path} (e.g., api://GET/users/{id})
62
-
63
- The server automatically proxies your Bearer token to all API requests to ${hostname}.
64
-
65
- Note: Deprecated endpoints are not exposed as resources and cannot be accessed. This hierarchical structure reduces initial context consumption by ~95-97%.
66
-
67
- ## Available Prompts
68
-
69
- This server provides helpful prompts for common workflows. Use these prompts to learn best practices:
70
-
71
- - **charging_session_workflow**: Learn how to implement the complete charging session lifecycle: finding charge points, starting/stopping sessions, and retrieving session details
72
- - **authentication_workflow**: Understand how authentication works with the AMPECO API and how to implement custom authentication in your backend
73
- - **reservation_workflow**: Learn how to reserve charge points for immediate or future use
74
- - **smart_charging_setup**: Guide for configuring smart charging modes including scheduled, solar, and dynamic pricing strategies
75
-
76
- To use a prompt, simply reference it by name when you need guidance on these workflows.
77
-
78
- ## AMPECO API Resource Relationships
79
-
80
- The AMPECO API has a hierarchical structure: **Locations** → **Charging Zones** → **Charge Points** → **EVSEs** → **Connectors**
81
-
82
- Each resource can be listed independently. EVSEs contain the currentType field ("ac"/"dc") for determining charging type.
83
-
84
- ## Best Practices for Data Fetching
85
-
86
- **Always prefer top-level listing endpoints over nested fetching:**
87
- - ✅ Use \`GET /evses\` to get all EVSEs across all charge points (1 API call)
88
- - ❌ Avoid fetching \`GET /charge-points/{id}/evses\` for each charge point (N API calls)
89
-
90
- The top-level listing endpoints are more efficient and reduce API load. Use nested endpoints only when you specifically need resources for a single parent entity.
91
-
92
- ## Session State & Webhooks
93
-
94
- The AMPECO API is event-driven and uses webhooks for real-time updates. When performing actions like starting/stopping charging sessions:
95
- - Initial API responses return immediately with session IDs
96
- - Actual state changes arrive via webhooks (SessionUpdateNotification, SessionMeterValuesNotification)
97
- - Clients should implement webhook handlers to track session status changes from "pending" → "active" → "finished"
98
- - Session details and final metrics are retrieved via GET requests after receiving webhook notifications
99
-
100
- ## Cursor Pagination Guide
101
-
102
- Many list endpoints support cursor-based pagination, which is faster and more reliable than offset-based pagination. Here's how to use it:
103
-
104
- **Initial Request** - Start with an empty cursor:
105
- \`\`\`
106
- api_request(endpoint: "/users", method: "GET", query_params: { limit: 100, cursor: "" })
107
- \`\`\`
108
-
109
- **Subsequent Requests** - Use the cursor from the previous response:
110
- \`\`\`
111
- api_request(endpoint: "/users", method: "GET", query_params: { limit: 100, cursor: "eyJpZCI6MTAwfQ==" })
112
- \`\`\`
113
-
114
- **Pagination Loop:**
115
- 1. Start with \`cursor=""\` (empty string)
116
- 2. Make request with current cursor
117
- 3. Collect items from response
118
- 4. If response contains a non-empty cursor, use it for next request
119
- 5. Stop when cursor is null/empty or data array is empty
120
-
121
- **Key Points:**
122
- - ✅ Always use \`cursor=""\` for first request
123
- - ✅ Use exact cursor value from responses
124
- - ✅ Check for null/empty cursor or empty data array to detect end
125
- - ✅ Adjust limit based on data size (100-1000 typical)
126
- - ❌ Don't use offset pagination when cursor is available`,
127
- });
128
- // Use the underlying Server API for dynamic resource handling with many endpoints
129
- // This gives us more control than the higher-level .resource() method
130
- // Register resources/list handler - returns tags (Tier 1)
131
- server.server.setRequestHandler(ListResourcesRequestSchema, async () => {
132
- const startTime = Date.now();
133
- console.log(`[Resources List] Building tag-based resource list...`);
134
- // Filter out deprecated endpoints first
135
- const activeEndpoints = endpoints.endpoints.filter(endpoint => !endpoint.deprecated);
136
- // Extract unique tags from all active endpoints
137
- const tagsSet = new Set();
138
- let untaggedCount = 0;
139
- for (const endpoint of activeEndpoints) {
140
- if (endpoint.tags && endpoint.tags.length > 0) {
141
- endpoint.tags.forEach(tag => tagsSet.add(tag));
142
- }
143
- else {
144
- untaggedCount++;
145
- }
146
- }
147
- // Convert to sorted array
148
- const tags = Array.from(tagsSet).sort();
149
- // Add "Untagged" category if there are untagged endpoints
150
- if (untaggedCount > 0) {
151
- tags.push('Untagged');
152
- }
153
- // Build resources list with tag URIs
154
- const resources = tags.map(tag => {
155
- const tagEndpoints = activeEndpoints.filter(e => tag === 'Untagged'
156
- ? !e.tags || e.tags.length === 0
157
- : e.tags?.includes(tag));
158
- return {
159
- uri: `tag://${tag}`,
160
- name: tag,
161
- description: `${tagEndpoints.length} endpoint${tagEndpoints.length !== 1 ? 's' : ''} tagged with '${tag}'`,
162
- mimeType: 'application/json',
163
- };
164
- });
165
- const duration = Date.now() - startTime;
166
- console.log(`[Resources List] Built ${resources.length} tag resources (filtered ${endpoints.endpoints.length - activeEndpoints.length} deprecated endpoints) in ${duration}ms`);
167
- return { resources };
168
- });
169
- // Register resources/read handler - supports tag:// (Tier 2) and api:// (Tier 3) URIs
170
- server.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
171
- const uri = request.params.uri;
172
- // Manual URI parsing to handle spaces and special characters in tag names
173
- // Format: tag://{TagName} or api://{METHOD}{path}
174
- const protocolMatch = uri.match(/^([^:]+):\/\/(.*)$/);
175
- if (!protocolMatch) {
176
- throw new Error(`Invalid URI format: ${uri}`);
177
- }
178
- const protocol = protocolMatch[1];
179
- const remainder = protocolMatch[2];
180
- // Tier 2: Handle tag:// URIs - return list of endpoints within the tag
181
- if (protocol === 'tag') {
182
- const tag = remainder;
183
- // Filter active endpoints by tag
184
- const activeEndpoints = endpoints.endpoints.filter(endpoint => !endpoint.deprecated);
185
- const taggedEndpoints = activeEndpoints.filter(e => tag === 'Untagged'
186
- ? !e.tags || e.tags.length === 0
187
- : e.tags?.includes(tag));
188
- if (taggedEndpoints.length === 0) {
189
- throw new Error(`No endpoints found for tag: ${tag}`);
190
- }
191
- // Build response with endpoint summaries
192
- const tagResource = {
193
- tag,
194
- count: taggedEndpoints.length,
195
- endpoints: taggedEndpoints.map(e => ({
196
- uri: `api://${e.method.toUpperCase()}${e.path}`,
197
- method: e.method.toUpperCase(),
198
- path: e.path,
199
- summary: e.summary || e.operationId || `${e.method} ${e.path}`,
200
- operationId: e.operationId,
201
- })),
202
- };
203
- return {
204
- contents: [
205
- {
206
- uri,
207
- mimeType: 'application/json',
208
- text: JSON.stringify(tagResource, null, 2),
209
- },
210
- ],
211
- };
212
- }
213
- // Tier 3: Handle api:// URIs - return individual endpoint details
214
- if (protocol === 'api') {
215
- // Parse URI format: api://METHOD/path
216
- // Example: api://POST/users or api://GET/users/{id}
217
- const match = remainder.match(/^([A-Z]+)(\/.*)/);
218
- if (!match) {
219
- throw new Error(`Invalid API URI format: ${uri}`);
220
- }
221
- const method = match[1];
222
- const path = match[2];
223
- // Find the matching endpoint
224
- const endpoint = endpoints.endpoints.find((e) => e.path === path && e.method.toUpperCase() === method.toUpperCase());
225
- if (!endpoint) {
226
- throw new Error(`Endpoint not found: ${method} ${path}`);
227
- }
228
- // Reject deprecated endpoints
229
- if (endpoint.deprecated) {
230
- throw new Error(`Endpoint is deprecated: ${method} ${path}`);
231
- }
232
- // Return comprehensive endpoint information
233
- return {
234
- contents: [
235
- {
236
- uri,
237
- mimeType: 'application/json',
238
- text: JSON.stringify(endpoint, null, 2),
239
- },
240
- ],
241
- };
242
- }
243
- // Unsupported protocol
244
- throw new Error(`Unsupported URI protocol: ${protocol}. Expected 'tag' or 'api'`);
245
- });
246
- // Register the API request tool
247
- server.tool('api_request', `Make an authenticated HTTP request to any active API endpoint from the OpenAPI specification. Deprecated endpoints cannot be called and will return an error. Authentication is handled automatically using the Bearer token from the MCP request. The target API hostname is: ${hostname}
248
-
249
- CRITICAL - NEVER GUESS: You MUST read the endpoint resource first (using api://{METHOD}{path} URI) before calling this tool. DO NOT guess parameter names, request body structure, or query parameters. The endpoint resource contains the complete specification including all required/optional parameters, request body schema, and response format. Guessing will result in API errors.
250
-
251
- IMPORTANT - Pagination: Always prefer cursor-based pagination when available. For list endpoints that support both offset and cursor pagination, you MUST use cursor pagination by including an empty "cursor" query parameter (cursor="") in your initial request to engage cursor-based pagination. Subsequent requests should use the cursor value returned in the response.`, {
252
- endpoint: z.string().describe('The API endpoint path (e.g., "/users/{id}")'),
253
- method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']).describe('HTTP method'),
254
- path_params: z.preprocess((val) => (val === null || val === undefined || val === '' ? {} : val), z.record(z.any()).default({})).optional().describe('Path parameter values as key-value pairs (optional)'),
255
- query_params: z.preprocess((val) => (val === null || val === undefined || val === '' ? {} : val), z.record(z.any()).default({})).optional().describe('Query string parameters as key-value pairs (optional)'),
256
- body: z.preprocess((val) => (val === null || val === undefined || val === '' ? undefined : val), z.any()).optional().describe('Request body for POST/PUT/PATCH requests (optional)'),
257
- headers: z.preprocess((val) => (val === null || val === undefined || val === '' ? {} : val), z.record(z.string()).default({})).optional().describe('Additional HTTP headers (optional)'),
258
- }, async (args) => {
259
- const { endpoint, method, path_params = {}, query_params = {}, body, headers = {}, } = args;
260
- // Check if endpoint is deprecated
261
- const endpointDef = endpoints.endpoints.find((e) => e.path === endpoint && e.method.toUpperCase() === method.toUpperCase());
262
- if (endpointDef?.deprecated) {
263
- return {
264
- content: [
265
- {
266
- type: 'text',
267
- text: JSON.stringify({
268
- error: true,
269
- message: `Endpoint is deprecated and cannot be called: ${method} ${endpoint}`,
270
- }, null, 2),
271
- },
272
- ],
273
- isError: true,
274
- };
275
- }
276
- // Construct the full URL
277
- let url = hostname.replace(/\/$/, '') + endpoint;
278
- // Replace path parameters
279
- for (const [key, value] of Object.entries(path_params)) {
280
- url = url.replace(`{${key}}`, encodeURIComponent(String(value)));
281
- }
282
- // Add query parameters
283
- const queryString = Object.entries(query_params)
284
- .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
285
- .join('&');
286
- if (queryString) {
287
- url += `?${queryString}`;
288
- }
289
- // Prepare headers with proxied Bearer token
290
- const requestHeaders = {
291
- 'Content-Type': 'application/json',
292
- ...headers,
293
- };
294
- // Add Authorization header if bearer token is available
295
- if (bearerToken) {
296
- requestHeaders['Authorization'] = `Bearer ${bearerToken}`;
297
- }
298
- // Log the request
299
- console.log(`[API Request] ${method} ${url}`);
300
- try {
301
- // Prepare fetch options
302
- const fetchOptions = {
303
- method,
304
- headers: requestHeaders,
305
- };
306
- // Only include body for methods that support it (not GET/HEAD)
307
- if (body && !['GET', 'HEAD'].includes(method.toUpperCase())) {
308
- fetchOptions.body = JSON.stringify(body);
309
- }
310
- // Make the HTTP request
311
- const response = await fetch(url, fetchOptions);
312
- // Parse response
313
- const rawResponseText = await response.text();
314
- let responseBody;
315
- try {
316
- responseBody = rawResponseText ? JSON.parse(rawResponseText) : null;
317
- }
318
- catch {
319
- responseBody = rawResponseText;
320
- }
321
- // Log the response
322
- console.log(`[API Response] ${response.status} ${response.statusText}`);
323
- // Prepare structured response with truncation
324
- const responseObject = {
325
- status: response.status,
326
- statusText: response.statusText,
327
- headers: Object.fromEntries(response.headers.entries()),
328
- body: responseBody,
329
- };
330
- let responseText = JSON.stringify(responseObject, null, 2);
331
- // Truncate if response exceeds 20000 characters (leaving room for metadata)
332
- const MAX_RESPONSE_LENGTH = 20000;
333
- let wasTruncated = false;
334
- if (responseText.length > MAX_RESPONSE_LENGTH) {
335
- wasTruncated = true;
336
- const originalLength = responseText.length;
337
- responseText = responseText.substring(0, MAX_RESPONSE_LENGTH);
338
- // Try to truncate at a reasonable boundary (end of a line)
339
- const lastNewlineIndex = responseText.lastIndexOf('\n');
340
- if (lastNewlineIndex > MAX_RESPONSE_LENGTH * 0.9) {
341
- responseText = responseText.substring(0, lastNewlineIndex);
342
- }
343
- // Add truncation notice
344
- responseText += `\n\n... [Response truncated - original size: ${originalLength} characters, showing first ${responseText.length} characters]\n\nNote: The response was too large to display in full. This is typically because the API returned detailed error information or a large dataset. Check the status code and the visible portion of the response for key information.`;
345
- }
346
- const toolResponse = {
347
- content: [
348
- {
349
- type: 'text',
350
- text: responseText,
351
- },
352
- ],
353
- };
354
- console.log(`[Tool Response] Returning response with ${responseText.length} characters${wasTruncated ? ' (truncated)' : ''}`);
355
- // Return structured response
356
- return toolResponse;
357
- }
358
- catch (error) {
359
- const errorMessage = error instanceof Error ? error.message : String(error);
360
- console.error(`[API Error] ${errorMessage}`);
361
- const errorObject = {
362
- error: true,
363
- message: errorMessage,
364
- details: error instanceof Error ? error.stack : undefined,
365
- };
366
- let errorText = JSON.stringify(errorObject, null, 2);
367
- // Truncate error responses too
368
- const MAX_ERROR_LENGTH = 5000;
369
- if (errorText.length > MAX_ERROR_LENGTH) {
370
- errorText = errorText.substring(0, MAX_ERROR_LENGTH) + '\n\n... [Error message truncated]';
371
- }
372
- return {
373
- content: [
374
- {
375
- type: 'text',
376
- text: errorText,
377
- },
378
- ],
379
- isError: true,
380
- };
381
- }
382
- });
383
- // Register prompts for common workflows
384
- server.server.setRequestHandler(ListPromptsRequestSchema, async () => {
385
- return {
386
- prompts: [
387
- {
388
- name: 'charging_session_workflow',
389
- description: 'Learn how to implement the complete charging session lifecycle: finding charge points, starting/stopping sessions, and retrieving session details',
390
- arguments: [
391
- {
392
- name: 'evseId',
393
- description: 'The EVSE network ID to start charging on (optional - if not provided, guidance will be generic)',
394
- required: false,
395
- },
396
- {
397
- name: 'sessionId',
398
- description: 'The session ID to monitor or stop (optional - if not provided, guidance will be generic)',
399
- required: false,
400
- },
401
- ],
402
- },
403
- {
404
- name: 'authentication_workflow',
405
- description: 'Understand how authentication works with the AMPECO API and how to implement custom authentication in your backend',
406
- arguments: [],
407
- },
408
- {
409
- name: 'reservation_workflow',
410
- description: 'Learn how to reserve charge points for immediate or future use',
411
- arguments: [
412
- {
413
- name: 'chargePointId',
414
- description: 'The charge point ID to reserve (optional)',
415
- required: false,
416
- },
417
- {
418
- name: 'evseNetworkId',
419
- description: 'The EVSE network ID to reserve (optional)',
420
- required: false,
421
- },
422
- {
423
- name: 'userId',
424
- description: 'The user ID making the reservation (optional)',
425
- required: false,
426
- },
427
- {
428
- name: 'reservationType',
429
- description: 'Type of reservation: "immediate" or "future" (optional, defaults to immediate)',
430
- required: false,
431
- },
432
- ],
433
- },
434
- {
435
- name: 'smart_charging_setup',
436
- description: 'Guide for configuring smart charging modes including scheduled, solar, and dynamic pricing strategies',
437
- arguments: [
438
- {
439
- name: 'chargePointId',
440
- description: 'The charge point ID to configure (optional)',
441
- required: false,
442
- },
443
- {
444
- name: 'mode',
445
- description: 'Smart charging mode to configure: "scheduled", "solar", "octopus_agile", "octopus_go", "nordpool", or "energi_elspot" (optional)',
446
- required: false,
447
- },
448
- ],
449
- },
450
- ],
451
- };
452
- });
453
- server.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
454
- const promptName = request.params.name;
455
- const args = request.params.arguments || {};
456
- switch (promptName) {
457
- case 'charging_session_workflow': {
458
- const evseId = args.evseId;
459
- const sessionId = args.sessionId;
460
- let specificGuidance = '';
461
- if (evseId) {
462
- specificGuidance += `\n## Specific Task: Work with EVSE ${evseId}\n\nYou have been provided with EVSE ID: ${evseId}\nUse this ID when starting a charging session in Step 2.\n`;
463
- }
464
- if (sessionId) {
465
- specificGuidance += `\n## Specific Task: Monitor/Stop Session ${sessionId}\n\nYou have been provided with session ID: ${sessionId}\nUse this ID to retrieve session details or stop the charging session.\n`;
466
- }
467
- return {
468
- messages: [
469
- {
470
- role: 'user',
471
- content: {
472
- type: 'text',
473
- text: `# Charging Session Workflow${specificGuidance}
474
-
475
- This guide explains how to implement a complete EV charging session using the AMPECO API.
476
-
477
- ## Overview
478
-
479
- The charging session lifecycle follows this sequence:
480
- 1. **Discovery**: Find available charge points
481
- 2. **Start Session**: Initiate charging
482
- 3. **Monitor**: Track session progress via webhooks
483
- 4. **Stop Session**: End charging
484
- 5. **Retrieve Details**: Get final session metrics and billing
485
-
486
- ## Step 1: Find Available Charge Points
487
-
488
- Use the top-level listing endpoints to discover charging infrastructure:
489
-
490
- **Get Locations:**
491
- \`\`\`
492
- GET /resources/locations/v1.0
493
- \`\`\`
494
- Returns all locations with geographic coordinates and availability status.
495
-
496
- **Get Charge Points:**
497
- \`\`\`
498
- GET /resources/charge-points/v1.0?status=active&accessType=public&networkStatus=available
499
- \`\`\`
500
- Filter for active, public, and available charge points.
501
-
502
- **Get EVSEs (Recommended for charging type):**
503
- \`\`\`
504
- GET /resources/evses
505
- \`\`\`
506
- EVSEs contain the \`currentType\` field ("ac" or "dc") which tells you the charging type. Each EVSE belongs to a charge point and contains connectors.
507
-
508
- ## Step 2: Start a Charging Session
509
-
510
- **Endpoint:**
511
- \`\`\`
512
- POST /actions/evse/v1.0/{evseId}/start
513
- \`\`\`
514
-
515
- **Required Parameters:**
516
- - \`evseId\`: The EVSE network ID (from Step 1)
517
-
518
- **Response:**
519
- \`\`\`json
520
- {
521
- "success": true,
522
- "sessionId": 12345,
523
- "message": "Session starting..."
524
- }
525
- \`\`\`
526
-
527
- **Important:** The response returns immediately with a session ID, but the session is not yet active.
528
-
529
- ## Step 3: Monitor Session Status
530
-
531
- **Webhook Notifications:**
532
-
533
- The AMPECO API is event-driven. After starting a session, you will receive:
534
-
535
- 1. **First Notification - SessionUpdateNotification:**
536
- - Status: \`"pending"\`
537
- - Indicates session is being prepared
538
-
539
- 2. **Second Notification - SessionUpdateNotification:**
540
- - Status: \`"active"\`
541
- - Charging has started
542
-
543
- 3. **Ongoing - SessionMeterValuesNotification:**
544
- - Contains real-time energy consumption (in Wh)
545
- - Provides meter readings during charging
546
-
547
- **To get current session details:**
548
- \`\`\`
549
- GET /resources/sessions/v1.0/{sessionId}
550
- \`\`\`
551
-
552
- ## Step 4: Stop Charging Session
553
-
554
- **Endpoint:**
555
- \`\`\`
556
- POST /actions/charge-point/v1.0/{chargePointId}/stop/{sessionId}
557
- \`\`\`
558
-
559
- **Required Parameters:**
560
- - \`chargePointId\`: The charge point ID
561
- - \`sessionId\`: The session ID from Step 2
562
-
563
- **Response:**
564
- Returns confirmation that stop request was sent.
565
-
566
- **Webhook Notification:**
567
- You will receive a \`SessionUpdateNotification\` with status \`"finished"\` when the session has fully terminated.
568
-
569
- ## Step 5: Retrieve Final Session Details
570
-
571
- **Endpoint:**
572
- \`\`\`
573
- GET /resources/sessions/v1.0/{sessionId}
574
- \`\`\`
575
-
576
- **Wait for:** The \`SessionUpdateNotification\` webhook with status \`"finished"\` before retrieving final details.
577
-
578
- **Response includes:**
579
- - Total energy consumed (kWh)
580
- - State of charge percentage
581
- - Total amount charged (billing)
582
- - Payment status
583
- - Charging periods breakdown
584
- - Price breakdown by tariff components
585
- - Complete tariff snapshot
586
-
587
- ## Best Practices
588
-
589
- 1. **Use webhooks for state changes** - Don't poll for status updates
590
- 2. **Wait for "finished" status** - Final metrics are only accurate after session completes
591
- 3. **Use top-level EVSE listing** - More efficient than nested queries
592
- 4. **Check currentType** - Verify AC vs DC compatibility before starting session
593
- 5. **Handle webhook delays** - Status changes may take 10-30 seconds
594
-
595
- ## Error Handling
596
-
597
- - If start request fails, check EVSE availability and network status
598
- - If session doesn't transition to "active" within 60 seconds, check charge point hardware status
599
- - If stop request fails, the session may already be finished or the charge point may be offline`,
600
- },
601
- },
602
- ],
603
- };
604
- }
605
- case 'authentication_workflow': {
606
- // No arguments needed for this workflow - it's purely educational
607
- return {
608
- messages: [
609
- {
610
- role: 'user',
611
- content: {
612
- type: 'text',
613
- text: `# Authentication Workflow
614
-
615
- This guide explains how authentication works with the AMPECO Public API and how to implement it in your application.
616
-
617
- ## Important: No Client-Level Authentication
618
-
619
- **The AMPECO Public API does NOT provide client-level authentication or authorization for mobile apps or end users.**
620
-
621
- This means:
622
- - There is no \`/login\` endpoint for end users
623
- - There is no user session management in the API
624
- - You cannot directly authenticate mobile app users against the API
625
- - You MUST implement your own authentication layer
626
-
627
- ## Authentication Architecture
628
-
629
- \`\`\`
630
- Mobile App → Custom Backend → AMPECO Public API
631
- (Your Auth) (Bearer Token)
632
- \`\`\`
633
-
634
- ### What You Build (Custom Backend)
635
- 1. User registration and login endpoints
636
- 2. Password hashing and validation
637
- 3. Session/JWT token management for your users
638
- 4. User credential storage in your database
639
- 5. Authorization logic (who can access what)
640
-
641
- ### What AMPECO Provides
642
- - **Admin-level Bearer Token** for your backend to call the API
643
- - User resource management via \`POST /resources/users\` (create users in AMPECO system)
644
- - User-scoped data filtering on API endpoints
645
-
646
- ## Implementation Guide
647
-
648
- ### Step 1: User Registration
649
-
650
- **Your Backend:**
651
- 1. Accept user registration (email, password, name) from mobile app
652
- 2. Hash password and store in your database
653
- 3. Call AMPECO API to create user resource:
654
-
655
- \`\`\`
656
- POST /resources/users
657
- Authorization: Bearer YOUR_ADMIN_TOKEN
658
- Content-Type: application/json
659
-
660
- {
661
- "email": "user@example.com",
662
- "first_name": "John",
663
- "last_name": "Doe"
664
- }
665
- \`\`\`
666
-
667
- 4. Store AMPECO user ID alongside your user record
668
- 5. Return success to mobile app
669
-
670
- ### Step 2: User Login
671
-
672
- **Your Backend:**
673
- 1. Accept login credentials (email, password) from mobile app
674
- 2. Validate credentials against your database
675
- 3. Generate your own session token/JWT
676
- 4. Return token to mobile app
677
-
678
- **Mobile App:**
679
- - Store your session token
680
- - Include it in all requests to your backend
681
- - Your backend validates this token on each request
682
-
683
- ### Step 3: Making API Calls on Behalf of Users
684
-
685
- **Your Backend:**
686
- 1. Validate user's session token
687
- 2. Retrieve user's AMPECO user ID from your database
688
- 3. Call AMPECO API with admin Bearer token
689
- 4. Use user filters to scope requests:
690
-
691
- \`\`\`
692
- GET /resources/sessions/v1.0?userId={ampecoUserId}
693
- Authorization: Bearer YOUR_ADMIN_TOKEN
694
- \`\`\`
695
-
696
- This returns only sessions for that specific user.
697
-
698
- ### Step 4: Securing the Bearer Token
699
-
700
- **Critical Security Practices:**
701
-
702
- 1. **NEVER expose admin Bearer token to mobile apps**
703
- - Token stays on your backend only
704
- - Mobile apps never see or use it
705
-
706
- 2. **Store token securely:**
707
- - Environment variables (recommended)
708
- - Secrets management service (AWS Secrets Manager, etc.)
709
- - Never commit to git
710
-
711
- 3. **Use HTTPS:**
712
- - All communication between mobile app and your backend
713
- - All communication between your backend and AMPECO API
714
-
715
- ## User-Scoped API Requests
716
-
717
- Many AMPECO endpoints support filtering by user:
718
-
719
- **Sessions:**
720
- \`\`\`
721
- GET /resources/sessions/v1.0?userId={id}
722
- \`\`\`
723
-
724
- **Charge Points (user's favorites/home chargers):**
725
- \`\`\`
726
- GET /resources/charge-points/v1.0?userId={id}
727
- \`\`\`
728
-
729
- **Reservations:**
730
- \`\`\`
731
- GET /resources/reservations?userId={id}
732
- \`\`\`
733
-
734
- Always use these filters to ensure users only see their own data.
735
-
736
- ## Example Flow: Start Charging Session
737
-
738
- \`\`\`
739
- 1. Mobile app → Your backend: "Start charging at EVSE 123"
740
- Headers: Authorization: Bearer YOUR_USER_SESSION_TOKEN
741
-
742
- 2. Your backend validates user session token
743
-
744
- 3. Your backend → AMPECO API:
745
- POST /actions/evse/v1.0/123/start
746
- Headers: Authorization: Bearer YOUR_ADMIN_TOKEN
747
-
748
- 4. AMPECO API → Your backend: {"sessionId": 456}
749
-
750
- 5. Your backend → Mobile app: {"sessionId": 456}
751
- \`\`\`
752
-
753
- ## Best Practices
754
-
755
- 1. **Implement rate limiting** - Protect your backend from abuse
756
- 2. **Log API calls** - Track which user triggered which AMPECO API call
757
- 3. **Handle token expiration** - Refresh admin token if needed
758
- 4. **Validate user ownership** - Ensure users can only stop their own sessions, etc.
759
- 5. **Use webhook authentication** - Verify webhooks are actually from AMPECO
760
-
761
- ## Summary
762
-
763
- - **You build:** User authentication, session management, password security
764
- - **AMPECO provides:** Admin-level API access, user resource storage, user-scoped filtering
765
- - **Your backend** acts as a trusted intermediary between untrusted mobile apps and the AMPECO API`,
766
- },
767
- },
768
- ],
769
- };
770
- }
771
- case 'reservation_workflow': {
772
- const chargePointId = args.chargePointId;
773
- const evseNetworkId = args.evseNetworkId;
774
- const userId = args.userId;
775
- const reservationType = args.reservationType || 'immediate';
776
- let specificGuidance = '';
777
- if (chargePointId || evseNetworkId || userId) {
778
- specificGuidance += `\n## Specific Task: Create ${reservationType === 'future' ? 'Future' : 'Immediate'} Reservation\n\n`;
779
- if (userId)
780
- specificGuidance += `User ID: ${userId}\n`;
781
- if (chargePointId)
782
- specificGuidance += `Charge Point ID: ${chargePointId}\n`;
783
- if (evseNetworkId)
784
- specificGuidance += `EVSE Network ID: ${evseNetworkId}\n`;
785
- specificGuidance += `\nUse these IDs when creating the reservation in the examples below.\n`;
786
- }
787
- return {
788
- messages: [
789
- {
790
- role: 'user',
791
- content: {
792
- type: 'text',
793
- text: `# Reservation Workflow${specificGuidance}
794
-
795
- This guide explains how to reserve charge points using the AMPECO API for both immediate and future use.
796
-
797
- ## Overview
798
-
799
- Reservations allow users to guarantee access to a specific charge point or EVSE. AMPECO supports two types:
800
- 1. **Immediate Reservation** ("Reserve Now") - Reserve for use right now
801
- 2. **Future Reservation** - Reserve for a specific date/time in the future
802
-
803
- ## Reservation Endpoint
804
-
805
- \`\`\`
806
- POST /actions/charge-point/v1.0/{chargePointId}/reserve/{evseNetworkId}
807
- \`\`\`
808
-
809
- **Path Parameters:**
810
- - \`chargePointId\`: The charge point ID (number)
811
- - \`evseNetworkId\`: The EVSE network ID (string) - specific connector to reserve
812
-
813
- **Request Body:**
814
- \`\`\`json
815
- {
816
- "userId": 123,
817
- "reason": "User requested reservation"
818
- }
819
- \`\`\`
820
-
821
- ## Immediate Reservation ("Reserve Now")
822
-
823
- **Use Case:** User wants to reserve a charge point to use immediately.
824
-
825
- **Steps:**
826
-
827
- 1. **Find available charge point:**
828
- \`\`\`
829
- GET /resources/charge-points/v1.0?networkStatus=available&hardwareStatus=available
830
- \`\`\`
831
-
832
- 2. **Get EVSE details:**
833
- \`\`\`
834
- GET /resources/evses?chargePointId={chargePointId}
835
- \`\`\`
836
- Note the \`networkId\` field from the EVSE you want to reserve.
837
-
838
- 3. **Create immediate reservation:**
839
- \`\`\`
840
- POST /actions/charge-point/v1.0/{chargePointId}/reserve/{evseNetworkId}
841
- Content-Type: application/json
842
- Authorization: Bearer YOUR_TOKEN
843
-
844
- {
845
- "userId": 123,
846
- "reason": "Immediate charging needed"
847
- }
848
- \`\`\`
849
-
850
- **Response:**
851
- \`\`\`json
852
- {
853
- "success": true,
854
- "reservationId": 456,
855
- "status": "accepted",
856
- "message": "Reservation created successfully"
857
- }
858
- \`\`\`
859
-
860
- 4. **Start charging session** (user should plug in soon):
861
- \`\`\`
862
- POST /actions/evse/v1.0/{evseNetworkId}/start
863
- \`\`\`
864
-
865
- **Important:** Immediate reservations typically expire after 10-15 minutes if not used.
866
-
867
- ## Future Reservation
868
-
869
- **Use Case:** User wants to reserve a charge point for a specific time (e.g., tomorrow at 8 AM).
870
-
871
- **Steps:**
872
-
873
- 1. **Identify charge point and EVSE** (same as immediate reservation)
874
-
875
- 2. **Create future reservation:**
876
- \`\`\`
877
- POST /actions/charge-point/v1.0/{chargePointId}/reserve/{evseNetworkId}
878
- Content-Type: application/json
879
- Authorization: Bearer YOUR_TOKEN
880
-
881
- {
882
- "userId": 123,
883
- "reason": "Reserved for tomorrow morning",
884
- "expiryDate": "2025-10-11T08:00:00Z",
885
- "reservationStart": "2025-10-11T08:00:00Z"
886
- }
887
- \`\`\`
888
-
889
- **Note:** Check the OpenAPI spec for exact field names - the API may use \`startDateTime\` or similar fields for scheduling.
890
-
891
- **Response:**
892
- \`\`\`json
893
- {
894
- "success": true,
895
- "reservationId": 789,
896
- "status": "scheduled",
897
- "startTime": "2025-10-11T08:00:00Z",
898
- "expiryTime": "2025-10-11T09:00:00Z"
899
- }
900
- \`\`\`
901
-
902
- ## Managing Reservations
903
-
904
- ### List User's Reservations
905
- \`\`\`
906
- GET /resources/reservations?userId={userId}
907
- \`\`\`
908
-
909
- Returns all active and past reservations for the user.
910
-
911
- ### Get Reservation Details
912
- \`\`\`
913
- GET /resources/reservations/{reservationId}
914
- \`\`\`
915
-
916
- ### Cancel Reservation
917
- Check the OpenAPI spec for the cancel endpoint (typically):
918
- \`\`\`
919
- DELETE /resources/reservations/{reservationId}
920
- \`\`\`
921
- or
922
- \`\`\`
923
- POST /actions/charge-point/v1.0/{chargePointId}/cancel-reservation/{reservationId}
924
- \`\`\`
925
-
926
- ## Reservation States
927
-
928
- Reservations go through these states:
929
- - **accepted** - Reservation created successfully
930
- - **scheduled** - Future reservation waiting for start time
931
- - **active** - Reservation time window is now active
932
- - **expired** - Reservation time passed without use
933
- - **used** - User started charging session with this reservation
934
- - **cancelled** - Reservation was cancelled
935
-
936
- ## Error Handling
937
-
938
- ### Reservation Fails
939
- Common reasons:
940
- - EVSE already reserved by another user
941
- - Charge point offline or unavailable
942
- - Invalid user ID
943
- - Invalid time range (past date, too far in future)
944
-
945
- **Check response:**
946
- \`\`\`json
947
- {
948
- "success": false,
949
- "status": "rejected",
950
- "message": "EVSE already reserved for that time"
951
- }
952
- \`\`\`
953
-
954
- **Recovery steps:**
955
- 1. Try different EVSE at same location
956
- 2. Adjust reservation time window
957
- 3. Find alternative charge point
958
-
959
- ### Reservation Expires
960
- If user doesn't show up:
961
- - Immediate reservations expire after 10-15 minutes
962
- - Future reservations expire after the scheduled time window ends
963
- - EVSE becomes available for other users
964
- - User's reservation record shows "expired" status
965
-
966
- ## Best Practices
967
-
968
- 1. **Check availability before reserving** - Use \`networkStatus\` and \`hardwareStatus\` filters
969
- 2. **Provide clear reason** - Helps with debugging and user support
970
- 3. **Set appropriate expiry** - Don't hold EVSEs longer than needed
971
- 4. **Notify users** - Send reminders before reservation time
972
- 5. **Handle expiry gracefully** - Let users know if their reservation expired
973
- 6. **Allow cancellation** - Users should be able to cancel if plans change
974
-
975
- ## Example: Reserve for Scheduled Charging
976
-
977
- **Scenario:** User wants to charge during cheap electricity hours (2 AM - 6 AM).
978
-
979
- 1. **Create overnight reservation:**
980
- \`\`\`
981
- POST /actions/charge-point/v1.0/42/reserve/EVSE-001
982
- {
983
- "userId": 123,
984
- "reason": "Scheduled charging during off-peak hours",
985
- "reservationStart": "2025-10-11T02:00:00Z",
986
- "expiryDate": "2025-10-11T06:00:00Z"
987
- }
988
- \`\`\`
989
-
990
- 2. **User plugs in before 2 AM** (or at any time if they're at home)
991
-
992
- 3. **Set smart charging profile** (see smart_charging_setup prompt)
993
-
994
- 4. **Charging starts automatically at 2 AM** using the reservation
995
-
996
- ## Integration with Smart Charging
997
-
998
- Reservations work well with smart charging modes:
999
- - Reserve the EVSE
1000
- - Set a scheduled charging profile
1001
- - Charging starts automatically during the reservation window
1002
- - Optimizes for electricity price or solar availability`,
1003
- },
1004
- },
1005
- ],
1006
- };
1007
- }
1008
- case 'smart_charging_setup': {
1009
- const chargePointId = args.chargePointId;
1010
- const mode = args.mode;
1011
- let specificGuidance = '';
1012
- if (chargePointId || mode) {
1013
- specificGuidance += `\n## Specific Task: Configure Smart Charging\n\n`;
1014
- if (chargePointId)
1015
- specificGuidance += `Charge Point ID: ${chargePointId}\n`;
1016
- if (mode) {
1017
- specificGuidance += `Target Mode: ${mode}\n\n`;
1018
- specificGuidance += `Focus on implementing the "${mode}" smart charging mode in the examples below.\n`;
1019
- }
1020
- else if (chargePointId) {
1021
- specificGuidance += `\nConfigure smart charging for this charge point using the appropriate mode from the options below.\n`;
1022
- }
1023
- }
1024
- return {
1025
- messages: [
1026
- {
1027
- role: 'user',
1028
- content: {
1029
- type: 'text',
1030
- text: `# Smart Charging Setup${specificGuidance}
1031
-
1032
- This guide explains how to configure smart charging modes for home chargers using the AMPECO API.
1033
-
1034
- ## Overview
1035
-
1036
- Smart charging optimizes when and how EVs charge based on various factors:
1037
- - **Scheduled** - Charge during specific time windows
1038
- - **Solar** - Charge using excess solar power
1039
- - **Dynamic Pricing** - Charge when electricity is cheapest
1040
- - **Grid Optimization** - Respond to grid demand
1041
-
1042
- AMPECO supports multiple smart charging modes that can be configured per charge point.
1043
-
1044
- ## Available Smart Charging Modes
1045
-
1046
- ### 1. Scheduled (user_controlled_schedule)
1047
- Time-based charging within defined windows.
1048
-
1049
- ### 2. Solar (solar)
1050
- Charges using excess solar power from home panels.
1051
-
1052
- ### 3. Octopus Agile (octopus_agile)
1053
- Dynamic pricing based on Octopus Energy's Agile tariff (UK).
1054
-
1055
- ### 4. Octopus Go (octopus_go)
1056
- Fixed off-peak window for Octopus Energy's Go tariff (UK).
1057
-
1058
- ### 5. Energi Elspot (energi_elspot)
1059
- Nordic spot market electricity pricing.
1060
-
1061
- ### 6. Nordpool (nordpool)
1062
- Nordic power exchange pricing.
1063
-
1064
- ## Step 1: Get Available Smart Charging Modes
1065
-
1066
- **Endpoint:**
1067
- \`\`\`
1068
- GET /resources/charge-points/v2.0/{chargePointId}/available-personal-smart-charging-modes
1069
- Authorization: Bearer YOUR_TOKEN
1070
- \`\`\`
1071
-
1072
- **Response:**
1073
- \`\`\`json
1074
- {
1075
- "modes": [
1076
- {
1077
- "name": "Scheduled",
1078
- "integrationId": "user_controlled_schedule",
1079
- "type": "user_controlled_schedule",
1080
- "defaults": {
1081
- "start_time": "00:00:00",
1082
- "end_time": "06:00:00",
1083
- "target_charge": null
1084
- }
1085
- },
1086
- {
1087
- "name": "Solar",
1088
- "integrationId": "solar",
1089
- "type": "solar",
1090
- "defaults": {
1091
- "min_charge_kWh": 10,
1092
- "max_charge_kWh": 50
1093
- }
1094
- },
1095
- {
1096
- "name": "Agile-Octopus",
1097
- "integrationId": "octopus_agile",
1098
- "type": "octopus_agile",
1099
- "defaults": {
1100
- "max_price_threshold": 15.0
1101
- }
1102
- }
1103
- ]
1104
- }
1105
- \`\`\`
1106
-
1107
- **Note:** Available modes depend on tenant configuration.
1108
-
1109
- ## Step 2: Configure Smart Charging Preferences
1110
-
1111
- **Endpoint:**
1112
- \`\`\`
1113
- POST /resources/charge-points/v2.0/{chargePointId}/personal-smart-charging-preferences
1114
- Authorization: Bearer YOUR_TOKEN
1115
- Content-Type: application/json
1116
- \`\`\`
1117
-
1118
- ### Configuration A: Scheduled Charging
1119
-
1120
- **Use Case:** Charge only during off-peak hours (2 AM - 6 AM).
1121
-
1122
- **Request Body:**
1123
- \`\`\`json
1124
- {
1125
- "type": "user_controlled_schedule",
1126
- "enabled": true,
1127
- "preferences": {
1128
- "start_time": "02:00:00",
1129
- "end_time": "06:00:00",
1130
- "target_charge": null
1131
- }
1132
- }
1133
- \`\`\`
1134
-
1135
- **Parameters:**
1136
- - \`start_time\`: When charging window begins (24h format)
1137
- - \`end_time\`: When charging window ends (24h format)
1138
- - \`target_charge\`: Target kWh (null = charge fully during window)
1139
-
1140
- **Response:**
1141
- \`\`\`json
1142
- {
1143
- "success": true,
1144
- "preferenceId": 123,
1145
- "message": "Smart charging preference saved"
1146
- }
1147
- \`\`\`
1148
-
1149
- ### Configuration B: Solar Charging
1150
-
1151
- **Use Case:** Charge using excess solar power, with minimum guaranteed charge.
1152
-
1153
- **Request Body:**
1154
- \`\`\`json
1155
- {
1156
- "type": "solar",
1157
- "enabled": true,
1158
- "preferences": {
1159
- "min_charge_kWh": 15,
1160
- "max_charge_kWh": 60,
1161
- "allow_grid_charging": true,
1162
- "grid_charging_threshold": 20
1163
- }
1164
- }
1165
- \`\`\`
1166
-
1167
- **Parameters:**
1168
- - \`min_charge_kWh\`: Minimum energy to charge (guaranteed even from grid)
1169
- - \`max_charge_kWh\`: Maximum energy to charge
1170
- - \`allow_grid_charging\`: Allow grid if solar insufficient for minimum
1171
- - \`grid_charging_threshold\`: Battery % below which to use grid
1172
-
1173
- **How it works:**
1174
- 1. Charges primarily from solar excess
1175
- 2. If solar insufficient and battery < threshold, uses grid to reach minimum
1176
- 3. Stops at maximum kWh
1177
-
1178
- ### Configuration C: Dynamic Pricing (Octopus Agile)
1179
-
1180
- **Use Case:** Charge when electricity price is below threshold.
1181
-
1182
- **Request Body:**
1183
- \`\`\`json
1184
- {
1185
- "type": "octopus_agile",
1186
- "enabled": true,
1187
- "preferences": {
1188
- "max_price_threshold": 12.0,
1189
- "location": "H",
1190
- "target_charge_kWh": 40
1191
- }
1192
- }
1193
- \`\`\`
1194
-
1195
- **Parameters:**
1196
- - \`max_price_threshold\`: Maximum p/kWh to charge at (pence)
1197
- - \`location\`: Octopus pricing region code
1198
- - \`target_charge_kWh\`: How much energy to add
1199
-
1200
- **How it works:**
1201
- 1. Monitors Octopus Agile half-hourly prices
1202
- 2. Charges when price drops below threshold
1203
- 3. Stops when target kWh reached
1204
-
1205
- ### Configuration D: Octopus Go (Fixed Off-Peak)
1206
-
1207
- **Use Case:** Charge during Octopus Go's fixed off-peak window (12:30 AM - 4:30 AM).
1208
-
1209
- **Request Body:**
1210
- \`\`\`json
1211
- {
1212
- "type": "octopus_go",
1213
- "enabled": true,
1214
- "preferences": {
1215
- "start_time": "00:30:00",
1216
- "end_time": "04:30:00"
1217
- }
1218
- }
1219
- \`\`\`
1220
-
1221
- ### Configuration E: Nordpool / Energi Elspot
1222
-
1223
- **Use Case:** Charge during cheapest Nordic spot market hours.
1224
-
1225
- **Request Body:**
1226
- \`\`\`json
1227
- {
1228
- "type": "nordpool",
1229
- "enabled": true,
1230
- "preferences": {
1231
- "max_price_threshold": 0.50,
1232
- "price_area": "SE3",
1233
- "target_charge_kWh": 50
1234
- }
1235
- }
1236
- \`\`\`
1237
-
1238
- **Parameters:**
1239
- - \`max_price_threshold\`: Maximum price in local currency
1240
- - \`price_area\`: Nordic price area code (SE1-4, NO1-5, DK1-2, FI)
1241
- - \`target_charge_kWh\`: Energy target
1242
-
1243
- ## Step 3: Get Current Smart Charging Preferences
1244
-
1245
- **Endpoint:**
1246
- \`\`\`
1247
- GET /resources/charge-points/v2.0/{chargePointId}/personal-smart-charging-preferences
1248
- Authorization: Bearer YOUR_TOKEN
1249
- \`\`\`
1250
-
1251
- **Response:**
1252
- \`\`\`json
1253
- {
1254
- "active": true,
1255
- "type": "user_controlled_schedule",
1256
- "preferences": {
1257
- "start_time": "02:00:00",
1258
- "end_time": "06:00:00",
1259
- "target_charge": null
1260
- },
1261
- "enabled": true
1262
- }
1263
- \`\`\`
1264
-
1265
- ## Step 4: Update or Disable Smart Charging
1266
-
1267
- ### Update Preferences
1268
- Use the same POST endpoint with new values:
1269
- \`\`\`
1270
- POST /resources/charge-points/v2.0/{chargePointId}/personal-smart-charging-preferences
1271
- {
1272
- "type": "user_controlled_schedule",
1273
- "enabled": true,
1274
- "preferences": {
1275
- "start_time": "01:00:00",
1276
- "end_time": "05:00:00"
1277
- }
1278
- }
1279
- \`\`\`
1280
-
1281
- ### Disable Smart Charging
1282
- Set \`enabled: false\`:
1283
- \`\`\`json
1284
- {
1285
- "type": "user_controlled_schedule",
1286
- "enabled": false,
1287
- "preferences": {}
1288
- }
1289
- \`\`\`
1290
-
1291
- Charging will revert to immediate/manual mode.
1292
-
1293
- ## Best Practices
1294
-
1295
- 1. **Check available modes first** - Not all modes enabled for all tenants
1296
- 2. **Validate time windows** - Ensure start < end for scheduled modes
1297
- 3. **Set reasonable thresholds** - Price thresholds should match local market
1298
- 4. **Combine with reservations** - Reserve EVSE + smart charging = guaranteed optimized charging
1299
- 5. **Monitor effectiveness** - Track sessions to see if smart charging is working
1300
- 6. **Fallback strategy** - Set minimum charge to ensure users aren't stranded
1301
-
1302
- ## Common Workflows
1303
-
1304
- ### Workflow 1: Home Charging Optimization
1305
-
1306
- \`\`\`
1307
- 1. User has home charger and solar panels
1308
- 2. User wants to charge from solar, but guarantee 20 kWh minimum
1309
-
1310
- Setup:
1311
- POST /resources/charge-points/v2.0/{id}/personal-smart-charging-preferences
1312
- {
1313
- "type": "solar",
1314
- "enabled": true,
1315
- "preferences": {
1316
- "min_charge_kWh": 20,
1317
- "max_charge_kWh": 60,
1318
- "allow_grid_charging": true,
1319
- "grid_charging_threshold": 30
1320
- }
1321
- }
1322
- \`\`\`
1323
-
1324
- ### Workflow 2: Cheap Night Charging
1325
-
1326
- \`\`\`
1327
- 1. User has variable electricity pricing
1328
- 2. Wants to charge only when price < $0.10/kWh
1329
-
1330
- Setup:
1331
- POST /resources/charge-points/v2.0/{id}/personal-smart-charging-preferences
1332
- {
1333
- "type": "nordpool",
1334
- "enabled": true,
1335
- "preferences": {
1336
- "max_price_threshold": 0.10,
1337
- "price_area": "SE3",
1338
- "target_charge_kWh": 40
1339
- }
1340
- }
1341
- \`\`\`
1342
-
1343
- ### Workflow 3: Fixed Schedule for Apartment
1344
-
1345
- \`\`\`
1346
- 1. User has fixed off-peak hours (midnight - 6 AM)
1347
- 2. Wants to fully charge during this window
1348
-
1349
- Setup:
1350
- POST /resources/charge-points/v2.0/{id}/personal-smart-charging-preferences
1351
- {
1352
- "type": "user_controlled_schedule",
1353
- "enabled": true,
1354
- "preferences": {
1355
- "start_time": "00:00:00",
1356
- "end_time": "06:00:00",
1357
- "target_charge": null
1358
- }
1359
- }
1360
- \`\`\`
1361
-
1362
- ## Integration with Charging Sessions
1363
-
1364
- Smart charging preferences apply automatically to charging sessions:
1365
-
1366
- 1. User plugs in EV
1367
- 2. Session starts (or waits for reservation)
1368
- 3. Smart charging controller manages charging based on active preferences
1369
- 4. Charging occurs only during optimal times/conditions
1370
- 5. Session completes when target reached or window closes
1371
-
1372
- **No additional API calls needed** - preferences persist until changed.
1373
-
1374
- ## Troubleshooting
1375
-
1376
- **Smart charging not working:**
1377
- - Verify \`enabled: true\`
1378
- - Check charge point supports smart charging (v2.0 endpoints)
1379
- - Ensure preferences are valid (time format, thresholds)
1380
- - Verify external integrations are configured (Octopus, Nordpool)
1381
-
1382
- **Charging outside schedule:**
1383
- - Check for overriding factors (low battery emergency charge)
1384
- - Verify no other smart mode is active
1385
- - Check charge point firmware version`,
1386
- },
1387
- },
1388
- ],
1389
- };
1390
- }
1391
- default:
1392
- throw new Error(`Unknown prompt: ${promptName}`);
1393
- }
1394
- });
1395
- return server;
1396
- }
12
+ import { createServer } from './factory.js';
13
+ import { loadEndpoints, loadResponseSchemas } from './loader.js';
1397
14
  /**
1398
15
  * Main server startup
1399
16
  */
@@ -1403,6 +20,9 @@ async function main() {
1403
20
  const endpoints = loadEndpoints();
1404
21
  console.log(`Loaded ${endpoints.endpoints.length} endpoints from ${endpoints.info.title} v${endpoints.info.version}`);
1405
22
  console.log(`Optimization: ${endpoints.stats.reductionPercent.toFixed(2)}% size reduction`);
23
+ console.log('Loading response schemas...');
24
+ const responseSchemas = loadResponseSchemas();
25
+ console.log(`Loaded ${Object.keys(responseSchemas).length} response schemas (${endpoints.stats.responseSchemas?.withSchemas || 0} with schemas)`);
1406
26
  // Create Express app
1407
27
  const app = express();
1408
28
  app.use(express.json());
@@ -1451,7 +71,11 @@ async function main() {
1451
71
  const hostname = `${protocol}://${hostnameWithoutSuffix}`;
1452
72
  console.log(`[MCP Request] Target API: ${hostname}, Method: ${req.body?.method || 'unknown'}`);
1453
73
  // Create server instance with the bearer token and hostname
1454
- const server = createServer(endpoints, bearerToken, hostname);
74
+ const server = createServer(endpoints, responseSchemas, {
75
+ hostname,
76
+ bearerToken,
77
+ emulateResourcesViaTools: false, // Legacy server uses native resources
78
+ });
1455
79
  try {
1456
80
  // Create stateless transport (no session management)
1457
81
  const transport = new StreamableHTTPServerTransport({