mcp-cos-upload 1.1.2 → 1.1.3

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/.env.example ADDED
@@ -0,0 +1,10 @@
1
+ # Tencent Cloud COS Configuration
2
+ # Copy this file to .env and fill in your values
3
+ # cp .env.example .env
4
+
5
+ COS_SECRET_ID=
6
+ COS_SECRET_KEY=
7
+ COS_DEFAULT_BUCKET=
8
+ COS_DEFAULT_REGION=ap-guangzhou
9
+ COS_KEY_PREFIX=figma-assets
10
+ COS_CDN_DOMAIN=
package/README.md CHANGED
@@ -71,38 +71,14 @@ COS_CDN_DOMAIN=cdn.example.com
71
71
 
72
72
  > **Note:** The `.env` file contains secrets — never commit it. Use `.env.example` as a reference template.
73
73
 
74
- The server searches for `.env` in the following order:
75
- 1. `$COS_ENV_PATH` (custom path via env var)
76
- 2. Project root (`process.cwd()/.env`, set by Cursor to workspace root)
77
- 3. `~/.mcp-cos-upload.env`
78
- 4. `~/.config/mcp-cos-upload/.env`
74
+ The server only reads one file:
75
+ 1. Project root `.env` (`/path/to/mcp-cos-upload/.env`)
79
76
 
80
- #### Method 2: MCP `env` Configuration
81
-
82
- Alternatively, pass credentials directly in MCP config (no `.env` file needed):
83
-
84
- ```json
85
- {
86
- "mcpServers": {
87
- "mcp-cos-upload": {
88
- "command": "sh",
89
- "args": ["-c", "command -v mcp-cos-upload >/dev/null 2>&1 || npm i -g mcp-cos-upload >/dev/null 2>&1; exec mcp-cos-upload"],
90
- "env": {
91
- "COS_SECRET_ID": "your_secret_id",
92
- "COS_SECRET_KEY": "your_secret_key",
93
- "COS_DEFAULT_BUCKET": "your_bucket_name",
94
- "COS_DEFAULT_REGION": "ap-guangzhou",
95
- "COS_KEY_PREFIX": "figma-assets",
96
- "COS_CDN_DOMAIN": "cdn.example.com"
97
- }
98
- }
99
- }
100
- }
101
- ```
102
-
103
- #### Priority
104
-
105
- If both are configured, project `.env` file takes **higher priority** over MCP `env` configuration.
77
+ It does **not** read:
78
+ 1. MCP `env` configuration
79
+ 2. Home directory `.env`
80
+ 3. Parent directory `.env`
81
+ 4. `COS_ENV_PATH`
106
82
 
107
83
  ### Environment Variables
108
84
 
@@ -271,7 +247,7 @@ This happens when using `npx -y mcp-cos-upload` directly — npm outputs package
271
247
 
272
248
  ### Missing COS_SECRET_ID or COS_SECRET_KEY
273
249
 
274
- The server cannot find your credentials. Make sure you have a `.env` file in your project root (or `~/.mcp-cos-upload.env`). Check the `.env.example` template for required variables.
250
+ The server only reads the project root `.env`. If that file does not exist, or if it is missing `COS_SECRET_ID` / `COS_SECRET_KEY`, tool calls will return a clear error telling you to fix that single file.
275
251
 
276
252
  ### MCP not working after code changes
277
253
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-cos-upload",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "MCP server for uploading files to Tencent Cloud COS",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -8,7 +8,8 @@
8
8
  "mcp-cos-upload": "src/index.js"
9
9
  },
10
10
  "files": [
11
- "src/**/*"
11
+ "src/**/*",
12
+ ".env.example"
12
13
  ],
13
14
  "keywords": [
14
15
  "mcp",
package/src/cos.js CHANGED
@@ -2,12 +2,14 @@
2
2
  import COS from "cos-nodejs-sdk-v5";
3
3
  import { readFileSync, existsSync } from "fs";
4
4
  import { join, dirname } from "path";
5
- import { homedir } from "os";
6
5
  import { fileURLToPath } from "url";
7
6
 
8
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const PROJECT_ENV_PATH = join(__dirname, "..", ".env");
9
+
10
+ function parseEnvFile(filePath) {
11
+ const env = {};
9
12
 
10
- function loadEnvFile(filePath) {
11
13
  try {
12
14
  const content = readFileSync(filePath, "utf-8");
13
15
  for (const line of content.split("\n")) {
@@ -17,43 +19,63 @@ function loadEnvFile(filePath) {
17
19
  if (eqIdx === -1) continue;
18
20
  const key = trimmed.slice(0, eqIdx).trim();
19
21
  const value = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, "");
20
- if (key && !process.env[key]) process.env[key] = value;
22
+ if (key) env[key] = value;
21
23
  }
22
- return true;
23
24
  } catch {
24
- return false;
25
+ return null;
25
26
  }
27
+
28
+ return env;
26
29
  }
27
30
 
28
- const envPaths = [
29
- process.env.COS_ENV_PATH,
30
- join(process.cwd(), ".env"),
31
- join(homedir(), ".mcp-cos-upload.env"),
32
- join(homedir(), ".config", "mcp-cos-upload", ".env"),
33
- join(__dirname, "..", ".env"),
34
- ].filter(Boolean);
35
-
36
- for (const envPath of envPaths) {
37
- if (existsSync(envPath)) {
38
- loadEnvFile(envPath);
39
- process.stderr.write(`[mcp-cos-upload] loaded env from ${envPath}\n`);
40
- break;
31
+ const fileEnv = existsSync(PROJECT_ENV_PATH) ? parseEnvFile(PROJECT_ENV_PATH) : null;
32
+
33
+ function getMissingCredentialMessage() {
34
+ if (!fileEnv) {
35
+ return [
36
+ "[mcp-cos-upload] ❌ 未找到配置文件",
37
+ `只会读取项目根目录 .env: ${PROJECT_ENV_PATH}`,
38
+ "请在该文件中设置:",
39
+ "COS_SECRET_ID=your_secret_id",
40
+ "COS_SECRET_KEY=your_secret_key",
41
+ "COS_DEFAULT_BUCKET=your_bucket_name",
42
+ "COS_DEFAULT_REGION=ap-guangzhou",
43
+ ].join("\n");
41
44
  }
45
+
46
+ return [
47
+ "[mcp-cos-upload] ❌ .env 缺少 COS_SECRET_ID 或 COS_SECRET_KEY",
48
+ `只读取此文件: ${PROJECT_ENV_PATH}`,
49
+ "请补全以下字段:",
50
+ "COS_SECRET_ID=your_secret_id",
51
+ "COS_SECRET_KEY=your_secret_key",
52
+ ].join("\n");
53
+ }
54
+
55
+ function getRequiredConfigValue(key) {
56
+ return fileEnv?.[key] || null;
57
+ }
58
+
59
+ export function getCosClientConfigError() {
60
+ const secretId = getRequiredConfigValue("COS_SECRET_ID");
61
+ const secretKey = getRequiredConfigValue("COS_SECRET_KEY");
62
+
63
+ if (secretId && secretKey) return null;
64
+ return getMissingCredentialMessage();
42
65
  }
43
66
 
44
67
  export function createCosClient() {
45
- const secretId = process.env.COS_SECRET_ID;
46
- const secretKey = process.env.COS_SECRET_KEY;
68
+ const configError = getCosClientConfigError();
47
69
 
48
- if (!secretId || !secretKey) {
49
- console.error("[mcp-cos-upload] ❌ 缺少 COS_SECRET_ID 或 COS_SECRET_KEY");
50
- process.exit(1);
51
- }
70
+ if (configError) throw new Error(configError);
52
71
 
53
- return new COS({ SecretId: secretId, SecretKey: secretKey });
72
+ return new COS({
73
+ SecretId: getRequiredConfigValue("COS_SECRET_ID"),
74
+ SecretKey: getRequiredConfigValue("COS_SECRET_KEY"),
75
+ });
54
76
  }
55
77
 
56
- export const DEFAULT_BUCKET = process.env.COS_DEFAULT_BUCKET || null;
57
- export const DEFAULT_REGION = process.env.COS_DEFAULT_REGION || null;
58
- export const KEY_PREFIX = process.env.COS_KEY_PREFIX || "figma-assets";
59
- export const CDN_DOMAIN = process.env.COS_CDN_DOMAIN || null;
78
+ export const DEFAULT_BUCKET = getRequiredConfigValue("COS_DEFAULT_BUCKET");
79
+ export const DEFAULT_REGION = getRequiredConfigValue("COS_DEFAULT_REGION");
80
+ export const KEY_PREFIX = getRequiredConfigValue("COS_KEY_PREFIX") || "figma-assets";
81
+ export const CDN_DOMAIN = getRequiredConfigValue("COS_CDN_DOMAIN");
package/src/index.js CHANGED
@@ -20,8 +20,6 @@ import {
20
20
  CDN_DOMAIN,
21
21
  } from "./cos.js";
22
22
 
23
- const cos = createCosClient();
24
-
25
23
  // 工具参数校验(zod)
26
24
  const CosUploadArgsSchema = z.object({
27
25
  bucket: z.string().optional().describe("COS Bucket 名,不传则用默认 COS_DEFAULT_BUCKET"),
@@ -58,7 +56,7 @@ const cosUploadInputSchema = {
58
56
 
59
57
  async function main() {
60
58
  const server = new Server(
61
- { name: "mcp-cos-upload", version: "1.0.0" },
59
+ { name: "mcp-cos-upload", version: "1.1.2" },
62
60
  { capabilities: { tools: {} } }
63
61
  );
64
62
 
@@ -103,6 +101,16 @@ async function main() {
103
101
  return { isError: true, content: [{ type: "text", text: "必须提供 content、url 或 filepath 其中一个参数。" }] };
104
102
  }
105
103
 
104
+ let cos;
105
+ try {
106
+ cos = createCosClient();
107
+ } catch (err) {
108
+ return {
109
+ isError: true,
110
+ content: [{ type: "text", text: err?.message || String(err) }],
111
+ };
112
+ }
113
+
106
114
  // 扩展名到 MIME 类型映射
107
115
  const extToMimeType = {
108
116
  png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg",