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:
@@ -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) ----------------------------------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user