Simplify MCP server to stdio-only (local desktop use)

Drop the Streamable HTTP transport and direct express dependency so the server has no network listener and cannot be reached from web chat — matches the desktop-only use case. Update README/.env.example accordingly. (express remains only as a transitive dep of the MCP SDK; unused.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
ang3l12
2026-06-16 14:09:12 -06:00
parent 89835d9498
commit f65cad865d
5 changed files with 236 additions and 685 deletions

View File

@@ -1,19 +1,18 @@
#!/usr/bin/env node
// SharePoint Lists MCP server.
// SharePoint Lists MCP server (stdio).
//
// Wraps the SAME core client the skill uses (graph.mjs) and exposes it as MCP
// tools. App-only Microsoft Graph auth; credentials come from the environment
// (or an mcp-server/.env file — git-ignored).
//
// Transports:
// stdio (default) — for Claude Desktop, Claude Code, Cowork (local)
// Streamable HTTP (--http or MCP_TRANSPORT=http) — for a hosted/remote connector
// Transport: stdio only — for the local Claude apps (Claude Desktop, Claude
// Code, Cowork). There is intentionally no network/HTTP transport, so the
// server cannot be reached remotely or from web chat.
//
// Env:
// SP_TENANT_ID, SP_CLIENT_ID, SP_CLIENT_SECRET (required)
// SP_SITE_URL optional default site so the `site` arg can be omitted
// SP_READONLY if "true"/"1", write tools (create/update/delete) are NOT registered
// PORT HTTP port (default 3838) when running with --http
import { readFileSync, existsSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -21,7 +20,6 @@ import { fileURLToPath } from "node:url";
import { z } from "zod";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
// Single source of truth — the same client the skill's CLI uses.
import { SharePointListsClient, GraphError } from "../.claude/skills/sharepoint-lists/scripts/lib/graph.mjs";
@@ -67,11 +65,9 @@ const SITE = z
.describe(
"SharePoint site URL, e.g. https://contoso.sharepoint.com/sites/Marketing. Omit to use the SP_SITE_URL default.",
);
const LIST = z
.string()
.describe("List display name, internal name, or GUID.");
const LIST = z.string().describe("List display name, internal name, or GUID.");
// ---- server factory (a fresh instance per stdio process / per HTTP request) -
// ---- server ---------------------------------------------------------------
function buildServer() {
const server = new McpServer({ name: "sharepoint-lists", version: "1.0.0" });
@@ -239,55 +235,13 @@ function buildServer() {
return server;
}
// ---- transports -----------------------------------------------------------
// ---- start (stdio) --------------------------------------------------------
async function runStdio() {
const server = buildServer();
await server.connect(new StdioServerTransport());
console.error(
`[sharepoint-lists] MCP server ready on stdio (${READONLY ? "read-only" : "read/write"}).`,
);
}
async function runHttp() {
const { default: express } = await import("express");
const app = express();
app.use(express.json({ limit: "4mb" }));
// Stateless: a fresh server + transport per request (no session persistence).
app.post("/mcp", async (req, res) => {
const server = buildServer();
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
res.on("close", () => {
transport.close();
server.close();
});
try {
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (e) {
console.error("[sharepoint-lists] request error:", e);
if (!res.headersSent) res.status(500).json({ error: String(e?.message || e) });
}
});
// Stateless mode doesn't support the GET (SSE) or DELETE session endpoints.
app.get("/mcp", (_req, res) => res.status(405).json({ error: "Method Not Allowed" }));
app.delete("/mcp", (_req, res) => res.status(405).json({ error: "Method Not Allowed" }));
app.get("/health", (_req, res) => res.json({ ok: true, readonly: READONLY }));
const port = Number(process.env.PORT) || 3838;
app.listen(port, () => {
console.error(
`[sharepoint-lists] MCP server on http://localhost:${port}/mcp (${READONLY ? "read-only" : "read/write"}).`,
);
});
}
const useHttp = process.argv.includes("--http") || /^(http)$/i.test(process.env.MCP_TRANSPORT ?? "");
(useHttp ? runHttp() : runStdio()).catch((e) => {
console.error("[sharepoint-lists] fatal:", e);
process.exit(1);
});
const server = buildServer();
await server.connect(new StdioServerTransport());
console.error(
`[sharepoint-lists] MCP server ready on stdio (${READONLY ? "read-only" : "read/write"}).`,
);
// ---- minimal .env loader (no dependency) ----------------------------------