@bedrockio/ai 0.4.4 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.5.0
2
+
3
+ - Added `McpServer` and basic handling of using it in tools.
4
+
1
5
  ## 0.4.3
2
6
 
3
7
  - Moved to files whitelist.
@@ -0,0 +1,211 @@
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_METHOD_NOT_FOUND = -32601;
6
+ const ERROR_INVALID_REQUEST = -32600;
7
+ const ERROR_INVALID_PARAMS = -32602;
8
+ class McpServer {
9
+ constructor(options = {}) {
10
+ this.validateOptions(options);
11
+ this.options = options;
12
+ }
13
+ async handleRequest(ctx) {
14
+ const { body } = ctx.request;
15
+ const { method } = body;
16
+ if (method === 'notifications/initialized') {
17
+ return;
18
+ }
19
+ this.assertValidTransport(body);
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
+ assertValidSession(ctx) {
61
+ if (!this.hasValidSessionId(ctx)) {
62
+ throw new InvalidSessionError();
63
+ }
64
+ }
65
+ // Calls
66
+ initialize(body) {
67
+ const { protocolVersion } = body.params;
68
+ if (!this.isSupportedVersion(protocolVersion)) {
69
+ return this.invalidVersion(body);
70
+ }
71
+ const { name, version } = this.options;
72
+ return {
73
+ result: {
74
+ protocolVersion,
75
+ serverInfo: {
76
+ name,
77
+ version,
78
+ },
79
+ capabilities: {
80
+ tools: {},
81
+ },
82
+ },
83
+ };
84
+ }
85
+ ping() {
86
+ return {
87
+ result: {},
88
+ };
89
+ }
90
+ listTools() {
91
+ const { tools } = this.options;
92
+ return {
93
+ result: {
94
+ tools: tools.map((tool) => {
95
+ const { handler, ...rest } = tool;
96
+ return rest;
97
+ }),
98
+ },
99
+ };
100
+ }
101
+ async callTool(body, ctx) {
102
+ const { name, arguments: args } = body.params;
103
+ if (this.hasTool(name)) {
104
+ return await this.callValidTool(name, args, ctx);
105
+ }
106
+ else {
107
+ return this.invalidToolCall(name);
108
+ }
109
+ }
110
+ async callValidTool(name, args, ctx) {
111
+ const tool = this.getTool(name);
112
+ const result = await tool.handler(args, ctx);
113
+ return {
114
+ result: {
115
+ content: [
116
+ {
117
+ type: 'text',
118
+ text: JSON.stringify(result),
119
+ },
120
+ ],
121
+ },
122
+ };
123
+ }
124
+ invalidToolCall(name) {
125
+ return {
126
+ error: {
127
+ code: ERROR_INVALID_PARAMS,
128
+ message: `Unknown tool: ${name}`,
129
+ },
130
+ };
131
+ }
132
+ // Error calls
133
+ unknownMethod() {
134
+ return {
135
+ error: {
136
+ code: ERROR_METHOD_NOT_FOUND,
137
+ message: 'Method not found',
138
+ },
139
+ };
140
+ }
141
+ invalidVersion(request) {
142
+ return {
143
+ error: {
144
+ code: ERROR_INVALID_PARAMS,
145
+ message: 'Unsupported protocol version',
146
+ data: {
147
+ requested: request.params.protocolVersion,
148
+ supported: SUPPORTED_VERSIONS,
149
+ },
150
+ },
151
+ };
152
+ }
153
+ // Tool helpers
154
+ getTool(name) {
155
+ const { tools = [] } = this.options;
156
+ return tools.find((tool) => {
157
+ return tool.name === name;
158
+ });
159
+ }
160
+ hasTool(name) {
161
+ return !!this.getTool(name);
162
+ }
163
+ // Session helpers
164
+ getSessionId(ctx) {
165
+ return this.options.getSessionId?.(ctx);
166
+ }
167
+ hasValidSessionId(ctx) {
168
+ const id = ctx.get('mcp-session-id');
169
+ if (id) {
170
+ return id === this.getSessionId(ctx);
171
+ }
172
+ else {
173
+ return true;
174
+ }
175
+ }
176
+ setNewSessionId(ctx) {
177
+ const sessionId = this.getSessionId(ctx);
178
+ if (sessionId) {
179
+ ctx.set('mcp-session-id', sessionId);
180
+ }
181
+ }
182
+ // Version helpers
183
+ isSupportedVersion(version) {
184
+ return SUPPORTED_VERSIONS.includes(version);
185
+ }
186
+ }
187
+ exports.default = McpServer;
188
+ class InvalidRequestError extends Error {
189
+ status = 400;
190
+ toJSON() {
191
+ return {
192
+ jsonrpc: '2.0',
193
+ error: {
194
+ code: ERROR_INVALID_REQUEST,
195
+ message: 'Invalid Request',
196
+ },
197
+ };
198
+ }
199
+ }
200
+ class InvalidSessionError extends Error {
201
+ status = 404;
202
+ toJSON() {
203
+ return {
204
+ jsonrpc: '2.0',
205
+ error: {
206
+ code: ERROR_INVALID_SESSION,
207
+ message: 'Invalid Session',
208
+ },
209
+ };
210
+ }
211
+ }
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,208 @@
1
+ const SUPPORTED_VERSIONS = ['2025-03-26', '2025-06-18'];
2
+ const ERROR_INVALID_SESSION = -32000;
3
+ const ERROR_METHOD_NOT_FOUND = -32601;
4
+ const ERROR_INVALID_REQUEST = -32600;
5
+ const ERROR_INVALID_PARAMS = -32602;
6
+ export default class McpServer {
7
+ constructor(options = {}) {
8
+ this.validateOptions(options);
9
+ this.options = options;
10
+ }
11
+ async handleRequest(ctx) {
12
+ const { body } = ctx.request;
13
+ const { method } = body;
14
+ if (method === 'notifications/initialized') {
15
+ return;
16
+ }
17
+ this.assertValidTransport(body);
18
+ let result;
19
+ if (method === 'initialize') {
20
+ this.setNewSessionId(ctx);
21
+ result = this.initialize(body);
22
+ }
23
+ this.assertValidSession(ctx);
24
+ if (method === 'ping') {
25
+ result = this.ping();
26
+ }
27
+ else if (method === 'tools/list') {
28
+ result = this.listTools();
29
+ }
30
+ else if (method === 'tools/call') {
31
+ result = await this.callTool(body, ctx);
32
+ }
33
+ else if (!result) {
34
+ result = this.unknownMethod();
35
+ }
36
+ return {
37
+ jsonrpc: '2.0',
38
+ id: body.id,
39
+ ...result,
40
+ };
41
+ }
42
+ // Validation
43
+ validateOptions(options) {
44
+ const { name, version } = options;
45
+ if (!name) {
46
+ throw new Error(`"name" required.`);
47
+ }
48
+ else if (!version) {
49
+ throw new Error(`"version" required.`);
50
+ }
51
+ }
52
+ assertValidTransport(body) {
53
+ const { id, method, jsonrpc } = body;
54
+ if (id == null || !method || !jsonrpc) {
55
+ throw new InvalidRequestError();
56
+ }
57
+ }
58
+ assertValidSession(ctx) {
59
+ if (!this.hasValidSessionId(ctx)) {
60
+ throw new InvalidSessionError();
61
+ }
62
+ }
63
+ // Calls
64
+ initialize(body) {
65
+ const { protocolVersion } = body.params;
66
+ if (!this.isSupportedVersion(protocolVersion)) {
67
+ return this.invalidVersion(body);
68
+ }
69
+ const { name, version } = this.options;
70
+ return {
71
+ result: {
72
+ protocolVersion,
73
+ serverInfo: {
74
+ name,
75
+ version,
76
+ },
77
+ capabilities: {
78
+ tools: {},
79
+ },
80
+ },
81
+ };
82
+ }
83
+ ping() {
84
+ return {
85
+ result: {},
86
+ };
87
+ }
88
+ listTools() {
89
+ const { tools } = this.options;
90
+ return {
91
+ result: {
92
+ tools: tools.map((tool) => {
93
+ const { handler, ...rest } = tool;
94
+ return rest;
95
+ }),
96
+ },
97
+ };
98
+ }
99
+ async callTool(body, ctx) {
100
+ const { name, arguments: args } = body.params;
101
+ if (this.hasTool(name)) {
102
+ return await this.callValidTool(name, args, ctx);
103
+ }
104
+ else {
105
+ return this.invalidToolCall(name);
106
+ }
107
+ }
108
+ async callValidTool(name, args, ctx) {
109
+ const tool = this.getTool(name);
110
+ const result = await tool.handler(args, ctx);
111
+ return {
112
+ result: {
113
+ content: [
114
+ {
115
+ type: 'text',
116
+ text: JSON.stringify(result),
117
+ },
118
+ ],
119
+ },
120
+ };
121
+ }
122
+ invalidToolCall(name) {
123
+ return {
124
+ error: {
125
+ code: ERROR_INVALID_PARAMS,
126
+ message: `Unknown tool: ${name}`,
127
+ },
128
+ };
129
+ }
130
+ // Error calls
131
+ unknownMethod() {
132
+ return {
133
+ error: {
134
+ code: ERROR_METHOD_NOT_FOUND,
135
+ message: 'Method not found',
136
+ },
137
+ };
138
+ }
139
+ invalidVersion(request) {
140
+ return {
141
+ error: {
142
+ code: ERROR_INVALID_PARAMS,
143
+ message: 'Unsupported protocol version',
144
+ data: {
145
+ requested: request.params.protocolVersion,
146
+ supported: SUPPORTED_VERSIONS,
147
+ },
148
+ },
149
+ };
150
+ }
151
+ // Tool helpers
152
+ getTool(name) {
153
+ const { tools = [] } = this.options;
154
+ return tools.find((tool) => {
155
+ return tool.name === name;
156
+ });
157
+ }
158
+ hasTool(name) {
159
+ return !!this.getTool(name);
160
+ }
161
+ // Session helpers
162
+ getSessionId(ctx) {
163
+ return this.options.getSessionId?.(ctx);
164
+ }
165
+ hasValidSessionId(ctx) {
166
+ const id = ctx.get('mcp-session-id');
167
+ if (id) {
168
+ return id === this.getSessionId(ctx);
169
+ }
170
+ else {
171
+ return true;
172
+ }
173
+ }
174
+ setNewSessionId(ctx) {
175
+ const sessionId = this.getSessionId(ctx);
176
+ if (sessionId) {
177
+ ctx.set('mcp-session-id', sessionId);
178
+ }
179
+ }
180
+ // Version helpers
181
+ isSupportedVersion(version) {
182
+ return SUPPORTED_VERSIONS.includes(version);
183
+ }
184
+ }
185
+ class InvalidRequestError extends Error {
186
+ status = 400;
187
+ toJSON() {
188
+ return {
189
+ jsonrpc: '2.0',
190
+ error: {
191
+ code: ERROR_INVALID_REQUEST,
192
+ message: 'Invalid Request',
193
+ },
194
+ };
195
+ }
196
+ }
197
+ class InvalidSessionError extends Error {
198
+ status = 404;
199
+ toJSON() {
200
+ return {
201
+ jsonrpc: '2.0',
202
+ error: {
203
+ code: ERROR_INVALID_SESSION,
204
+ message: 'Invalid Session',
205
+ },
206
+ };
207
+ }
208
+ }
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.0",
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,98 @@
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
+ assertValidSession(ctx: any): void;
19
+ initialize(body: any): {
20
+ error: {
21
+ code: number;
22
+ message: string;
23
+ data: {
24
+ requested: any;
25
+ supported: string[];
26
+ };
27
+ };
28
+ } | {
29
+ result: {
30
+ protocolVersion: any;
31
+ serverInfo: {
32
+ name: any;
33
+ version: any;
34
+ };
35
+ capabilities: {
36
+ tools: {};
37
+ };
38
+ };
39
+ };
40
+ ping(): {
41
+ result: {};
42
+ };
43
+ listTools(): {
44
+ result: {
45
+ tools: any;
46
+ };
47
+ };
48
+ callTool(body: any, ctx: any, ...args: any[]): Promise<{
49
+ result: {
50
+ content: {
51
+ type: string;
52
+ text: string;
53
+ }[];
54
+ };
55
+ } | {
56
+ error: {
57
+ code: number;
58
+ message: string;
59
+ };
60
+ }>;
61
+ callValidTool(name: any, args: any, ctx: any): Promise<{
62
+ result: {
63
+ content: {
64
+ type: string;
65
+ text: string;
66
+ }[];
67
+ };
68
+ }>;
69
+ invalidToolCall(name: any): {
70
+ error: {
71
+ code: number;
72
+ message: string;
73
+ };
74
+ };
75
+ unknownMethod(): {
76
+ error: {
77
+ code: number;
78
+ message: string;
79
+ };
80
+ };
81
+ invalidVersion(request: any): {
82
+ error: {
83
+ code: number;
84
+ message: string;
85
+ data: {
86
+ requested: any;
87
+ supported: string[];
88
+ };
89
+ };
90
+ };
91
+ getTool(name: any): any;
92
+ hasTool(name: any): boolean;
93
+ getSessionId(ctx: any): any;
94
+ hasValidSessionId(ctx: any): boolean;
95
+ setNewSessionId(ctx: any): void;
96
+ isSupportedVersion(version: any): boolean;
97
+ }
98
+ //# sourceMappingURL=McpServer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"McpServer.d.ts","sourceRoot":"","sources":["../src/McpServer.js"],"names":[],"mappings":"AAOA;IACE,0BAGC;IADC,YAAsB;IAGxB;;;;;;;;;;;OAkCC;IAID,oCAOC;IAED,sCAKC;IAED,mCAIC;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,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"}