smbc-mcp-server 1.0.3 → 1.0.5
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/README.md +2 -22
- package/build/index.js +3 -3
- package/build/openapi-loader.js +8 -6
- package/package.json +3 -4
- package/build/config.js +0 -6
- package/build/error-sanitizer.js +0 -21
- package/build/http-client.js +0 -90
- package/build/openapi-type.yml +0 -11391
- package/build/schema-converter.js +0 -17
- package/build/server.js +0 -52
- package/build/tool-generator.js +0 -29
- package/build/validator.js +0 -41
package/README.md
CHANGED
|
@@ -122,29 +122,9 @@ smbc://operation/orderInquiry
|
|
|
122
122
|
|
|
123
123
|
## 5. API が更新されたとき
|
|
124
124
|
|
|
125
|
-
SMBC が API
|
|
125
|
+
このサーバーは起動時に SMBC 公式サイトから最新の API 仕様を取得します。SMBC が API を更新した場合は、**MCP サーバーを再起動するだけで自動的に最新の仕様が反映**されます。
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
本パッケージが更新されると、`npx smbc-mcp-server` が自動的に最新版を使用します。
|
|
130
|
-
|
|
131
|
-
### B. 最新の YML ファイルを直接指定する
|
|
132
|
-
|
|
133
|
-
[公式サイト](https://docs.smbc-gp.co.jp/mulpay/apis/openapi-type/intro) から最新の `openapi-type.yml` をダウンロードし、環境変数で指定します。
|
|
134
|
-
|
|
135
|
-
```json
|
|
136
|
-
{
|
|
137
|
-
"mcpServers": {
|
|
138
|
-
"smbc-api-docs": {
|
|
139
|
-
"command": "npx",
|
|
140
|
-
"args": ["smbc-mcp-server"],
|
|
141
|
-
"env": {
|
|
142
|
-
"SMBC_OPENAPI_PATH": "/path/to/latest/openapi-type.yml"
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
```
|
|
127
|
+
Cursor であれば設定画面でサーバーを再起動、Claude Desktop であればアプリを再起動してください。
|
|
148
128
|
|
|
149
129
|
---
|
|
150
130
|
|
package/build/index.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
-
import { getOpenapiPath } from "./config.js";
|
|
6
5
|
import { loadOperations } from "./openapi-loader.js";
|
|
7
6
|
import { buildIndex, buildOperationDetail } from "./resource-handler.js";
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const OPENAPI_URL = "https://docs.smbc-gp.co.jp/mulpay/openapi-spec/openapi-type.yml";
|
|
8
|
+
// 1. 公式サイトから OpenAPI 仕様書を取得してエンドポイントを抽出
|
|
9
|
+
const { operations } = await loadOperations(OPENAPI_URL);
|
|
10
10
|
// 2. MCPサーバー初期化
|
|
11
11
|
const server = new Server({ name: "smbc-multipayment", version: "1.0.0" }, { capabilities: { resources: {} } });
|
|
12
12
|
// 3. リソース一覧ハンドラー
|
package/build/openapi-loader.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { readFile } from "fs/promises";
|
|
2
1
|
import { parse } from "yaml";
|
|
3
2
|
function resolveRefs(schema, schemas, resolving = new Set()) {
|
|
4
3
|
if (!schema || typeof schema !== "object")
|
|
@@ -39,13 +38,16 @@ function resolveRefs(schema, schemas, resolving = new Set()) {
|
|
|
39
38
|
}
|
|
40
39
|
return result;
|
|
41
40
|
}
|
|
42
|
-
export async function loadOperations(
|
|
41
|
+
export async function loadOperations(url) {
|
|
43
42
|
let content;
|
|
44
43
|
try {
|
|
45
|
-
|
|
44
|
+
const res = await fetch(url);
|
|
45
|
+
if (!res.ok)
|
|
46
|
+
throw new Error(`HTTP ${res.status}`);
|
|
47
|
+
content = await res.text();
|
|
46
48
|
}
|
|
47
|
-
catch {
|
|
48
|
-
process.stderr.write(`Error: Cannot
|
|
49
|
+
catch (e) {
|
|
50
|
+
process.stderr.write(`Error: Cannot fetch OpenAPI spec: ${url}\n(${e})\n`);
|
|
49
51
|
process.exit(1);
|
|
50
52
|
return { operations: [], defaultServerUrl: "" }; // unreachable, for type narrowing
|
|
51
53
|
}
|
|
@@ -117,6 +119,6 @@ export async function loadOperations(filePath) {
|
|
|
117
119
|
});
|
|
118
120
|
}
|
|
119
121
|
}
|
|
120
|
-
process.stderr.write(`Loaded ${operations.length}
|
|
122
|
+
process.stderr.write(`Loaded ${operations.length} operations from ${url}\n`);
|
|
121
123
|
return { operations, defaultServerUrl };
|
|
122
124
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smbc-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "SMBC マルチペイメントサービス OpenAPI仕様をMCPリソースとして公開するサーバー",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -20,13 +20,12 @@
|
|
|
20
20
|
"build"
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
|
-
"build": "tsc &&
|
|
23
|
+
"build": "tsc && chmod 755 build/index.js",
|
|
24
24
|
"test": "vitest run"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
28
|
-
"yaml": "^2.9.0"
|
|
29
|
-
"zod": "^3.25.76"
|
|
28
|
+
"yaml": "^2.9.0"
|
|
30
29
|
},
|
|
31
30
|
"devDependencies": {
|
|
32
31
|
"@types/node": "^25.9.1",
|
package/build/config.js
DELETED
package/build/error-sanitizer.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export function isApiError(result) {
|
|
2
|
-
return (result.startsWith("Error ") ||
|
|
3
|
-
result === "Network error occurred" ||
|
|
4
|
-
result === "Request timed out");
|
|
5
|
-
}
|
|
6
|
-
export function sanitizeError(e) {
|
|
7
|
-
if (e instanceof Error && e.name === "AbortError") {
|
|
8
|
-
return "Request timed out";
|
|
9
|
-
}
|
|
10
|
-
return "Network error occurred";
|
|
11
|
-
}
|
|
12
|
-
export function sanitizeHttpError(status, body) {
|
|
13
|
-
if (body !== null && typeof body === "object") {
|
|
14
|
-
const obj = body;
|
|
15
|
-
const title = typeof obj["title"] === "string" ? obj["title"] : undefined;
|
|
16
|
-
if (title) {
|
|
17
|
-
return `Error ${status}: ${title}`;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return `Error ${status}: request failed`;
|
|
21
|
-
}
|
package/build/http-client.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { sanitizeError, sanitizeHttpError } from "./error-sanitizer.js";
|
|
2
|
-
const MAX_ATTEMPTS = 3;
|
|
3
|
-
const BASE_DELAY_MS = 1000;
|
|
4
|
-
function sleep(ms) {
|
|
5
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6
|
-
}
|
|
7
|
-
function buildBasicAuth(shopId, shopPassword) {
|
|
8
|
-
const encoded = Buffer.from(`${shopId}:${shopPassword}`).toString("base64");
|
|
9
|
-
return `Basic ${encoded}`;
|
|
10
|
-
}
|
|
11
|
-
export async function callApi(options, config) {
|
|
12
|
-
const { method, serverUrl, path, body, contentType, idempotencyKey, requiresAuth } = options;
|
|
13
|
-
const url = `${serverUrl}${path}`;
|
|
14
|
-
const headers = {
|
|
15
|
-
"Content-Type": contentType,
|
|
16
|
-
};
|
|
17
|
-
// 認証ヘッダー
|
|
18
|
-
if (requiresAuth) {
|
|
19
|
-
if (config.accessToken) {
|
|
20
|
-
headers["Authorization"] = `Bearer ${config.accessToken}`;
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
headers["Authorization"] = buildBasicAuth(config.shopId, config.shopPassword);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
// 冪等キー
|
|
27
|
-
if (idempotencyKey) {
|
|
28
|
-
headers["Idempotency-Key"] = idempotencyKey;
|
|
29
|
-
}
|
|
30
|
-
// ボディ構築
|
|
31
|
-
let requestBody;
|
|
32
|
-
if (contentType === "application/x-www-form-urlencoded") {
|
|
33
|
-
const params = new URLSearchParams();
|
|
34
|
-
for (const [k, v] of Object.entries(body)) {
|
|
35
|
-
if (v != null)
|
|
36
|
-
params.append(k, String(v));
|
|
37
|
-
}
|
|
38
|
-
requestBody = params;
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
requestBody = JSON.stringify(body);
|
|
42
|
-
}
|
|
43
|
-
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
44
|
-
// タイムアウトは各試行でリセット
|
|
45
|
-
const controller = new AbortController();
|
|
46
|
-
const timer = setTimeout(() => controller.abort(), 30_000);
|
|
47
|
-
try {
|
|
48
|
-
const response = await fetch(url, {
|
|
49
|
-
method: method.toUpperCase(),
|
|
50
|
-
headers,
|
|
51
|
-
body: Object.keys(body).length > 0 ? requestBody : undefined,
|
|
52
|
-
signal: controller.signal,
|
|
53
|
-
});
|
|
54
|
-
clearTimeout(timer);
|
|
55
|
-
const responseText = await response.text();
|
|
56
|
-
let responseData;
|
|
57
|
-
try {
|
|
58
|
-
responseData = JSON.parse(responseText);
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
responseData = responseText;
|
|
62
|
-
}
|
|
63
|
-
if (response.ok) {
|
|
64
|
-
return JSON.stringify(responseData, null, 2);
|
|
65
|
-
}
|
|
66
|
-
// 4xx: リトライしない
|
|
67
|
-
if (response.status < 500) {
|
|
68
|
-
return sanitizeHttpError(response.status, responseData);
|
|
69
|
-
}
|
|
70
|
-
// 5xx: 最終試行ならエラー返却、それ以外は backoff
|
|
71
|
-
if (attempt === MAX_ATTEMPTS - 1) {
|
|
72
|
-
return sanitizeHttpError(response.status, responseData);
|
|
73
|
-
}
|
|
74
|
-
await sleep(BASE_DELAY_MS * Math.pow(2, attempt));
|
|
75
|
-
}
|
|
76
|
-
catch (e) {
|
|
77
|
-
clearTimeout(timer);
|
|
78
|
-
// タイムアウト: リトライしない
|
|
79
|
-
if (e instanceof Error && e.name === "AbortError") {
|
|
80
|
-
return sanitizeError(e);
|
|
81
|
-
}
|
|
82
|
-
// ネットワークエラー: 最終試行ならエラー返却、それ以外は backoff
|
|
83
|
-
if (attempt === MAX_ATTEMPTS - 1) {
|
|
84
|
-
return sanitizeError(e);
|
|
85
|
-
}
|
|
86
|
-
await sleep(BASE_DELAY_MS * Math.pow(2, attempt));
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return sanitizeError(new Error("Unexpected retry exhaustion"));
|
|
90
|
-
}
|