@sodiumhq/mcp-pm 0.1.0-beta.4441 → 0.1.0-beta.4448
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 +1 -0
- package/dist/http.js +18 -63
- package/dist/index.js +1 -1
- package/dist/{src-CTHTAM7g.mjs → src-DEbTNFXg.mjs} +339 -173
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ Only enable write mode with an AI client you trust — it hands the client the a
|
|
|
62
62
|
- **`get_practice_dashboard`** — one-call "state of the practice right now": overdue/blocked/due-soon work, what clients are sitting on, unassigned steps, unbilled time, open proposal pipeline. *"Give me my morning briefing."*
|
|
63
63
|
- **`get_practice_details`** — consolidated practice overview (counts, connections, settings)
|
|
64
64
|
- **`whoami`** — show the authenticated user (name, email, code), tenant (name, code, status), and practice name. Useful for verifying which account is connected.
|
|
65
|
+
- **`list_practices`** — list the practices you belong to. *"Which practices can I access?"* If you belong to more than one, tools take an optional `practice` code to say which one a request is about; with a single practice it is selected automatically.
|
|
65
66
|
|
|
66
67
|
**Clients**
|
|
67
68
|
- **`list_clients`** — list and filter clients by search, status, type, assignee, services, saved filters
|
package/dist/http.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as buildServer } from "./src-
|
|
2
|
+
import { t as buildServer } from "./src-DEbTNFXg.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
-
import { createHash
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
5
|
import { createServer } from "node:http";
|
|
6
6
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
7
7
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -140,26 +140,6 @@ const WRITES_ENABLED = writesRaw === "true" || writesRaw === "1";
|
|
|
140
140
|
const AUTH = loadAuthConfig();
|
|
141
141
|
const OBO = AUTH ? loadOboConfig(AUTH.issuer) : null;
|
|
142
142
|
const ENDPOINT_PATH = AUTH ? new URL(AUTH.resourceUrl).pathname : process.env.MCP_ENDPOINT_PATH ?? "/mcp";
|
|
143
|
-
const sessions = /* @__PURE__ */ new Map();
|
|
144
|
-
const SESSION_IDLE_MS = Number(process.env.MCP_SESSION_IDLE_MS ?? 30 * 6e4);
|
|
145
|
-
const SWEEP_INTERVAL_MS = 6e4;
|
|
146
|
-
function touch(sessionId) {
|
|
147
|
-
const session = sessions.get(sessionId);
|
|
148
|
-
if (session) session.lastActivity = Date.now();
|
|
149
|
-
}
|
|
150
|
-
async function sweepIdleSessions() {
|
|
151
|
-
const cutoff = Date.now() - SESSION_IDLE_MS;
|
|
152
|
-
for (const [sessionId, session] of sessions) {
|
|
153
|
-
if (session.lastActivity > cutoff) continue;
|
|
154
|
-
sessions.delete(sessionId);
|
|
155
|
-
console.error(`[sodium-pm-mcp-http] session ${sessionId} swept (idle)`);
|
|
156
|
-
try {
|
|
157
|
-
await session.transport.close();
|
|
158
|
-
} catch (error) {
|
|
159
|
-
console.error(`[sodium-pm-mcp-http] error closing swept session: ${String(error)}`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
143
|
const MAX_BODY_BYTES = 4 * 1024 * 1024;
|
|
164
144
|
async function readJsonBody(req) {
|
|
165
145
|
const chunks = [];
|
|
@@ -182,7 +162,7 @@ function sendJsonError(res, status, message) {
|
|
|
182
162
|
id: null
|
|
183
163
|
}));
|
|
184
164
|
}
|
|
185
|
-
async function
|
|
165
|
+
async function handleMcpPost(req, res, body) {
|
|
186
166
|
const authHeader = req.headers.authorization;
|
|
187
167
|
if (!authHeader?.startsWith("Bearer ")) throw new HttpError(401, "Authorization: Bearer <token> header is required.");
|
|
188
168
|
const bearerToken = authHeader.slice(7).trim();
|
|
@@ -195,7 +175,7 @@ async function createSession(req) {
|
|
|
195
175
|
}
|
|
196
176
|
const tenantHeader = req.headers["x-sodium-tenant"];
|
|
197
177
|
const tenant = (Array.isArray(tenantHeader) ? tenantHeader[0] : tenantHeader) ?? process.env.SODIUM_TENANT;
|
|
198
|
-
|
|
178
|
+
const isInit = isInitializeRequest(body);
|
|
199
179
|
const server = await buildServer({
|
|
200
180
|
context: {
|
|
201
181
|
bearerToken: apiToken,
|
|
@@ -204,26 +184,17 @@ async function createSession(req) {
|
|
|
204
184
|
writesEnabled: writesAllowed
|
|
205
185
|
},
|
|
206
186
|
serverName: "Sodium Practice Management",
|
|
207
|
-
serverVersion: VERSION
|
|
187
|
+
serverVersion: VERSION,
|
|
188
|
+
includeStartupContext: isInit
|
|
208
189
|
});
|
|
209
|
-
const transport = new StreamableHTTPServerTransport({
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
transport,
|
|
214
|
-
lastActivity: Date.now()
|
|
215
|
-
});
|
|
216
|
-
console.error(`[sodium-pm-mcp-http] session ${sessionId} started (tenant: ${tenant}, live: ${sessions.size})`);
|
|
217
|
-
}
|
|
190
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
191
|
+
res.on("close", () => {
|
|
192
|
+
transport.close();
|
|
193
|
+
server.close();
|
|
218
194
|
});
|
|
219
|
-
|
|
220
|
-
if (transport.sessionId) {
|
|
221
|
-
sessions.delete(transport.sessionId);
|
|
222
|
-
console.error(`[sodium-pm-mcp-http] session ${transport.sessionId} closed`);
|
|
223
|
-
}
|
|
224
|
-
};
|
|
195
|
+
if (isInit) console.error(`[sodium-pm-mcp-http] initialize (tenant: ${tenant || "per-call"}, writes: ${writesAllowed})`);
|
|
225
196
|
await server.connect(transport);
|
|
226
|
-
|
|
197
|
+
await transport.handleRequest(req, res, body);
|
|
227
198
|
}
|
|
228
199
|
var HttpError = class extends Error {
|
|
229
200
|
constructor(status, message) {
|
|
@@ -236,8 +207,7 @@ async function handle(req, res) {
|
|
|
236
207
|
if (req.method === "GET" && url.pathname === "/health") {
|
|
237
208
|
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({
|
|
238
209
|
status: "ok",
|
|
239
|
-
version: VERSION
|
|
240
|
-
sessions: sessions.size
|
|
210
|
+
version: VERSION
|
|
241
211
|
}));
|
|
242
212
|
return;
|
|
243
213
|
}
|
|
@@ -249,25 +219,14 @@ async function handle(req, res) {
|
|
|
249
219
|
sendJsonError(res, 404, `Not found. MCP endpoint is ${ENDPOINT_PATH}.`);
|
|
250
220
|
return;
|
|
251
221
|
}
|
|
252
|
-
const sessionId = req.headers["mcp-session-id"];
|
|
253
|
-
const existing = typeof sessionId === "string" ? sessions.get(sessionId) : void 0;
|
|
254
|
-
if (existing && typeof sessionId === "string") {
|
|
255
|
-
touch(sessionId);
|
|
256
|
-
await existing.transport.handleRequest(req, res);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
222
|
if (req.method !== "POST") {
|
|
260
|
-
|
|
223
|
+
res.setHeader("Allow", "POST");
|
|
224
|
+
sendJsonError(res, 405, "Stateless MCP endpoint. Send requests as POST.");
|
|
261
225
|
return;
|
|
262
226
|
}
|
|
263
|
-
|
|
264
|
-
if (!isInitializeRequest(body)) {
|
|
265
|
-
sendJsonError(res, 400, "No valid MCP session. Send an initialize request first.");
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
await (await createSession(req)).handleRequest(req, res, body);
|
|
227
|
+
await handleMcpPost(req, res, await readJsonBody(req));
|
|
269
228
|
}
|
|
270
|
-
|
|
229
|
+
createServer((req, res) => {
|
|
271
230
|
handle(req, res).catch((error) => {
|
|
272
231
|
const message = error instanceof Error ? error.message : String(error);
|
|
273
232
|
console.error(`[sodium-pm-mcp-http] ${req.method} ${req.url} failed: ${message}`);
|
|
@@ -281,11 +240,7 @@ const httpServer = createServer((req, res) => {
|
|
|
281
240
|
if (AUTH && error instanceof HttpError && error.status === 401) res.setHeader("WWW-Authenticate", buildChallenge(AUTH));
|
|
282
241
|
sendJsonError(res, error instanceof HttpError ? error.status : 500, message);
|
|
283
242
|
});
|
|
284
|
-
})
|
|
285
|
-
setInterval(() => {
|
|
286
|
-
sweepIdleSessions();
|
|
287
|
-
}, SWEEP_INTERVAL_MS).unref();
|
|
288
|
-
httpServer.listen(PORT, HOST, () => {
|
|
243
|
+
}).listen(PORT, HOST, () => {
|
|
289
244
|
console.error(`[sodium-pm-mcp-http] v${VERSION} listening on http://${HOST}:${PORT}${ENDPOINT_PATH} (api: ${BASE_URL}, writes: ${WRITES_ENABLED ? "enabled" : "disabled"})`);
|
|
290
245
|
});
|
|
291
246
|
//#endregion
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as buildServer } from "./src-
|
|
2
|
+
import { t as buildServer } from "./src-DEbTNFXg.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
import { parseArgs } from "node:util";
|