diff --git a/.github/workflows/generate-lockfile.yml b/.github/workflows/generate-lockfile.yml new file mode 100644 index 0000000..ad6f083 --- /dev/null +++ b/.github/workflows/generate-lockfile.yml @@ -0,0 +1,51 @@ +name: Generate lockfile + +# Manual, on-demand regeneration of package-lock.json. +# +# History: an earlier version of this workflow (removed in PR #10) pushed +# the regenerated lockfile straight to main using the built-in GITHUB_TOKEN. +# That's broken by design: GITHUB_TOKEN-authored pushes do NOT trigger other +# workflow runs (a deliberate GitHub Actions anti-recursion safeguard), so +# the branch ruleset's required "test" check never ran on those commits -- +# meaning the bot's own pushes were likely to get blocked by the very +# ruleset meant to protect main. This version avoids that by opening a PR +# instead of pushing directly: a PR created by GITHUB_TOKEN DOES trigger the +# normal push/pull_request CI workflow on its branch, so the required check +# runs like it would for any human-authored change, and the PR merges (or +# not) through the normal ruleset-gated path. +on: + workflow_dispatch: {} + +permissions: + contents: write + pull-requests: write + +jobs: + generate-lockfile: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Regenerate package-lock.json + run: | + set -e + rm -f package-lock.json + npm install + + - name: Open PR with the regenerated lockfile + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "chore: regenerate package-lock.json" + branch: chore/regenerate-lockfile + delete-branch: true + title: "chore: regenerate package-lock.json" + body: | + Automated lockfile regeneration, triggered manually via the "Generate lockfile" workflow. + + Opened as a PR (not pushed directly to main) so the required CI check runs on these commits before they merge -- see the workflow file's header comment for why the old push-to-main version couldn't do that. + labels: dependencies diff --git a/README.md b/README.md index 1cedde1..506ca9e 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ yet, open `demo.html` directly.) ## Deploy & connect (quickstart) [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/allocsys/madmcp) -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/allocsys/madmcp&env=MCP_SHARED_KEY,GITHUB_TOKEN,NOTION_TOKEN,MEM0_API_KEY,CLOUDFLARE_API_TOKEN,CLOUDFLARE_ACCOUNT_ID&envDescription=Generate+a+long+random+string+yourself+for+MCP_SHARED_KEY+(Vercel+can%27t+auto-generate+it).+Leave+any+connector+token+blank+to+skip+it.&envLink=https://github.com/allocsys/madmcp%23configuration&project-name=manufact-mcp-server&repository-name=manufact-mcp-server) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/allocsys/madmcp&env=MCP_SHARED_KEY,GITHUB_TOKEN,NOTION_TOKEN,MEM0_API_KEY,CLOUDFLARE_API_TOKEN,CLOUDFLARE_ACCOUNT_ID&envDescription=Generate+a+long+random+string+yourself+for+MCP_SHARED_KEY+(Vercel+can%27t+auto-generate+it).+Leave+any+connector+token+blank+to+skip+it.&envLink=https://github.com/allocsys/madmcp%23configuration&project-name=madmcp-server&repository-name=madmcp-server) Get tokens, deploy, connect to Claude — in that order. diff --git a/connectors/cloudflare/client.js b/connectors/cloudflare/client.js index 28a7642..a516972 100644 --- a/connectors/cloudflare/client.js +++ b/connectors/cloudflare/client.js @@ -10,12 +10,12 @@ import { CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API } from "../ function assertConfigured() { if (!CLOUDFLARE_API_TOKEN) { throw new Error( - "CLOUDFLARE_API_TOKEN is not set. Add it as an environment variable on the Manufact server." + "CLOUDFLARE_API_TOKEN is not set. Add it as an environment variable on the madmcp server." ); } if (!CLOUDFLARE_ACCOUNT_ID) { throw new Error( - "CLOUDFLARE_ACCOUNT_ID is not set. Add it as an environment variable on the Manufact server." + "CLOUDFLARE_ACCOUNT_ID is not set. Add it as an environment variable on the madmcp server." ); } } @@ -29,7 +29,7 @@ export async function cfRequest(path, { method = "GET", body, accept } = {}) { Authorization: `Bearer ${CLOUDFLARE_API_TOKEN}`, Accept: accept || "application/json", "Content-Type": "application/json", - "User-Agent": "manufact-mcp-server", + "User-Agent": "madmcp-server", }, body: body !== undefined ? JSON.stringify(body) : undefined, }); diff --git a/connectors/cloudflare/observability.js b/connectors/cloudflare/observability.js index 99fcfdc..f2b8bb3 100644 --- a/connectors/cloudflare/observability.js +++ b/connectors/cloudflare/observability.js @@ -78,7 +78,7 @@ export async function queryTelemetry({ const allFilters = rawFilters.map(normalizeFilter); const body = { - queryId: query_id || `manufact-${Date.now()}`, + queryId: query_id || `madmcp-${Date.now()}`, view, datasets: [dataset], timeframe: { from: toEpochMillis(timeframe_from), to: toEpochMillis(timeframe_to) }, diff --git a/connectors/fetch/client.js b/connectors/fetch/client.js index ec7468d..359824c 100644 --- a/connectors/fetch/client.js +++ b/connectors/fetch/client.js @@ -113,7 +113,7 @@ export async function fetchUrl(url, { method = "GET", headers = {}, body } = {}) const res = await fetch(parsed, { method, headers: { - "User-Agent": "manufact-mcp-server/2.0", + "User-Agent": "madmcp-server/2.0", ...headers, }, body: body === undefined ? undefined : (typeof body === "string" ? body : JSON.stringify(body)), diff --git a/connectors/gemini/client.js b/connectors/gemini/client.js index 9ff0e76..afdacf6 100644 --- a/connectors/gemini/client.js +++ b/connectors/gemini/client.js @@ -8,7 +8,7 @@ import { GEMINI_API_KEY, GEMINI_API, GEMINI_MODEL, GEMINI_FALLBACK_MODELS } from import { isModelCoolingDown, setModelCooldown, parseRetryDelaySeconds } from "./cooldown.js"; async function callGenerateContentOnce(body, model) { - if (!GEMINI_API_KEY) throw new Error("GEMINI_API_KEY is not set. Add it as an environment variable on the Manufact server."); + if (!GEMINI_API_KEY) throw new Error("GEMINI_API_KEY is not set. Add it as an environment variable on the madmcp server."); const res = await fetch(`${GEMINI_API}/models/${model}:generateContent`, { method: "POST", diff --git a/connectors/gemini/delegate.js b/connectors/gemini/delegate.js index 63e555a..0181dc5 100644 --- a/connectors/gemini/delegate.js +++ b/connectors/gemini/delegate.js @@ -797,16 +797,29 @@ export async function runInvestigation({ task, max_steps = 6, resume_run_id }) { let contents; let transcript; let startStep = 1; + // The task text actually in effect for this run -- the caller-supplied + // one on a fresh run, or the one restored from a resumed checkpoint. + // Tracked (and persisted in every checkpoint below) so callers/tools.js + // can log/title a resumed run without needing the caller to re-supply + // task text the loop itself ignores on resume. + let effectiveTask = task; const checkpoint = resume_run_id ? await loadCheckpoint(resume_run_id) : null; if (checkpoint) { contents = checkpoint.contents; transcript = checkpoint.transcript; startStep = checkpoint.stepsDone + 1; + // Prefer the checkpoint's own record of the original task -- `task` is + // genuinely ignored on a live resume (see file header), so this is the + // only reliable source once a run is past step 1. Checkpoints saved + // before this field existed won't have it; fall back to whatever the + // caller passed (may be undefined) rather than erroring. + effectiveTask = checkpoint.task || task; } else { // Either no resume_run_id was given, or the checkpoint had already - // expired/wasn't found -- start a fresh run either way rather than - // erroring, since `task` is always available to build one. + // expired/wasn't found -- start a fresh run either way. Requires a real + // `task` (the caller-facing tool in tools.js already guards against a + // missing task on a non-resumable call, so `task` is trustworthy here). runId = randomUUID(); contents = [{ role: "user", parts: [{ text: `${SYSTEM_PREAMBLE}\n\nTask: ${task}` }] }]; transcript = []; @@ -822,12 +835,13 @@ export async function runInvestigation({ task, max_steps = 6, resume_run_id }) { // away. Persist it (redundant with the save at the end of the prior // iteration, but cheap and safe) and hand the caller everything they // need to resume instead of restarting. - await saveCheckpoint(runId, { contents, transcript, stepsDone: step - 1 }); + await saveCheckpoint(runId, { contents, transcript, stepsDone: step - 1, task: effectiveTask }); return { answer: `(Gemini call failed on step ${step}: ${err.message} -- ${transcript.length} tool call(s) already completed this run are saved. Call gemini_investigate again with resume_run_id: "${runId}" to continue from here instead of starting over. Checkpoint expires in 1 hour.)`, steps: step - 1, transcript, runId, + task: effectiveTask, failed: true, }; } @@ -839,9 +853,9 @@ export async function runInvestigation({ task, max_steps = 6, resume_run_id }) { const answer = parts.map((p) => p.text || "").join("").trim(); await deleteCheckpoint(runId); if (!answer) { - return { answer: `(Gemini stopped without a final answer -- finishReason: ${candidate.finishReason || "unknown"})`, steps: step, transcript, runId }; + return { answer: `(Gemini stopped without a final answer -- finishReason: ${candidate.finishReason || "unknown"})`, steps: step, transcript, runId, task: effectiveTask }; } - return { answer, steps: step, transcript, runId }; + return { answer, steps: step, transcript, runId, task: effectiveTask }; } // Record the model's turn (including its functionCall parts) before @@ -870,13 +884,30 @@ export async function runInvestigation({ task, max_steps = 6, resume_run_id }) { // original functionCall.id so the API can thread multi-call turns correctly. responseParts.push({ functionResponse: { name, id, response: { result: resultText } } }); } + // Step-budget reminder (added after the 2026-07-26 resume-truncation + // bug): SYSTEM_PREAMBLE and the task's own formatting instructions only + // ever appear once, in turn 1 -- by the last couple of steps before + // cappedSteps, those instructions are many turns back in a long tool-use + // history, and a model under a tight remaining budget has an incentive + // to produce SOME answer rather than none, which can mean quietly + // dropping the originally requested format/exhaustiveness. Surfacing the + // remaining-step count explicitly turns a silent quality regression into + // an honest one: the model is told to say it couldn't finish, rather + // than presenting a rushed, incomplete answer as if it were complete. + const remainingAfterThisStep = cappedSteps - step; + if (remainingAfterThisStep <= 1) { + responseParts.push({ + text: `[SYSTEM NOTE: only ${remainingAfterThisStep} step(s) remain before this investigation is forced to stop. If you cannot fully complete the task -- including any specific format requested (e.g. an exhaustive table, per-item breakdown) -- in the remaining budget, say so explicitly and describe what's missing, rather than presenting a partial or reformatted-for-brevity answer as if it were complete.]`, + }); + } + contents.push({ role: "user", parts: responseParts }); // Checkpoint after every fully-completed step, so a failure on the NEXT // Gemini call (or a hosting-platform timeout) doesn't lose this one. - await saveCheckpoint(runId, { contents, transcript, stepsDone: step }); + await saveCheckpoint(runId, { contents, transcript, stepsDone: step, task: effectiveTask }); } await deleteCheckpoint(runId); - return { answer: `(Investigation stopped after reaching the step cap of ${cappedSteps} without a final answer -- the task may need to be narrowed, or more steps requested up to the hard cap of ${HARD_MAX_STEPS}.)`, steps: cappedSteps, transcript, runId }; + return { answer: `(Investigation stopped after reaching the step cap of ${cappedSteps} without a final answer -- the task may need to be narrowed, or more steps requested up to the hard cap of ${HARD_MAX_STEPS}.)`, steps: cappedSteps, transcript, runId, task: effectiveTask }; } diff --git a/connectors/gemini/tools.js b/connectors/gemini/tools.js index 79fa5bb..80368e7 100644 --- a/connectors/gemini/tools.js +++ b/connectors/gemini/tools.js @@ -97,12 +97,25 @@ export function register(server) { "delegate_gemini", "Delegate an open-ended, multi-step READ-ONLY investigation to Gemini instead of doing it yourself one tool call at a time. Gemini runs its own loop server-side -- reading GitHub files/trees/commits, Cloudflare Workers logs, and Notion pages/databases across as many turns as it needs (bounded by max_steps) -- and returns one synthesized answer. Use this for things like \"why is CI failing on PR #42\" or \"summarize what changed in this repo over the last week\" where you'd otherwise need 5-10 separate manual tool calls. Not for anything requiring a write -- this tool is read-only by design. If a run fails partway through (e.g. Gemini rate-limited), the response includes a resume_run_id -- pass it back on a follow-up call to continue from the last completed step instead of starting over. (Formerly named gemini_investigate.)", { - task: z.string().describe("The investigation task/question, described with enough context (repo names, time ranges, etc.) for Gemini to act without needing to ask you anything back -- it can't. Ignored when resume_run_id resolves to a live checkpoint (the original task from that run is reused)."), + task: z.string().optional().describe("The investigation task/question, described with enough context (repo names, time ranges, etc.) for Gemini to act without needing to ask you anything back -- it can't. Ignored when resume_run_id resolves to a live checkpoint (the original task from that run is reused). Optional ONLY when resume_run_id is given and its checkpoint is still live; required otherwise -- omitting it on a fresh run (no resume_run_id, or an expired one) returns an error rather than silently proceeding with no task."), max_steps: z.number().optional().describe("Max tool-use turns Gemini gets before being forced to answer (default 6, hard cap 20 regardless of this value). On a resumed run this is the new ceiling, not additional steps on top of what's already done."), log_to_notion: z.boolean().optional().describe("Whether to log the task, step-by-step tool calls, and final answer as a page under the Gemini section of Notion (default: false). Write always targets the fixed Gemini root page."), resume_run_id: z.string().optional().describe("A runId returned from a previous failed/partial gemini_investigate call. If its checkpoint is still live (1 hour TTL), continues that run's conversation instead of starting fresh."), }, async ({ task, max_steps = 6, log_to_notion = false, resume_run_id }) => { + // task is only genuinely optional when resuming a live checkpoint -- + // runInvestigation ignores task entirely in that branch (it rebuilds + // `contents` straight from the saved checkpoint). On a fresh run (no + // resume_run_id, or one whose checkpoint already expired), there is no + // saved task to fall back on, so fail loudly here rather than letting + // runInvestigation start a conversation with an undefined task. + if (!task && !resume_run_id) { + return { + content: [{ type: "text", text: "Missing required argument: task must be provided unless resuming a live checkpoint via resume_run_id." }], + isError: true, + }; + } + let result; try { result = await runInvestigation({ task, max_steps, resume_run_id }); @@ -110,14 +123,20 @@ export function register(server) { return { content: [{ type: "text", text: `Investigation failed: ${err.message}` }], isError: true }; } + // On a resumed run, `task` may be undefined here (a fresh run always has + // it, per the guard above) -- runInvestigation returns the effective + // task text it actually used (the caller-supplied one, or the one + // restored from the checkpoint) so logging/titling never has to guess. + const effectiveTask = task || result.task || "(resumed run)"; + let notionNote = ""; if (log_to_notion) { try { const logged = await doCreatePage({ parent_id: GEMINI_NOTION_ROOT_PAGE_ID, parent_type: "page", - title: `${result.failed ? "investigate (partial): " : "investigate: "}${task.slice(0, 80)}`, - content: `Task: ${task}\n\nrunId: ${result.runId}${result.failed ? " (resumable)" : ""}\n\nSteps taken: ${result.steps}\n\nTool calls:\n${result.transcript.join("\n") || "(none)"}\n\nAnswer:\n${result.answer}`, + title: `${result.failed ? "investigate (partial): " : "investigate: "}${effectiveTask.slice(0, 80)}`, + content: `Task: ${effectiveTask}\n\nrunId: ${result.runId}${result.failed ? " (resumable)" : ""}\n\nSteps taken: ${result.steps}\n\nTool calls:\n${result.transcript.join("\n") || "(none)"}\n\nAnswer:\n${result.answer}`, one_off: true, }); notionNote = `\n\n(Logged to Notion: ${logged.url})`; diff --git a/connectors/github/client.js b/connectors/github/client.js index 15be2f6..55fb5c2 100644 --- a/connectors/github/client.js +++ b/connectors/github/client.js @@ -13,7 +13,7 @@ import { function assertConfigured() { if (!GITHUB_TOKEN) { throw new Error( - "GITHUB_TOKEN is not set. Add it as an environment variable on the Manufact server." + "GITHUB_TOKEN is not set. Add it as an environment variable on the madmcp server." ); } } @@ -87,7 +87,7 @@ async function doFetch(path, { method, body, accept }) { Accept: accept || "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28", "Content-Type": "application/json", - "User-Agent": "manufact-mcp-server", + "User-Agent": "madmcp-server", }, body: body ? JSON.stringify(body) : undefined, }); @@ -135,7 +135,7 @@ export async function githubGraphQL(query, variables = {}) { headers: { Authorization: `Bearer ${GITHUB_TOKEN}`, "Content-Type": "application/json", - "User-Agent": "manufact-mcp-server", + "User-Agent": "madmcp-server", }, body: JSON.stringify({ query, variables }), }); diff --git a/connectors/mem/client.js b/connectors/mem/client.js index 367a6d7..24a2e29 100644 --- a/connectors/mem/client.js +++ b/connectors/mem/client.js @@ -7,7 +7,7 @@ import { MEM0_API_KEY, MEM0_API } from "../../config.js"; export async function mem0Request(path, { method = "GET", body } = {}) { - if (!MEM0_API_KEY) throw new Error("MEM0_API_KEY is not set. Add it as an environment variable on the Manufact server."); + if (!MEM0_API_KEY) throw new Error("MEM0_API_KEY is not set. Add it as an environment variable on the madmcp server."); const res = await fetch(`${MEM0_API}${path}`, { method, headers: { diff --git a/connectors/mem/tools.js b/connectors/mem/tools.js index dcf7303..b4ade63 100644 --- a/connectors/mem/tools.js +++ b/connectors/mem/tools.js @@ -103,7 +103,7 @@ // content distinct. // // NOTE on relations (2026-07-13, relational-info step of the anti-bloat plan -// rev 2 — storage/write-side only, see manufact-mem0-relations-plan): +// rev 2 — storage/write-side only, see madmcp-mem0-relations-plan): // mem0_add/mem0_add_batch/mem0_update accept an optional `relations` array // of {to_entity_id, relation}, stored under metadata.relations — same // "store in metadata, resolve client-side" mechanism as tags/entity_id/ @@ -143,7 +143,7 @@ // patched and deleted in the same call, though that's not a real use case). // // NOTE on relations traversal/read-side (2026-07-13, completes -// manufact-mem0-relations-plan's relational-info step): +// madmcp-mem0-relations-plan's relational-info step): // Adds findReferencingEntities (reverse lookup — who points AT this // entity_id, since relations are stored one-directional on the source // memory only), a resolveRelationTarget helper that distinguishes three @@ -297,7 +297,7 @@ function filterFlaggedDuplicates(memories, flaggedOnly) { return memories.filter((m) => Array.isArray(m.metadata?.possible_duplicate_of) && m.metadata.possible_duplicate_of.length); } -// REGRESSION FIX (2026-07-13, see manufact-mem0-relations-plan): the +// REGRESSION FIX (2026-07-13, see madmcp-mem0-relations-plan): the // /v3/memories/ list endpoint does not reliably surface metadata.relations // contents, even though it does reliably surface metadata.entity_id (which // is why entity_id matching below still works off list results directly). @@ -374,7 +374,7 @@ async function findByEntityIdAnyScope({ user_id, entity_id }) { // single direct lookup. Returns [{ fromEntityId, fromId, relation }, ...]. // Same ~1000-memory-per-scope pagination ceiling as findByEntityId. // -// REGRESSION FIX (2026-07-13, see manufact-mem0-relations-plan and the note +// REGRESSION FIX (2026-07-13, see madmcp-mem0-relations-plan and the note // above findByEntityId): list-page results can't be trusted for // metadata.relations, so every candidate in every page gets refetched via // fetchSingleForMetadata (single-get) before its relations are inspected. @@ -525,7 +525,7 @@ async function findPossibleDuplicates({ user_id, agent_id, run_id, content, thre return memories.filter((m) => typeof m.score === "number" && m.score >= threshold); } -// NOTE on add-then-verify (2026-07-10, following manufact-mem0-add-silent- +// NOTE on add-then-verify (2026-07-10, following madmcp-mem0-add-silent- // failure-diagnostic): /v3/memories/add/ returning a 2xx with an event_id // only means Mem0 ACCEPTED the job, not that its async extraction/indexing // pipeline actually materialized the memory — that step has been observed @@ -736,7 +736,7 @@ export function register(server) { const landed = await verifyLanded({ user_id, agent_id, run_id, entity_id, content }); const landedNote = landed ? ` Confirmed landed (id: ${landed.id}).` - : `\n\n⚠ Could not confirm this memory landed after several verification attempts — Mem0's async job may have silently failed (see manufact-mem0-add-silent-failure-diagnostic). Re-run mem0_search/mem0_list shortly to check, and retry mem0_add if it's still missing.`; + : `\n\n⚠ Could not confirm this memory landed after several verification attempts — Mem0's async job may have silently failed (see madmcp-mem0-add-silent-failure-diagnostic). Re-run mem0_search/mem0_list shortly to check, and retry mem0_add if it's still missing.`; const relationNote = relationWarnings.length ? `\n\n⚠ Relations:\n${relationWarnings.map((w) => ` ${w}`).join("\n")}` : ""; return { content: [{ diff --git a/connectors/notion/client.js b/connectors/notion/client.js index 9b35efa..9f90c89 100644 --- a/connectors/notion/client.js +++ b/connectors/notion/client.js @@ -5,7 +5,7 @@ import { NOTION_TOKEN, NOTION_API, NOTION_VERSION, NOTION_INDEX_DATABASE_ID } from "../../config.js"; export async function notionRequest(path, { method = "GET", body } = {}) { - if (!NOTION_TOKEN) throw new Error("NOTION_TOKEN is not set. Add it as an environment variable on the Manufact server."); + if (!NOTION_TOKEN) throw new Error("NOTION_TOKEN is not set. Add it as an environment variable on the madmcp server."); const res = await fetch(`${NOTION_API}${path}`, { method, headers: { diff --git a/package-lock.json b/package-lock.json index 3827f55..0a112dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "manufact", - "version": "2.0.0", + "name": "madmcp", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "manufact", - "version": "2.0.0", + "name": "madmcp", + "version": "2.1.0", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@modelcontextprotocol/sdk": "^1.12.0", diff --git a/package.json b/package.json index 28bf67d..9576f3e 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "manufact", + "name": "madmcp", "version": "2.1.0", "description": "MCP server with GitHub, Cloudflare, Notion, Mem0, Context7, Gemini-powered delegation, and Fetch connectors.", "main": "server.js", diff --git a/render.yaml b/render.yaml index af74ea8..a0c65e2 100644 --- a/render.yaml +++ b/render.yaml @@ -1,6 +1,6 @@ services: - type: web - name: manufact-mcp-server + name: madmcp-server runtime: node plan: free buildCommand: npm install diff --git a/server.js b/server.js index a0b59a7..cda29b5 100644 --- a/server.js +++ b/server.js @@ -23,7 +23,7 @@ import * as sync from "./connectors/sync/mem0_notion.js"; // Build the MCP server once at startup and reuse it across all requests. const mcpServer = new McpServer({ - name: "manufact-mcp-server", + name: "madmcp-server", version: "2.1.0", }); @@ -116,7 +116,7 @@ app.use(express.json({ limit: "10mb" })); app.get("/", requireMcpKey, requireAllowedIp, (_req, res) => { res.json({ status: "ok", - service: "manufact-mcp-server", + service: "madmcp-server", version: "2.1.0", configured: { github: Boolean(GITHUB_TOKEN), @@ -151,7 +151,7 @@ app.post("/mcp/:key", mcpLimiter, requireMcpKey, requireAllowedIp, handleMcp); const PORT = process.env.PORT || 8080; app.listen(PORT, () => { - console.log(`manufact-mcp-server v2.1.0 listening on port ${PORT}`); + console.log(`madmcp-server v2.1.0 listening on port ${PORT}`); if (!GITHUB_TOKEN) console.warn("WARNING: GITHUB_TOKEN is not set."); if (!NOTION_TOKEN) console.warn("WARNING: NOTION_TOKEN is not set. Notion tools will fail."); if (!MEM0_API_KEY) console.warn("WARNING: MEM0_API_KEY is not set. Mem0 tools will fail."); diff --git a/test/version-sync.test.js b/test/version-sync.test.js index 03dab5c..22695a6 100644 --- a/test/version-sync.test.js +++ b/test/version-sync.test.js @@ -16,10 +16,10 @@ describe("version sync", () => { // and not the other (or to server.js but not package.json) fails CI // immediately instead of silently drifting again. const mcpServerMatch = serverSrc.match(/new McpServer\(\{[^}]*version:\s*"([^"]+)"/s); - const logMatch = serverSrc.match(/manufact-mcp-server v([\d.]+) listening/); + const logMatch = serverSrc.match(/madmcp-server v([\d.]+) listening/); expect(mcpServerMatch, "Could not find McpServer({ version: \"...\" }) in server.js -- update this test's regex if that constructor call changed shape.").not.toBeNull(); - expect(logMatch, "Could not find the 'manufact-mcp-server vX.Y.Z listening' startup log in server.js -- update this test's regex if that message changed.").not.toBeNull(); + expect(logMatch, "Could not find the 'madmcp-server vX.Y.Z listening' startup log in server.js -- update this test's regex if that message changed.").not.toBeNull(); expect(mcpServerMatch[1]).toBe(pkg.version); expect(logMatch[1]).toBe(pkg.version);