@bedrockio/ai 0.4.4 → 0.5.1

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/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 0.5.1
2
+
3
+ - Added basic api key authorization.
4
+
5
+ ## 0.5.0
6
+
7
+ - Added `McpServer` and basic handling of using it in tools.
8
+
1
9
  ## 0.4.3
2
10
 
3
11
  - Moved to files whitelist.
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const SUPPORTED_VERSIONS = ['2025-03-26', '2025-06-18'];
4
+ const ERROR_INVALID_SESSION = -32000;
5
+ const ERROR_UNAUTHORIZED = -32001;
6
+ const ERROR_METHOD_NOT_FOUND = -32601;
7
+ const ERROR_INVALID_REQUEST = -32600;
8
+ const ERROR_INVALID_PARAMS = -32602;
9
+ class McpServer {
10
+ constructor(options = {}) {
11
+ this.validateOptions(options);
12
+ this.options = options;
13
+ }
14
+ async handleRequest(ctx) {
15
+ const { body } = ctx.request;
16
+ const { method } = body;
17
+ if (method === 'notifications/initialized') {
18
+ return;
19
+ }
20
+ this.assertValidTransport(body);
21
+ await this.assertAuthorization(ctx);
22
+ let result;
23
+ if (method === 'initialize') {
24
+ this.setNewSessionId(ctx);
25
+ result = this.initialize(body);
26
+ }
27
+ this.assertValidSession(ctx);
28
+ if (method === 'ping') {
29
+ result = this.ping();
30
+ }
31
+ else if (method === 'tools/list') {
32
+ result = this.listTools();
33
+ }
34
+ else if (method === 'tools/call') {
35
+ result = await this.callTool(body, ctx);
36
+ }
37
+ else if (!result) {
38
+ result = this.unknownMethod();
39
+ }
40
+ return {
41
+ jsonrpc: '2.0',
42
+ id: body.id,
43
+ ...result,
44
+ };
45
+ }
46
+ // Validation
47
+ validateOptions(options) {
48
+ const { name, version } = options;
49
+ if (!name) {
50
+ throw new Error(`"name" required.`);
51
+ }
52
+ else if (!version) {
53
+ throw new Error(`"version" required.`);
54
+ }
55
+ }
56
+ assertValidTransport(body) {
57
+ const { id, method, jsonrpc } = body;
58
+ if (id == null || !method || !jsonrpc) {
59
+ throw new InvalidRequestError();
60
+ }
61
+ }
62
+ async assertAuthorization(ctx) {
63
+ const { apiKeyRequired, isValidApiKey } = this.options;
64
+ const bearer = this.getBearer(ctx);
65
+ if (apiKeyRequired || bearer) {
66
+ const isValid = await isValidApiKey(bearer, ctx);
67
+ if (!isValid) {
68
+ throw new UnauthorizedError();
69
+ }
70
+ }
71
+ }
72
+ assertValidSession(ctx) {
73
+ if (!this.hasValidSessionId(ctx)) {
74
+ throw new InvalidSessionError();
75
+ }
76
+ ctx.set('content-type', 'application/json; charset=utf-8');
77
+ }
78
+ // Calls
79
+ initialize(body) {
80
+ const { protocolVersion } = body.params;
81
+ if (!this.isSupportedVersion(protocolVersion)) {
82
+ return this.invalidVersion(body);
83
+ }
84
+ const { name, version } = this.options;
85
+ return {
86
+ result: {
87
+ protocolVersion,
88
+ serverInfo: {
89
+ name,
90
+ version,
91
+ },
92
+ capabilities: {
93
+ tools: {},
94
+ },
95
+ },
96
+ };
97
+ }
98
+ ping() {
99
+ return {
100
+ result: {},
101
+ };
102
+ }
103
+ listTools() {
104
+ const { tools = [] } = this.options;
105
+ return {
106
+ result: {
107
+ tools: tools.map((tool) => {
108
+ const { handler, ...rest } = tool;
109
+ return rest;
110
+ }),
111
+ },
112
+ };
113
+ }
114
+ async callTool(body, ctx) {
115
+ const { name, arguments: args } = body.params;
116
+ if (this.hasTool(name)) {
117
+ return await this.callValidTool(name, args, ctx);
118
+ }
119
+ else {
120
+ return this.invalidToolCall(name);
121
+ }
122
+ }
123
+ async callValidTool(name, args, ctx) {
124
+ const tool = this.getTool(name);
125
+ const result = await tool.handler(args, ctx);
126
+ return {
127
+ result: {
128
+ content: [
129
+ {
130
+ type: 'text',
131
+ text: JSON.stringify(result),
132
+ },
133
+ ],
134
+ },
135
+ };
136
+ }
137
+ invalidToolCall(name) {
138
+ return {
139
+ error: {
140
+ code: ERROR_INVALID_PARAMS,
141
+ message: `Unknown tool: ${name}`,
142
+ },
143
+ };
144
+ }
145
+ // Error calls
146
+ unknownMethod() {
147
+ return {
148
+ error: {
149
+ code: ERROR_METHOD_NOT_FOUND,
150
+ message: 'Method not found',
151
+ },
152
+ };
153
+ }
154
+ invalidVersion(request) {
155
+ return {
156
+ error: {
157
+ code: ERROR_INVALID_PARAMS,
158
+ message: 'Unsupported protocol version',
159
+ data: {
160
+ requested: request.params.protocolVersion,
161
+ supported: SUPPORTED_VERSIONS,
162
+ },
163
+ },
164
+ };
165
+ }
166
+ // Tool helpers
167
+ getTool(name) {
168
+ const { tools = [] } = this.options;
169
+ return tools.find((tool) => {
170
+ return tool.name === name;
171
+ });
172
+ }
173
+ hasTool(name) {
174
+ return !!this.getTool(name);
175
+ }
176
+ // Session helpers
177
+ getSessionId(ctx) {
178
+ return this.options.getSessionId?.(ctx);
179
+ }
180
+ hasValidSessionId(ctx) {
181
+ const id = ctx.get('mcp-session-id');
182
+ if (id) {
183
+ return id === this.getSessionId(ctx);
184
+ }
185
+ else {
186
+ return true;
187
+ }
188
+ }
189
+ setNewSessionId(ctx) {
190
+ const sessionId = this.getSessionId(ctx);
191
+ if (sessionId) {
192
+ ctx.set('mcp-session-id', sessionId);
193
+ }
194
+ }
195
+ // Authorization helpers
196
+ getBearer(ctx) {
197
+ const authorization = ctx.get('authorization') || '';
198
+ return authorization.match(/Bearer (.+)/)?.[1];
199
+ }
200
+ // Version helpers
201
+ isSupportedVersion(version) {
202
+ return SUPPORTED_VERSIONS.includes(version);
203
+ }
204
+ }
205
+ exports.default = McpServer;
206
+ class InvalidRequestError extends Error {
207
+ status = 400;
208
+ toJSON() {
209
+ return {
210
+ jsonrpc: '2.0',
211
+ error: {
212
+ code: ERROR_INVALID_REQUEST,
213
+ message: 'Invalid Request',
214
+ },
215
+ };
216
+ }
217
+ }
218
+ class UnauthorizedError extends Error {
219
+ status = 401;
220
+ toJSON() {
221
+ return {
222
+ jsonrpc: '2.0',
223
+ error: {
224
+ code: ERROR_UNAUTHORIZED,
225
+ message: 'Unauthorized',
226
+ },
227
+ };
228
+ }
229
+ }
230
+ class InvalidSessionError extends Error {
231
+ status = 404;
232
+ toJSON() {
233
+ return {
234
+ jsonrpc: '2.0',
235
+ error: {
236
+ code: ERROR_INVALID_SESSION,
237
+ message: 'Invalid Session',
238
+ },
239
+ };
240
+ }
241
+ }
package/dist/cjs/index.js CHANGED
@@ -1,10 +1,16 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.McpServer = void 0;
3
7
  exports.createClient = createClient;
4
8
  const anthropic_js_1 = require("./anthropic.js");
5
9
  const google_js_1 = require("./google.js");
6
10
  const openai_js_1 = require("./openai.js");
7
11
  const xai_js_1 = require("./xai.js");
12
+ var McpServer_js_1 = require("./McpServer.js");
13
+ Object.defineProperty(exports, "McpServer", { enumerable: true, get: function () { return __importDefault(McpServer_js_1).default; } });
8
14
  function createClient(options = {}) {
9
15
  const { platform } = options;
10
16
  if (!platform) {
@@ -49,7 +49,23 @@ class OpenAiClient extends BaseClient_js_1.default {
49
49
  return response.output_text;
50
50
  }
51
51
  getStructuredResponse(response) {
52
- return JSON.parse(response.output_text);
52
+ // Note here that certain cases (tool usage etc)
53
+ // can result in multiple outputs with identical
54
+ // content. These outputs are simply concatenated
55
+ // together in output_text which will result in a
56
+ // JSON parse error, so take the LAST output_text
57
+ // entry assuming that this is its "final answer".
58
+ const outputs = response.output
59
+ .filter((item) => {
60
+ return item.type === 'message';
61
+ })
62
+ .flatMap((item) => {
63
+ return item.content.filter((c) => {
64
+ return c.type === 'output_text';
65
+ });
66
+ });
67
+ const last = outputs[outputs.length - 1];
68
+ return JSON.parse(last.text);
53
69
  }
54
70
  getMessagesResponse(input, response) {
55
71
  return {
@@ -0,0 +1,238 @@
1
+ const SUPPORTED_VERSIONS = ['2025-03-26', '2025-06-18'];
2
+ const ERROR_INVALID_SESSION = -32000;
3
+ const ERROR_UNAUTHORIZED = -32001;
4
+ const ERROR_METHOD_NOT_FOUND = -32601;
5
+ const ERROR_INVALID_REQUEST = -32600;
6
+ const ERROR_INVALID_PARAMS = -32602;
7
+ export default class McpServer {
8
+ constructor(options = {}) {
9
+ this.validateOptions(options);
10
+ this.options = options;
11
+ }
12
+ async handleRequest(ctx) {
13
+ const { body } = ctx.request;
14
+ const { method } = body;
15
+ if (method === 'notifications/initialized') {
16
+ return;
17
+ }
18
+ this.assertValidTransport(body);
19
+ await this.assertAuthorization(ctx);
20
+ let result;
21
+ if (method === 'initialize') {
22
+ this.setNewSessionId(ctx);
23
+ result = this.initialize(body);
24
+ }
25
+ this.assertValidSession(ctx);
26
+ if (method === 'ping') {
27
+ result = this.ping();
28
+ }
29
+ else if (method === 'tools/list') {
30
+ result = this.listTools();
31
+ }
32
+ else if (method === 'tools/call') {
33
+ result = await this.callTool(body, ctx);
34
+ }
35
+ else if (!result) {
36
+ result = this.unknownMethod();
37
+ }
38
+ return {
39
+ jsonrpc: '2.0',
40
+ id: body.id,
41
+ ...result,
42
+ };
43
+ }
44
+ // Validation
45
+ validateOptions(options) {
46
+ const { name, version } = options;
47
+ if (!name) {
48
+ throw new Error(`"name" required.`);
49
+ }
50
+ else if (!version) {
51
+ throw new Error(`"version" required.`);
52
+ }
53
+ }
54
+ assertValidTransport(body) {
55
+ const { id, method, jsonrpc } = body;
56
+ if (id == null || !method || !jsonrpc) {
57
+ throw new InvalidRequestError();
58
+ }
59
+ }
60
+ async assertAuthorization(ctx) {
61
+ const { apiKeyRequired, isValidApiKey } = this.options;
62
+ const bearer = this.getBearer(ctx);
63
+ if (apiKeyRequired || bearer) {
64
+ const isValid = await isValidApiKey(bearer, ctx);
65
+ if (!isValid) {
66
+ throw new UnauthorizedError();
67
+ }
68
+ }
69
+ }
70
+ assertValidSession(ctx) {
71
+ if (!this.hasValidSessionId(ctx)) {
72
+ throw new InvalidSessionError();
73
+ }
74
+ ctx.set('content-type', 'application/json; charset=utf-8');
75
+ }
76
+ // Calls
77
+ initialize(body) {
78
+ const { protocolVersion } = body.params;
79
+ if (!this.isSupportedVersion(protocolVersion)) {
80
+ return this.invalidVersion(body);
81
+ }
82
+ const { name, version } = this.options;
83
+ return {
84
+ result: {
85
+ protocolVersion,
86
+ serverInfo: {
87
+ name,
88
+ version,
89
+ },
90
+ capabilities: {
91
+ tools: {},
92
+ },
93
+ },
94
+ };
95
+ }
96
+ ping() {
97
+ return {
98
+ result: {},
99
+ };
100
+ }
101
+ listTools() {
102
+ const { tools = [] } = this.options;
103
+ return {
104
+ result: {
105
+ tools: tools.map((tool) => {
106
+ const { handler, ...rest } = tool;
107
+ return rest;
108
+ }),
109
+ },
110
+ };
111
+ }
112
+ async callTool(body, ctx) {
113
+ const { name, arguments: args } = body.params;
114
+ if (this.hasTool(name)) {
115
+ return await this.callValidTool(name, args, ctx);
116
+ }
117
+ else {
118
+ return this.invalidToolCall(name);
119
+ }
120
+ }
121
+ async callValidTool(name, args, ctx) {
122
+ const tool = this.getTool(name);
123
+ const result = await tool.handler(args, ctx);
124
+ return {
125
+ result: {
126
+ content: [
127
+ {
128
+ type: 'text',
129
+ text: JSON.stringify(result),
130
+ },
131
+ ],
132
+ },
133
+ };
134
+ }
135
+ invalidToolCall(name) {
136
+ return {
137
+ error: {
138
+ code: ERROR_INVALID_PARAMS,
139
+ message: `Unknown tool: ${name}`,
140
+ },
141
+ };
142
+ }
143
+ // Error calls
144
+ unknownMethod() {
145
+ return {
146
+ error: {
147
+ code: ERROR_METHOD_NOT_FOUND,
148
+ message: 'Method not found',
149
+ },
150
+ };
151
+ }
152
+ invalidVersion(request) {
153
+ return {
154
+ error: {
155
+ code: ERROR_INVALID_PARAMS,
156
+ message: 'Unsupported protocol version',
157
+ data: {
158
+ requested: request.params.protocolVersion,
159
+ supported: SUPPORTED_VERSIONS,
160
+ },
161
+ },
162
+ };
163
+ }
164
+ // Tool helpers
165
+ getTool(name) {
166
+ const { tools = [] } = this.options;
167
+ return tools.find((tool) => {
168
+ return tool.name === name;
169
+ });
170
+ }
171
+ hasTool(name) {
172
+ return !!this.getTool(name);
173
+ }
174
+ // Session helpers
175
+ getSessionId(ctx) {
176
+ return this.options.getSessionId?.(ctx);
177
+ }
178
+ hasValidSessionId(ctx) {
179
+ const id = ctx.get('mcp-session-id');
180
+ if (id) {
181
+ return id === this.getSessionId(ctx);
182
+ }
183
+ else {
184
+ return true;
185
+ }
186
+ }
187
+ setNewSessionId(ctx) {
188
+ const sessionId = this.getSessionId(ctx);
189
+ if (sessionId) {
190
+ ctx.set('mcp-session-id', sessionId);
191
+ }
192
+ }
193
+ // Authorization helpers
194
+ getBearer(ctx) {
195
+ const authorization = ctx.get('authorization') || '';
196
+ return authorization.match(/Bearer (.+)/)?.[1];
197
+ }
198
+ // Version helpers
199
+ isSupportedVersion(version) {
200
+ return SUPPORTED_VERSIONS.includes(version);
201
+ }
202
+ }
203
+ class InvalidRequestError extends Error {
204
+ status = 400;
205
+ toJSON() {
206
+ return {
207
+ jsonrpc: '2.0',
208
+ error: {
209
+ code: ERROR_INVALID_REQUEST,
210
+ message: 'Invalid Request',
211
+ },
212
+ };
213
+ }
214
+ }
215
+ class UnauthorizedError extends Error {
216
+ status = 401;
217
+ toJSON() {
218
+ return {
219
+ jsonrpc: '2.0',
220
+ error: {
221
+ code: ERROR_UNAUTHORIZED,
222
+ message: 'Unauthorized',
223
+ },
224
+ };
225
+ }
226
+ }
227
+ class InvalidSessionError extends Error {
228
+ status = 404;
229
+ toJSON() {
230
+ return {
231
+ jsonrpc: '2.0',
232
+ error: {
233
+ code: ERROR_INVALID_SESSION,
234
+ message: 'Invalid Session',
235
+ },
236
+ };
237
+ }
238
+ }
package/dist/esm/index.js CHANGED
@@ -2,6 +2,7 @@ import { AnthropicClient } from './anthropic.js';
2
2
  import { GoogleClient } from './google.js';
3
3
  import { OpenAiClient } from './openai.js';
4
4
  import { XAiClient } from './xai.js';
5
+ export { default as McpServer } from './McpServer.js';
5
6
  export function createClient(options = {}) {
6
7
  const { platform } = options;
7
8
  if (!platform) {
@@ -43,7 +43,23 @@ export class OpenAiClient extends BaseClient {
43
43
  return response.output_text;
44
44
  }
45
45
  getStructuredResponse(response) {
46
- return JSON.parse(response.output_text);
46
+ // Note here that certain cases (tool usage etc)
47
+ // can result in multiple outputs with identical
48
+ // content. These outputs are simply concatenated
49
+ // together in output_text which will result in a
50
+ // JSON parse error, so take the LAST output_text
51
+ // entry assuming that this is its "final answer".
52
+ const outputs = response.output
53
+ .filter((item) => {
54
+ return item.type === 'message';
55
+ })
56
+ .flatMap((item) => {
57
+ return item.content.filter((c) => {
58
+ return c.type === 'output_text';
59
+ });
60
+ });
61
+ const last = outputs[outputs.length - 1];
62
+ return JSON.parse(last.text);
47
63
  }
48
64
  getMessagesResponse(input, response) {
49
65
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrockio/ai",
3
- "version": "0.4.4",
3
+ "version": "0.5.1",
4
4
  "description": "Bedrock wrapper for common AI chatbots.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -45,7 +45,7 @@
45
45
  "devDependencies": {
46
46
  "@bedrockio/eslint-plugin": "^1.2.2",
47
47
  "@bedrockio/prettier-config": "^1.0.2",
48
- "@bedrockio/yada": "^1.8.0",
48
+ "@bedrockio/yada": "^1.8.3",
49
49
  "eslint": "^9.36.0",
50
50
  "tsc-alias": "^1.8.16",
51
51
  "typescript": "^5.9.3",
@@ -0,0 +1,100 @@
1
+ export default class McpServer {
2
+ constructor(options?: {});
3
+ options: {};
4
+ handleRequest(ctx: any): Promise<{
5
+ result: {};
6
+ jsonrpc: string;
7
+ id: any;
8
+ } | {
9
+ error: {
10
+ code: number;
11
+ message: string;
12
+ };
13
+ jsonrpc: string;
14
+ id: any;
15
+ }>;
16
+ validateOptions(options: any): void;
17
+ assertValidTransport(body: any): void;
18
+ assertAuthorization(ctx: any): Promise<void>;
19
+ assertValidSession(ctx: any): void;
20
+ initialize(body: any): {
21
+ error: {
22
+ code: number;
23
+ message: string;
24
+ data: {
25
+ requested: any;
26
+ supported: string[];
27
+ };
28
+ };
29
+ } | {
30
+ result: {
31
+ protocolVersion: any;
32
+ serverInfo: {
33
+ name: any;
34
+ version: any;
35
+ };
36
+ capabilities: {
37
+ tools: {};
38
+ };
39
+ };
40
+ };
41
+ ping(): {
42
+ result: {};
43
+ };
44
+ listTools(): {
45
+ result: {
46
+ tools: any;
47
+ };
48
+ };
49
+ callTool(body: any, ctx: any, ...args: any[]): Promise<{
50
+ result: {
51
+ content: {
52
+ type: string;
53
+ text: string;
54
+ }[];
55
+ };
56
+ } | {
57
+ error: {
58
+ code: number;
59
+ message: string;
60
+ };
61
+ }>;
62
+ callValidTool(name: any, args: any, ctx: any): Promise<{
63
+ result: {
64
+ content: {
65
+ type: string;
66
+ text: string;
67
+ }[];
68
+ };
69
+ }>;
70
+ invalidToolCall(name: any): {
71
+ error: {
72
+ code: number;
73
+ message: string;
74
+ };
75
+ };
76
+ unknownMethod(): {
77
+ error: {
78
+ code: number;
79
+ message: string;
80
+ };
81
+ };
82
+ invalidVersion(request: any): {
83
+ error: {
84
+ code: number;
85
+ message: string;
86
+ data: {
87
+ requested: any;
88
+ supported: string[];
89
+ };
90
+ };
91
+ };
92
+ getTool(name: any): any;
93
+ hasTool(name: any): boolean;
94
+ getSessionId(ctx: any): any;
95
+ hasValidSessionId(ctx: any): boolean;
96
+ setNewSessionId(ctx: any): void;
97
+ getBearer(ctx: any): any;
98
+ isSupportedVersion(version: any): boolean;
99
+ }
100
+ //# sourceMappingURL=McpServer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"McpServer.d.ts","sourceRoot":"","sources":["../src/McpServer.js"],"names":[],"mappings":"AAQA;IACE,0BAGC;IADC,YAAsB;IAGxB;;;;;;;;;;;OAmCC;IAID,oCAOC;IAED,sCAKC;IAED,6CAUC;IAED,mCAKC;IAID;;;;;;;;;;;;;;;;;;;;MAmBC;IAED;;MAIC;IAED;;;;MAUC;IAED;;;;;;;;;;;;OAOC;IAED;;;;;;;OAaC;IAED;;;;;MAOC;IAID;;;;;MAOC;IAED;;;;;;;;;MAWC;IAID,wBAKC;IAED,4BAEC;IAID,4BAEC;IAED,qCAOC;IAED,gCAKC;IAID,yBAGC;IAID,0CAEC;CACF"}
package/types/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export function createClient(options?: {}): AnthropicClient | GoogleClient | OpenAiClient;
2
+ export { default as McpServer } from "./McpServer.js";
2
3
  import { AnthropicClient } from './anthropic.js';
3
4
  import { GoogleClient } from './google.js';
4
5
  import { OpenAiClient } from './openai.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAKA,0FAkBC;gCAvB+B,gBAAgB;6BACnB,aAAa;6BACb,aAAa"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAMA,0FAkBC;;gCAxB+B,gBAAgB;6BACnB,aAAa;6BACb,aAAa"}
@@ -1 +1 @@
1
- {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../src/openai.js"],"names":[],"mappings":"AAIA;IACE,6BAAoC;IAIlC,eAAiC;IAGnC;;;OAGG;IACH,4BAGC;IAED;;yFA+BC;IAED;;yFAKC;IAED,oCAEC;IAMD;;;MAaC;IAID;;;;;;;;;;MAmBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;MAyBC;CACF;uBAnIsB,iBAAiB;mBAFrB,QAAQ"}
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../src/openai.js"],"names":[],"mappings":"AAIA;IACE,6BAAoC;IAIlC,eAAiC;IAGnC;;;OAGG;IACH,4BAGC;IAED;;yFA+BC;IAED;;yFAKC;IAED,oCAEC;IAwBD;;;MAaC;IAID;;;;;;;;;;MAmBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;MAyBC;CACF;uBArJsB,iBAAiB;mBAFrB,QAAQ"}