diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,3 @@
+# Changelog for moss
+
+## Unreleased changes
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Michael B. Gale
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,28 @@
+# moss
+
+Haskell client library for [Moss](https://theory.stanford.edu/~aiken/moss/).
+
+## Example
+
+In order to use Moss, you need to register on the Moss website. Once you have your access token, using the library is fairly easy:
+
+```haskell
+import Stanford.Moss
+
+cfg :: MossCfg
+cfg = defaultMossCfg {
+    mossLanguage = Haskell,
+    mossUser = "[YOUR ACCESS TOKEN HERE]"
+}
+
+main :: IO ()
+main = do
+    url <- withMoss cfg $ do
+        addBaseFile "Skeleton" "Skeleton.hs"
+        addFile "StudentA" "StudentA.hs"
+        addFile "StudentB" "StudentB.hs"
+        query "Test"
+    print url
+```
+
+This example establishes a connection with Moss using Haskell as the selected programming language. We assume that some skeleton code (`Skeleton.hs`) has been made available to students and relevant parts from that file should be ignored for plagiarism checking. The example then uploads two students' submissions (`StudentA.hs` and `StudentB.hs`) before telling Moss to run the plagiarism check with `query` which eventually returns a URL to the results.
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/moss.cabal b/moss.cabal
new file mode 100644
--- /dev/null
+++ b/moss.cabal
@@ -0,0 +1,46 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.31.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 242278afc3ff26ab06aac60f132d9b70b071d6757c7763d6bf804c6cf8260412
+
+name:           moss
+version:        0.1.0.0
+synopsis:       Haskell client for Moss
+description:    Please see the README on Github at <https://github.com/mbg/moss#readme>
+category:       Web
+homepage:       https://github.com/mbg/moss#readme
+bug-reports:    https://github.com/mbg/moss/issues
+author:         Michael B. Gale
+maintainer:     m.gale@warwick.ac.uk
+copyright:      Copyright (c) 2018 Michael B. Gale
+license:        MIT
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    ChangeLog.md
+
+source-repository head
+  type: git
+  location: https://github.com/mbg/moss
+
+library
+  hs-source-dirs:
+      src
+  default-extensions: RecordWildCards OverloadedStrings
+  build-depends:
+      base >=4.7 && <5
+    , bytestring
+    , conduit-extra
+    , mtl
+    , network
+    , network-simple
+    , unix-compat
+  exposed-modules:
+      Stanford.Moss
+  other-modules:
+      Paths_moss
+  default-language: Haskell2010
diff --git a/src/Stanford/Moss.hs b/src/Stanford/Moss.hs
new file mode 100644
--- /dev/null
+++ b/src/Stanford/Moss.hs
@@ -0,0 +1,199 @@
+--------------------------------------------------------------------------------
+-- Haskell client for Moss                                                    --
+-- Copyright (c) 2018 Michael B. Gale (m.gale@warwick.ac.uk)                  --
+--------------------------------------------------------------------------------
+
+module Stanford.Moss (
+    MossCfg(..),
+    defaultMossCfg,
+
+    Language(..),
+    Moss,
+    liftIO,
+
+    withMoss,
+    addBaseFile,
+    addFile,
+    addFilesForStudent,
+    query
+) where
+
+--------------------------------------------------------------------------------
+
+import Control.Exception
+import Control.Monad.State
+
+import Data.Monoid
+
+import Network.Simple.TCP
+
+import System.IO
+import System.PosixCompat
+
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Char8 as C
+
+--------------------------------------------------------------------------------
+
+-- | Represents the configuration for a Moss connection.
+data MossCfg = MossCfg {
+    mossServer     :: HostName,
+    mossPort       :: ServiceName,
+    mossUser       :: BS.ByteString,
+    mossDir        :: Maybe FilePath,
+    mossX          :: Bool,
+    mossMaxMatches :: Int,
+    mossShow       :: Bool,
+    mossLanguage   :: Language
+}
+
+-- | 'defaultMossCfg' is the default configuration for a Moss connection.
+defaultMossCfg :: MossCfg
+defaultMossCfg = MossCfg {
+    mossServer     = "moss.stanford.edu",
+    mossPort       = "7690",
+    mossUser       = "",
+    mossDir        = Nothing,
+    mossX          = False,
+    mossMaxMatches = 250,
+    mossShow       = True,
+    mossLanguage   = Haskell
+}
+
+--------------------------------------------------------------------------------
+
+-- | Enumerates programming languages supported by Moss.
+data Language
+    = C
+    | CPP
+    | Java
+    | CSharp
+    | Python
+    | VisualBasic
+    | Javascript
+    | FORTRAN
+    | ML
+    | Haskell
+    | Lisp
+    | Scheme
+    | Pascal
+    | Modula2
+    | Ada
+    | Perl
+    | TCL
+    | Matlab
+    | VHDL
+    | Verilog
+    | Spice
+    | MIPS
+    | A8086
+    | HCL2
+    deriving (Enum)
+
+instance Show Language where
+    show C       = "c"
+    show CPP     = "cc"
+    show Java    = "java"
+    show ML      = "ml"
+    show Pascal  = "pascal"
+    show Ada     = "ada"
+    show Lisp    = "lisp"
+    show Scheme  = "scheme"
+    show Haskell = "haskell"
+    show FORTRAN = "fortran"
+
+--------------------------------------------------------------------------------
+
+-- | Represents the state of a Moss connection.
+data MossSt = MossSt {
+    mossSocket  :: Socket,
+    mossCounter :: Int,
+    mossCfg     :: MossCfg
+}
+
+type Moss = StateT MossSt IO
+
+-- | 'sendCmd' @socket bytestring@ sends @bytestring@ as a command over the
+-- connection represented by @socket@.
+sendCmd :: Socket -> BS.ByteString -> IO ()
+sendCmd s xs = do
+    putStr "Send: "
+    putStrLn (show xs)
+    send s (xs <> "\n")
+
+-- | 'withMoss' @cfg m@ runs a computation @m@ using a Moss connection whose
+-- configuration is reprsented by @cfg@.
+withMoss :: MossCfg -> Moss a -> IO a
+withMoss (cfg@MossCfg {..}) m =
+    connect mossServer mossPort $ \(s, addr) -> do
+        sendCmd s ("moss " <> mossUser)
+        sendCmd s ("X " <> C.pack (show (fromEnum mossX)))
+        sendCmd s ("maxmatches " <> C.pack (show mossMaxMatches))
+        sendCmd s ("language " <> C.pack (show mossLanguage))
+
+        ls <- recv s 1024
+
+        case ls of
+            Nothing -> error "No data received."
+            Just "no" -> do
+                sendCmd s "end"
+                error "Language not supported"
+            Just _ -> do
+                putStrLn "Language supported."
+                r <- evalStateT m (MossSt s 1 cfg)
+                sendCmd s "end"
+                return r
+
+{-send = withSocketsDo $ do
+    h <- connectTo mossServer mossPort
+    hClose h-}
+
+-- | 'uploadFile' @index name path@ uploads a file located at @path@ to Moss
+-- and assigns it to the collection of files at @index@ (e.g. representing
+-- a student) with the name given by @name@.
+uploadFile :: Int -> String -> FilePath -> Moss ()
+uploadFile i dn fp = do
+    s <- gets mossSocket
+    MossCfg{..} <- gets mossCfg
+
+    liftIO $ do
+        size <- fileSize <$> getFileStatus fp
+        sendCmd s ( "file "
+             <> C.pack (show i) <> " "
+             <> C.pack (show mossLanguage) <> " "
+             <> C.pack (show size) <> " "
+             <> C.pack dn)
+
+        xs <- BS.readFile fp
+        send s xs
+
+-- | 'addBaseFile' @file@ adds @file@ as part of the skeleton code.
+addBaseFile :: String -> FilePath -> Moss ()
+addBaseFile = uploadFile 0
+
+-- | 'addFile' @name file@ adds @file@ as a submission to Moss with @name@.
+addFile :: String -> FilePath -> Moss ()
+addFile desc fp = do
+    st <- get
+    uploadFile (mossCounter st) desc fp
+    put $ st { mossCounter = mossCounter st + 1 }
+
+-- | 'addFilesForStudent' @filesWithNames@ uploads multiple files for
+-- the same student. I.e. in the Moss submission they will share the same ID.
+addFilesForStudent :: [(String, FilePath)] -> Moss ()
+addFilesForStudent fs = do
+    st <- get
+    forM_ fs $ \(dn,fn) ->
+        uploadFile (mossCounter st) dn fn
+    put $ st { mossCounter = mossCounter st + 1 }
+
+-- | 'query' @comment@ runs the plagiarism check on all submitted files
+query :: BS.ByteString -> Moss (Maybe BS.ByteString)
+query cmt = do
+    s <- gets mossSocket
+    liftIO $ do
+        putStrLn "Querying, this may take several minutes..."
+        sendCmd s ("query 0 " <> cmt)
+        recv s 1024
+
+--------------------------------------------------------------------------------
