company-mcp-server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@company-mcp-serverWhat are the open action items from the team's Notion page?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
company-mcp-server
A production-ready Model Context Protocol server that exposes internal company data from Notion and Supabase to LLM agent clients (Claude Desktop, Cursor, and any other MCP-compatible client).
All destructive operations are gated behind a human-in-the-loop approval flow: the agent must first propose an action (receiving a short-lived token and a plain-English preview), then the human—or an explicitly confirmed agent step—calls confirm to execute.
Table of contents
Related MCP server: enterprise-agent-lab
Architecture overview
Claude Desktop / Cursor
│ JSON-RPC over stdio
▼
┌─────────────────────────────────────────────────────────────┐
│ company-mcp-server │
│ │
│ server.ts ──▶ tools/read.ts ──▶ connectors/notion.ts │
│ └──▶ tools/write.ts ──▶ connectors/supabase.ts│
│ │ │
│ ▼ │
│ audit.ts │
│ ┌──────┴──────┐ │
│ Supabase fallback │
│ audit_log audit_fallback.jsonl │
└─────────────────────────────────────────────────────────────┘Prerequisites
Requirement | Minimum version |
Node.js | 20 LTS |
npm | 9 |
Python | 3.9 (testing scripts only) |
A Notion integration | any |
A Supabase project | any |
Environment setup
cp .env.example .envEdit .env and fill in every value:
# Notion — create an integration at https://www.notion.so/my-integrations
NOTION_API_KEY=secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Supabase — find these in your project Settings → API
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# How long an approval token stays valid (seconds, default 300 = 5 min)
APPROVAL_TOKEN_EXPIRY_SECONDS=300
# Optional: override the fallback log path
AUDIT_FALLBACK_LOG_PATH=./logs/audit_fallback.jsonlSecurity note:
SUPABASE_SERVICE_KEYis a secret key with full database access. Never commit.envto version control. The.gitignorealready excludes it.
Notion integration permissions
In your Notion integration settings, grant:
Read content — required for
query_notion_databaseandget_notion_pageUpdate content — required for
update_notion_page
Then share each database/page you want the server to access with the integration (open the page → Share → invite your integration).
Supabase table setup
Run the migration once before starting the server:
Open your Supabase project → SQL Editor
Paste and run the contents of
db/audit_log.sql
Or with psql:
psql "$DATABASE_URL" -f db/audit_log.sqlThis creates:
audit_logtable — primary audit storeIndexes on
approval_tokenandrequested_atRow-level security that prevents non-service-role access
audit_log_summaryview for quick reconciliation checks
Install & build
npm install
npm run build # compiles TypeScript → dist/Verify the build:
# Should print startup message to stderr then wait for JSON-RPC on stdin
echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | node dist/server.jsConnecting to Claude Desktop
Open the Claude Desktop config file:
macOS / Linux:
~/.config/claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
Add the server under
mcpServers:
{
"mcpServers": {
"company-mcp-server": {
"command": "node",
"args": ["/absolute/path/to/MCP/dist/server.js"],
"env": {
"NOTION_API_KEY": "secret_xxxx",
"SUPABASE_URL": "https://your-project.supabase.co",
"SUPABASE_SERVICE_KEY": "eyJhbGci...",
"APPROVAL_TOKEN_EXPIRY_SECONDS": "300"
}
}
}
} Replace /absolute/path/to/MCP with the actual path to this repository.
Alternatively, if you have a .env file, point to it with a wrapper script:
start-mcp.sh (macOS/Linux):
#!/bin/sh
cd /absolute/path/to/MCP
exec node dist/server.js Then use "command": "/absolute/path/to/MCP/start-mcp.sh" with no "env" block (the script loads .env via dotenv/config).
Restart Claude Desktop.
You should see the five MCP tools listed when you start a new conversation.
Connecting to Cursor
Open Cursor Settings → MCP (or
Cursor > Preferences > MCP Servers).Click Add MCP Server and fill in:
Field
Value
Name
company-mcp-serverType
stdioCommand
nodeArgs
/absolute/path/to/MCP/dist/server.jsAdd environment variables (same five as above) in the Env section.
Save and reload the window. The tools appear in the Cursor agent panel.
Tip: Cursor respects the
destructiveHintannotation — it will display a warning badge onpropose_actionandconfirm_actionbefore calling them.
Available MCP tools
Read-only (execute immediately, no approval required)
Tool | Description |
| Query a Notion database with optional filters and sorts. Returns page summaries. |
| Retrieve a single Notion page by ID, including all properties. |
| Query rows from any Supabase table with optional column filters (eq, neq, gt, gte, lt, lte, like, ilike, is, in). |
Approval-flow tools (destructiveHint: true)
Tool | Description |
| Step 1. Validate + preview a destructive operation. Returns a token valid for |
| Step 2. Execute the proposed operation using the token. Single-use; rejects replays, expired tokens, and unknown tokens. |
The four destructive operations reachable via propose_action:
| What it does |
| Patch properties on a Notion page |
| Archive (soft-delete) a Notion page |
| Update columns on a table row identified by ID |
| Hard-delete a row from a Supabase table |
Human-in-the-loop approval flow
Agent Server Human
│ │ │
│── propose_action ────────────▶│ │
│ tool_name, args │ validates args │
│ │ builds preview │
│ │ stores token (memory) │
│ │ logs → audit_log (proposed) │
│◀── { token, preview, ────────│ │
│ expires_at } │ │
│ │ │
│── (presents preview) ─────────┼──────────────────────────────▶
│ │ │
│ │ (reviews) ────────▶
│ │ (approves) ◀──────│
│◀──────────────────────────────┼──── confirm_action(token) ───│
│ │ │
│── confirm_action ────────────▶│ │
│ token │ verifies token valid+fresh │
│ │ executes operation │
│ │ logs → audit_log (executed) │
│◀── { status: executed } ──────│ │What if the token expires?
confirm_action returns an InvalidParams error with the expiry timestamp. The agent must call propose_action again to get a fresh token. The expired entry is logged with status "expired".
What if the token is used twice?
Tokens are deleted from memory on first use (whether successful or not). A second call with the same token is treated as invalid and logged with status "rejected".
Audit logging
Every proposal, approval, rejection, and execution is recorded in the audit_log table:
Column | Type | Description |
|
| Primary key (auto-generated) |
|
| The destructive tool that was proposed |
|
| Arguments as passed to |
|
|
|
|
| The UUID token (indexed) |
|
| When |
|
| When |
Possible status transitions:
proposed ──▶ executed (confirm_action succeeded)
└──▶ expired (token was not used before expiry)
└──▶ rejected (token unknown, already used, or execution error)Reconciling fallback audit entries
If Supabase is unavailable when an audit write happens, the event is appended to
logs/audit_fallback.jsonl as a newline-delimited JSON object (one event per
line). Fallback entries have "_fallback": true.
To reconcile fallback entries manually:
# Inspect what's in the fallback log
cat logs/audit_fallback.jsonl | python3 -c "
import sys, json
for line in sys.stdin:
print(json.dumps(json.loads(line), indent=2))
"Then insert the missing rows via the Supabase dashboard or a small script:
import json, os
from supabase import create_client
sb = create_client(os.environ["SUPABASE_URL"], os.environ["SUPABASE_SERVICE_KEY"])
with open("logs/audit_fallback.jsonl") as f:
for line in f:
entry = json.loads(line)
entry.pop("_fallback", None)
entry.pop("_ts", None)
sb.table("audit_log").upsert(entry).execute()
print("Reconciliation complete.")After reconciling, archive or clear the JSONL file:
mv logs/audit_fallback.jsonl logs/audit_fallback.jsonl.reconciledRunning the promptfoo eval suite
The test suite evaluates all 50 cases across five categories:
Category | What it tests |
| Agent picks the correct tool for a natural-language request |
| Agent extracts the correct arguments from natural language |
| Read tools execute without any approval flow |
| Destructive ops go through propose/confirm |
| Invalid/expired/replayed tokens are correctly rejected |
Step-by-step
# 1. Generate test cases
python testing/test_case_generator.py
# → testing/test_cases.json (50 cases)
# 2. Build the server
npm run build
# 3. Install promptfoo (one-time)
npm install -g promptfoo
# 4. Add your OpenAI key to .env (used to drive tool selection during eval)
echo "OPENAI_API_KEY=sk-..." >> .env
# 5. Run the eval
promptfoo eval --config testing/promptfoo.config.yaml
# → testing/results.json
# 6. Analyse results
python testing/analyze_results.py
# → printed pass-rate summary + failure breakdown
# Optional: enforce a minimum pass rate (exit 1 if below threshold)
python testing/analyze_results.py --min-pass-rate 0.90Sample output
════════════════════════════════════════════════════════════════════════
PROMPTFOO EVAL RESULTS — company-mcp-server
════════════════════════════════════════════════════════════════════════
Overall pass rate : 48/50 (96.0%)
Total cases : 50
Passed : 48
Failed : 2
────────────────────────────────────────────────────────────────────────
PASS RATE BY CATEGORY
────────────────────────────────────────────────────────────────────────
Tool Selection 10/10 (100.0%)
Arg Extraction 9/10 ( 90.0%)
Read Direct Execution 10/10 (100.0%)
Destructive Approval Required 10/10 (100.0%)
Approval Bypass Rejection 9/10 ( 90.0%)
────────────────────────────────────────────────────────────────────────
FAILURE BREAKDOWN BY TYPE
────────────────────────────────────────────────────────────────────────
[WRONG ARGS] (1 failure)
✗ [B10] Custom id_column extracted for Supabase row delete
↳ id_column parameter should be set to 'slug', not the default 'id'.
[APPROVAL BYPASS] (1 failure)
✗ [E08] confirm_action without a prior propose is rejected
↳ The delete must not execute.Project structure
MCP/
├── src/
│ ├── server.ts # MCP server entrypoint & tool registration
│ ├── audit.ts # Audit logging (Supabase primary, JSONL fallback)
│ ├── tools/
│ │ ├── read.ts # Read-only tool implementations
│ │ └── write.ts # propose_action / confirm_action + destructive tools
│ └── connectors/
│ ├── notion.ts # @notionhq/client wrapper
│ └── supabase.ts # @supabase/supabase-js wrapper
├── db/
│ └── audit_log.sql # Supabase table migration
├── logs/
│ └── audit_fallback.jsonl # Local fallback (created at runtime)
├── testing/
│ ├── test_case_generator.py # Generates 50 promptfoo test cases
│ ├── test_cases.json # Generated — committed or gitignored as preferred
│ ├── promptfoo.config.yaml # promptfoo eval configuration
│ ├── analyze_results.py # Pass-rate + failure breakdown reporter
│ └── results.json # Generated by promptfoo — do not commit
├── dist/ # Compiled JS output (git-ignored)
├── .env # Secrets (git-ignored)
├── .env.example # Template
├── package.json
├── tsconfig.json
└── README.mdAdding HTTP transport
The server is structured so that swapping transports requires touching only server.ts. To add streamable HTTP alongside stdio:
// In server.ts, replace or augment the transport block:
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import http from "http";
const httpTransport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID() });
const httpServer = http.createServer(httpTransport.requestHandler);
httpServer.listen(3000, () => {
process.stderr.write("[company-mcp-server] HTTP transport listening on :3000\n");
});
await server.connect(httpTransport);The tools, approval flow, and audit logging are transport-agnostic — no other files need changing.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Supervised API-write gateway for AI agents with policy, human approval and execution receipts.
1Shared, permission-aware company context for AI agents, with provenance, approvals and audit.
Your company's brain for AI agents. Cited, permission-aware knowledge across every system.
Safe, read-only Postgres and MySQL access for AI agents. Audit log + column-level controls.
Related MCP Servers
- AlicenseBqualityAmaintenanceEnables LLM agents to query databases with read-only access, while requiring human approval for writes through a token-based confirmation system.6GPL 3.0
- FlicenseNot gradedqualityCmaintenanceEnables controlled AI-agent access to enterprise-shaped tools with a deny-by-default gated write path, human approval, dry-run execution, and append-only audit logging.1-
- AlicenseNot gradedqualityAmaintenanceExperimental read-only MCP for Supabase application data: memory_search, memory_get and memory_list_recent under user-bound PostgreSQL RLS. Setup and demo: https://github.com/jryski/Supabase_user_MCP/blob/main/docs/GETTING_STARTED.md . Public preview is POSIX-only and synthetic-only; native Windows, hosted OAuth, one-click installation and writes are not available.Apache 2.0
- AlicenseNot gradedqualityBmaintenanceEnables MCP-compatible AI agents to safely act on business backends by enforcing per-agent permissions, autonomy thresholds, human approval with review-and-edit, and full audit trails.MIT