diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for servant-polysemy
+
+## 0.1.0 -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2020, Alex Chapman
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Alex Chapman nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+# servant-polysemy
+
+`servant-polysemy` is a Haskell library that makes it easier to use [Servant](https://hackage.haskell.org/package/servant) and [Polysemy](https://hackage.haskell.org/package/polysemy) together.
+
+## Examples
+
+Check out these examples for how to use it:
+
+- [Server](https://github.com/AJChapman/servant-polysemy/blob/master/example/Server.hs)
+- [Server with Swagger docs](https://github.com/AJChapman/servant-polysemy/blob/master/example/ServerWithSwagger.hs)
+- [Client](https://github.com/AJChapman/servant-polysemy/blob/master/example/Client.hs)
+
+The client will connect to either version of the server and interact with its endpoint, which simply serves up the package version.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/example/Client.hs b/example/Client.hs
new file mode 100644
--- /dev/null
+++ b/example/Client.hs
@@ -0,0 +1,31 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeOperators #-}
+module Main (main) where
+
+import Data.Function ((&))
+import Data.Version (Version, showVersion)
+import Polysemy
+import Polysemy.Error
+import Servant
+import Servant.Client.Streaming (ClientM, client)
+import Servant.Polysemy.Client (ServantClient, runClient, runServantClient)
+
+type MyApi = "api" :> "v1" :> "version" :> Get '[JSON] Version
+
+versionClient :: ClientM Version
+versionClient = client (Proxy @MyApi)
+
+program :: Members '[ServantClient, Embed IO] r => Sem r ()
+program = do
+  ev <- runClient versionClient & runError
+  case ev of
+    Left err ->
+      embed $ putStrLn $ "Client failed: " <> show err
+    Right v ->
+      embed $ putStrLn $ "Received version: " <> showVersion v
+
+main :: IO ()
+main = program
+  & runServantClient "localhost:8080"
+  & runM
diff --git a/example/Server.hs b/example/Server.hs
new file mode 100644
--- /dev/null
+++ b/example/Server.hs
@@ -0,0 +1,27 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeOperators #-}
+module Main (main) where
+
+import Control.Lens.Operators
+import Data.Version (Version, showVersion)
+import Paths_servant_polysemy as Paths
+import Polysemy
+import Polysemy.Error
+import Servant
+import Servant.Polysemy.Server
+
+type MyApi = "api" :> "v1" :> "version" :> Get '[JSON] Version
+
+myServer :: Member (Embed IO) r => ServerT MyApi (Sem (Error ServerError ': r))
+myServer = do
+  embed $ putStrLn $ "Returning version " <> showVersion Paths.version
+  pure Paths.version
+
+main :: IO ()
+main =
+  runWarpServer @MyApi 8080 True myServer
+    & runM
+
diff --git a/example/ServerWithSwagger.hs b/example/ServerWithSwagger.hs
new file mode 100644
--- /dev/null
+++ b/example/ServerWithSwagger.hs
@@ -0,0 +1,57 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeOperators #-}
+module Main (main) where
+
+import Control.Lens.Operators
+import Data.Swagger (Swagger, description, host, info, license, title, version)
+import Data.Text (Text)
+import qualified Data.Text as T
+import Data.Version (Version, showVersion)
+import qualified Paths_servant_polysemy as Paths
+import Polysemy
+import Polysemy.Error
+import Servant
+import Servant.Polysemy.Server
+import Servant.Swagger (toSwagger)
+import Servant.Swagger.UI (SwaggerSchemaUI, swaggerSchemaUIServer)
+
+type MyApi = "api" :> "v1" :> "version" :> Get '[JSON] Version
+
+type SwaggerDocs = "api" :> "v1" :> SwaggerSchemaUI "swagger-ui" "swagger.json"
+
+type MyApiWithSwagger =
+  MyApi
+  :<|> SwaggerDocs
+  :<|> "api" :> "v1" :> Redirect 302 Text -- Redirect /api/v1 to the swagger docs
+  :<|> "api" :> Redirect 302 Text -- Redirect /api to the swagger docs
+  :<|> Redirect 302 Text -- Redirect / to the swagger docs
+
+myServer :: Member (Embed IO) r => ServerT MyApi (Sem (Error ServerError ': r))
+myServer = do
+  embed $ putStrLn $ "Returning version " <> showVersion Paths.version
+  pure Paths.version
+
+mySwagger :: Swagger
+mySwagger = toSwagger (Proxy @MyApi)
+  & info.title       .~ "My API"
+  & info.version     .~ (T.pack . showVersion) Paths.version
+  & info.description ?~ "This is just an example API."
+  & info.license     ?~ "Public Domain"
+  & host             ?~ "localhost:8080"
+
+mySwaggerServer :: Member (Embed IO) r => ServerT MyApiWithSwagger (Sem (Error ServerError ': r))
+mySwaggerServer =
+  myServer
+  :<|> hoistServerIntoSem @SwaggerDocs (swaggerSchemaUIServer mySwagger)
+  :<|> redirect "/api/v1/swagger-ui"
+  :<|> redirect "/api/v1/swagger-ui"
+  :<|> redirect "/api/v1/swagger-ui"
+
+main :: IO ()
+main =
+  runWarpServer @MyApiWithSwagger 8080 True mySwaggerServer
+    & runM
diff --git a/servant-polysemy.cabal b/servant-polysemy.cabal
new file mode 100644
--- /dev/null
+++ b/servant-polysemy.cabal
@@ -0,0 +1,108 @@
+cabal-version:       2.4
+name:                servant-polysemy
+version:             0.1.0
+synopsis:            Utilities for using servant in a polysemy stack 
+description:         It's possible to use servant and polysemy together without this library, but it's not easy. This library makes it easy.
+homepage:         https://github.com/AJChapman/servant-polysemy#readme
+bug-reports:         https://github.com/AJChapman/servant-polysemy/issues
+license:             BSD-3-Clause
+license-file:        LICENSE
+author:              Alex Chapman
+maintainer:          alex@farfromthere.net
+copyright:           2020 Alex Chapman
+category:            Servant, Web
+build-type:          Simple
+extra-source-files:  CHANGELOG.md
+                     README.md
+extra-doc-files:     example/Server.hs
+                     example/ServerWithSwagger.hs
+                     example/Client.hs
+tested-with:           GHC == 8.8.4
+                     , GHC == 8.10.1
+                     --  GHC == 8.4.4 -- Not working due to missing extension BlockArguments
+                     --, GHC == 8.6.5 -- Not working due to Polysemy.Plugin not found when generating haddocks
+
+common deps
+  build-depends:       base             >= 4.11.0.0 && < 5
+                     , deepseq          >= 1.4.3.0 && < 1.5
+                     , http-client     ^>= 0.6.4.1
+                     , http-client-tls ^>= 0.3.5.3
+                     , mtl             ^>= 2.2.2
+                     , polysemy        ^>= 1.3.0.0
+                     , polysemy-plugin ^>= 0.2.4.0
+                     , polysemy-zoo    ^>= 0.7.0.1
+                     , servant-server   >= 0.16 && < 0.19
+                     , servant-client   >= 0.16 && < 0.19
+                     , wai             ^>= 3.2.1.2
+                     , warp             >= 3.2.22 && < 3.4
+
+-- Warnings list list taken from
+-- https://medium.com/mercury-bank/enable-all-the-warnings-a0517bc081c3
+-- Enable all warnings with -Weverything, then disable the ones we
+-- don’t care about
+  default-language:  Haskell2010
+  ghc-options:       -Weverything
+                     -Wno-all-missed-specialisations
+                     -Wno-implicit-prelude
+                     -Wno-missed-specialisations
+                     -Wno-missing-exported-signatures
+                     -Wno-missing-import-lists
+                     -Wno-missing-local-signatures
+                     -Wno-monomorphism-restriction
+                     -Wno-missing-deriving-strategies
+                     -Wno-safe
+                     -Wno-unsafe
+                     -fprint-potential-instances
+                     -fplugin=Polysemy.Plugin
+  if impl(ghc >= 8.10)
+    ghc-options:     -Wno-prepositive-qualified-module
+                     -Wno-missing-safe-haskell-mode
+
+library
+  import:              deps
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  exposed-modules:     Servant.Polysemy.Client
+                       Servant.Polysemy.Server
+                       Paths_servant_polysemy
+  autogen-modules:     Paths_servant_polysemy
+
+executable example-server
+  import:            deps
+  main-is:             Server.hs
+  hs-source-dirs:      example
+  default-language:    Haskell2010
+  ghc-options:       -threaded
+                     -rtsopts
+                     -with-rtsopts=-N
+  build-depends:       servant-polysemy
+                     , lens
+
+executable example-server-with-swagger
+  import:            deps
+  main-is:             ServerWithSwagger.hs
+  hs-source-dirs:      example
+  default-language:    Haskell2010
+  ghc-options:       -threaded
+                     -rtsopts
+                     -with-rtsopts=-N
+  build-depends:       servant-polysemy
+                     , lens
+                     , servant-swagger    ^>= 1.1
+                     , servant-swagger-ui ^>= 0.3
+                     , swagger2            >= 2.4 && < 2.7
+                     , text               ^>= 1.2.3.1
+
+executable example-client
+  import:            deps
+  main-is:             Client.hs
+  hs-source-dirs:      example
+  default-language:    Haskell2010
+  ghc-options:       -threaded
+                     -rtsopts
+                     -with-rtsopts=-N
+  build-depends:       servant-polysemy
+
+source-repository head
+  type:     git
+  location: https://github.com/AJChapman/servant-polysemy
diff --git a/src/Servant/Polysemy/Client.hs b/src/Servant/Polysemy/Client.hs
new file mode 100644
--- /dev/null
+++ b/src/Servant/Polysemy/Client.hs
@@ -0,0 +1,139 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE InstanceSigs #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE PolyKinds #-}
+{-# LANGUAGE Rank2Types #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeOperators #-}
+{-|
+Module      : Servant.Polysemy.Client
+Copyright   : (c) 2020 Alex Chapman
+License     : BSD3
+Maintainer  : alex@farfromthere.net
+Stability   : experimental
+Portability : GHC
+Description : A Polysemy effect for running Servant commands (ClientM).
+
+This module allows you to act as a client of a Servant API, within a Polysemy 'Sem'.
+Use the servant-client package to generate your clients, which return in the 'ClientM' monad.
+You can then use 'runClient' (or 'runClientStreaming') to run your client in 'Sem', and 'runServantClient' (or 'runServantClientStreaming') to interpret the effect.
+
+See <example/Client.hs> for a simple example that can interact with the example servers in the same directory.
+-}
+module Servant.Polysemy.Client
+  (
+  -- * Effects
+  -- ** Non-Streaming
+    ServantClient
+  , runClient'
+  , runClient
+
+  -- ** Streaming
+  , ServantClientStreaming
+  , runClientStreaming
+
+  -- * Interpreters
+  -- ** Non-Streaming
+  , runServantClientUrl
+  , runServantClient
+
+  -- ** Streaming
+  , runServantClientStreamingUrl
+  , runServantClientStreaming
+
+  -- * Re-exported from Servant
+  , ClientError
+  ) where
+
+import Control.DeepSeq (NFData)
+import Control.Monad ((>=>))
+import Network.HTTP.Client (newManager)
+import Network.HTTP.Client.TLS (tlsManagerSettings)
+import Polysemy
+import Polysemy.Cont
+import Polysemy.Error
+import Servant.Client.Streaming
+       ( BaseUrl
+       , ClientError
+       , ClientM
+       , mkClientEnv
+       , parseBaseUrl
+       , runClientM
+       , withClientM
+       )
+
+-- | The 'ServantClient' effect allows you to run a 'ClientM' as automatically generated for your API by the servant-client package.
+data ServantClient m a where
+  RunClient' :: NFData o => ClientM o -> ServantClient m (Either ClientError o)
+
+makeSem ''ServantClient
+
+-- | Run this 'ClientM' in the 'Sem' monad.
+runClient
+  :: (Members '[ServantClient, Error ClientError] r, NFData o)
+  => ClientM o -> Sem r o
+runClient = runClient' >=> fromEither
+
+-- | Interpret the 'ServantClient' effect by running any calls to 'RunClient'' against the given 'BaseUrl'.
+runServantClientUrl
+  :: Member (Embed IO) r
+  => BaseUrl -> Sem (ServantClient ': r) a -> Sem r a
+runServantClientUrl server m = do
+  manager <- embed $ newManager tlsManagerSettings
+  let env = mkClientEnv manager server
+  interpret (\case
+    RunClient' client ->
+      embed $ runClientM client env
+    ) m
+
+-- | Parse the given string as a URL and then behave as 'runServantClientUrl' does.
+runServantClient
+  :: Member (Embed IO) r
+  => String -> Sem (ServantClient ': r) a -> Sem r a
+runServantClient server m = do
+  server' <- embed $ parseBaseUrl server
+  runServantClientUrl server' m
+
+-- | The 'ServantClientStreaming' effect is just like the 'ServantClient' effect,
+-- but allows streaming connections.
+data ServantClientStreaming m a where
+  RunClientStreaming :: ClientM o -> ServantClientStreaming m o
+
+makeSem ''ServantClientStreaming
+
+-- | Interpret the 'ServantClientStreaming' effect by running any calls to 'RunClientStreaming' against the given URL.
+-- Note that this adds a 'Cont' effect, which you can interpret using 'runContM', probably just before your call to 'runM'.
+runServantClientStreamingUrl
+  :: Members
+    '[ Cont ref
+     , Embed IO
+     , Error ClientError
+     ] r
+  => BaseUrl -> Sem (ServantClientStreaming ': r) a -> Sem r a
+runServantClientStreamingUrl server m = do
+  manager <- embed $ newManager tlsManagerSettings
+  let env = mkClientEnv manager server
+  interpret (\case
+    RunClientStreaming client ->
+      subst (\continue ->
+        withLowerToIO $ \unliftIO _ ->
+          withClientM client env (unliftIO . jump continue)
+        ) fromEither
+    ) m
+
+-- | Parse the given string as a URL and then behave as 'runServantClientStreamingUrl'.
+runServantClientStreaming
+ :: Members
+    '[ Cont ref
+     , Embed IO
+     , Error ClientError
+     ] r
+  => String -> Sem (ServantClientStreaming ': r) a -> Sem r a
+runServantClientStreaming server m = do
+  server' <- embed $ parseBaseUrl server
+  runServantClientStreamingUrl server' m
diff --git a/src/Servant/Polysemy/Server.hs b/src/Servant/Polysemy/Server.hs
new file mode 100644
--- /dev/null
+++ b/src/Servant/Polysemy/Server.hs
@@ -0,0 +1,149 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeOperators #-}
+{-|
+Module      : Servant.Polysemy.Server
+Copyright   : (c) 2020 Alex Chapman
+License     : BSD3
+Maintainer  : alex@farfromthere.net
+Stability   : experimental
+Portability : GHC
+Description : Utilities for running a Servant server in a polysemy stack using Warp.
+
+A simple usage scenario is that you create your API,
+then implement a server for it in a 'ServerT api (Sem (Error ServerError ': r))' monad (where 'api' is your API type),
+then run it with 'runWarpServer'.
+See <example/Server.hs> for a trivial example of this.
+
+If you need to take your Servant-Polysemy server and run it in an ordinary Servant server then you can use 'hoistServerIntoSem'.
+This can be used to e.g. add Swagger docs to your server, as in <example/ServerWithSwagger.hs>.
+-}
+module Servant.Polysemy.Server
+  (
+  -- * Use ordinary Servant code in a Polysemy 'Sem'
+    hoistServerIntoSem
+  , liftHandler
+
+  -- * Use Servant-Polysemy code in an ordinary Servant/WAI system
+  , serveSem
+  , semHandler
+
+  -- * Use Warp to serve a Servant-Polysemy API in a 'Sem' stack.
+  , runWarpServer
+  , runWarpServerSettings
+
+  -- * Redirect paths in a Servant-Polysemy API
+  , Redirect
+  , redirect
+  ) where
+
+import Control.Monad.Except (ExceptT(..))
+import Data.Function ((&))
+import Data.Proxy (Proxy(..))
+import GHC.TypeLits (Nat)
+import qualified Network.Wai.Handler.Warp as Warp
+import Polysemy
+import Polysemy.Error
+import Servant
+       ( Application
+       , Handler(..)
+       , HasServer
+       , Header
+       , Headers
+       , JSON
+       , NoContent(..)
+       , Server
+       , ServerError
+       , ServerT
+       , StdMethod(GET)
+       , ToHttpApiData
+       , Verb
+       , addHeader
+       , hoistServer
+       , runHandler
+       , serve
+       )
+
+-- | Make a Servant 'Handler' run in a Polysemy 'Sem' instead.
+liftHandler :: Members '[Error ServerError, Embed IO] r => Handler a -> Sem r a
+liftHandler handler =
+  embed (runHandler handler) >>= fromEither
+
+-- | Hoist an ordinary Servant 'Server' into a 'ServerT' whose monad is 'Sem',
+-- so that it can be used with 'serveSem'.
+hoistServerIntoSem
+  :: forall api r
+   . ( HasServer api '[]
+     , Members '[Error ServerError, Embed IO] r
+     )
+  => Server api -> ServerT api (Sem r)
+hoistServerIntoSem =
+  hoistServer (Proxy @api) (liftHandler @r)
+
+-- | Turn a 'Sem' that can throw 'ServerError's into a Servant 'Handler'.
+semHandler
+  :: (forall x. Sem r x -> IO x)
+  -> Sem (Error ServerError ': r) a
+  -> Handler a
+semHandler lowerToIO =
+  Handler . ExceptT . lowerToIO . runError
+
+-- | Turn a 'ServerT' that contains a 'Sem' (as returned by 'hoistServerIntoSem') into a WAI 'Application'.
+serveSem
+  :: forall api r
+   . HasServer api '[]
+  => (forall x. Sem r x -> IO x)
+  -> ServerT api (Sem (Error ServerError ': r))
+  -> Application
+serveSem lowerToIO m = let api = Proxy @api
+  in serve api (hoistServer api (semHandler lowerToIO) m)
+
+-- | Run the given server on the given port, possibly showing exceptions in the responses.
+runWarpServer
+  :: forall api r
+   . ( HasServer api '[]
+     , Member (Embed IO) r
+     )
+  => Warp.Port -- ^ The port to listen on, e.g. '8080'
+  -> Bool -- ^ Whether to show exceptions in the http response (good for debugging but potentially a security risk)
+  -> ServerT api (Sem (Error ServerError ': r)) -- ^ The server to run. You can create one of these with 'hoistServerIntoSem'.
+  -> Sem r ()
+runWarpServer port showExceptionResponse server =
+  let warpSettings = Warp.defaultSettings
+        & Warp.setPort port
+        & if showExceptionResponse
+            then Warp.setOnExceptionResponse Warp.exceptionResponseForDebug
+            else id
+  in
+    runWarpServerSettings @api warpSettings server
+
+-- | Run the given server with these Warp settings.
+runWarpServerSettings
+  :: forall api r
+   . ( HasServer api '[]
+     , Member (Embed IO) r
+     )
+  => Warp.Settings
+  -> ServerT api (Sem (Error ServerError ': r))
+  -> Sem r ()
+runWarpServerSettings settings server = withLowerToIO $ \lowerToIO finished -> do
+  Warp.runSettings settings (serveSem @api lowerToIO server)
+  finished
+
+-- | A redirect response with the given code, the new location given in the given type, e.g:
+-- > Redirect 302 Text
+-- This will return a '302 Found' response, and we will use 'Text' in the server to say where it will redirect to.
+type Redirect (code :: Nat) loc
+  = Verb 'GET code '[JSON] (Headers '[Header "Location" loc] NoContent)
+
+-- | Serve a redirect response to the given location, e.g:
+-- > redirect "/api/v1"
+redirect :: ToHttpApiData a => a -> Sem r (Headers '[Header "Location" a] NoContent)
+redirect a = pure $ addHeader a NoContent
+
