Doorknock
Provides tools for interacting with HubSpot CRM, allowing agents to verify the connection, find companies by exact domain, create or update company records, and add timeline notes with lead research and qualification results.
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., "@Doorknockresearch acme.com and score against our lead profile"
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.
Doorknock
A remote MCP server that researches a company from its own domain, judges it against a lead profile you supply, and writes the result into HubSpot.
Everything it reports was read live from the company's own website or its public DNS records, and carries the URL or DNS query that produced it. It buys nothing from a data provider and infers nothing about revenue, headcount or ownership.
Live: https://doorknock-eight.vercel.app MCP endpoint: https://doorknock-eight.vercel.app/mcp OpenAPI: https://doorknock-eight.vercel.app/openapi.json Health: https://doorknock-eight.vercel.app/health
Connect it
claude mcp add --transport http doorknock https://doorknock-eight.vercel.app/mcpAny MCP client that speaks Streamable HTTP can use the same URL. It is POST only: the server is stateless, so GET and DELETE answer 405 by design.
The research tools need no credentials at all. The HubSpot tools need a private
app token with crm.objects.companies.read and .write, supplied as a header:
claude mcp add --transport http doorknock https://doorknock-eight.vercel.app/mcp \
--header "X-HubSpot-Token: pat-na1-..."For anything that does not speak MCP, the identical operations are at
/v1/<operation> and described by /openapi.json, which is generated from the
same constants the router uses so the two cannot drift. That is what a custom
GPT Action or an n8n HTTP node should point at.
Related MCP server: HubSpot Extended MCP Server
The seven tools
Tool | What it does |
| Reads the home page and the DNS. Returns the marketing and CRM tools loaded on the page, which standard pages exist, who handles their email, and whether their outbound email is protected. |
| Runs the research, then scores it against a profile you pass in, returning a tier with the reasoning attached to every rule. |
| MX, SPF and DMARC, read in plain words. Keeps "will my mail reach them" and "does their mail land" apart, because they are different questions. |
| The vocabulary that profile rules are written against: every named fact, what it means, and what its absence does and does not prove. |
| Confirms the token works and reports the portal and scopes, before a write fails at the worst moment. |
| Finds a company by exact domain, so an enrichment does not create a duplicate. |
| Creates or updates the company and optionally adds a timeline note. |
Three decisions worth arguing about
The credential never touches the model. The obvious design is a token
parameter on the HubSpot tools. That puts the secret in the prompt, in the
context window, in the client's transcript and in any log that records tool
calls, and it makes the token something a model can be talked into sending
somewhere else. Here it travels on the HTTP request instead, in an
X-HubSpot-Token header, and no tool schema has a field for it. The model can
ask for a write; it never handles the credential. One of the adversarial checks
exists purely to assert that no tool schema has ever grown a credential field.
The rules are data, not code. Qualification rules arrive with the call and come back with the answer, so the logic that produced a verdict is always visible and changing who counts as a good lead never needs a deployment. A rule naming a signal that does not exist is reported as an unknown signal rather than silently counted as a miss, and a rule whose signal could not be determined blocks the tier instead of failing it, because "we could not tell" and "no" send a lead to different places.
Absence is reported as absence. A tag can load through a tag manager, sit behind a consent banner, or live only on an inner page. So the answer says "not detected on the home page", never "they do not use HubSpot", and every signal carries a sentence about what its absence does not prove.
Run it locally
npm install
npm run typecheck # tsc, no emit
npm run smoke # every operation against the real internet
npm run smoke:mcp # every endpoint over a real node:http server
npm run smoke:live -- https://doorknock-eight.vercel.app # the deployment, via the official client SDK
npm run adversarial -- https://doorknock-eight.vercel.app # tries to break it
npm run prove -- https://doorknock-eight.vercel.app # independent evidenceThere are no mocks anywhere. A mocked resolver proves that my mock returns what I told it to; the failures worth catching are an upstream changing shape, and no mock has ever caught one.
npm run adversarial is the one that finds things. It aims at the cloud
metadata endpoint, throws hostile hostnames, wrong types, 4000-character
domains and malformed protocol frames at the deployed server, and treats a
confident answer to a question the server should have refused as the worst kind
of failure. It found three real defects on its first run.
Checking it without trusting this repository
npm run prove exists because my own tests passing proves very little to
someone who did not write them. It calls the deployed server through the
official MCP client SDK, then re-fetches every DNS fact from a different
resolver than the server used and re-fetches the page straight from the company,
comparing field by field and printing every source URL.
Three more ways that need nothing from here:
# Anthropic's own inspector, not my code
npx @modelcontextprotocol/inspector --cli https://doorknock-eight.vercel.app/mcp --transport http --method tools/list
# raw JSON-RPC, no client, no session, because it is stateless
curl -s -X POST https://doorknock-eight.vercel.app/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# let your own client run its health check against it
claude mcp add --transport http doorknock https://doorknock-eight.vercel.app/mcp
claude mcp listAnd the version that needs only a browser. Open both and compare:
https://doorknock-eight.vercel.app/v1/email-posture?domain=servicem8.com
https://dns.google/resolve?name=_dmarc.servicem8.com&type=TXT
What broke, and what I did about it
Six defects so far. Three came from the adversarial script on its first run against the deployment, one from the independent-verification script, and one was in a test rather than in the server.
1. A refusal wrapped in a success. Asking the server about
169.254.169.254 or metadata.google.internal returned HTTP 200 with a result
object. Nothing leaked: the fetch guard refused the request correctly, so no
internal address was ever contacted. But the research call runs the site fetch
and the DNS lookups in parallel, and the DNS half carried on happily, so the
caller got a 200 wrapped around a refusal. Defence in depth had worked and the
response contract had not. The address rules now run in normaliseDomain, at
the front door, so the whole operation refuses with 400.
2. Redirects were followed by the runtime, which checked nothing. The first
version passed redirect: 'follow' and validated only the first hop. A public
host answering 302 to an internal address would have sailed straight through the
guard. Redirects are now followed by hand, five maximum, with every hop
re-resolved and re-checked before it is taken, and the chain returned in the
answer because where a domain sends you is itself a finding.
3. A 4000-character domain was accepted. No length check anywhere. DNS itself stops at 253 characters and 63 per label, so anything longer cannot be a real name. Now refused with an explanation.
4. Equal-priority MX records came back in a different order every call. Found by the independent-verification script, which reported a difference between what the server said and what a second resolver said. The records were identical; only the order differed, because resolvers rotate equal-priority answers and the sort was by priority alone, which is not a total order. Ties are now broken by host name, so the same records always produce the same output.
5. A test that failed on something it never looked at. The adversarial script truncated every response body to 600 characters before checking it, so the unknown-signal assertion looked for a field about four kilobytes into the response, never saw it, and reported the server broken when the server was correct. That cost more time than any of the real defects. The body is now kept whole and truncated only when it is printed. A test that decides a pass or a fail from something it never actually read is worse than no test, because it is believed.
6. The platform hands the handler a Node IncomingMessage, not a Web
Request. Inherited from the previous server I built this way, and the reason
npm run smoke:mcp stands up a real node:http server and talks to it over a
socket rather than building request objects in memory. A test that builds its
own request shape only ever tests that shape.
Limits, stated plainly
It reads the home page only. A tag loaded through a tag manager or living on an inner page is missed, and the answer says so rather than implying the tool is absent.
It knows nothing about headcount, revenue, ownership or contact names, and will not guess a domain from a company name.
DKIM is not checked. The selector cannot be discovered from DNS, so checking it means guessing, and a guess that misses looks identical to a domain with no DKIM at all.
The address guard resolves the name, checks the addresses, then fetches by name, so a DNS record changing between those two steps is not fully closed. Pinning the connection to the checked address needs an agent this runtime does not expose. The mitigation is that no response body ever reaches the caller raw, only named matched signals, so a successful rebind returns nothing readable to whoever attempted it. This is written in the code as well as here.
How it is laid out
src/core.ts the operations, shared by both front doors
src/mcpServer.ts the MCP tool surface
src/lib/guard.ts what this server is allowed to fetch
src/lib/dns.ts DNS over HTTPS, so every fact has a URL
src/lib/site.ts redirect-by-hand page fetching and reading
src/lib/fingerprints.ts the technology table, one literal string per entry
src/lib/email.ts MX, SPF and DMARC, read in plain words
src/lib/signals.ts the published vocabulary
src/lib/qualify.ts the rules engine
src/lib/hubspot.ts the CRM writes, with the caller's own token
api/ the four endpoints
scripts/ five suites, no mocksMIT licensed.
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 Servers
- AlicenseAqualityDmaintenanceAn MCP server that provides AI agents with ICP Triangulation Framework™ for scoring prospects across firmographics, behaviors, and growth signals, plus RFM analysis and pipeline health scoring, with optional HubSpot integration.3Business Source 1.1
- FlicenseNot gradedqualityDmaintenanceA standalone MCP server that extends HubSpot functionality for post-call processing and pre-call preparation workflows.
- FlicenseNot gradedqualityBmaintenanceAn MCP server that uses a browser extension to interact with HubSpot through the logged-in browser, enabling reading, searching, creating, and updating HubSpot records without API tokens.9
- AlicenseNot gradedqualityBmaintenanceMCP-native sales intelligence server enabling prospect enrichment, LinkedIn scraping, and CRM push to HubSpot/Salesforce via natural language.80Mozilla Public 2.0
Related MCP Connectors
Remote MCP server to enrich company profiles with structured B2B data and confidence scores.
A paid remote MCP for hosted MCP server, built to return verdicts, receipts, usage logs, and audit-r
A paid remote MCP for Skybridge, built to return verdicts, receipts, usage logs, and audit-ready JSO
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Aa-ronJS/doorknock'
If you have feedback or need assistance with the MCP directory API, please join our Discord server