SF Tech Map

SF Tech Map

Developers: MCP server and REST API.

The same verified dataset that powers the map, as a free Model Context Protocol server and a read-only JSON API. San Francisco city limits only. No sign-in. Every response carries the dataset version and the attribution line below.

MCP endpoint

https://sftechmap.app/mcp

REST base

https://sftechmap.app/v1

What it covers

Startups, scaleups, big tech, corporates, VCs, accelerators, coworking spaces, hacker houses, community spaces, cafes and landmarks inside San Francisco, plus events, a public change log and editorial walking tours. Facts are bands (funding, headcount, check size) and our own words; every record cites its sources with retrieval dates. Points are public points: hacker houses and residential addresses are never more precise than a neighbourhood centroid, and an exact address always has a first-party, press or public-domain source behind it.

Place vocabulary: 8 districts (soma-south-beach, fidi-jackson-square, mission-bay-dogpatch, mission-potrero, hayes-valley, presidio-marina, civic-center-mid-market, outer-sf), the 41 DataSF analysis neighbourhoods, and the aliases people use (Cerebral Valley, Area AI, Showplace Square, FiDi, Dogpatch). list_neighborhoods returns all of them.

Licence and attribution

Data: Creative Commons Attribution 4.0. Code: MIT. Logos are never redistributed (only a domain). The one rule: show the attribution wherever you show the data.

Data: SF Tech Map, CC BY 4.0, https://sftechmap.app

Machine-readable: meta.attribution and meta.license_url on every response. Upstream credits are in CREDITS.md and the licence summary on /about.

Connect the MCP server

Streamable HTTP, protocol 2026-07-28 with a stateless fallback for 2025-era clients. No OAuth, no API key; tool results include structuredContent validated against each tool's output schema and a compact text block.

Claude.ai (custom connector)

Settings, Connectors, Add custom connector. Name: SF Tech Map. URL: https://sftechmap.app/mcp. Authentication: No sign in. Enable it in a chat and ask “which VCs are within walking distance of Jackson Square?”.

Claude Code

claude mcp add --transport http sftechmap https://sftechmap.app/mcp

Cursor

Add to .cursor/mcp.json (project) or ~/.cursor/mcp.json (global):

{
  "mcpServers": {
    "sftechmap": { "url": "https://sftechmap.app/mcp" }
  }
}

ChatGPT (developer mode)

Settings, Connectors, Advanced, Developer mode, Create. URL https://sftechmap.app/mcp, no authentication. The search and fetch tools follow the connector contract (search returns ids, fetch returns a document); the other tools are available in developer mode. Availability depends on your ChatGPT plan.

Any client, by hand

curl -s -X POST https://sftechmap.app/mcp \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Tools

All tools are read-only, idempotent and closed-world (our dataset only). Lists default to 10 items and cap at 25; responses stay under 30 KB and say has_more when trimmed.

ToolDoesREST
changes_sincePublic change log since a dateGET /v1/changes
events_this_weekUpcoming events in a windowGET /v1/events
get_entityFull entity record by slugGET /v1/entities/{slug}
get_neighborhoodNeighbourhood profileGET /v1/neighborhoods/{slug}
get_tourFull tour with stops and legsGET /v1/tours/{slug}
list_coworkingCoworking, community spaces and cafesGET /v1/coworking
list_investorsInvestors with an SF officeGET /v1/investors
list_neighborhoodsNeighbourhoods and districtsGET /v1/neighborhoods
list_toursPublished walking toursGET /v1/tours
nearbyEntities within walking distanceGET /v1/nearby
resolve_placeResolve a place name to a pointGET /v1/places/resolve
search_entitiesSearch entitiesGET /v1/entities
statsDataset statisticsGET /v1/stats

Connector aliases: fetch and search (for ChatGPT-style clients) mirror get_entity and search_entities.

Resources: sftechmap://entities/{slug}, sftechmap://neighborhoods/{slug}, sftechmap://tours/{slug}, sftechmap://dataset/version. Prompts: first_week_in_sf, investor_coffee_route, founder_office_hunt, whats_new_near_me.

REST

Every tool has a GET twin under https://sftechmap.app/v1 with the same parameter names (arrays comma-separated, near=lat,lng). The full description is at /openapi.json (OpenAPI 3.1). 200 responses carry a weak ETag (a digest of the body; send it back as If-None-Match for a 304), X-Dataset-Version, a Link: rel="next" header on paged lists, CORS for any origin and Cache-Control: public, s-maxage=300, stale-while-revalidate=86400. The Vercel CDN consumes s-maxage and stale-while-revalidate (clients see public): a response can be served for 5 minutes and a stale copy for up to a day while it revalidates, so compare the ETag or X-Dataset-Version to notice a newer dataset.

A 304 Not Modified is answered by the edge and carries only Cache-Control, Date, ETag and Vary: no CORS, X-Dataset-Version or RateLimit-* headers, and it still counts against the rate limit. Browser code should let the HTTP cache revalidate (plain fetch, no hand-set If-None-Match, which fails the CORS check); a matching ETag already means the body is unchanged.

# Coworking near the Ferry Building with day passes
curl -s 'https://sftechmap.app/v1/nearby?lat=37.7955&lng=-122.3937&radius_m=1200&kinds=coworking&tags=day-pass'

# VCs in Jackson Square (FiDi district), sorted by name
curl -s 'https://sftechmap.app/v1/entities?kinds=vc&neighborhood=fidi-jackson-square&sort=name'

# One record, then revalidate cheaply
curl -si 'https://sftechmap.app/v1/entities/example-robotics' | grep -i etag
curl -si -H 'If-None-Match: W/"..."' 'https://sftechmap.app/v1/entities/example-robotics'

# What changed in the last 30 days, as GeoJSON-friendly cards
curl -s 'https://sftechmap.app/v1/changes?since=2026-09-07'

# Everything as GeoJSON
curl -s 'https://sftechmap.app/v1/entities?format=geojson&limit=25'

Submissions: POST https://sftechmap.app/api/submit with {kind, entity_slug?, payload, source_url, license_ack: true}. Submissions are CC0 and reviewed against their source before anything is published. The route answers 202 only when the submission was stored; if it cannot store one it answers 503 application/problem+json.

Embed this

A compact, framable list of places for your own page. No script, no sign-in; each row opens the full map in a new tab and the CC BY attribution line is part of the frame. Only /embed can be framed; the rest of the site cannot.

<iframe
  src="https://sftechmap.app/embed?n=soma&k=startup,vc&limit=8"
  title="SF tech places"
  width="100%" height="560" style="border:0;max-width:560px"
  loading="lazy"></iframe>
  • n neighbourhood or district slug, e.g. soma or hayes-valley.
  • k comma list of kinds, e.g. startup,vc,coworking.
  • limit 1 to 20 rows (default 8).
  • tour a tour slug, e.g. vc-row; shows its stops in order and ignores n and k.

Rate limits

Anonymous REST: 60 requests per minute per IP (an unverified Authorization header does not open a separate budget). MCP: 60 tools/call per minute per client (IP plus client name, because hosted assistants share egress IPs) within an overall per-IP ceiling across all client names; tools/list, server/discover, initialize and ping are never limited. Loop guard: the same tool with identical arguments more than 10 times in 30 seconds gets a 429. 429 responses include RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and Retry-After; cacheable REST 200s do not carry them because a CDN hit never reaches the limiter, while MCP responses do. Counters are shared across servers once the shared store is enabled and best-effort per server instance until then. Keys with higher budgets come after launch.

Errors

REST errors are RFC 9457 application/problem+json with a hint that says what to change. MCP tool errors come back as isError: true, never as a JSON-RPC failure, so an agent can correct itself: schema errors name the offending field, and data errors (unknown slug, a point outside San Francisco) add a Hint: line.

validation
400. A query parameter or argument is out of range or malformed; `errors[]` lists paths and `hint` says what to change.
bad-request
400. The body is not JSON, or the Mcp-Method header disagrees with the JSON-RPC body.
not-found
404. Unknown slug, or the API is not enabled for this city.
method-not-allowed
405 with Allow: GET, HEAD, OPTIONS. The /v1 API is read-only; submissions go to POST /api/submit.
payload-too-large
413. Bodies are capped at 64 KB.
unsupported-media-type
415. POST bodies must be application/json.
rate-limited
429 with Retry-After. The per-minute budget or the loop guard tripped.
disabled
503 with Retry-After. The kill switch is on for maintenance, or (POST /api/submit) the server cannot store submissions right now; nothing was stored.
internal
500. Our fault; the dataset version in the problem helps us reproduce it.
{
  "type": "https://sftechmap.app/developers#problem-validation",
  "title": "Invalid request",
  "status": 400,
  "detail": "One or more query parameters are invalid.",
  "instance": "/v1/nearby?lat=91",
  "errors": [{ "path": "lat", "message": "Too big: expected number to be <=90" }],
  "hint": "lat: Too big: expected number to be <=90"
}

Terms

Use the data for anything, commercial included, with attribution. No scraping of the map UI (the API is faster). Do not republish residential addresses or try to sharpen neighbourhood-level points; opt-outs are honoured within 72 hours at /legal/remove. The API is provided as is; breaking changes go to /v2 with a 12-month overlap, and MCP tool changes are additive only.

Registry manifest: /server.json. Orientation for models: /llms.txt.