nworks 0.1.0 → 0.2.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/README.md +38 -10
- package/dist/index.js +357 -44
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +143 -1
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp.js
CHANGED
|
@@ -13802,6 +13802,7 @@ import { join } from "path";
|
|
|
13802
13802
|
var CONFIG_DIR = join(homedir(), ".config", "nworks");
|
|
13803
13803
|
var CREDENTIALS_PATH = join(CONFIG_DIR, "credentials.json");
|
|
13804
13804
|
var TOKEN_PATH = join(CONFIG_DIR, "token.json");
|
|
13805
|
+
var USER_TOKEN_PATH = join(CONFIG_DIR, "user-token.json");
|
|
13805
13806
|
async function ensureConfigDir() {
|
|
13806
13807
|
if (!existsSync(CONFIG_DIR)) {
|
|
13807
13808
|
await mkdir(CONFIG_DIR, { recursive: true });
|
|
@@ -13862,6 +13863,29 @@ async function saveToken(token, profile = "default") {
|
|
|
13862
13863
|
tokens[profile] = token;
|
|
13863
13864
|
await writeFile(TOKEN_PATH, JSON.stringify(tokens, null, 2), "utf-8");
|
|
13864
13865
|
}
|
|
13866
|
+
async function loadUserToken(profile = "default") {
|
|
13867
|
+
if (!existsSync(USER_TOKEN_PATH)) return null;
|
|
13868
|
+
const raw = await readFile(USER_TOKEN_PATH, "utf-8");
|
|
13869
|
+
const tokens = JSON.parse(raw);
|
|
13870
|
+
const entry = tokens[profile];
|
|
13871
|
+
if (!entry) return null;
|
|
13872
|
+
return {
|
|
13873
|
+
accessToken: String(entry["accessToken"]),
|
|
13874
|
+
refreshToken: String(entry["refreshToken"]),
|
|
13875
|
+
expiresAt: Number(entry["expiresAt"]),
|
|
13876
|
+
scope: String(entry["scope"] ?? "")
|
|
13877
|
+
};
|
|
13878
|
+
}
|
|
13879
|
+
async function saveUserToken(token, profile = "default") {
|
|
13880
|
+
await ensureConfigDir();
|
|
13881
|
+
let tokens = {};
|
|
13882
|
+
if (existsSync(USER_TOKEN_PATH)) {
|
|
13883
|
+
const raw = await readFile(USER_TOKEN_PATH, "utf-8");
|
|
13884
|
+
tokens = JSON.parse(raw);
|
|
13885
|
+
}
|
|
13886
|
+
tokens[profile] = token;
|
|
13887
|
+
await writeFile(USER_TOKEN_PATH, JSON.stringify(tokens, null, 2), "utf-8");
|
|
13888
|
+
}
|
|
13865
13889
|
|
|
13866
13890
|
// src/auth/jwt.ts
|
|
13867
13891
|
import { readFile as readFile2 } from "fs/promises";
|
|
@@ -14036,6 +14060,89 @@ async function listUsers(profile = "default") {
|
|
|
14036
14060
|
return { users: result.users ?? [], responseMetaData: result.responseMetaData };
|
|
14037
14061
|
}
|
|
14038
14062
|
|
|
14063
|
+
// src/auth/oauth-user.ts
|
|
14064
|
+
import { createServer } from "http";
|
|
14065
|
+
import { URL as URL2 } from "url";
|
|
14066
|
+
var TOKEN_URL = "https://auth.worksmobile.com/oauth2/v2.0/token";
|
|
14067
|
+
var REDIRECT_PORT = 9876;
|
|
14068
|
+
var REDIRECT_URI = `http://localhost:${REDIRECT_PORT}/callback`;
|
|
14069
|
+
async function refreshUserToken(refreshToken2, profile = "default") {
|
|
14070
|
+
const creds = await loadCredentials(profile);
|
|
14071
|
+
const body = new URLSearchParams({
|
|
14072
|
+
grant_type: "refresh_token",
|
|
14073
|
+
refresh_token: refreshToken2,
|
|
14074
|
+
client_id: creds.clientId,
|
|
14075
|
+
client_secret: creds.clientSecret
|
|
14076
|
+
});
|
|
14077
|
+
const res = await fetch(TOKEN_URL, {
|
|
14078
|
+
method: "POST",
|
|
14079
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
14080
|
+
body: body.toString()
|
|
14081
|
+
});
|
|
14082
|
+
if (!res.ok) {
|
|
14083
|
+
const text = await res.text();
|
|
14084
|
+
throw new AuthError(`Token refresh failed (${res.status}): ${text}`);
|
|
14085
|
+
}
|
|
14086
|
+
const data = await res.json();
|
|
14087
|
+
return {
|
|
14088
|
+
accessToken: data.access_token,
|
|
14089
|
+
refreshToken: data.refresh_token ?? refreshToken2,
|
|
14090
|
+
expiresAt: Math.floor(Date.now() / 1e3) + Number(data.expires_in),
|
|
14091
|
+
scope: data.scope
|
|
14092
|
+
};
|
|
14093
|
+
}
|
|
14094
|
+
|
|
14095
|
+
// src/auth/token-user.ts
|
|
14096
|
+
async function getValidUserToken(profile = "default") {
|
|
14097
|
+
const cached2 = await loadUserToken(profile);
|
|
14098
|
+
if (!cached2) {
|
|
14099
|
+
throw new AuthError(
|
|
14100
|
+
"User OAuth token not found. Run `nworks login --user` first."
|
|
14101
|
+
);
|
|
14102
|
+
}
|
|
14103
|
+
if (cached2.expiresAt > Date.now() / 1e3 + 300) {
|
|
14104
|
+
return cached2.accessToken;
|
|
14105
|
+
}
|
|
14106
|
+
const refreshed = await refreshUserToken(cached2.refreshToken, profile);
|
|
14107
|
+
await saveUserToken(refreshed, profile);
|
|
14108
|
+
return refreshed.accessToken;
|
|
14109
|
+
}
|
|
14110
|
+
|
|
14111
|
+
// src/api/calendar.ts
|
|
14112
|
+
var BASE_URL2 = "https://www.worksapis.com/v1.0";
|
|
14113
|
+
async function listEvents(fromDateTime, untilDateTime, userId = "me", profile = "default") {
|
|
14114
|
+
const token = await getValidUserToken(profile);
|
|
14115
|
+
const from = encodeURIComponent(fromDateTime);
|
|
14116
|
+
const until = encodeURIComponent(untilDateTime);
|
|
14117
|
+
const url2 = `${BASE_URL2}/users/${userId}/calendar/events?fromDateTime=${from}&untilDateTime=${until}`;
|
|
14118
|
+
if (process.env["NWORKS_VERBOSE"] === "1") {
|
|
14119
|
+
console.error(`[nworks] GET ${url2}`);
|
|
14120
|
+
}
|
|
14121
|
+
const res = await fetch(url2, {
|
|
14122
|
+
method: "GET",
|
|
14123
|
+
headers: {
|
|
14124
|
+
Authorization: `Bearer ${token}`,
|
|
14125
|
+
"Content-Type": "application/json"
|
|
14126
|
+
}
|
|
14127
|
+
});
|
|
14128
|
+
if (res.status === 401) {
|
|
14129
|
+
throw new AuthError("User token expired. Run `nworks login --user` again.");
|
|
14130
|
+
}
|
|
14131
|
+
if (!res.ok) {
|
|
14132
|
+
let code = "UNKNOWN";
|
|
14133
|
+
let description = `HTTP ${res.status}`;
|
|
14134
|
+
try {
|
|
14135
|
+
const errorBody = await res.json();
|
|
14136
|
+
code = errorBody.code ?? code;
|
|
14137
|
+
description = errorBody.description ?? description;
|
|
14138
|
+
} catch {
|
|
14139
|
+
}
|
|
14140
|
+
throw new ApiError(code, description, res.status);
|
|
14141
|
+
}
|
|
14142
|
+
const data = await res.json();
|
|
14143
|
+
return { events: data.events ?? [] };
|
|
14144
|
+
}
|
|
14145
|
+
|
|
14039
14146
|
// src/mcp/tools.ts
|
|
14040
14147
|
function registerTools(server) {
|
|
14041
14148
|
server.tool(
|
|
@@ -14111,6 +14218,41 @@ function registerTools(server) {
|
|
|
14111
14218
|
}
|
|
14112
14219
|
}
|
|
14113
14220
|
);
|
|
14221
|
+
server.tool(
|
|
14222
|
+
"nworks_calendar_list",
|
|
14223
|
+
"\uC0AC\uC6A9\uC790\uC758 \uCE98\uB9B0\uB354 \uC77C\uC815\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4 (User OAuth calendar.read scope \uD544\uC694. \uBA3C\uC800 nworks login --user \uD544\uC694)",
|
|
14224
|
+
{
|
|
14225
|
+
fromDateTime: external_exports.string().describe("\uC2DC\uC791 \uC77C\uC2DC (YYYY-MM-DDThh:mm:ss+09:00)"),
|
|
14226
|
+
untilDateTime: external_exports.string().describe("\uC885\uB8CC \uC77C\uC2DC (YYYY-MM-DDThh:mm:ss+09:00)"),
|
|
14227
|
+
userId: external_exports.string().optional().describe("\uB300\uC0C1 \uC0AC\uC6A9\uC790 ID (\uBBF8\uC9C0\uC815 \uC2DC me)")
|
|
14228
|
+
},
|
|
14229
|
+
async ({ fromDateTime, untilDateTime, userId }) => {
|
|
14230
|
+
try {
|
|
14231
|
+
const result = await listEvents(
|
|
14232
|
+
fromDateTime,
|
|
14233
|
+
untilDateTime,
|
|
14234
|
+
userId ?? "me"
|
|
14235
|
+
);
|
|
14236
|
+
const events = result.events.flatMap(
|
|
14237
|
+
(e) => e.eventComponents.map((c) => ({
|
|
14238
|
+
summary: c.summary,
|
|
14239
|
+
start: c.start.dateTime ?? c.start.date ?? "",
|
|
14240
|
+
end: c.end.dateTime ?? c.end.date ?? "",
|
|
14241
|
+
location: c.location ?? ""
|
|
14242
|
+
}))
|
|
14243
|
+
);
|
|
14244
|
+
return {
|
|
14245
|
+
content: [{ type: "text", text: JSON.stringify({ events, count: events.length }) }]
|
|
14246
|
+
};
|
|
14247
|
+
} catch (err) {
|
|
14248
|
+
const error48 = err;
|
|
14249
|
+
return {
|
|
14250
|
+
content: [{ type: "text", text: `Error: ${error48.message}` }],
|
|
14251
|
+
isError: true
|
|
14252
|
+
};
|
|
14253
|
+
}
|
|
14254
|
+
}
|
|
14255
|
+
);
|
|
14114
14256
|
server.tool(
|
|
14115
14257
|
"nworks_whoami",
|
|
14116
14258
|
"\uD604\uC7AC \uC778\uC99D\uB41C NAVER WORKS \uACC4\uC815 \uC815\uBCF4\uB97C \uD655\uC778\uD569\uB2C8\uB2E4",
|
|
@@ -14144,7 +14286,7 @@ function registerTools(server) {
|
|
|
14144
14286
|
async function startMcpServer() {
|
|
14145
14287
|
const server = new McpServer({
|
|
14146
14288
|
name: "nworks",
|
|
14147
|
-
version: "0.
|
|
14289
|
+
version: "0.2.0"
|
|
14148
14290
|
});
|
|
14149
14291
|
registerTools(server);
|
|
14150
14292
|
const transport = new StdioServerTransport();
|