smbc-mcp-server 1.0.3 → 1.0.4

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 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
- ### A. パッケージの新バージョンを待つ(推奨)
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
- // 1. OpenAPI仕様書からエンドポイントを抽出
9
- const { operations } = await loadOperations(getOpenapiPath());
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. リソース一覧ハンドラー
@@ -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(filePath) {
41
+ export async function loadOperations(url) {
43
42
  let content;
44
43
  try {
45
- content = await readFile(filePath, "utf-8");
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 read OpenAPI file: ${filePath}\n`);
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} tools from ${filePath}\n`);
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",
3
+ "version": "1.0.4",
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 && cp openapi-type.yml build/openapi-type.yml && chmod 755 build/index.js",
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
@@ -1,6 +0,0 @@
1
- import { fileURLToPath } from "url";
2
- import { join, dirname } from "path";
3
- const __dirname = dirname(fileURLToPath(import.meta.url));
4
- export function getOpenapiPath() {
5
- return process.env.SMBC_OPENAPI_PATH ?? join(__dirname, "openapi-type.yml");
6
- }
@@ -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
- }
@@ -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
- }